Compare commits

...
Author SHA1 Message Date
rekram1-node 6fab33d59b fix(tui): preserve resumable activity 2026-08-31 22:13:18 +00:00
rekram1-node 8b1091c35a fix(tui): settle parked session tabs 2026-08-31 21:58:32 +00:00
7 changed files with 129 additions and 3 deletions
+12 -2
View File
@@ -153,7 +153,12 @@ export type SessionActive = { type: "running" }
export type SessionInboxDelivery = "steer" | "queue"
export type SessionInboxSyntheticPayload = { text: string; description?: string; metadata?: { [x: string]: JsonValue } }
export type SessionInboxSyntheticPayload = {
text: string
description?: string
metadata?: { [x: string]: JsonValue }
resume?: boolean
}
export type SessionInboxCompactionPayload = {}
@@ -161,7 +166,12 @@ export type InstructionEntryKey = string
export type SessionGenerateResponse = { data: { text: string } }
export type SessionInboxSyntheticPayload1 = { text: string; description?: string; metadata?: { [x: string]: any } }
export type SessionInboxSyntheticPayload1 = {
text: string
description?: string
metadata?: { [x: string]: any }
resume?: boolean
}
export type ShellInfo = {
id: string
+1
View File
@@ -316,6 +316,7 @@ export const make = Effect.fn("Session.make")(function* () {
text: input.text,
description: input.description,
metadata: input.metadata,
resume: input.resume === false ? false : undefined,
}),
delivery: SessionInbox.Delivery.make(input.delivery ?? "steer"),
} satisfies SessionInbox.Item
@@ -1112,6 +1112,7 @@ describe("Session.prompt", () => {
text: "Background work completed",
description: "shell completion",
metadata: { job: "shell" },
resume: false,
},
})
+1
View File
@@ -22,6 +22,7 @@ export const SyntheticPayload = Schema.Struct({
text: Schema.String,
description: Schema.String.pipe(optional),
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
resume: Schema.Boolean.pipe(optional),
}).annotate({ identifier: "Session.Inbox.SyntheticPayload" })
export interface CompactionPayload extends Schema.Schema.Type<typeof CompactionPayload> {}
@@ -53,8 +53,13 @@ describe("contract hygiene", () => {
text: "completed",
description: undefined,
metadata: undefined,
resume: undefined,
}),
).toEqual({ text: "completed" })
expect(Schema.encodeSync(SessionInbox.SyntheticPayload)({ text: "parked", resume: false })).toEqual({
text: "parked",
resume: false,
})
const info = Session.Info.make({
id: Session.ID.make("ses_untitled"),
+5 -1
View File
@@ -172,7 +172,11 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
: members.some((id) => (data.session.form.list(id)?.length ?? 0) > 0)
? ("question" as const)
: (false as const),
busy: members.some((id) => data.session.status(id) === "running" || data.session.pending.list(id).length > 0),
busy: members.some(
(id) =>
data.session.status(id) === "running" ||
data.session.pending.list(id).some((item) => item.type !== "synthetic" || item.payload.resume !== false),
),
renaming: data.session.title.pending(session),
}
}
@@ -932,6 +932,110 @@ test("user prompt admissions pulse an already-busy background tab", async () =>
}
})
test("parked synthetic context does not keep a tab busy", async () => {
const setup = await renderSessionTabs("shell")
try {
await wait(() => setup.tabs.current() === "shell")
setup.emit({
id: "evt_shell_complete",
created: Date.now(),
type: "session.inbox.enqueued",
durable: { aggregateID: "shell", seq: 1, version: 1 },
data: {
sessionID: "shell",
inboxID: "msg_shell_complete",
item: {
type: "synthetic",
payload: {
text: "The following shell command was executed by the user",
metadata: { source: "shell", state: "completed" },
resume: false,
},
delivery: "steer",
},
},
})
await wait(() => setup.data.session.pending.list("shell").length === 1)
expect(setup.tabs.status("shell").busy).toBe(false)
setup.emit({
id: "evt_execution_started",
created: Date.now(),
type: "session.execution.started",
durable: { aggregateID: "shell", seq: 2, version: 1 },
data: { sessionID: "shell" },
})
await wait(() => setup.tabs.status("shell").busy)
setup.emit({
id: "evt_execution_succeeded",
created: Date.now(),
type: "session.execution.succeeded",
durable: { aggregateID: "shell", seq: 3, version: 1 },
data: { sessionID: "shell" },
})
await wait(() => !setup.tabs.status("shell").busy)
setup.emit(admitted("shell", "msg_4"))
await wait(() => setup.tabs.status("shell").busy)
} finally {
await setup.destroy()
}
})
test("resumable synthetic context keeps a tab busy until execution settles", async () => {
const setup = await renderSessionTabs("background")
try {
await wait(() => setup.tabs.current() === "background")
setup.emit({
id: "evt_completion",
created: Date.now(),
type: "session.inbox.enqueued",
durable: { aggregateID: "background", seq: 1, version: 1 },
data: {
sessionID: "background",
inboxID: "msg_completion",
item: {
type: "synthetic",
payload: { text: "Background subagent completed" },
delivery: "steer",
},
},
})
await wait(() => setup.tabs.status("background").busy)
setup.emit({
id: "evt_background_started",
created: Date.now(),
type: "session.execution.started",
durable: { aggregateID: "background", seq: 2, version: 1 },
data: { sessionID: "background" },
})
setup.emit({
id: "evt_completion_delivered",
created: Date.now(),
type: "session.inbox.delivered",
durable: { aggregateID: "background", seq: 3, version: 1 },
data: { sessionID: "background", inboxID: "msg_completion" },
})
await wait(() => setup.data.session.pending.list("background").length === 0 && setup.tabs.status("background").busy)
setup.emit({
id: "evt_background_succeeded",
created: Date.now(),
type: "session.execution.succeeded",
durable: { aggregateID: "background", seq: 4, version: 1 },
data: { sessionID: "background" },
})
await wait(() => !setup.tabs.status("background").busy)
} finally {
await setup.destroy()
}
})
test("tracks a temporary new session tab across close and creation", async () => {
const setup = await renderSessionTabs("first")