feat(plugin): observe terminals across connected hosts

Share host-wide terminal observations across plugin surfaces and gate them on terminalCollection. Keep directory observations compatible, and carry creation time in worker v6 state so v5 terminals can drain with an unknown timestamp.
This commit is contained in:
Mohamed Boudra
2026-11-06 09:22:19 +01:00
parent 62bd21214b
commit c4e5a0e739
30 changed files with 795 additions and 61 deletions
+40
View File
@@ -571,3 +571,43 @@ test.each(["all", "base", "uncommitted"] as const)(
expect(parsed.attachments).toEqual([attachment]);
},
);
describe("terminal collection protocol compatibility", () => {
test("accepts released directory observations and their snapshots", () => {
expect(
SessionInboundMessageSchema.parse({
type: "subscribe_terminals_request",
cwd: "/repo",
requestId: "old",
}),
).toMatchObject({ cwd: "/repo" });
expect(
SessionOutboundMessageSchema.parse({
type: "terminals_changed",
payload: { cwd: "/repo", terminals: [{ id: "terminal", name: "Shell" }] },
}),
).toMatchObject({ payload: { cwd: "/repo" } });
});
test("accepts capability-gated host observations and additive metadata", () => {
expect(
SessionInboundMessageSchema.parse({ type: "subscribe_terminals_request", requestId: "host" }),
).toMatchObject({ requestId: "host" });
expect(
SessionOutboundMessageSchema.parse({
type: "terminals_changed",
payload: {
terminals: [
{
id: "terminal",
name: "Shell",
cwd: "/repo",
workspaceId: "workspace",
createdAt: null,
isScript: true,
},
],
},
}),
).toMatchObject({ payload: { terminals: [{ isScript: true, createdAt: null }] } });
});
});
+7 -4
View File
@@ -3059,13 +3059,13 @@ export const ListTerminalsRequestSchema = z.object({
export const SubscribeTerminalsRequestSchema = z.object({
type: z.literal("subscribe_terminals_request"),
requestId: z.string().optional(),
cwd: z.string(),
cwd: z.string().optional(),
workspaceId: z.string().optional(),
});
export const UnsubscribeTerminalsRequestSchema = z.object({
type: z.literal("unsubscribe_terminals_request"),
cwd: z.string(),
cwd: z.string().optional(),
workspaceId: z.string().optional(),
});
@@ -3732,6 +3732,7 @@ export const ServerInfoStatusPayloadSchema = z
directorySync: z.boolean().optional(),
// COMPAT(workspaceLabels): added in v0.5.0, remove after 2027-08-14.
workspaceLabels: z.boolean().optional(),
terminalCollection: z.boolean().optional(),
// COMPAT(workspaceBaseBranch): added after v0.11.2, remove gate after 2027-10-14.
workspaceBaseBranch: z.boolean().optional(),
// COMPAT(workspaceSetupRun): added in v0.8.0, remove gate after 2027-09-02.
@@ -6591,6 +6592,8 @@ export const ListCommandsResponseSchema = z.object({
// ============================================================================
const TerminalInfoSchema = z.object({
createdAt: z.string().nullable().optional(),
isScript: z.boolean().optional(),
id: z.string(),
name: z.string(),
cwd: z.string(),
@@ -6654,8 +6657,8 @@ export const TerminalsChangedSchema = z.object({
subscriptionId: z.string().optional(),
requestId: z.string().optional(),
workspaceId: z.string().optional(),
cwd: z.string(),
terminals: z.array(TerminalInfoSchema.omit({ cwd: true })),
cwd: z.string().optional(),
terminals: z.array(TerminalInfoSchema.partial({ cwd: true })),
}),
});
@@ -3,6 +3,11 @@
// and tear down its own live subscription without disturbing the other. An
// absent workspaceId (old clients) keys to the cwd on its own. The `::`
// separator cannot collide with a workspace id, which is always `wks_<hex>`.
export function terminalSubscriptionKey(cwd: string, workspaceId: string | undefined): string {
export function terminalSubscriptionKey(
cwd: string | undefined,
workspaceId: string | undefined,
): string {
if (cwd === undefined)
return workspaceId === undefined ? "terminal-host" : `${workspaceId}::terminal-host`;
return workspaceId === undefined ? cwd : `${workspaceId}::${cwd}`;
}