From de476aa51b768e390809f343bff41df9c2d5d4d5 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 3 Jul 2026 12:35:35 -0400 Subject: [PATCH] refactor(schema): declare event durability at definition level (#35172) --- .../client/src/promise/generated/types.ts | 242 ++--- packages/core/src/event.ts | 3 +- packages/core/src/session/projector.ts | 4 +- packages/core/test/event.test.ts | 14 +- packages/core/test/session-log.test.ts | 2 +- .../test/cli/run/noninteractive.test.ts | 12 +- .../test/cli/run/stream-v2.transport.test.ts | 114 ++- .../test/v2/session-message-updater.test.ts | 15 + packages/protocol/src/groups/event.ts | 1 - packages/schema/src/agent.ts | 4 +- packages/schema/src/catalog.ts | 4 +- packages/schema/src/command.ts | 4 +- packages/schema/src/durable-event-manifest.ts | 7 +- packages/schema/src/event-manifest.ts | 8 +- packages/schema/src/event.ts | 82 +- packages/schema/src/filesystem-watcher.ts | 4 +- packages/schema/src/filesystem.ts | 4 +- packages/schema/src/form.ts | 16 +- packages/schema/src/ide-event.ts | 2 +- packages/schema/src/installation-event.ts | 4 +- packages/schema/src/integration.ts | 6 +- packages/schema/src/lsp-event.ts | 2 +- packages/schema/src/mcp-event.ts | 6 +- packages/schema/src/models-dev.ts | 4 +- packages/schema/src/permission.ts | 6 +- packages/schema/src/plugin.ts | 4 +- packages/schema/src/project-directories.ts | 4 +- packages/schema/src/project.ts | 4 +- packages/schema/src/pty.ts | 10 +- packages/schema/src/question.ts | 8 +- packages/schema/src/reference.ts | 4 +- packages/schema/src/server-event.ts | 4 +- .../schema/src/session-compaction-event.ts | 2 +- packages/schema/src/session-event.ts | 110 +-- packages/schema/src/session-status-event.ts | 4 +- packages/schema/src/session-todo.ts | 4 +- packages/schema/src/shell.ts | 8 +- packages/schema/src/skill.ts | 4 +- packages/schema/src/tui-event.ts | 8 +- packages/schema/src/v1/legacy-event.ts | 4 +- packages/schema/src/v1/permission.ts | 6 +- packages/schema/src/v1/question.ts | 8 +- packages/schema/src/v1/session.ts | 22 +- packages/schema/src/vcs-event.ts | 2 +- packages/schema/src/workspace-event.ts | 6 +- packages/schema/src/worktree-event.ts | 4 +- packages/schema/test/event-manifest.test.ts | 69 +- packages/schema/test/event.test.ts | 10 +- packages/sdk-next/test/embedded.test.ts | 2 +- packages/sdk/js/src/v2/gen/types.gen.ts | 930 ++++-------------- .../test/cli/cmd/tui/notifications.test.ts | 12 +- packages/tui/test/cli/tui/data.test.tsx | 22 +- specs/v2/schema-changelog.md | 20 + 53 files changed, 700 insertions(+), 1165 deletions(-) diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 6c8286fc52..798155e88e 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -1109,7 +1109,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.agent.switched" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1122,7 +1122,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.model.switched" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1135,7 +1135,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.moved" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1148,7 +1148,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.renamed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number; readonly sessionID: string; readonly title: string } } @@ -1156,7 +1156,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.forked" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1169,7 +1169,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.prompted" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1196,7 +1196,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.prompt.admitted" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1223,7 +1223,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.context.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1236,7 +1236,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.synthetic" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1251,7 +1251,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.skill.activated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1265,7 +1265,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.shell.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1279,7 +1279,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.shell.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1292,7 +1292,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.step.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1307,7 +1307,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.step.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1329,7 +1329,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.step.failed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1342,7 +1342,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.text.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1355,7 +1355,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.text.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1365,11 +1365,40 @@ export type SessionLogOutput = readonly text: string } } + | { + readonly id: string + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.next.reasoning.started" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { + readonly timestamp: number + readonly sessionID: string + readonly assistantMessageID: string + readonly reasoningID: string + readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } } + } + } + | { + readonly id: string + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.next.reasoning.ended" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { + readonly timestamp: number + readonly sessionID: string + readonly assistantMessageID: string + readonly reasoningID: string + readonly text: string + readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } } + } + } | { readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.input.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1383,7 +1412,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.input.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1397,7 +1426,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.called" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1416,7 +1445,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.progress" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1434,7 +1463,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.success" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1458,7 +1487,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.failed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1473,40 +1502,11 @@ export type SessionLogOutput = } } } - | { - readonly id: string - readonly metadata?: { readonly [x: string]: unknown } - readonly type: "session.next.reasoning.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } - readonly location?: { readonly directory: string; readonly workspaceID?: string } - readonly data: { - readonly timestamp: number - readonly sessionID: string - readonly assistantMessageID: string - readonly reasoningID: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } } - } - } - | { - readonly id: string - readonly metadata?: { readonly [x: string]: unknown } - readonly type: "session.next.reasoning.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } - readonly location?: { readonly directory: string; readonly workspaceID?: string } - readonly data: { - readonly timestamp: number - readonly sessionID: string - readonly assistantMessageID: string - readonly reasoningID: string - readonly text: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } } - } - } | { readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.retried" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1526,7 +1526,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.compaction.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1539,7 +1539,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.compaction.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1554,7 +1554,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.revert.staged" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -1578,7 +1578,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.revert.cleared" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number; readonly sessionID: string } } @@ -1586,7 +1586,7 @@ export type SessionLogOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.revert.committed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string } } @@ -3773,7 +3773,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "models-dev.refreshed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: {} } @@ -3781,7 +3780,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "integration.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: {} } @@ -3789,7 +3787,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "integration.connection.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly integrationID: string } } @@ -3797,7 +3794,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "catalog.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: {} } @@ -3805,7 +3801,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "agent.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: {} } @@ -3813,7 +3808,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.created" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -3874,7 +3869,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -3935,7 +3930,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.deleted" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -3996,7 +3991,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "message.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -4100,7 +4095,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "message.removed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string; readonly messageID: string } } @@ -4108,7 +4103,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "message.part.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -4346,7 +4341,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "message.part.removed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string; readonly messageID: string; readonly partID: string } } @@ -4354,7 +4349,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.agent.switched" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4367,7 +4362,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.model.switched" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4380,7 +4375,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.moved" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4393,7 +4388,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.renamed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number; readonly sessionID: string; readonly title: string } } @@ -4401,7 +4396,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.forked" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4414,7 +4409,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.prompted" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4441,7 +4436,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.prompt.admitted" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4468,7 +4463,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.execution.settled" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4481,7 +4475,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.context.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4494,7 +4488,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.synthetic" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4509,7 +4503,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.skill.activated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4523,7 +4517,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.shell.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4537,7 +4531,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.shell.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4550,7 +4544,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.step.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4565,7 +4559,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.step.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4587,7 +4581,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.step.failed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4600,7 +4594,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.text.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4613,7 +4607,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.text.delta" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4627,7 +4620,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.text.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4641,7 +4634,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.reasoning.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4655,7 +4648,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.reasoning.delta" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4669,7 +4661,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.reasoning.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4684,7 +4676,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.input.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4698,7 +4690,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.input.delta" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4712,7 +4703,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.input.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4726,7 +4717,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.called" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4745,7 +4736,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.progress" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4763,7 +4754,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.success" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4787,7 +4778,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.tool.failed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4806,7 +4797,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.retried" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4826,7 +4817,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.compaction.started" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4839,7 +4830,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.compaction.delta" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4852,7 +4842,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.compaction.ended" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4867,7 +4857,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.revert.staged" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number @@ -4891,7 +4881,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.revert.cleared" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number; readonly sessionID: string } } @@ -4899,7 +4889,7 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.next.revert.committed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string } } @@ -4907,7 +4897,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "file.edited" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly file: string } } @@ -4915,7 +4904,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "reference.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: {} } @@ -4923,7 +4911,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "permission.v2.asked" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string @@ -4939,7 +4926,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "permission.v2.replied" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -4951,7 +4937,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "plugin.added" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string } } @@ -4959,7 +4944,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "project.directories.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly projectID: string } } @@ -4967,7 +4951,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "command.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: {} } @@ -4975,7 +4958,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "skill.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: {} } @@ -4983,7 +4965,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "file.watcher.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly file: string; readonly event: "add" | "change" | "unlink" } } @@ -4991,7 +4972,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "pty.created" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly info: { @@ -5010,7 +4990,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "pty.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly info: { @@ -5029,7 +5008,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "pty.exited" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string; readonly exitCode: number } } @@ -5037,7 +5015,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "pty.deleted" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string } } @@ -5045,7 +5022,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "shell.created" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly info: { @@ -5066,7 +5042,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "shell.exited" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string @@ -5078,7 +5053,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "shell.deleted" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string } } @@ -5086,7 +5060,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "question.v2.asked" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string @@ -5105,7 +5078,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "question.v2.replied" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -5117,7 +5089,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "question.v2.rejected" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string; readonly requestID: string } } @@ -5125,7 +5096,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "form.created" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly form: @@ -5240,7 +5210,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "form.replied" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string @@ -5252,7 +5221,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "form.cancelled" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string; readonly sessionID: string } } @@ -5260,7 +5228,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "todo.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -5271,7 +5238,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.status" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -5298,7 +5264,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.idle" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string } } @@ -5306,7 +5271,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "tui.prompt.append" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly text: string } } @@ -5314,7 +5278,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "tui.command.execute" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly command: @@ -5342,7 +5305,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "tui.toast.show" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly title?: string @@ -5355,7 +5317,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "tui.session.select" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string } } @@ -5363,7 +5324,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "installation.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly version: string } } @@ -5371,7 +5331,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "installation.update-available" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly version: string } } @@ -5379,7 +5338,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "vcs.branch.updated" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly branch?: string } } @@ -5387,7 +5345,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "mcp.status.changed" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly server: string } } @@ -5395,7 +5352,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "permission.asked" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string @@ -5411,7 +5367,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "permission.replied" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -5423,7 +5378,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "question.asked" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly id: string @@ -5442,7 +5396,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "question.replied" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string @@ -5454,7 +5407,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "question.rejected" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string; readonly requestID: string } } @@ -5462,7 +5414,6 @@ export type EventSubscribeOutput = readonly id: string readonly metadata?: { readonly [x: string]: unknown } readonly type: "session.error" - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID?: string | undefined @@ -5503,7 +5454,6 @@ export type EventSubscribeOutput = | { readonly id: string readonly metadata?: { readonly [x: string]: unknown } | undefined - readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } | undefined readonly location?: { readonly directory: string; readonly workspaceID?: string } | undefined readonly type: "server.connected" readonly data: {} diff --git a/packages/core/src/event.ts b/packages/core/src/event.ts index 66ee91c6c6..dd0aa43181 100644 --- a/packages/core/src/event.ts +++ b/packages/core/src/event.ts @@ -92,8 +92,9 @@ export class SubscriberOverflowError extends Schema.TaggedErrorClass +type MessageEvent = Exclude const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message) const encodeMessage = Schema.encodeSync(SessionMessage.Message) @@ -417,7 +417,7 @@ function run(db: DatabaseService, event: MessageEvent) { }) } -function insertMessage(db: DatabaseService, event: SessionEvent.Event, message: SessionMessage.Message) { +function insertMessage(db: DatabaseService, event: SessionEvent.DurableEvent, message: SessionMessage.Message) { if (event.durable === undefined) return Effect.die("Durable Session event is missing aggregate sequence") const encoded = encodeMessage(message) const { id, type, ...data } = encoded diff --git a/packages/core/test/event.test.ts b/packages/core/test/event.test.ts index b40f088c5d..a9220df422 100644 --- a/packages/core/test/event.test.ts +++ b/packages/core/test/event.test.ts @@ -22,14 +22,14 @@ const locationLayer = Layer.succeed( location({ directory: AbsolutePath.make("project"), workspaceID: WorkspaceV2.ID.make("wrk_test") }), ), ) -const Message = EventV2.define({ +const Message = EventV2.ephemeral({ type: "test.message", schema: { text: Schema.String, }, }) -const SyncMessage = EventV2.define({ +const SyncMessage = EventV2.durable({ type: "test.sync", durable: { version: 1, @@ -41,7 +41,7 @@ const SyncMessage = EventV2.define({ }, }) -const SyncSent = EventV2.define({ +const SyncSent = EventV2.durable({ type: "test.sent", durable: { version: 1, @@ -53,14 +53,14 @@ const SyncSent = EventV2.define({ }, }) -const GlobalMessage = EventV2.define({ +const GlobalMessage = EventV2.ephemeral({ type: "test.global", schema: { text: Schema.String, }, }) -const VersionedMessage = EventV2.define({ +const VersionedMessage = EventV2.durable({ type: "test.versioned", durable: { version: 2, @@ -129,12 +129,12 @@ describe("EventV2", () => { it.effect("selects the latest durable definition independent of declaration order", () => Effect.sync(() => { - const latest = EventV2.define({ + const latest = EventV2.durable({ type: "test.out-of-order", durable: { version: 2, aggregate: "id" }, schema: { id: Schema.String }, }) - const historical = EventV2.define({ + const historical = EventV2.durable({ type: "test.out-of-order", durable: { version: 1, aggregate: "id" }, schema: { id: Schema.String }, diff --git a/packages/core/test/session-log.test.ts b/packages/core/test/session-log.test.ts index b80dd2c206..3b9229589c 100644 --- a/packages/core/test/session-log.test.ts +++ b/packages/core/test/session-log.test.ts @@ -78,7 +78,7 @@ describe("SessionV2.log", () => { it.effect("reads across undecodable gaps in aggregate order and marks the true log position", () => Effect.gen(function* () { - const GapEvent = EventV2.define({ + const GapEvent = EventV2.durable({ type: "test.session.log.gap", durable: { aggregate: "sessionID", version: 1 }, schema: { sessionID: SessionV2.ID, value: Schema.String }, diff --git a/packages/opencode/test/cli/run/noninteractive.test.ts b/packages/opencode/test/cli/run/noninteractive.test.ts index 15cf799c75..d8bff61a73 100644 --- a/packages/opencode/test/cli/run/noninteractive.test.ts +++ b/packages/opencode/test/cli/run/noninteractive.test.ts @@ -25,6 +25,7 @@ function prompted(messageID: string): V2Event { return { id: "evt_prompted", type: "session.next.prompted", + durable: { aggregateID: "ses_1", seq: 0, version: 1 }, data: { timestamp: 1, sessionID: "ses_1", messageID, prompt: { text: "hello" }, delivery: "steer" }, } } @@ -39,11 +40,7 @@ function settled(outcome: "success" | "interrupted" = "success"): V2Event { // Runs one non-interactive prompt against a mocked SDK. `turn` produces the // live events the prompt admission triggers, keyed by the generated message ID. -async function run(input: { - turn: (messageID: string) => V2Event[] - pendingForms?: FormInfo[] - attached?: boolean -}) { +async function run(input: { turn: (messageID: string) => V2Event[]; pendingForms?: FormInfo[]; attached?: boolean }) { const sdk = new OpencodeClient() const values: V2Event[] = [{ id: "evt_connected", type: "server.connected", data: {} }] let wake: (() => void) | undefined @@ -64,8 +61,9 @@ async function run(input: { ) spyOn(sdk.v2.session.permission, "list").mockImplementation(() => ok({ data: [] }) as never) spyOn(sdk.v2.session.question, "list").mockImplementation(() => ok({ data: [] }) as never) - spyOn(sdk.v2.session.form, "list").mockImplementation((request) => - ok({ data: input.pendingForms?.filter((item) => item.sessionID === request.sessionID) ?? [] }) as never, + spyOn(sdk.v2.session.form, "list").mockImplementation( + (request) => + ok({ data: input.pendingForms?.filter((item) => item.sessionID === request.sessionID) ?? [] }) as never, ) spyOn(sdk.v2.session.form, "cancel").mockImplementation(() => ok(undefined) as never) spyOn(sdk.v2.session, "prompt").mockImplementation((request) => { diff --git a/packages/opencode/test/cli/run/stream-v2.transport.test.ts b/packages/opencode/test/cli/run/stream-v2.transport.test.ts index 7c2fc9f70e..2f78d106cf 100644 --- a/packages/opencode/test/cli/run/stream-v2.transport.test.ts +++ b/packages/opencode/test/cli/run/stream-v2.transport.test.ts @@ -53,6 +53,10 @@ function connected(id = "evt_connected") { return { id, type: "server.connected", data: {} } satisfies RunV2Event } +function durable(sessionID: string, seq = 0, version = 1) { + return { aggregateID: sessionID, seq, version } +} + function footer() { const commits: StreamCommit[] = [] const events: FooterEvent[] = [] @@ -94,7 +98,10 @@ function sdk(input: { const client = new OpencodeClient() let subscription = 0 spyOn(client.v2.event, "subscribe").mockImplementation( - () => Promise.resolve({ stream: input.streams[subscription++]?.stream ?? feed().stream }) as ReturnType, + () => + Promise.resolve({ stream: input.streams[subscription++]?.stream ?? feed().stream }) as ReturnType< + typeof client.v2.event.subscribe + >, ) spyOn(client.v2.session, "messages").mockImplementation((request) => ok({ @@ -193,32 +200,33 @@ describe("V2 mini transport", () => { }) while (!admitted) await Bun.sleep(0) events.push({ - id: "evt_prompted", - type: "session.next.prompted", - data: { - timestamp: 2, - sessionID: "ses_1", - messageID: "msg_prompt", - prompt: { text: "hello" }, - delivery: "steer", - }, - }) - events.push({ - id: "evt_text", - type: "session.next.text.delta", - data: { - timestamp: 3, - sessionID: "ses_1", - assistantMessageID: "msg_assistant", - textID: "txt_1", - delta: "answer", - }, - }) - events.push({ - id: "evt_settled", - type: "session.next.execution.settled", - data: { timestamp: 4, sessionID: "ses_1", outcome: "success" }, - }) + id: "evt_prompted", + type: "session.next.prompted", + durable: durable("ses_1"), + data: { + timestamp: 2, + sessionID: "ses_1", + messageID: "msg_prompt", + prompt: { text: "hello" }, + delivery: "steer", + }, + }) + events.push({ + id: "evt_text", + type: "session.next.text.delta", + data: { + timestamp: 3, + sessionID: "ses_1", + assistantMessageID: "msg_assistant", + textID: "txt_1", + delta: "answer", + }, + }) + events.push({ + id: "evt_settled", + type: "session.next.execution.settled", + data: { timestamp: 4, sessionID: "ses_1", outcome: "success" }, + }) await turn expect(ui.commits.map((item) => item.text)).toEqual(["previous prompt", "answer"]) @@ -245,9 +253,7 @@ describe("V2 mini transport", () => { limits: () => ({}), footer: ui.api, }) - let request: - | Parameters[0] - | undefined + let request: Parameters[0] | undefined // The generated method has conditional return types for throwOnError; this mock represents the successful branch. // @ts-expect-error successful SDK response is valid for both modes at runtime spyOn(client.v2.session, "prompt").mockImplementation((input) => { @@ -256,6 +262,7 @@ describe("V2 mini transport", () => { events.push({ id: "evt_prompted", type: "session.next.prompted", + durable: durable("ses_1"), data: { timestamp: 2, sessionID: "ses_1", @@ -341,9 +348,7 @@ describe("V2 mini transport", () => { limits: () => ({}), footer: ui.api, }) - let request: - | Parameters[0] - | undefined + let request: Parameters[0] | undefined // The generated method has conditional return types for throwOnError; this mock represents the successful branch. // @ts-expect-error successful SDK response is valid for both modes at runtime spyOn(client.v2.session, "prompt").mockImplementation((input) => { @@ -352,6 +357,7 @@ describe("V2 mini transport", () => { events.push({ id: "evt_prompted", type: "session.next.prompted", + durable: durable("ses_1"), data: { timestamp: 2, sessionID: "ses_1", @@ -440,9 +446,7 @@ describe("V2 mini transport", () => { limits: () => ({}), footer: ui.api, }) - let request: - | Parameters[0] - | undefined + let request: Parameters[0] | undefined // The generated method has conditional return types for throwOnError; this mock represents the successful branch. // @ts-expect-error successful SDK response is valid for both modes at runtime spyOn(client.v2.session, "prompt").mockImplementation((input) => { @@ -451,6 +455,7 @@ describe("V2 mini transport", () => { events.push({ id: "evt_prompted", type: "session.next.prompted", + durable: durable("ses_1"), data: { timestamp: 2, sessionID: "ses_1", @@ -593,7 +598,9 @@ describe("V2 mini transport", () => { const messageID = request.id ?? "msg_prompt" const prompt = request.prompt ?? { text: "" } admitted = true - return ok({ data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 } }) + return ok({ + data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 }, + }) }) const turn = transport.runPromptTurn({ @@ -663,7 +670,9 @@ describe("V2 mini transport", () => { const messageID = request.id ?? "msg_prompt" const prompt = request.prompt ?? { text: "" } admitted = true - return ok({ data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 } }) + return ok({ + data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 }, + }) }) const turn = transport.runPromptTurn({ @@ -805,6 +814,7 @@ describe("V2 mini transport", () => { events.push({ id: "evt_reasoning", type: "session.next.reasoning.ended", + durable: durable("ses_1"), data: { timestamp: 3, sessionID: "ses_1", @@ -841,7 +851,9 @@ describe("V2 mini transport", () => { const messageID = request.id ?? "msg_prompt" const prompt = request.prompt ?? { text: "" } admitted = true - return ok({ data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 } }) + return ok({ + data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 }, + }) }) const interrupted = spyOn(client.v2.session, "interrupt").mockImplementation(() => ok(undefined)) @@ -896,7 +908,9 @@ describe("V2 mini transport", () => { const messageID = request.id ?? "msg_prompt" const prompt = request.prompt ?? { text: "" } admitted = true - return ok({ data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 } }) + return ok({ + data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 }, + }) }) const turn = transport.runPromptTurn({ @@ -911,6 +925,7 @@ describe("V2 mini transport", () => { events.push({ id: "evt_prompted", type: "session.next.prompted", + durable: durable("ses_1"), data: { timestamp: 2, sessionID: "ses_1", @@ -952,7 +967,9 @@ describe("V2 mini transport", () => { const messageID = request.id ?? "msg_prompt" const prompt = request.prompt ?? { text: "" } admitted = true - return ok({ data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 } }) + return ok({ + data: { admittedSeq: 1, id: messageID, sessionID: "ses_1", prompt, delivery: "steer" as const, timeCreated: 2 }, + }) }) const interrupted = spyOn(client.v2.session, "interrupt").mockImplementation(() => ok(undefined)) const controller = new AbortController() @@ -969,6 +986,7 @@ describe("V2 mini transport", () => { events.push({ id: "evt_prompted", type: "session.next.prompted", + durable: durable("ses_1"), data: { timestamp: 2, sessionID: "ses_1", @@ -1031,13 +1049,13 @@ describe("V2 mini transport", () => { limits: () => ({}), footer: ui.api, }) - const states = () => - ui.events.flatMap((event) => (event.type === "stream.subagent" ? [event.state] : [])) + const states = () => ui.events.flatMap((event) => (event.type === "stream.subagent" ? [event.state] : [])) transport.selectSubagent("ses_child") events.push({ id: "evt_child_step", type: "session.next.step.started", + durable: durable("ses_child"), data: { timestamp: 2, sessionID: "ses_child", @@ -1107,13 +1125,13 @@ describe("V2 mini transport", () => { limits: () => ({}), footer: ui.api, }) - const states = () => - ui.events.flatMap((event) => (event.type === "stream.subagent" ? [event.state] : [])) + const states = () => ui.events.flatMap((event) => (event.type === "stream.subagent" ? [event.state] : [])) // Both events arrive while session.get is still in flight. events.push({ id: "evt_child_step", type: "session.next.step.started", + durable: durable("ses_child"), data: { timestamp: 2, sessionID: "ses_child", @@ -1165,13 +1183,13 @@ describe("V2 mini transport", () => { limits: () => ({}), footer: ui.api, }) - const states = () => - ui.events.flatMap((event) => (event.type === "stream.subagent" ? [event.state] : [])) + const states = () => ui.events.flatMap((event) => (event.type === "stream.subagent" ? [event.state] : [])) // Child event arrives first and gets buffered behind the gated session.get. events.push({ id: "evt_child_step", type: "session.next.step.started", + durable: durable("ses_child"), data: { timestamp: 2, sessionID: "ses_child", @@ -1184,6 +1202,7 @@ describe("V2 mini transport", () => { events.push({ id: "evt_parent_call", type: "session.next.tool.called", + durable: durable("ses_1"), data: { timestamp: 3, sessionID: "ses_1", @@ -1197,6 +1216,7 @@ describe("V2 mini transport", () => { events.push({ id: "evt_parent_success", type: "session.next.tool.success", + durable: durable("ses_1", 1), data: { timestamp: 4, sessionID: "ses_1", diff --git a/packages/opencode/test/v2/session-message-updater.test.ts b/packages/opencode/test/v2/session-message-updater.test.ts index 668a353f67..e85afaeeff 100644 --- a/packages/opencode/test/v2/session-message-updater.test.ts +++ b/packages/opencode/test/v2/session-message-updater.test.ts @@ -9,6 +9,10 @@ import { SessionEvent } from "@opencode-ai/core/session/event" import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater" import { SessionMessage } from "@opencode-ai/core/session/message" +function durable(sessionID: SessionID, seq = 0, version = 1) { + return { aggregateID: sessionID, seq: EventV2.Seq.make(seq), version: EventV2.Version.make(version) } +} + test.skip("step snapshots carry over to assistant messages", () => { const state: SessionMessageUpdater.MemoryState = { messages: [] } const sessionID = SessionID.make("session") @@ -18,6 +22,7 @@ test.skip("step snapshots carry over to assistant messages", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.step.started", + durable: durable(sessionID), data: { sessionID, assistantMessageID, @@ -39,6 +44,7 @@ test.skip("step snapshots carry over to assistant messages", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.step.ended", + durable: durable(sessionID, 1, 2), data: { sessionID, assistantMessageID, @@ -71,6 +77,7 @@ test.skip("text ended populates assistant text content", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.step.started", + durable: durable(sessionID), data: { sessionID, assistantMessageID, @@ -89,6 +96,7 @@ test.skip("text ended populates assistant text content", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.text.started", + durable: durable(sessionID, 1), data: { sessionID, assistantMessageID, @@ -102,6 +110,7 @@ test.skip("text ended populates assistant text content", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.text.ended", + durable: durable(sessionID, 2), data: { sessionID, assistantMessageID, @@ -127,6 +136,7 @@ test.skip("tool completion stores completed timestamp", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.step.started", + durable: durable(sessionID), data: { sessionID, assistantMessageID, @@ -145,6 +155,7 @@ test.skip("tool completion stores completed timestamp", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.tool.input.started", + durable: durable(sessionID, 1), data: { sessionID, assistantMessageID, @@ -159,6 +170,7 @@ test.skip("tool completion stores completed timestamp", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.tool.called", + durable: durable(sessionID, 2), data: { sessionID, assistantMessageID, @@ -175,6 +187,7 @@ test.skip("tool completion stores completed timestamp", () => { SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.tool.success", + durable: durable(sessionID, 3), data: { sessionID, assistantMessageID, @@ -205,6 +218,7 @@ test("compaction events reduce to compaction message only when completed", () => SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id, type: "session.next.compaction.started", + durable: durable(sessionID), data: { sessionID, messageID: compactionID, @@ -246,6 +260,7 @@ test("compaction events reduce to compaction message only when completed", () => SessionMessageUpdater.update(SessionMessageUpdater.memory(state), { id: EventV2.ID.create(), type: "session.next.compaction.ended", + durable: durable(sessionID, 1), data: { sessionID, messageID: compactionID, diff --git a/packages/protocol/src/groups/event.ts b/packages/protocol/src/groups/event.ts index f5a0c0c7bb..9860aa0cad 100644 --- a/packages/protocol/src/groups/event.ts +++ b/packages/protocol/src/groups/event.ts @@ -9,7 +9,6 @@ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/un const fields = { id: Event.ID, metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), - durable: Schema.optional(Schema.Struct({ aggregateID: Schema.String, seq: Event.Seq, version: Event.Version })), location: Schema.optional(Location.Ref), } diff --git a/packages/schema/src/agent.ts b/packages/schema/src/agent.ts index 3d8068b89a..eb84cf500b 100644 --- a/packages/schema/src/agent.ts +++ b/packages/schema/src/agent.ts @@ -1,14 +1,14 @@ export * as Agent from "./agent.js" import { Schema } from "effect" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { optional } from "./schema.js" import { Model } from "./model.js" import { Permission } from "./permission.js" import { Provider } from "./provider.js" import { PositiveInt, statics } from "./schema.js" -const Updated = define({ type: "agent.updated", schema: {} }) +const Updated = ephemeral({ type: "agent.updated", schema: {} }) export const ID = Schema.String.pipe(Schema.brand("AgentV2.ID")) export type ID = typeof ID.Type diff --git a/packages/schema/src/catalog.ts b/packages/schema/src/catalog.ts index 19e2c26b9f..58545cd86b 100644 --- a/packages/schema/src/catalog.ts +++ b/packages/schema/src/catalog.ts @@ -1,6 +1,6 @@ export * as Catalog from "./catalog.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" -const Updated = define({ type: "catalog.updated", schema: {} }) +const Updated = ephemeral({ type: "catalog.updated", schema: {} }) export const Event = { Updated, Definitions: inventory(Updated) } diff --git a/packages/schema/src/command.ts b/packages/schema/src/command.ts index fb88087dbd..81e37157c6 100644 --- a/packages/schema/src/command.ts +++ b/packages/schema/src/command.ts @@ -1,11 +1,11 @@ export * as Command from "./command.js" import { Schema } from "effect" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { optional } from "./schema.js" import { Model } from "./model.js" -const Updated = define({ type: "command.updated", schema: {} }) +const Updated = ephemeral({ type: "command.updated", schema: {} }) export interface Info extends Schema.Schema.Type {} export const Info = Schema.Struct({ diff --git a/packages/schema/src/durable-event-manifest.ts b/packages/schema/src/durable-event-manifest.ts index 6b481dcfb9..8f8870f581 100644 --- a/packages/schema/src/durable-event-manifest.ts +++ b/packages/schema/src/durable-event-manifest.ts @@ -5,11 +5,8 @@ import { SessionEvent } from "./session-event.js" import { SessionV1 } from "./session-v1.js" export const SessionDurable = { - definitions: Event.durable(SessionEvent.DurableDefinitions), + definitions: Event.durableMap(SessionEvent.Definitions), schema: SessionEvent.Durable, } as const -export const Durable = Event.durable([ - ...SessionV1.Event.Definitions.filter((definition) => definition.durable !== undefined), - ...SessionEvent.DurableDefinitions, -]) +export const Durable = Event.durableMap([...SessionV1.Event.Definitions, ...SessionEvent.Definitions]) diff --git a/packages/schema/src/event-manifest.ts b/packages/schema/src/event-manifest.ts index 19fca5e79d..e17ff96bc9 100644 --- a/packages/schema/src/event-manifest.ts +++ b/packages/schema/src/event-manifest.ts @@ -36,8 +36,12 @@ import { VcsEvent } from "./vcs-event.js" import { WorkspaceEvent } from "./workspace-event.js" import { WorktreeEvent } from "./worktree-event.js" -const sessionV1DurableDefinitions = SessionV1.Event.Definitions.filter((definition) => definition.durable !== undefined) -const sessionV1LiveDefinitions = SessionV1.Event.Definitions.filter((definition) => definition.durable === undefined) +const sessionV1DurableDefinitions = SessionV1.Event.Definitions.filter( + (definition) => definition.durability === "durable", +) +const sessionV1LiveDefinitions = SessionV1.Event.Definitions.filter( + (definition) => definition.durability === "ephemeral", +) const coreDefinitions = Event.inventory(...sessionV1DurableDefinitions, ...SessionEvent.Definitions) diff --git a/packages/schema/src/event.ts b/packages/schema/src/event.ts index 1157a2bc44..aac2f0315e 100644 --- a/packages/schema/src/event.ts +++ b/packages/schema/src/event.ts @@ -24,50 +24,70 @@ export type Seq = typeof Seq.Type export const Version = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)).pipe(Schema.brand("Event.Version")) export type Version = typeof Version.Type -export type Definition< +const DurableEnvelope = Schema.Struct({ aggregateID: Schema.String, seq: Seq, version: Version }) +export type DurableEnvelope = typeof DurableEnvelope.Type + +export type DurableDefinition< Type extends string = string, DataSchema extends Schema.Codec = Schema.Codec, > = Schema.Top & { readonly type: Type - readonly durable?: { + readonly durability: "durable" + readonly durable: { readonly version: number readonly aggregate: string } readonly data: DataSchema } +export type EphemeralDefinition< + Type extends string = string, + DataSchema extends Schema.Codec = Schema.Codec, +> = Schema.Top & { + readonly type: Type + readonly durability: "ephemeral" + readonly durable?: never + readonly data: DataSchema +} + +export type Definition< + Type extends string = string, + DataSchema extends Schema.Codec = Schema.Codec, +> = DurableDefinition | EphemeralDefinition + export type Data = Schema.Schema.Type -export type Payload = { +type PayloadBase = { readonly id: ID readonly type: D["type"] readonly data: Data - readonly durable?: { - readonly aggregateID: string - readonly seq: Seq - readonly version: Version - } readonly location?: Location.Ref readonly metadata?: Record } -export function define< - const Type extends string, - const Fields extends Readonly>>, ->(input: { +export type Payload = D extends DurableDefinition + ? PayloadBase & { readonly durable: DurableEnvelope } + : PayloadBase & { readonly durable?: never } + +type Input>>> = { readonly type: Type readonly durable?: { readonly version: number readonly aggregate: string } readonly schema: Fields -}) { +} + +export function durable< + const Type extends string, + const Fields extends Readonly>>, +>(input: Input & { readonly durable: NonNullable["durable"]> }) { const data = Schema.Struct(input.schema) return Schema.Struct({ id: ID, metadata: optional(Schema.Record(Schema.String, Schema.Unknown)), type: Schema.Literal(input.type), - durable: optional(Schema.Struct({ aggregateID: Schema.String, seq: Seq, version: Version })), + durable: DurableEnvelope, location: optional(Location.Ref), data, }) @@ -75,10 +95,34 @@ export function define< .pipe( statics(() => ({ type: input.type, - ...(input.durable === undefined ? {} : { durable: input.durable }), + durability: "durable" as const, + durable: input.durable, data, })), - ) satisfies Definition + ) satisfies DurableDefinition +} + +export function ephemeral< + const Type extends string, + const Fields extends Readonly>>, +>(input: Omit, "durable">) { + const data = Schema.Struct(input.schema) + return Schema.Struct({ + id: ID, + metadata: optional(Schema.Record(Schema.String, Schema.Unknown)), + type: Schema.Literal(input.type), + location: optional(Location.Ref), + data, + }) + .annotate({ identifier: input.type }) + .pipe( + statics(() => ({ + type: input.type, + durability: "ephemeral" as const, + durable: undefined, + data, + })), + ) satisfies EphemeralDefinition } export function inventory>(...definitions: Definitions) { @@ -107,15 +151,15 @@ export function versionedType(type: string, version: number) { return `${type}.${version}` } -export function durable>(definitions: Definitions) { +export function durableMap>(definitions: Definitions) { return readonlyMap( definitions.reduce((result, definition) => { - if (!definition.durable) return result + if (definition.durability !== "durable") return result const key = versionedType(definition.type, definition.durable.version) if (result.has(key)) throw new Error(`Duplicate durable event definition for ${key}`) result.set(key, definition) return result - }, new Map()), + }, new Map()), ) } diff --git a/packages/schema/src/filesystem-watcher.ts b/packages/schema/src/filesystem-watcher.ts index e1e1d557d6..debe0914d1 100644 --- a/packages/schema/src/filesystem-watcher.ts +++ b/packages/schema/src/filesystem-watcher.ts @@ -1,9 +1,9 @@ export * as FileSystemWatcher from "./filesystem-watcher.js" import { Schema } from "effect" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" -const Updated = define({ +const Updated = ephemeral({ type: "file.watcher.updated", schema: { file: Schema.String, diff --git a/packages/schema/src/filesystem.ts b/packages/schema/src/filesystem.ts index 882c3f0abf..3599e48c7c 100644 --- a/packages/schema/src/filesystem.ts +++ b/packages/schema/src/filesystem.ts @@ -2,10 +2,10 @@ export * as FileSystem from "./filesystem.js" import { Schema } from "effect" import { optional } from "./schema.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { NonNegativeInt, PositiveInt, RelativePath } from "./schema.js" -const Edited = define({ +const Edited = ephemeral({ type: "file.edited", schema: { file: Schema.String }, }) diff --git a/packages/schema/src/form.ts b/packages/schema/src/form.ts index 820192d06f..df9d455f13 100644 --- a/packages/schema/src/form.ts +++ b/packages/schema/src/form.ts @@ -1,7 +1,7 @@ export * as Form from "./form.js" import { Schema } from "effect" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { ascending } from "./identifier.js" import { NonNegativeInt, optional, statics } from "./schema.js" @@ -127,9 +127,11 @@ export interface UrlInfo extends Schema.Schema.Type {} export const Info = Schema.Union([FormInfo, UrlInfo]).pipe(Schema.toTaggedUnion("mode")) export type Info = FormInfo | UrlInfo -export const Value = Schema.Union([Schema.String, Schema.Number, Schema.Boolean, Schema.Array(Schema.String)]).annotate({ - identifier: "Form.Value", -}) +export const Value = Schema.Union([Schema.String, Schema.Number, Schema.Boolean, Schema.Array(Schema.String)]).annotate( + { + identifier: "Form.Value", + }, +) export type Value = typeof Value.Type export const Answer = Schema.Record(Schema.String, Value).annotate({ identifier: "Form.Answer" }) @@ -149,8 +151,8 @@ export const Reply = Schema.Struct({ }).annotate({ identifier: "Form.Reply" }) export interface Reply extends Schema.Schema.Type {} -const Created = define({ type: "form.created", schema: { form: Info } }) -const Replied = define({ type: "form.replied", schema: { id: ID, sessionID: Schema.String, answer: Answer } }) -const Cancelled = define({ type: "form.cancelled", schema: { id: ID, sessionID: Schema.String } }) +const Created = ephemeral({ type: "form.created", schema: { form: Info } }) +const Replied = ephemeral({ type: "form.replied", schema: { id: ID, sessionID: Schema.String, answer: Answer } }) +const Cancelled = ephemeral({ type: "form.cancelled", schema: { id: ID, sessionID: Schema.String } }) export const Event = { Created, Replied, Cancelled, Definitions: inventory(Created, Replied, Cancelled) } diff --git a/packages/schema/src/ide-event.ts b/packages/schema/src/ide-event.ts index ec01d9fa36..dd88975b38 100644 --- a/packages/schema/src/ide-event.ts +++ b/packages/schema/src/ide-event.ts @@ -3,7 +3,7 @@ export * as IdeEvent from "./ide-event.js" import { Schema } from "effect" import { Event } from "./event.js" -export const Installed = Event.define({ +export const Installed = Event.ephemeral({ type: "ide.installed", schema: { ide: Schema.String, diff --git a/packages/schema/src/installation-event.ts b/packages/schema/src/installation-event.ts index dde53f7b72..f36a2dfc14 100644 --- a/packages/schema/src/installation-event.ts +++ b/packages/schema/src/installation-event.ts @@ -3,14 +3,14 @@ export * as InstallationEvent from "./installation-event.js" import { Schema } from "effect" import { Event } from "./event.js" -export const Updated = Event.define({ +export const Updated = Event.ephemeral({ type: "installation.updated", schema: { version: Schema.String, }, }) -export const UpdateAvailable = Event.define({ +export const UpdateAvailable = Event.ephemeral({ type: "installation.update-available", schema: { version: Schema.String, diff --git a/packages/schema/src/integration.ts b/packages/schema/src/integration.ts index f1d9c63784..329bf8207f 100644 --- a/packages/schema/src/integration.ts +++ b/packages/schema/src/integration.ts @@ -2,7 +2,7 @@ export * as Integration from "./integration.js" import { Schema } from "effect" import { optional } from "./schema.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { Connection } from "./connection.js" import { ascending } from "./identifier.js" import { statics } from "./schema.js" @@ -76,11 +76,11 @@ export type Method = typeof Method.Type export const Inputs = Schema.Record(Schema.String, Schema.String).annotate({ identifier: "Integration.Inputs" }) export type Inputs = typeof Inputs.Type -const Updated = define({ +const Updated = ephemeral({ type: "integration.updated", schema: {}, }) -const ConnectionUpdated = define({ +const ConnectionUpdated = ephemeral({ type: "integration.connection.updated", schema: { integrationID: ID }, }) diff --git a/packages/schema/src/lsp-event.ts b/packages/schema/src/lsp-event.ts index 68f31fbc6c..7928fa00a0 100644 --- a/packages/schema/src/lsp-event.ts +++ b/packages/schema/src/lsp-event.ts @@ -2,6 +2,6 @@ export * as LspEvent from "./lsp-event.js" import { Event } from "./event.js" -export const Updated = Event.define({ type: "lsp.updated", schema: {} }) +export const Updated = Event.ephemeral({ type: "lsp.updated", schema: {} }) export const Definitions = Event.inventory(Updated) diff --git a/packages/schema/src/mcp-event.ts b/packages/schema/src/mcp-event.ts index 70be7a91d8..f4221335e9 100644 --- a/packages/schema/src/mcp-event.ts +++ b/packages/schema/src/mcp-event.ts @@ -3,14 +3,14 @@ export * as McpEvent from "./mcp-event.js" import { Schema } from "effect" import { Event } from "./event.js" -export const ToolsChanged = Event.define({ +export const ToolsChanged = Event.ephemeral({ type: "mcp.tools.changed", schema: { server: Schema.String, }, }) -export const BrowserOpenFailed = Event.define({ +export const BrowserOpenFailed = Event.ephemeral({ type: "mcp.browser.open.failed", schema: { mcpName: Schema.String, @@ -20,7 +20,7 @@ export const BrowserOpenFailed = Event.define({ // Emitted whenever a server's connection status settles (connected, failed, needs_auth, closed) so // observers can refresh status without polling. -export const StatusChanged = Event.define({ +export const StatusChanged = Event.ephemeral({ type: "mcp.status.changed", schema: { server: Schema.String, diff --git a/packages/schema/src/models-dev.ts b/packages/schema/src/models-dev.ts index 60d630ecfa..925639aef3 100644 --- a/packages/schema/src/models-dev.ts +++ b/packages/schema/src/models-dev.ts @@ -1,8 +1,8 @@ export * as ModelsDev from "./models-dev.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" -const Refreshed = define({ +const Refreshed = ephemeral({ type: "models-dev.refreshed", schema: {}, }) diff --git a/packages/schema/src/permission.ts b/packages/schema/src/permission.ts index de86fd1b1e..ca79081eda 100644 --- a/packages/schema/src/permission.ts +++ b/packages/schema/src/permission.ts @@ -2,7 +2,7 @@ export * as Permission from "./permission.js" import { Schema } from "effect" import { optional } from "./schema.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { ascending } from "./identifier.js" import { SessionID } from "./session-id.js" import { statics } from "./schema.js" @@ -40,8 +40,8 @@ export interface Request extends Schema.Schema.Type {} export const Reply = Schema.Literals(["once", "always", "reject"]).annotate({ identifier: "PermissionV2.Reply" }) export type Reply = typeof Reply.Type -const Asked = define({ type: "permission.v2.asked", schema: Request.fields }) -const Replied = define({ +const Asked = ephemeral({ type: "permission.v2.asked", schema: Request.fields }) +const Replied = ephemeral({ type: "permission.v2.replied", schema: { sessionID: SessionID, diff --git a/packages/schema/src/plugin.ts b/packages/schema/src/plugin.ts index 3a9b8b55b1..40379e3051 100644 --- a/packages/schema/src/plugin.ts +++ b/packages/schema/src/plugin.ts @@ -1,7 +1,7 @@ export * as Plugin from "./plugin.js" import { Schema } from "effect" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" export const ID = Schema.String.pipe(Schema.brand("Plugin.ID")) export type ID = typeof ID.Type @@ -11,7 +11,7 @@ export const Info = Schema.Struct({ id: ID, }).annotate({ identifier: "Plugin.Info" }) -const Added = define({ +const Added = ephemeral({ type: "plugin.added", schema: { id: ID }, }) diff --git a/packages/schema/src/project-directories.ts b/packages/schema/src/project-directories.ts index d80322287c..aa5d51f0cf 100644 --- a/packages/schema/src/project-directories.ts +++ b/packages/schema/src/project-directories.ts @@ -1,9 +1,9 @@ export * as ProjectDirectories from "./project-directories.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { Project } from "./project.js" -const Updated = define({ +const Updated = ephemeral({ type: "project.directories.updated", schema: { projectID: Project.ID }, }) diff --git a/packages/schema/src/project.ts b/packages/schema/src/project.ts index fce8d19ebf..e8524300a9 100644 --- a/packages/schema/src/project.ts +++ b/packages/schema/src/project.ts @@ -1,7 +1,7 @@ export * as Project from "./project.js" import { Schema } from "effect" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { AbsolutePath, NonNegativeInt, optional } from "./schema.js" import { ProjectID } from "./project-id.js" @@ -56,5 +56,5 @@ export const Info = Schema.Struct({ }).annotate({ identifier: "Project" }) export interface Info extends Schema.Schema.Type {} -const Updated = define({ type: "project.updated", schema: Info.fields }) +const Updated = ephemeral({ type: "project.updated", schema: Info.fields }) export const Event = { Updated, Definitions: inventory(Updated) } diff --git a/packages/schema/src/pty.ts b/packages/schema/src/pty.ts index 58dc9cb833..b2da9093f0 100644 --- a/packages/schema/src/pty.ts +++ b/packages/schema/src/pty.ts @@ -2,7 +2,7 @@ export * as Pty from "./pty.js" import { Schema } from "effect" import { optional } from "./schema.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { ascending } from "./identifier.js" import { NonNegativeInt, PositiveInt, statics } from "./schema.js" @@ -31,10 +31,10 @@ export const Info = Schema.Struct({ }).annotate({ identifier: "Pty" }) export interface Info extends Schema.Schema.Type {} -const Created = define({ type: "pty.created", schema: { info: Info } }) -const Updated = define({ type: "pty.updated", schema: { info: Info } }) -const Exited = define({ type: "pty.exited", schema: { id: ID, exitCode: NonNegativeInt } }) -const Deleted = define({ type: "pty.deleted", schema: { id: ID } }) +const Created = ephemeral({ type: "pty.created", schema: { info: Info } }) +const Updated = ephemeral({ type: "pty.updated", schema: { info: Info } }) +const Exited = ephemeral({ type: "pty.exited", schema: { id: ID, exitCode: NonNegativeInt } }) +const Deleted = ephemeral({ type: "pty.deleted", schema: { id: ID } }) export const Event = { Created, Updated, Exited, Deleted, Definitions: inventory(Created, Updated, Exited, Deleted) } export const CreateInput = Schema.Struct({ diff --git a/packages/schema/src/question.ts b/packages/schema/src/question.ts index 56fa7a277b..617ad2a895 100644 --- a/packages/schema/src/question.ts +++ b/packages/schema/src/question.ts @@ -2,7 +2,7 @@ export * as Question from "./question.js" import { Schema } from "effect" import { optional } from "./schema.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { ascending } from "./identifier.js" import { SessionID } from "./session-id.js" import { statics } from "./schema.js" @@ -67,8 +67,8 @@ export const Reply = Schema.Struct({ }).annotate({ identifier: "QuestionV2.Reply" }) export interface Reply extends Schema.Schema.Type {} -const Asked = define({ type: "question.v2.asked", schema: Request.fields }) -const Replied = define({ +const Asked = ephemeral({ type: "question.v2.asked", schema: Request.fields }) +const Replied = ephemeral({ type: "question.v2.replied", schema: { sessionID: SessionID, @@ -76,7 +76,7 @@ const Replied = define({ answers: Schema.Array(Answer), }, }) -const Rejected = define({ +const Rejected = ephemeral({ type: "question.v2.rejected", schema: { sessionID: SessionID, diff --git a/packages/schema/src/reference.ts b/packages/schema/src/reference.ts index 5d63bea9df..9dd277f62b 100644 --- a/packages/schema/src/reference.ts +++ b/packages/schema/src/reference.ts @@ -2,10 +2,10 @@ export * as Reference from "./reference.js" import { Schema } from "effect" import { optional } from "./schema.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { AbsolutePath } from "./schema.js" -const Updated = define({ type: "reference.updated", schema: {} }) +const Updated = ephemeral({ type: "reference.updated", schema: {} }) export const Event = { Updated, Definitions: inventory(Updated) } export interface LocalSource extends Schema.Schema.Type {} diff --git a/packages/schema/src/server-event.ts b/packages/schema/src/server-event.ts index ec8599ad32..4f4963cfbf 100644 --- a/packages/schema/src/server-event.ts +++ b/packages/schema/src/server-event.ts @@ -2,7 +2,7 @@ export * as ServerEvent from "./server-event.js" import { Event } from "./event.js" -export const Connected = Event.define({ type: "server.connected", schema: {} }) -export const Disposed = Event.define({ type: "global.disposed", schema: {} }) +export const Connected = Event.ephemeral({ type: "server.connected", schema: {} }) +export const Disposed = Event.ephemeral({ type: "global.disposed", schema: {} }) export const Definitions = Event.inventory(Connected, Disposed) diff --git a/packages/schema/src/session-compaction-event.ts b/packages/schema/src/session-compaction-event.ts index 56782fd5d5..a9c0144c98 100644 --- a/packages/schema/src/session-compaction-event.ts +++ b/packages/schema/src/session-compaction-event.ts @@ -3,7 +3,7 @@ export * as SessionCompactionEvent from "./session-compaction-event.js" import { Event } from "./event.js" import { SessionID } from "./session-id.js" -export const Compacted = Event.define({ +export const Compacted = Event.ephemeral({ type: "session.compacted", schema: { sessionID: SessionID, diff --git a/packages/schema/src/session-event.ts b/packages/schema/src/session-event.ts index df78fa1493..fe1a698555 100644 --- a/packages/schema/src/session-event.ts +++ b/packages/schema/src/session-event.ts @@ -51,7 +51,7 @@ const stepSettlementOptions = { export const UnknownError = SessionMessage.UnknownError export type UnknownError = SessionMessage.UnknownError -export const AgentSwitched = Event.define({ +export const AgentSwitched = Event.durable({ type: "session.next.agent.switched", ...options, schema: { @@ -62,7 +62,7 @@ export const AgentSwitched = Event.define({ }) export type AgentSwitched = typeof AgentSwitched.Type -export const ModelSwitched = Event.define({ +export const ModelSwitched = Event.durable({ type: "session.next.model.switched", ...options, schema: { @@ -73,7 +73,7 @@ export const ModelSwitched = Event.define({ }) export type ModelSwitched = typeof ModelSwitched.Type -export const Moved = Event.define({ +export const Moved = Event.durable({ type: "session.next.moved", ...options, schema: { @@ -84,7 +84,7 @@ export const Moved = Event.define({ }) export type Moved = typeof Moved.Type -export const Renamed = Event.define({ +export const Renamed = Event.durable({ type: "session.next.renamed", ...options, schema: { @@ -94,7 +94,7 @@ export const Renamed = Event.define({ }) export type Renamed = typeof Renamed.Type -export const Forked = Event.define({ +export const Forked = Event.durable({ type: "session.next.forked", ...options, schema: { @@ -105,21 +105,21 @@ export const Forked = Event.define({ }) export type Forked = typeof Forked.Type -export const Prompted = Event.define({ +export const Prompted = Event.durable({ type: "session.next.prompted", ...options, schema: PromptFields, }) export type Prompted = typeof Prompted.Type -export const PromptAdmitted = Event.define({ +export const PromptAdmitted = Event.durable({ type: "session.next.prompt.admitted", ...options, schema: PromptFields, }) export type PromptAdmitted = typeof PromptAdmitted.Type -export const ExecutionSettled = Event.define({ +export const ExecutionSettled = Event.ephemeral({ type: "session.next.execution.settled", schema: { ...Base, @@ -129,7 +129,7 @@ export const ExecutionSettled = Event.define({ }) export type ExecutionSettled = typeof ExecutionSettled.Type -export const ContextUpdated = Event.define({ +export const ContextUpdated = Event.durable({ type: "session.next.context.updated", ...options, schema: { @@ -140,7 +140,7 @@ export const ContextUpdated = Event.define({ }) export type ContextUpdated = typeof ContextUpdated.Type -export const Synthetic = Event.define({ +export const Synthetic = Event.durable({ type: "session.next.synthetic", ...options, schema: { @@ -154,7 +154,7 @@ export const Synthetic = Event.define({ export type Synthetic = typeof Synthetic.Type export namespace Skill { - export const Activated = Event.define({ + export const Activated = Event.durable({ type: "session.next.skill.activated", ...options, schema: { @@ -168,7 +168,7 @@ export namespace Skill { } export namespace Shell { - export const Started = Event.define({ + export const Started = Event.durable({ type: "session.next.shell.started", ...options, schema: { @@ -180,7 +180,7 @@ export namespace Shell { }) export type Started = typeof Started.Type - export const Ended = Event.define({ + export const Ended = Event.durable({ type: "session.next.shell.ended", ...options, schema: { @@ -193,7 +193,7 @@ export namespace Shell { } export namespace Step { - export const Started = Event.define({ + export const Started = Event.durable({ type: "session.next.step.started", ...options, schema: { @@ -206,7 +206,7 @@ export namespace Step { }) export type Started = typeof Started.Type - export const Ended = Event.define({ + export const Ended = Event.durable({ type: "session.next.step.ended", ...stepSettlementOptions, schema: { @@ -229,7 +229,7 @@ export namespace Step { }) export type Ended = typeof Ended.Type - export const Failed = Event.define({ + export const Failed = Event.durable({ type: "session.next.step.failed", ...stepSettlementOptions, schema: { @@ -242,7 +242,7 @@ export namespace Step { } export namespace Text { - export const Started = Event.define({ + export const Started = Event.durable({ type: "session.next.text.started", ...options, schema: { @@ -254,7 +254,7 @@ export namespace Text { export type Started = typeof Started.Type // Stream fragments are live-only; Text.Ended is the replayable full-value boundary. - export const Delta = Event.define({ + export const Delta = Event.ephemeral({ type: "session.next.text.delta", schema: { ...Base, @@ -265,7 +265,7 @@ export namespace Text { }) export type Delta = typeof Delta.Type - export const Ended = Event.define({ + export const Ended = Event.durable({ type: "session.next.text.ended", ...options, schema: { @@ -279,7 +279,7 @@ export namespace Text { } export namespace Reasoning { - export const Started = Event.define({ + export const Started = Event.durable({ type: "session.next.reasoning.started", ...options, schema: { @@ -292,7 +292,7 @@ export namespace Reasoning { export type Started = typeof Started.Type // Stream fragments are live-only; Reasoning.Ended is the replayable full-value boundary. - export const Delta = Event.define({ + export const Delta = Event.ephemeral({ type: "session.next.reasoning.delta", schema: { ...Base, @@ -303,7 +303,7 @@ export namespace Reasoning { }) export type Delta = typeof Delta.Type - export const Ended = Event.define({ + export const Ended = Event.durable({ type: "session.next.reasoning.ended", ...options, schema: { @@ -325,7 +325,7 @@ export namespace Tool { } export namespace Input { - export const Started = Event.define({ + export const Started = Event.durable({ type: "session.next.tool.input.started", ...options, schema: { @@ -336,7 +336,7 @@ export namespace Tool { export type Started = typeof Started.Type // Stream fragments are live-only; Input.Ended is the replayable raw-input boundary. - export const Delta = Event.define({ + export const Delta = Event.ephemeral({ type: "session.next.tool.input.delta", schema: { ...ToolBase, @@ -345,7 +345,7 @@ export namespace Tool { }) export type Delta = typeof Delta.Type - export const Ended = Event.define({ + export const Ended = Event.durable({ type: "session.next.tool.input.ended", ...options, schema: { @@ -356,7 +356,7 @@ export namespace Tool { export type Ended = typeof Ended.Type } - export const Called = Event.define({ + export const Called = Event.durable({ type: "session.next.tool.called", ...options, schema: { @@ -375,7 +375,7 @@ export namespace Tool { * Replayable bounded running-tool state. Tools should checkpoint semantic * transitions or at a bounded cadence, not persist every stdout/stderr chunk. */ - export const Progress = Event.define({ + export const Progress = Event.durable({ type: "session.next.tool.progress", ...options, schema: { @@ -386,7 +386,7 @@ export namespace Tool { }) export type Progress = typeof Progress.Type - export const Success = Event.define({ + export const Success = Event.durable({ type: "session.next.tool.success", ...options, schema: { @@ -403,7 +403,7 @@ export namespace Tool { }) export type Success = typeof Success.Type - export const Failed = Event.define({ + export const Failed = Event.durable({ type: "session.next.tool.failed", ...options, schema: { @@ -431,7 +431,7 @@ export const RetryError = Schema.Struct({ }) export interface RetryError extends Schema.Schema.Type {} -export const Retried = Event.define({ +export const Retried = Event.durable({ type: "session.next.retried", ...options, schema: { @@ -443,7 +443,7 @@ export const Retried = Event.define({ export type Retried = typeof Retried.Type export namespace Compaction { - export const Started = Event.define({ + export const Started = Event.durable({ type: "session.next.compaction.started", ...options, schema: { @@ -454,7 +454,7 @@ export namespace Compaction { }) export type Started = typeof Started.Type - export const Delta = Event.define({ + export const Delta = Event.ephemeral({ type: "session.next.compaction.delta", schema: { ...Base, @@ -464,7 +464,7 @@ export namespace Compaction { }) export type Delta = typeof Delta.Type - export const Ended = Event.define({ + export const Ended = Event.durable({ type: "session.next.compaction.ended", ...options, schema: { @@ -479,53 +479,19 @@ export namespace Compaction { } export namespace RevertEvent { - export const Staged = Event.define({ + export const Staged = Event.durable({ type: "session.next.revert.staged", ...options, schema: { ...Base, revert: Revert.State }, }) - export const Cleared = Event.define({ type: "session.next.revert.cleared", ...options, schema: Base }) - export const Committed = Event.define({ + export const Cleared = Event.durable({ type: "session.next.revert.cleared", ...options, schema: Base }) + export const Committed = Event.durable({ type: "session.next.revert.committed", ...options, schema: { ...Base, messageID: SessionMessage.ID }, }) } -export const DurableDefinitions = Event.inventory( - AgentSwitched, - ModelSwitched, - Moved, - Renamed, - Forked, - Prompted, - PromptAdmitted, - ContextUpdated, - Synthetic, - Skill.Activated, - Shell.Started, - Shell.Ended, - Step.Started, - Step.Ended, - Step.Failed, - Text.Started, - Text.Ended, - Tool.Input.Started, - Tool.Input.Ended, - Tool.Called, - Tool.Progress, - Tool.Success, - Tool.Failed, - Reasoning.Started, - Reasoning.Ended, - Retried, - Compaction.Started, - Compaction.Ended, - RevertEvent.Staged, - RevertEvent.Cleared, - RevertEvent.Committed, -) - export const Definitions = Event.inventory( AgentSwitched, ModelSwitched, @@ -565,6 +531,10 @@ export const Definitions = Event.inventory( RevertEvent.Committed, ) +export const DurableDefinitions = Event.inventory( + ...Definitions.filter((definition) => definition.durability === "durable"), +) + export const Durable = Schema.Union(DurableDefinitions, { mode: "oneOf" }) .pipe(Schema.toTaggedUnion("type")) .annotate({ identifier: "SessionDurableEvent" }) diff --git a/packages/schema/src/session-status-event.ts b/packages/schema/src/session-status-event.ts index 331c58ba1c..03e6940215 100644 --- a/packages/schema/src/session-status-event.ts +++ b/packages/schema/src/session-status-event.ts @@ -32,7 +32,7 @@ export const Info = Schema.Union([ ]).annotate({ identifier: "SessionStatus" }) export type Info = Schema.Schema.Type -export const Status = Event.define({ +export const Status = Event.ephemeral({ type: "session.status", schema: { sessionID: SessionID, @@ -41,7 +41,7 @@ export const Status = Event.define({ }) // deprecated -export const Idle = Event.define({ +export const Idle = Event.ephemeral({ type: "session.idle", schema: { sessionID: SessionID, diff --git a/packages/schema/src/session-todo.ts b/packages/schema/src/session-todo.ts index f72abcd17e..f4d68268ff 100644 --- a/packages/schema/src/session-todo.ts +++ b/packages/schema/src/session-todo.ts @@ -1,7 +1,7 @@ export * as SessionTodo from "./session-todo.js" import { Schema } from "effect" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { SessionID } from "./session-id.js" export const Info = Schema.Struct({ @@ -15,7 +15,7 @@ export const Info = Schema.Struct({ }).annotate({ identifier: "Todo" }) export interface Info extends Schema.Schema.Type {} -const Updated = define({ +const Updated = ephemeral({ type: "todo.updated", schema: { sessionID: SessionID, diff --git a/packages/schema/src/shell.ts b/packages/schema/src/shell.ts index a627a15950..fbe877c974 100644 --- a/packages/schema/src/shell.ts +++ b/packages/schema/src/shell.ts @@ -2,7 +2,7 @@ export * as Shell from "./shell.js" import { Schema } from "effect" import { optional } from "./schema.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" import { ascending } from "./identifier.js" import { NonNegativeInt, statics } from "./schema.js" @@ -49,9 +49,9 @@ export const Info = Schema.Struct({ }).annotate({ identifier: "Shell" }) export interface Info extends Schema.Schema.Type {} -const Created = define({ type: "shell.created", schema: { info: Info } }) -const Exited = define({ type: "shell.exited", schema: { id: ID, exit: optional(Schema.Number), status: Status } }) -const Deleted = define({ type: "shell.deleted", schema: { id: ID } }) +const Created = ephemeral({ type: "shell.created", schema: { info: Info } }) +const Exited = ephemeral({ type: "shell.exited", schema: { id: ID, exit: optional(Schema.Number), status: Status } }) +const Deleted = ephemeral({ type: "shell.deleted", schema: { id: ID } }) export const Event = { Created, Exited, Deleted, Definitions: inventory(Created, Exited, Deleted) } export const CreateInput = Schema.Struct({ diff --git a/packages/schema/src/skill.ts b/packages/schema/src/skill.ts index bf0dd7aa2b..184266f2ad 100644 --- a/packages/schema/src/skill.ts +++ b/packages/schema/src/skill.ts @@ -3,7 +3,7 @@ export * as Skill from "./skill.js" import { Schema } from "effect" import { optional } from "./schema.js" import { AbsolutePath } from "./schema.js" -import { define, inventory } from "./event.js" +import { ephemeral, inventory } from "./event.js" export interface DirectorySource extends Schema.Schema.Type {} export const DirectorySource = Schema.Struct({ @@ -27,7 +27,7 @@ export const Info = Schema.Struct({ content: Schema.String, }).annotate({ identifier: "SkillV2.Info" }) -const Updated = define({ type: "skill.updated", schema: {} }) +const Updated = ephemeral({ type: "skill.updated", schema: {} }) export const Event = { Updated, Definitions: inventory(Updated) } export interface EmbeddedSource extends Schema.Schema.Type {} diff --git a/packages/schema/src/tui-event.ts b/packages/schema/src/tui-event.ts index 0de2b6e369..ba9fd29ef8 100644 --- a/packages/schema/src/tui-event.ts +++ b/packages/schema/src/tui-event.ts @@ -8,9 +8,9 @@ import { SessionID } from "./session-id.js" const DEFAULT_TOAST_DURATION = 5000 -export const PromptAppend = Event.define({ type: "tui.prompt.append", schema: { text: Schema.String } }) +export const PromptAppend = Event.ephemeral({ type: "tui.prompt.append", schema: { text: Schema.String } }) -export const CommandExecute = Event.define({ +export const CommandExecute = Event.ephemeral({ type: "tui.command.execute", schema: { command: Schema.Union([ @@ -38,7 +38,7 @@ export const CommandExecute = Event.define({ }, }) -export const ToastShow = Event.define({ +export const ToastShow = Event.ephemeral({ type: "tui.toast.show", schema: { title: optional(Schema.String), @@ -50,7 +50,7 @@ export const ToastShow = Event.define({ }, }) -export const SessionSelect = Event.define({ +export const SessionSelect = Event.ephemeral({ type: "tui.session.select", schema: { sessionID: SessionID.annotate({ description: "Session ID to navigate to" }), diff --git a/packages/schema/src/v1/legacy-event.ts b/packages/schema/src/v1/legacy-event.ts index 12037e104f..ac22fff5e3 100644 --- a/packages/schema/src/v1/legacy-event.ts +++ b/packages/schema/src/v1/legacy-event.ts @@ -1,11 +1,11 @@ export * as LegacyEvent from "./legacy-event.js" import { Schema } from "effect" -import { define, inventory } from "../event.js" +import { ephemeral, inventory } from "../event.js" import { SessionID } from "../session-id.js" import { SessionV1 } from "./session.js" -export const CommandExecuted = define({ +export const CommandExecuted = ephemeral({ type: "command.executed", schema: { name: Schema.String, diff --git a/packages/schema/src/v1/permission.ts b/packages/schema/src/v1/permission.ts index af1d0098b8..1faa46a508 100644 --- a/packages/schema/src/v1/permission.ts +++ b/packages/schema/src/v1/permission.ts @@ -1,7 +1,7 @@ export * as PermissionV1 from "./permission.js" import { Schema } from "effect" -import { define, inventory } from "../event.js" +import { ephemeral, inventory } from "../event.js" import { ascending } from "../identifier.js" import { Project } from "../project.js" import { statics } from "../schema.js" @@ -58,8 +58,8 @@ export const ReplyInput = Schema.Struct({ requestID: ID, ...ReplyBody.fields }). }) export type ReplyInput = typeof ReplyInput.Type -const Asked = define({ type: "permission.asked", schema: Request.fields }) -const Replied = define({ +const Asked = ephemeral({ type: "permission.asked", schema: Request.fields }) +const Replied = ephemeral({ type: "permission.replied", schema: { sessionID: SessionID, requestID: ID, reply: Reply }, }) diff --git a/packages/schema/src/v1/question.ts b/packages/schema/src/v1/question.ts index 557646c44e..da47f37ffd 100644 --- a/packages/schema/src/v1/question.ts +++ b/packages/schema/src/v1/question.ts @@ -1,7 +1,7 @@ export * as QuestionV1 from "./question.js" import { Schema } from "effect" -import { define, inventory } from "../event.js" +import { ephemeral, inventory } from "../event.js" import { ascending } from "../identifier.js" import { statics } from "../schema.js" import { SessionID } from "../session-id.js" @@ -55,9 +55,9 @@ export const Rejected = Schema.Struct({ sessionID: SessionID, requestID: ID }).a identifier: "QuestionRejected", }) -const Asked = define({ type: "question.asked", schema: Request.fields }) -const RepliedEvent = define({ type: "question.replied", schema: Replied.fields }) -const RejectedEvent = define({ type: "question.rejected", schema: Rejected.fields }) +const Asked = ephemeral({ type: "question.asked", schema: Request.fields }) +const RepliedEvent = ephemeral({ type: "question.replied", schema: Replied.fields }) +const RejectedEvent = ephemeral({ type: "question.rejected", schema: Rejected.fields }) export const Event = { Asked, Replied: RepliedEvent, diff --git a/packages/schema/src/v1/session.ts b/packages/schema/src/v1/session.ts index 452ba3a186..1c2827f0d8 100644 --- a/packages/schema/src/v1/session.ts +++ b/packages/schema/src/v1/session.ts @@ -1,7 +1,7 @@ export * as SessionV1 from "./session.js" import { Effect, Schema, Types } from "effect" -import { define, inventory } from "../event.js" +import { durable, ephemeral, inventory } from "../event.js" import { FileDiff } from "../file-diff.js" import { Project } from "../project.js" import { Provider } from "../provider.js" @@ -569,7 +569,7 @@ export const SessionInfo = Schema.Struct({ export type SessionInfo = typeof SessionInfo.Type const events = { - Created: define({ + Created: durable({ type: "session.created", ...options, schema: { @@ -577,7 +577,7 @@ const events = { info: SessionInfo, }, }), - Updated: define({ + Updated: durable({ type: "session.updated", ...options, schema: { @@ -585,7 +585,7 @@ const events = { info: SessionInfo, }, }), - Deleted: define({ + Deleted: durable({ type: "session.deleted", ...options, schema: { @@ -593,7 +593,7 @@ const events = { info: SessionInfo, }, }), - MessageUpdated: define({ + MessageUpdated: durable({ type: "message.updated", ...options, schema: { @@ -601,7 +601,7 @@ const events = { info: Info, }, }), - MessageRemoved: define({ + MessageRemoved: durable({ type: "message.removed", ...options, schema: { @@ -609,7 +609,7 @@ const events = { messageID: MessageID, }, }), - PartUpdated: define({ + PartUpdated: durable({ type: "message.part.updated", ...options, schema: { @@ -618,7 +618,7 @@ const events = { time: Schema.Finite, }, }), - PartRemoved: define({ + PartRemoved: durable({ type: "message.part.removed", ...options, schema: { @@ -629,7 +629,7 @@ const events = { }), } -export const PartDelta = define({ +export const PartDelta = ephemeral({ type: "message.part.delta", schema: { sessionID: SessionID, @@ -640,7 +640,7 @@ export const PartDelta = define({ }, }) -export const Diff = define({ +export const Diff = ephemeral({ type: "session.diff", schema: { sessionID: SessionID, @@ -648,7 +648,7 @@ export const Diff = define({ }, }) -export const Error = define({ +export const Error = ephemeral({ type: "session.error", schema: { sessionID: Schema.optional(SessionID), diff --git a/packages/schema/src/vcs-event.ts b/packages/schema/src/vcs-event.ts index 2428b432b4..5ece3cd844 100644 --- a/packages/schema/src/vcs-event.ts +++ b/packages/schema/src/vcs-event.ts @@ -4,7 +4,7 @@ import { Schema } from "effect" import { optional } from "./schema.js" import { Event } from "./event.js" -export const BranchUpdated = Event.define({ +export const BranchUpdated = Event.ephemeral({ type: "vcs.branch.updated", schema: { branch: optional(Schema.String), diff --git a/packages/schema/src/workspace-event.ts b/packages/schema/src/workspace-event.ts index 6468a4410c..2b6a752ef5 100644 --- a/packages/schema/src/workspace-event.ts +++ b/packages/schema/src/workspace-event.ts @@ -10,21 +10,21 @@ export const ConnectionStatus = Schema.Struct({ }).annotate({ identifier: "WorkspaceEvent.ConnectionStatus" }) export interface ConnectionStatus extends Schema.Schema.Type {} -export const Ready = Event.define({ +export const Ready = Event.ephemeral({ type: "workspace.ready", schema: { name: Schema.String, }, }) -export const Failed = Event.define({ +export const Failed = Event.ephemeral({ type: "workspace.failed", schema: { message: Schema.String, }, }) -export const Status = Event.define({ +export const Status = Event.ephemeral({ type: "workspace.status", schema: ConnectionStatus.fields, }) diff --git a/packages/schema/src/worktree-event.ts b/packages/schema/src/worktree-event.ts index 2acb72fe7e..db80d794e5 100644 --- a/packages/schema/src/worktree-event.ts +++ b/packages/schema/src/worktree-event.ts @@ -4,7 +4,7 @@ import { Schema } from "effect" import { optional } from "./schema.js" import { Event } from "./event.js" -export const Ready = Event.define({ +export const Ready = Event.ephemeral({ type: "worktree.ready", schema: { name: Schema.String, @@ -12,7 +12,7 @@ export const Ready = Event.define({ }, }) -export const Failed = Event.define({ +export const Failed = Event.ephemeral({ type: "worktree.failed", schema: { message: Schema.String, diff --git a/packages/schema/test/event-manifest.test.ts b/packages/schema/test/event-manifest.test.ts index 452b0ac149..4e0c6eaa18 100644 --- a/packages/schema/test/event-manifest.test.ts +++ b/packages/schema/test/event-manifest.test.ts @@ -1,5 +1,15 @@ import { describe, expect, test } from "bun:test" -import { Agent, FileSystem, Form, Integration, Permission, Project, Reference, Session, Workspace } from "../src/index.js" +import { + Agent, + FileSystem, + Form, + Integration, + Permission, + Project, + Reference, + Session, + Workspace, +} from "../src/index.js" import { EventManifest } from "../src/event-manifest.js" import { IdeEvent } from "../src/ide-event.js" import { SessionEvent } from "../src/session-event.js" @@ -9,11 +19,11 @@ import { WorkspaceEvent } from "../src/workspace-event.js" describe("public event manifest", () => { test("owns the complete public event surface", () => { - expect(EventManifest.ServerDefinitions.filter((definition) => definition.type !== "agent.updated").length).toBe(86) + expect(EventManifest.ServerDefinitions).toContain(Agent.Event.Updated) expect(EventManifest.ServerDefinitions.filter((definition) => definition.type === "agent.updated")).toEqual([ Agent.Event.Updated, ]) - expect(EventManifest.Definitions.filter((definition) => definition.type !== "agent.updated").length).toBe(101) + expect(EventManifest.Definitions).toContain(Agent.Event.Updated) expect(EventManifest.Definitions.filter((definition) => definition.type === "agent.updated")).toEqual([ Agent.Event.Updated, ]) @@ -29,7 +39,9 @@ describe("public event manifest", () => { SessionV1.Event.Diff, SessionV1.Event.Error, ]) - expect(Array.from(EventManifest.Latest.keys()).filter((type) => type !== "agent.updated").length).toBe(101) + expect(Array.from(EventManifest.Latest.keys())).toEqual( + EventManifest.Definitions.map((definition) => definition.type), + ) expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated) expect(Agent.Event.Updated.durable).toBeUndefined() expect(EventManifest.Durable.has("agent.updated")).toBe(false) @@ -62,4 +74,53 @@ describe("public event manifest", () => { expect(EventManifest.Durable.has("session.next.step.ended.1")).toBe(false) expect(EventManifest.Durable.get("session.next.step.ended.2")).toBe(SessionEvent.Step.Ended) }) + + test("derives durable definitions from explicit definition durability", () => { + expect(Array.from(EventManifest.Durable.keys()).toSorted()).toEqual( + [ + "session.created.1", + "session.updated.1", + "session.deleted.1", + "message.updated.1", + "message.removed.1", + "message.part.updated.1", + "message.part.removed.1", + "session.next.agent.switched.1", + "session.next.model.switched.1", + "session.next.moved.1", + "session.next.renamed.1", + "session.next.forked.1", + "session.next.prompted.1", + "session.next.prompt.admitted.1", + "session.next.context.updated.1", + "session.next.synthetic.1", + "session.next.skill.activated.1", + "session.next.shell.started.1", + "session.next.shell.ended.1", + "session.next.step.started.1", + "session.next.step.ended.2", + "session.next.step.failed.2", + "session.next.text.started.1", + "session.next.text.ended.1", + "session.next.tool.input.started.1", + "session.next.tool.input.ended.1", + "session.next.tool.called.1", + "session.next.tool.progress.1", + "session.next.tool.success.1", + "session.next.tool.failed.1", + "session.next.reasoning.started.1", + "session.next.reasoning.ended.1", + "session.next.retried.1", + "session.next.compaction.started.1", + "session.next.compaction.ended.1", + "session.next.revert.staged.1", + "session.next.revert.cleared.1", + "session.next.revert.committed.1", + ].toSorted(), + ) + expect(SessionEvent.DurableDefinitions).toEqual( + SessionEvent.Definitions.filter((definition) => definition.durability === "durable"), + ) + expect(EventManifest.Definitions.every((definition) => definition.durability !== undefined)).toBe(true) + }) }) diff --git a/packages/schema/test/event.test.ts b/packages/schema/test/event.test.ts index a05dca0c6e..404b49aa74 100644 --- a/packages/schema/test/event.test.ts +++ b/packages/schema/test/event.test.ts @@ -6,17 +6,17 @@ import { EventLog } from "../src/event-log.js" describe("public event schemas", () => { test("definition is pure", () => { const definitions = Event.inventory() - Event.define({ type: "test.pure", schema: { value: Schema.String } }) + Event.ephemeral({ type: "test.pure", schema: { value: Schema.String } }) expect(definitions).toEqual([]) }) test("latest selection is independent of declaration order", () => { - const historical = Event.define({ + const historical = Event.durable({ type: "test.versioned", durable: { aggregate: "id", version: 1 }, schema: { id: Schema.String }, }) - const current = Event.define({ + const current = Event.durable({ type: "test.versioned", durable: { aggregate: "id", version: 2 }, schema: { id: Schema.String, value: Schema.String }, @@ -27,13 +27,13 @@ describe("public event schemas", () => { }) test("durable definitions are indexed by type and version", () => { - const definition = Event.define({ + const definition = Event.durable({ type: "test.durable", durable: { aggregate: "id", version: 1 }, schema: { id: Schema.String }, }) - expect(Event.durable([definition]).get("test.durable.1")).toBe(definition) + expect(Event.durableMap([definition]).get("test.durable.1")).toBe(definition) }) test("synced marker encodes the captured watermark", () => { diff --git a/packages/sdk-next/test/embedded.test.ts b/packages/sdk-next/test/embedded.test.ts index 572cdd9665..81f3d480ad 100644 --- a/packages/sdk-next/test/embedded.test.ts +++ b/packages/sdk-next/test/embedded.test.ts @@ -149,7 +149,7 @@ it.live( const opencode = yield* fixture.sdk.OpenCode.create() const id = sessionID(fixture) const connected = yield* Latch.make(false) - const prompted = yield* Deferred.make() + const prompted = yield* Deferred.make>() yield* opencode.events.subscribe().pipe( Stream.runForEach((event) => diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 2f476f2844..a1ba35e5ae 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2928,14 +2928,14 @@ export type SessionDurableEvent = | SessionNextStepFailed | SessionNextTextStarted | SessionNextTextEnded + | SessionNextReasoningStarted + | SessionNextReasoningEnded | SessionNextToolInputStarted | SessionNextToolInputEnded | SessionNextToolCalled | SessionNextToolProgress | SessionNextToolSuccess | SessionNextToolFailed - | SessionNextReasoningStarted - | SessionNextReasoningEnded | SessionNextRetried | SessionNextCompactionStarted | SessionNextCompactionEnded @@ -3020,11 +3020,6 @@ export type SessionStatus2 = { [key: string]: unknown } type: "session.status" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -3038,11 +3033,6 @@ export type QuestionReplied2 = { [key: string]: unknown } type: "question.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -3057,11 +3047,6 @@ export type QuestionRejected2 = { [key: string]: unknown } type: "question.rejected" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -4634,7 +4619,7 @@ export type SessionNextAgentSwitched = { [key: string]: unknown } type: "session.next.agent.switched" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4654,7 +4639,7 @@ export type SessionNextModelSwitched = { [key: string]: unknown } type: "session.next.model.switched" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4674,7 +4659,7 @@ export type SessionNextMoved = { [key: string]: unknown } type: "session.next.moved" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4694,7 +4679,7 @@ export type SessionNextRenamed = { [key: string]: unknown } type: "session.next.renamed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4713,7 +4698,7 @@ export type SessionNextForked = { [key: string]: unknown } type: "session.next.forked" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4733,7 +4718,7 @@ export type SessionNextPrompted = { [key: string]: unknown } type: "session.next.prompted" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4754,7 +4739,7 @@ export type SessionNextPromptAdmitted = { [key: string]: unknown } type: "session.next.prompt.admitted" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4775,7 +4760,7 @@ export type SessionNextContextUpdated = { [key: string]: unknown } type: "session.next.context.updated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4795,7 +4780,7 @@ export type SessionNextSynthetic = { [key: string]: unknown } type: "session.next.synthetic" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4819,7 +4804,7 @@ export type SessionNextSkillActivated = { [key: string]: unknown } type: "session.next.skill.activated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4840,7 +4825,7 @@ export type SessionNextShellStarted = { [key: string]: unknown } type: "session.next.shell.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4861,7 +4846,7 @@ export type SessionNextShellEnded = { [key: string]: unknown } type: "session.next.shell.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4881,7 +4866,7 @@ export type SessionNextStepStarted = { [key: string]: unknown } type: "session.next.step.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4903,7 +4888,7 @@ export type SessionNextStepEnded = { [key: string]: unknown } type: "session.next.step.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4935,7 +4920,7 @@ export type SessionNextStepFailed = { [key: string]: unknown } type: "session.next.step.failed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4955,7 +4940,7 @@ export type SessionNextTextStarted = { [key: string]: unknown } type: "session.next.text.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4975,7 +4960,7 @@ export type SessionNextTextEnded = { [key: string]: unknown } type: "session.next.text.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -4990,13 +4975,56 @@ export type SessionNextTextEnded = { } } +export type SessionNextReasoningStarted = { + id: string + metadata?: { + [key: string]: unknown + } + type: "session.next.reasoning.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + timestamp: number + sessionID: string + assistantMessageID: string + reasoningID: string + providerMetadata?: LlmProviderMetadata + } +} + +export type SessionNextReasoningEnded = { + id: string + metadata?: { + [key: string]: unknown + } + type: "session.next.reasoning.ended" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + timestamp: number + sessionID: string + assistantMessageID: string + reasoningID: string + text: string + providerMetadata?: LlmProviderMetadata + } +} + export type SessionNextToolInputStarted = { id: string metadata?: { [key: string]: unknown } type: "session.next.tool.input.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5017,7 +5045,7 @@ export type SessionNextToolInputEnded = { [key: string]: unknown } type: "session.next.tool.input.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5038,7 +5066,7 @@ export type SessionNextToolCalled = { [key: string]: unknown } type: "session.next.tool.called" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5066,7 +5094,7 @@ export type SessionNextToolProgress = { [key: string]: unknown } type: "session.next.tool.progress" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5090,7 +5118,7 @@ export type SessionNextToolSuccess = { [key: string]: unknown } type: "session.next.tool.success" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5120,7 +5148,7 @@ export type SessionNextToolFailed = { [key: string]: unknown } type: "session.next.tool.failed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5140,56 +5168,13 @@ export type SessionNextToolFailed = { } } -export type SessionNextReasoningStarted = { - id: string - metadata?: { - [key: string]: unknown - } - type: "session.next.reasoning.started" - durable?: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef - data: { - timestamp: number - sessionID: string - assistantMessageID: string - reasoningID: string - providerMetadata?: LlmProviderMetadata - } -} - -export type SessionNextReasoningEnded = { - id: string - metadata?: { - [key: string]: unknown - } - type: "session.next.reasoning.ended" - durable?: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef - data: { - timestamp: number - sessionID: string - assistantMessageID: string - reasoningID: string - text: string - providerMetadata?: LlmProviderMetadata - } -} - export type SessionNextRetried = { id: string metadata?: { [key: string]: unknown } type: "session.next.retried" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5209,7 +5194,7 @@ export type SessionNextCompactionStarted = { [key: string]: unknown } type: "session.next.compaction.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5229,7 +5214,7 @@ export type SessionNextCompactionEnded = { [key: string]: unknown } type: "session.next.compaction.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5251,7 +5236,7 @@ export type SessionNextRevertStaged = { [key: string]: unknown } type: "session.next.revert.staged" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5270,7 +5255,7 @@ export type SessionNextRevertCleared = { [key: string]: unknown } type: "session.next.revert.cleared" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5288,7 +5273,7 @@ export type SessionNextRevertCommitted = { [key: string]: unknown } type: "session.next.revert.committed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5634,11 +5619,6 @@ export type ModelsDevRefreshed = { [key: string]: unknown } type: "models-dev.refreshed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -5651,11 +5631,6 @@ export type IntegrationUpdated = { [key: string]: unknown } type: "integration.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -5668,11 +5643,6 @@ export type IntegrationConnectionUpdated = { [key: string]: unknown } type: "integration.connection.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { integrationID: string @@ -5685,11 +5655,6 @@ export type CatalogUpdated = { [key: string]: unknown } type: "catalog.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -5702,11 +5667,6 @@ export type AgentUpdated = { [key: string]: unknown } type: "agent.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -5719,7 +5679,7 @@ export type SessionCreated = { [key: string]: unknown } type: "session.created" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5737,7 +5697,7 @@ export type SessionUpdated = { [key: string]: unknown } type: "session.updated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5755,7 +5715,7 @@ export type SessionDeleted = { [key: string]: unknown } type: "session.deleted" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5773,7 +5733,7 @@ export type MessageUpdated = { [key: string]: unknown } type: "message.updated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5791,7 +5751,7 @@ export type MessageRemoved = { [key: string]: unknown } type: "message.removed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5809,7 +5769,7 @@ export type MessagePartUpdated = { [key: string]: unknown } type: "message.part.updated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5828,7 +5788,7 @@ export type MessagePartRemoved = { [key: string]: unknown } type: "message.part.removed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -5847,11 +5807,6 @@ export type SessionNextExecutionSettled = { [key: string]: unknown } type: "session.next.execution.settled" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { timestamp: number @@ -5867,11 +5822,6 @@ export type SessionNextTextDelta = { [key: string]: unknown } type: "session.next.text.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { timestamp: number @@ -5888,11 +5838,6 @@ export type SessionNextReasoningDelta = { [key: string]: unknown } type: "session.next.reasoning.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { timestamp: number @@ -5909,11 +5854,6 @@ export type SessionNextToolInputDelta = { [key: string]: unknown } type: "session.next.tool.input.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { timestamp: number @@ -5930,11 +5870,6 @@ export type SessionNextCompactionDelta = { [key: string]: unknown } type: "session.next.compaction.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { timestamp: number @@ -5950,11 +5885,6 @@ export type MessagePartDelta = { [key: string]: unknown } type: "message.part.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -5971,11 +5901,6 @@ export type SessionDiff = { [key: string]: unknown } type: "session.diff" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -5989,11 +5914,6 @@ export type SessionError = { [key: string]: unknown } type: "session.error" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID?: string @@ -6015,11 +5935,6 @@ export type InstallationUpdated = { [key: string]: unknown } type: "installation.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { version: string @@ -6032,11 +5947,6 @@ export type InstallationUpdateAvailable = { [key: string]: unknown } type: "installation.update-available" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { version: string @@ -6049,11 +5959,6 @@ export type FileEdited = { [key: string]: unknown } type: "file.edited" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { file: string @@ -6066,11 +5971,6 @@ export type ReferenceUpdated = { [key: string]: unknown } type: "reference.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -6083,11 +5983,6 @@ export type PermissionV2Asked = { [key: string]: unknown } type: "permission.v2.asked" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6108,11 +6003,6 @@ export type PermissionV2Replied = { [key: string]: unknown } type: "permission.v2.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -6127,11 +6017,6 @@ export type PluginAdded = { [key: string]: unknown } type: "plugin.added" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6144,11 +6029,6 @@ export type ProjectDirectoriesUpdated = { [key: string]: unknown } type: "project.directories.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { projectID: string @@ -6161,11 +6041,6 @@ export type CommandUpdated = { [key: string]: unknown } type: "command.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -6178,11 +6053,6 @@ export type SkillUpdated = { [key: string]: unknown } type: "skill.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -6195,11 +6065,6 @@ export type FileWatcherUpdated = { [key: string]: unknown } type: "file.watcher.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { file: string @@ -6213,11 +6078,6 @@ export type PtyCreated = { [key: string]: unknown } type: "pty.created" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { info: Pty @@ -6230,11 +6090,6 @@ export type PtyUpdated = { [key: string]: unknown } type: "pty.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { info: Pty @@ -6247,11 +6102,6 @@ export type PtyExited = { [key: string]: unknown } type: "pty.exited" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6265,11 +6115,6 @@ export type PtyDeleted = { [key: string]: unknown } type: "pty.deleted" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6282,11 +6127,6 @@ export type ShellCreated = { [key: string]: unknown } type: "shell.created" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { info: Shell1 @@ -6299,11 +6139,6 @@ export type ShellExited = { [key: string]: unknown } type: "shell.exited" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6318,11 +6153,6 @@ export type ShellDeleted = { [key: string]: unknown } type: "shell.deleted" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6335,11 +6165,6 @@ export type QuestionV2Asked = { [key: string]: unknown } type: "question.v2.asked" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6358,11 +6183,6 @@ export type QuestionV2Replied = { [key: string]: unknown } type: "question.v2.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -6377,11 +6197,6 @@ export type QuestionV2Rejected = { [key: string]: unknown } type: "question.v2.rejected" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -6425,11 +6240,6 @@ export type FormCreated = { [key: string]: unknown } type: "form.created" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { form: FormFormInfo | FormUrlInfo @@ -6444,11 +6254,6 @@ export type FormReplied = { [key: string]: unknown } type: "form.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6463,11 +6268,6 @@ export type FormCancelled = { [key: string]: unknown } type: "form.cancelled" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6481,11 +6281,6 @@ export type TodoUpdated = { [key: string]: unknown } type: "todo.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -6499,11 +6294,6 @@ export type LspUpdated = { [key: string]: unknown } type: "lsp.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -6516,11 +6306,6 @@ export type PermissionAsked = { [key: string]: unknown } type: "permission.asked" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6544,11 +6329,6 @@ export type PermissionReplied = { [key: string]: unknown } type: "permission.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -6563,11 +6343,6 @@ export type TuiPromptAppend = { [key: string]: unknown } type: "tui.prompt.append" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { text: string @@ -6580,11 +6355,6 @@ export type TuiCommandExecute = { [key: string]: unknown } type: "tui.command.execute" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { command: @@ -6615,11 +6385,6 @@ export type TuiToastShow = { [key: string]: unknown } type: "tui.toast.show" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { title?: string @@ -6635,11 +6400,6 @@ export type TuiSessionSelect = { [key: string]: unknown } type: "tui.session.select" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { /** @@ -6655,11 +6415,6 @@ export type McpToolsChanged = { [key: string]: unknown } type: "mcp.tools.changed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { server: string @@ -6672,11 +6427,6 @@ export type McpBrowserOpenFailed = { [key: string]: unknown } type: "mcp.browser.open.failed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { mcpName: string @@ -6690,11 +6440,6 @@ export type McpStatusChanged = { [key: string]: unknown } type: "mcp.status.changed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { server: string @@ -6707,11 +6452,6 @@ export type CommandExecuted = { [key: string]: unknown } type: "command.executed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { name: string @@ -6727,11 +6467,6 @@ export type ProjectUpdated = { [key: string]: unknown } type: "project.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6751,11 +6486,6 @@ export type SessionIdle = { [key: string]: unknown } type: "session.idle" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -6768,11 +6498,6 @@ export type QuestionAsked = { [key: string]: unknown } type: "question.asked" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { id: string @@ -6791,11 +6516,6 @@ export type SessionCompacted = { [key: string]: unknown } type: "session.compacted" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { sessionID: string @@ -6808,11 +6528,6 @@ export type VcsBranchUpdated = { [key: string]: unknown } type: "vcs.branch.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { branch?: string @@ -6825,11 +6540,6 @@ export type WorkspaceReady = { [key: string]: unknown } type: "workspace.ready" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { name: string @@ -6842,11 +6552,6 @@ export type WorkspaceFailed = { [key: string]: unknown } type: "workspace.failed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { message: string @@ -6859,11 +6564,6 @@ export type WorkspaceStatus = { [key: string]: unknown } type: "workspace.status" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { workspaceID: string @@ -6877,11 +6577,6 @@ export type WorktreeReady = { [key: string]: unknown } type: "worktree.ready" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { name: string @@ -6895,11 +6590,6 @@ export type WorktreeFailed = { [key: string]: unknown } type: "worktree.failed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { message: string @@ -6912,11 +6602,6 @@ export type ServerConnected = { [key: string]: unknown } type: "server.connected" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -6929,11 +6614,6 @@ export type GlobalDisposed = { [key: string]: unknown } type: "global.disposed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef data: { [key: string]: unknown @@ -8627,7 +8307,7 @@ export type SessionNextAgentSwitched2 = { [key: string]: unknown } type: "session.next.agent.switched" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8647,7 +8327,7 @@ export type SessionNextModelSwitched2 = { [key: string]: unknown } type: "session.next.model.switched" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8667,7 +8347,7 @@ export type SessionNextMoved2 = { [key: string]: unknown } type: "session.next.moved" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8687,7 +8367,7 @@ export type SessionNextRenamed2 = { [key: string]: unknown } type: "session.next.renamed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8706,7 +8386,7 @@ export type SessionNextForked2 = { [key: string]: unknown } type: "session.next.forked" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8726,7 +8406,7 @@ export type SessionNextPrompted2 = { [key: string]: unknown } type: "session.next.prompted" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8747,7 +8427,7 @@ export type SessionNextPromptAdmitted2 = { [key: string]: unknown } type: "session.next.prompt.admitted" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8768,7 +8448,7 @@ export type SessionNextContextUpdated2 = { [key: string]: unknown } type: "session.next.context.updated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8788,7 +8468,7 @@ export type SessionNextSynthetic2 = { [key: string]: unknown } type: "session.next.synthetic" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8812,7 +8492,7 @@ export type SessionNextSkillActivated2 = { [key: string]: unknown } type: "session.next.skill.activated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8833,7 +8513,7 @@ export type SessionNextShellStarted2 = { [key: string]: unknown } type: "session.next.shell.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8854,7 +8534,7 @@ export type SessionNextShellEnded2 = { [key: string]: unknown } type: "session.next.shell.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8874,7 +8554,7 @@ export type SessionNextStepStarted2 = { [key: string]: unknown } type: "session.next.step.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8896,7 +8576,7 @@ export type SessionNextStepEnded2 = { [key: string]: unknown } type: "session.next.step.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8928,7 +8608,7 @@ export type SessionNextStepFailed2 = { [key: string]: unknown } type: "session.next.step.failed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8948,7 +8628,7 @@ export type SessionNextTextStarted2 = { [key: string]: unknown } type: "session.next.text.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8968,7 +8648,7 @@ export type SessionNextTextEnded2 = { [key: string]: unknown } type: "session.next.text.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -8983,13 +8663,68 @@ export type SessionNextTextEnded2 = { } } +export type LlmProviderMetadata3 = { + [key: string]: { + [key: string]: unknown + } +} + +export type SessionNextReasoningStarted2 = { + id: string + metadata?: { + [key: string]: unknown + } + type: "session.next.reasoning.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef2 + data: { + timestamp: number + sessionID: string + assistantMessageID: string + reasoningID: string + providerMetadata?: LlmProviderMetadata3 + } +} + +export type LlmProviderMetadata4 = { + [key: string]: { + [key: string]: unknown + } +} + +export type SessionNextReasoningEnded2 = { + id: string + metadata?: { + [key: string]: unknown + } + type: "session.next.reasoning.ended" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef2 + data: { + timestamp: number + sessionID: string + assistantMessageID: string + reasoningID: string + text: string + providerMetadata?: LlmProviderMetadata4 + } +} + export type SessionNextToolInputStarted2 = { id: string metadata?: { [key: string]: unknown } type: "session.next.tool.input.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9010,7 +8745,7 @@ export type SessionNextToolInputEnded2 = { [key: string]: unknown } type: "session.next.tool.input.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9025,7 +8760,7 @@ export type SessionNextToolInputEnded2 = { } } -export type LlmProviderMetadata3 = { +export type LlmProviderMetadata5 = { [key: string]: { [key: string]: unknown } @@ -9037,7 +8772,7 @@ export type SessionNextToolCalled2 = { [key: string]: unknown } type: "session.next.tool.called" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9054,7 +8789,7 @@ export type SessionNextToolCalled2 = { } provider: { executed: boolean - metadata?: LlmProviderMetadata3 + metadata?: LlmProviderMetadata5 } } } @@ -9065,7 +8800,7 @@ export type SessionNextToolProgress2 = { [key: string]: unknown } type: "session.next.tool.progress" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9083,7 +8818,7 @@ export type SessionNextToolProgress2 = { } } -export type LlmProviderMetadata4 = { +export type LlmProviderMetadata6 = { [key: string]: { [key: string]: unknown } @@ -9095,7 +8830,7 @@ export type SessionNextToolSuccess2 = { [key: string]: unknown } type: "session.next.tool.success" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9114,12 +8849,12 @@ export type SessionNextToolSuccess2 = { result?: unknown provider: { executed: boolean - metadata?: LlmProviderMetadata4 + metadata?: LlmProviderMetadata6 } } } -export type LlmProviderMetadata5 = { +export type LlmProviderMetadata7 = { [key: string]: { [key: string]: unknown } @@ -9131,7 +8866,7 @@ export type SessionNextToolFailed2 = { [key: string]: unknown } type: "session.next.tool.failed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9146,66 +8881,11 @@ export type SessionNextToolFailed2 = { result?: unknown provider: { executed: boolean - metadata?: LlmProviderMetadata5 + metadata?: LlmProviderMetadata7 } } } -export type LlmProviderMetadata6 = { - [key: string]: { - [key: string]: unknown - } -} - -export type SessionNextReasoningStarted2 = { - id: string - metadata?: { - [key: string]: unknown - } - type: "session.next.reasoning.started" - durable?: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - timestamp: number - sessionID: string - assistantMessageID: string - reasoningID: string - providerMetadata?: LlmProviderMetadata6 - } -} - -export type LlmProviderMetadata7 = { - [key: string]: { - [key: string]: unknown - } -} - -export type SessionNextReasoningEnded2 = { - id: string - metadata?: { - [key: string]: unknown - } - type: "session.next.reasoning.ended" - durable?: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - timestamp: number - sessionID: string - assistantMessageID: string - reasoningID: string - text: string - providerMetadata?: LlmProviderMetadata7 - } -} - export type SessionNextRetryError2 = { message: string statusCode?: number @@ -9225,7 +8905,7 @@ export type SessionNextRetried2 = { [key: string]: unknown } type: "session.next.retried" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9245,7 +8925,7 @@ export type SessionNextCompactionStarted2 = { [key: string]: unknown } type: "session.next.compaction.started" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9265,7 +8945,7 @@ export type SessionNextCompactionEnded2 = { [key: string]: unknown } type: "session.next.compaction.ended" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9287,7 +8967,7 @@ export type SessionNextRevertStaged2 = { [key: string]: unknown } type: "session.next.revert.staged" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9306,7 +8986,7 @@ export type SessionNextRevertCleared2 = { [key: string]: unknown } type: "session.next.revert.cleared" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9324,7 +9004,7 @@ export type SessionNextRevertCommitted2 = { [key: string]: unknown } type: "session.next.revert.committed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -9355,14 +9035,14 @@ export type SessionDurableEventV2 = | SessionNextStepFailed2 | SessionNextTextStarted2 | SessionNextTextEnded2 + | SessionNextReasoningStarted2 + | SessionNextReasoningEnded2 | SessionNextToolInputStarted2 | SessionNextToolInputEnded2 | SessionNextToolCalled2 | SessionNextToolProgress2 | SessionNextToolSuccess2 | SessionNextToolFailed2 - | SessionNextReasoningStarted2 - | SessionNextReasoningEnded2 | SessionNextRetried2 | SessionNextCompactionStarted2 | SessionNextCompactionEnded2 @@ -9889,11 +9569,6 @@ export type ModelsDevRefreshed2 = { [key: string]: unknown } type: "models-dev.refreshed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: | { @@ -9908,11 +9583,6 @@ export type IntegrationUpdated2 = { [key: string]: unknown } type: "integration.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: | { @@ -9927,11 +9597,6 @@ export type IntegrationConnectionUpdated2 = { [key: string]: unknown } type: "integration.connection.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { integrationID: string @@ -9944,11 +9609,6 @@ export type CatalogUpdated2 = { [key: string]: unknown } type: "catalog.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: | { @@ -9963,11 +9623,6 @@ export type AgentUpdated2 = { [key: string]: unknown } type: "agent.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: | { @@ -10053,7 +9708,7 @@ export type SessionCreated2 = { [key: string]: unknown } type: "session.created" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -10071,7 +9726,7 @@ export type SessionUpdated2 = { [key: string]: unknown } type: "session.updated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -10089,7 +9744,7 @@ export type SessionDeleted2 = { [key: string]: unknown } type: "session.deleted" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -10263,7 +9918,7 @@ export type MessageUpdated2 = { [key: string]: unknown } type: "message.updated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -10281,7 +9936,7 @@ export type MessageRemoved2 = { [key: string]: unknown } type: "message.removed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -10562,7 +10217,7 @@ export type MessagePartUpdated2 = { [key: string]: unknown } type: "message.part.updated" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -10581,7 +10236,7 @@ export type MessagePartRemoved2 = { [key: string]: unknown } type: "message.part.removed" - durable?: { + durable: { aggregateID: string seq: number version: number @@ -10600,11 +10255,6 @@ export type SessionNextExecutionSettled2 = { [key: string]: unknown } type: "session.next.execution.settled" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { timestamp: number @@ -10620,11 +10270,6 @@ export type SessionNextTextDelta2 = { [key: string]: unknown } type: "session.next.text.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { timestamp: number @@ -10641,11 +10286,6 @@ export type SessionNextReasoningDelta2 = { [key: string]: unknown } type: "session.next.reasoning.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { timestamp: number @@ -10662,11 +10302,6 @@ export type SessionNextToolInputDelta2 = { [key: string]: unknown } type: "session.next.tool.input.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { timestamp: number @@ -10683,11 +10318,6 @@ export type SessionNextCompactionDelta2 = { [key: string]: unknown } type: "session.next.compaction.delta" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { timestamp: number @@ -10703,11 +10333,6 @@ export type FileEdited2 = { [key: string]: unknown } type: "file.edited" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { file: string @@ -10720,11 +10345,6 @@ export type ReferenceUpdated2 = { [key: string]: unknown } type: "reference.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: | { @@ -10739,11 +10359,6 @@ export type PermissionV2Asked2 = { [key: string]: unknown } type: "permission.v2.asked" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -10764,11 +10379,6 @@ export type PermissionV2Replied2 = { [key: string]: unknown } type: "permission.v2.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -10783,11 +10393,6 @@ export type PluginAdded2 = { [key: string]: unknown } type: "plugin.added" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -10800,11 +10405,6 @@ export type ProjectDirectoriesUpdated2 = { [key: string]: unknown } type: "project.directories.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { projectID: string @@ -10817,11 +10417,6 @@ export type CommandUpdated2 = { [key: string]: unknown } type: "command.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: | { @@ -10836,11 +10431,6 @@ export type SkillUpdated2 = { [key: string]: unknown } type: "skill.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: | { @@ -10855,11 +10445,6 @@ export type FileWatcherUpdated2 = { [key: string]: unknown } type: "file.watcher.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { file: string @@ -10884,11 +10469,6 @@ export type PtyCreated2 = { [key: string]: unknown } type: "pty.created" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { info: PtyV2 @@ -10901,11 +10481,6 @@ export type PtyUpdated2 = { [key: string]: unknown } type: "pty.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { info: PtyV2 @@ -10918,11 +10493,6 @@ export type PtyExited2 = { [key: string]: unknown } type: "pty.exited" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -10936,11 +10506,6 @@ export type PtyDeleted2 = { [key: string]: unknown } type: "pty.deleted" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -10971,11 +10536,6 @@ export type ShellCreated2 = { [key: string]: unknown } type: "shell.created" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { info: ShellV2 @@ -10988,11 +10548,6 @@ export type ShellExited2 = { [key: string]: unknown } type: "shell.exited" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -11007,11 +10562,6 @@ export type ShellDeleted2 = { [key: string]: unknown } type: "shell.deleted" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -11057,11 +10607,6 @@ export type QuestionV2Asked2 = { [key: string]: unknown } type: "question.v2.asked" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -11082,11 +10627,6 @@ export type QuestionV2Replied2 = { [key: string]: unknown } type: "question.v2.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -11101,11 +10641,6 @@ export type QuestionV2Rejected2 = { [key: string]: unknown } type: "question.v2.rejected" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -11212,11 +10747,6 @@ export type FormCreated2 = { [key: string]: unknown } type: "form.created" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { form: FormFormInfo1 | FormUrlInfo1 @@ -11235,11 +10765,6 @@ export type FormReplied2 = { [key: string]: unknown } type: "form.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -11254,11 +10779,6 @@ export type FormCancelled2 = { [key: string]: unknown } type: "form.cancelled" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -11287,11 +10807,6 @@ export type TodoUpdated2 = { [key: string]: unknown } type: "todo.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -11327,11 +10842,6 @@ export type SessionStatusV22 = { [key: string]: unknown } type: "session.status" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -11345,11 +10855,6 @@ export type SessionIdle2 = { [key: string]: unknown } type: "session.idle" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -11362,11 +10867,6 @@ export type TuiPromptAppend2 = { [key: string]: unknown } type: "tui.prompt.append" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { text: string @@ -11379,11 +10879,6 @@ export type TuiCommandExecute2 = { [key: string]: unknown } type: "tui.command.execute" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { command: @@ -11414,11 +10909,6 @@ export type TuiToastShow2 = { [key: string]: unknown } type: "tui.toast.show" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { title?: string @@ -11434,11 +10924,6 @@ export type TuiSessionSelect2 = { [key: string]: unknown } type: "tui.session.select" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { /** @@ -11454,11 +10939,6 @@ export type InstallationUpdated2 = { [key: string]: unknown } type: "installation.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { version: string @@ -11471,11 +10951,6 @@ export type InstallationUpdateAvailable2 = { [key: string]: unknown } type: "installation.update-available" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { version: string @@ -11488,11 +10963,6 @@ export type VcsBranchUpdated2 = { [key: string]: unknown } type: "vcs.branch.updated" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { branch?: string @@ -11505,11 +10975,6 @@ export type McpStatusChanged2 = { [key: string]: unknown } type: "mcp.status.changed" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { server: string @@ -11522,11 +10987,6 @@ export type PermissionAsked2 = { [key: string]: unknown } type: "permission.asked" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -11550,11 +11010,6 @@ export type PermissionReplied2 = { [key: string]: unknown } type: "permission.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -11608,11 +11063,6 @@ export type QuestionAsked2 = { [key: string]: unknown } type: "question.asked" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { id: string @@ -11633,11 +11083,6 @@ export type QuestionRepliedV2 = { [key: string]: unknown } type: "question.replied" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -11652,11 +11097,6 @@ export type QuestionRejectedV2 = { [key: string]: unknown } type: "question.rejected" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID: string @@ -11670,11 +11110,6 @@ export type SessionError2 = { [key: string]: unknown } type: "session.error" - durable?: { - aggregateID: string - seq: number - version: number - } location?: LocationRef2 data: { sessionID?: string | null @@ -11696,11 +11131,6 @@ export type V2EventServerConnected = { metadata?: { [key: string]: unknown } | null - durable?: { - aggregateID: string - seq: number - version: number - } | null location?: LocationRef2 | null type: "server.connected" data: diff --git a/packages/tui/test/cli/cmd/tui/notifications.test.ts b/packages/tui/test/cli/cmd/tui/notifications.test.ts index 1a19f7be25..a914b34dbc 100644 --- a/packages/tui/test/cli/cmd/tui/notifications.test.ts +++ b/packages/tui/test/cli/cmd/tui/notifications.test.ts @@ -33,10 +33,7 @@ async function setup() { }, }, event: { - on: ( - type: Type, - handler: (event: Extract) => void, - ) => { + on: (type: Type, handler: (event: Extract) => void) => { const list = handlers.get(type) ?? [] const wrapped = handler as (event: V2Event) => void list.push(wrapped) @@ -86,10 +83,15 @@ function permission(id: string, sessionID = "session"): PermissionRequest { } } +function durable(sessionID: string) { + return { aggregateID: sessionID, seq: 0, version: 1 } +} + function stepStarted(id: string, sessionID = "session"): V2Event { return { id, type: "session.next.step.started", + durable: durable(sessionID), data: { sessionID, assistantMessageID: `msg_${id}`, @@ -104,6 +106,7 @@ function stepEnded(id: string, sessionID = "session", finish = "stop"): V2Event return { id, type: "session.next.step.ended", + durable: durable(sessionID), data: { sessionID, assistantMessageID: `msg_${id}`, @@ -119,6 +122,7 @@ function stepFailed(id: string, sessionID = "session"): V2Event { return { id, type: "session.next.step.failed", + durable: durable(sessionID), data: { sessionID, assistantMessageID: `msg_${id}`, diff --git a/packages/tui/test/cli/tui/data.test.tsx b/packages/tui/test/cli/tui/data.test.tsx index fc7af6c8ed..b2bacfc0dd 100644 --- a/packages/tui/test/cli/tui/data.test.tsx +++ b/packages/tui/test/cli/tui/data.test.tsx @@ -21,6 +21,10 @@ function emitEvent(events: ReturnType, event: V2Event) events.emit({ ...event, location: { directory } }) } +function durable(sessionID: string, seq = 0, version = 1) { + return { aggregateID: sessionID, seq, version } +} + test("refreshes resources into reactive getters", async () => { const events = createEventStream() const location = { @@ -261,6 +265,7 @@ test("tracks session status from active sessions and execution events", async () emitEvent(events, { id: "evt_step_started", type: "session.next.step.started", + durable: durable("session-live"), data: { sessionID: "session-live", assistantMessageID: "message-live", @@ -274,6 +279,7 @@ test("tracks session status from active sessions and execution events", async () emitEvent(events, { id: "evt_step_ended", type: "session.next.step.ended", + durable: durable("session-live", 1, 2), data: { sessionID: "session-live", assistantMessageID: "message-live", @@ -303,6 +309,7 @@ test("tracks session status from active sessions and execution events", async () emitEvent(events, { id: "evt_failed_step_started", type: "session.next.step.started", + durable: durable("session-failed"), data: { sessionID: "session-failed", assistantMessageID: "message-failed", @@ -316,6 +323,7 @@ test("tracks session status from active sessions and execution events", async () emitEvent(events, { id: "evt_step_failed", type: "session.next.step.failed", + durable: durable("session-failed", 1, 2), data: { sessionID: "session-failed", assistantMessageID: "message-failed", @@ -548,7 +556,10 @@ test("keeps shell state scoped to location", async () => { if (url.pathname !== "/api/shell") return const requestDirectory = url.searchParams.get("location[directory]") return json({ - location: { directory: requestDirectory ?? directory, project: { id: "proj_test", directory: requestDirectory ?? directory } }, + location: { + directory: requestDirectory ?? directory, + project: { id: "proj_test", directory: requestDirectory ?? directory }, + }, data: [ { id: requestDirectory === other ? "sh_other" : "sh_default", @@ -773,11 +784,13 @@ test("settles pending tools when a live failure arrives", async () => { emitEvent(events, { id: "evt_agent_1", type: "session.next.agent.switched", + durable: durable("session-1"), data: { sessionID: "session-1", messageID: "msg_agent_1", timestamp: 0, agent: "build" }, }) emitEvent(events, { id: "evt_model_1", type: "session.next.model.switched", + durable: durable("session-1", 1), data: { sessionID: "session-1", messageID: "msg_model_1", @@ -788,6 +801,7 @@ test("settles pending tools when a live failure arrives", async () => { emitEvent(events, { id: "evt_step_started_1", type: "session.next.step.started", + durable: durable("session-1", 2), data: { sessionID: "session-1", assistantMessageID: "msg_explicit_assistant_9", @@ -799,6 +813,7 @@ test("settles pending tools when a live failure arrives", async () => { emitEvent(events, { id: "evt_input_1", type: "session.next.tool.input.started", + durable: durable("session-1", 3), data: { sessionID: "session-1", assistantMessageID: "msg_explicit_assistant_9", @@ -810,6 +825,7 @@ test("settles pending tools when a live failure arrives", async () => { emitEvent(events, { id: "evt_called_1", type: "session.next.tool.called", + durable: durable("session-1", 4), data: { sessionID: "session-1", timestamp: 2, @@ -823,6 +839,7 @@ test("settles pending tools when a live failure arrives", async () => { emitEvent(events, { id: "evt_failed_1", type: "session.next.tool.failed", + durable: durable("session-1", 5), data: { sessionID: "session-1", timestamp: 3, @@ -912,6 +929,7 @@ test("renders admitted prompts immediately with queued marker and clears when pr emitEvent(events, { id: "evt_admitted_1", type: "session.next.prompt.admitted", + durable: durable(sessionID), data: { sessionID, messageID, @@ -930,6 +948,7 @@ test("renders admitted prompts immediately with queued marker and clears when pr emitEvent(events, { id: "evt_prompted_1", type: "session.next.prompted", + durable: durable(sessionID, 1), data: { sessionID, messageID, @@ -989,6 +1008,7 @@ test("projects live context updates with their message ID", async () => { emitEvent(events, { id: "evt_context_1", type: "session.next.context.updated", + durable: durable("session-1"), data: { sessionID: "session-1", messageID: "msg_context_1", diff --git a/specs/v2/schema-changelog.md b/specs/v2/schema-changelog.md index 794db93c95..d526b5d8e7 100644 --- a/specs/v2/schema-changelog.md +++ b/specs/v2/schema-changelog.md @@ -1,5 +1,25 @@ # V2 Schema Changelog +## 2026-07-03: Require Durable Envelope On Durable Events + +- Make the wire `durable` envelope required on durable event definitions. +- Remove the `durable` envelope field from live-only event definitions. + +Compatibility: + +- No stored event row, database, or runtime publish behavior change; runtime already attaches the envelope only after durable commit/replay. +- Generated clients now model the existing invariant: durable events carry `durable`, live-only events do not. + +## 2026-07-03: Declare Event Durability At Definition Level + +- Add explicit `Event.durable(...)` and `Event.ephemeral(...)` definition constructors. +- Preserve the existing durable and live-only event classifications while deriving durable inventories from definition metadata instead of hand-maintained lists. + +Compatibility: + +- No wire payload, stored event row, database, or behavior change. +- Generated clients were regenerated from the unchanged public event schemas. + ## 2026-07-02: Rename Session Log Replay Marker - Rename the replay boundary marker from `log.caught_up` / `EventLog.CaughtUp` to `log.synced` / `EventLog.Synced`.