fix: theme in ui attach

This commit is contained in:
Cristina Poncela Cubeiro
2026-06-18 16:11:04 +02:00
parent 337de9b078
commit 1d5fd23568
4 changed files with 31 additions and 6 deletions
+13 -4
View File
@@ -6,8 +6,6 @@ import type {
RpcExtensionUIResponse, RpcExtensionUIResponse,
} from "@earendil-works/pi-coding-agent"; } from "@earendil-works/pi-coding-agent";
import { theme } from "../../coding-agent/src/modes/interactive/theme/theme.ts";
type DialogRequest = type DialogRequest =
| Extract<RpcExtensionUIRequest, { method: "select" }> | Extract<RpcExtensionUIRequest, { method: "select" }>
| Extract<RpcExtensionUIRequest, { method: "confirm" }> | Extract<RpcExtensionUIRequest, { method: "confirm" }>
@@ -24,6 +22,7 @@ interface PendingExtensionRequest {
export class AttachUiBridge { export class AttachUiBridge {
private readonly pendingRequests = new Map<string, PendingExtensionRequest>(); private readonly pendingRequests = new Map<string, PendingExtensionRequest>();
private onRequest?: (request: RpcExtensionUIRequest) => void; private onRequest?: (request: RpcExtensionUIRequest) => void;
private themeOverride?: unknown;
attach(onRequest: (request: RpcExtensionUIRequest) => void): () => void { attach(onRequest: (request: RpcExtensionUIRequest) => void): () => void {
this.onRequest = onRequest; this.onRequest = onRequest;
@@ -34,6 +33,13 @@ export class AttachUiBridge {
}; };
} }
setThemeOverride(theme: unknown): void {
// This comes from attach host context over JSONL IPC. Pi's Theme is a runtime class,
// not a wire-safe protocol type, so this remains opaque until we define a proper
// serializable theme snapshot/DTO for the attach protocol.
this.themeOverride = theme;
}
handleResponse(response: RpcExtensionUIResponse): void { handleResponse(response: RpcExtensionUIResponse): void {
const pending = this.pendingRequests.get(response.id); const pending = this.pendingRequests.get(response.id);
if (!pending) { if (!pending) {
@@ -44,6 +50,7 @@ export class AttachUiBridge {
} }
createUiContext(): ExtensionUIContext { createUiContext(): ExtensionUIContext {
const uiBridge = this;
const requestDialog = <T>( const requestDialog = <T>(
request: DialogRequest, request: DialogRequest,
fallbackValue: T, fallbackValue: T,
@@ -154,8 +161,10 @@ export class AttachUiBridge {
addAutocompleteProvider: () => {}, addAutocompleteProvider: () => {},
setEditorComponent: () => {}, setEditorComponent: () => {},
getEditorComponent: () => undefined, getEditorComponent: () => undefined,
get theme() { get theme(): ExtensionUIContext["theme"] {
return theme; // If the attach host provides a theme object, forward it. Otherwise attach mode
// still has no real TUI/theme runtime, so fall back to an empty placeholder.
return (uiBridge.themeOverride ?? {}) as ExtensionUIContext["theme"];
}, },
getAllThemes: () => [], getAllThemes: () => [],
getTheme: () => undefined, getTheme: () => undefined,
+8 -1
View File
@@ -5,6 +5,7 @@ import type {
RpcExtensionUIResponse, RpcExtensionUIResponse,
} from "@earendil-works/pi-coding-agent"; } from "@earendil-works/pi-coding-agent";
import type { import type {
AttachHostContextRequest,
AttachReadyResponse, AttachReadyResponse,
AttachRequest, AttachRequest,
AttachRpcResponse, AttachRpcResponse,
@@ -136,7 +137,9 @@ export function attachIpcInstance(
onUiRequest: (request: RpcExtensionUIRequest) => void, onUiRequest: (request: RpcExtensionUIRequest) => void,
): ):
| { | {
handleRequest(request: { type: "attach_rpc"; command: RpcCommand } | RpcExtensionUIResponse): Promise<void>; handleRequest(
request: { type: "attach_rpc"; command: RpcCommand } | AttachHostContextRequest | RpcExtensionUIResponse,
): Promise<void>;
close(): void; close(): void;
} }
| undefined { | undefined {
@@ -152,6 +155,10 @@ export function attachIpcInstance(
onResponse({ type: "attach_rpc_result", response }); onResponse({ type: "attach_rpc_result", response });
return; return;
} }
if (request.type === "attach_host_context") {
handle.setHostTheme(request.theme);
return;
}
handle.handleUiResponse(request); handle.handleUiResponse(request);
}, },
close(): void { close(): void {
+6 -1
View File
@@ -45,6 +45,11 @@ export interface AttachRpcRequest {
command: RpcCommand; command: RpcCommand;
} }
export interface AttachHostContextRequest {
type: "attach_host_context";
theme?: unknown;
}
export interface RequestMap { export interface RequestMap {
spawn: SpawnRequest; spawn: SpawnRequest;
list: ListRequest; list: ListRequest;
@@ -127,7 +132,7 @@ export interface ResponseMap {
} }
export type OrchestratorResponse = ResponseMap[keyof ResponseMap] | ErrorResponse; export type OrchestratorResponse = ResponseMap[keyof ResponseMap] | ErrorResponse;
export type AttachClientRequest = AttachRpcRequest | RpcExtensionUIResponse; export type AttachClientRequest = AttachRpcRequest | AttachHostContextRequest | RpcExtensionUIResponse;
export type AttachServerResponse = export type AttachServerResponse =
| AttachReadyResponse | AttachReadyResponse
| AttachEventResponse | AttachEventResponse
+4
View File
@@ -104,6 +104,7 @@ export class OrchestratorSupervisor {
| { | {
handleRpc(command: RpcCommand): Promise<RpcResponse>; handleRpc(command: RpcCommand): Promise<RpcResponse>;
handleUiResponse(response: RpcExtensionUIResponse): void; handleUiResponse(response: RpcExtensionUIResponse): void;
setHostTheme(theme: unknown): void;
close(): void; close(): void;
} }
| undefined { | undefined {
@@ -122,6 +123,9 @@ export class OrchestratorSupervisor {
handleUiResponse: (response) => { handleUiResponse: (response) => {
live.uiBridge.handleResponse(response); live.uiBridge.handleResponse(response);
}, },
setHostTheme: (theme) => {
live.uiBridge.setThemeOverride(theme);
},
close: () => { close: () => {
detachUi(); detachUi();
live.subscribers.delete(onEvent); live.subscribers.delete(onEvent);