Просмотр исходного кода

refactor(schema): session shell payloads and event prefix restore (#35229)

Kit Langton 1 месяц назад
Родитель
Сommit
650d774372
49 измененных файлов с 1078 добавлено и 859 удалено
  1. 1 1
      packages/app/src/components/dialog-custom-provider.tsx
  2. 1 1
      packages/app/src/components/settings-providers.tsx
  3. 1 1
      packages/app/src/components/settings-v2/providers.tsx
  4. 198 76
      packages/client/src/promise/generated/types.ts
  5. 5 5
      packages/client/test/effect.test.ts
  6. 2 2
      packages/client/test/promise.test.ts
  7. 1 0
      packages/core/src/database/migration.gen.ts
  8. 14 0
      packages/core/src/database/migration/20260703190000_reset_v2_shell_event_payloads.ts
  9. 44 22
      packages/core/src/session.ts
  10. 1 1
      packages/core/src/session/compaction.ts
  11. 54 52
      packages/core/src/session/message-updater.ts
  12. 14 7
      packages/core/src/session/projector.ts
  13. 1 1
      packages/core/src/session/runner/to-llm-message.ts
  14. 10 8
      packages/core/test/session-create.test.ts
  15. 4 4
      packages/core/test/session-log.test.ts
  16. 25 6
      packages/core/test/session-projector.test.ts
  17. 5 5
      packages/core/test/session-prompt.test.ts
  18. 13 3
      packages/core/test/session-runner-message.test.ts
  19. 6 6
      packages/core/test/session-runner-recorded.test.ts
  20. 4 4
      packages/core/test/session-runner-tool-events.test.ts
  21. 3 3
      packages/core/test/session-runner.test.ts
  22. 15 15
      packages/opencode/src/cli/cmd/run/noninteractive.ts
  23. 15 15
      packages/opencode/src/cli/cmd/run/session-data.ts
  24. 15 15
      packages/opencode/src/cli/cmd/run/stream-v2.subagent.ts
  25. 77 48
      packages/opencode/src/cli/cmd/run/stream-v2.transport.ts
  26. 2 2
      packages/opencode/test/cli/run/noninteractive.test.ts
  27. 6 6
      packages/opencode/test/cli/run/session-data.test.ts
  28. 94 45
      packages/opencode/test/cli/run/stream-v2.transport.test.ts
  29. 2 2
      packages/opencode/test/server/httpapi-pty.test.ts
  30. 1 1
      packages/opencode/test/server/httpapi-sdk.test.ts
  31. 2 2
      packages/opencode/test/tool/apply_patch.test.ts
  32. 2 2
      packages/opencode/test/tool/fixtures/models-api.json
  33. 13 13
      packages/opencode/test/v2/session-message-updater.test.ts
  34. 38 38
      packages/schema/src/session-event.ts
  35. 3 3
      packages/schema/src/session-message.ts
  36. 32 32
      packages/schema/test/event-manifest.test.ts
  37. 6 6
      packages/sdk-next/test/embedded.test.ts
  38. 2 2
      packages/sdk/js/script/build.ts
  39. 233 307
      packages/sdk/js/src/v2/gen/types.gen.ts
  40. 3 3
      packages/sdk/openapi.json
  41. 38 39
      packages/tui/src/context/data.tsx
  42. 8 8
      packages/tui/src/feature-plugins/system/notifications.ts
  43. 7 11
      packages/tui/src/routes/session/index.tsx
  44. 14 14
      packages/tui/src/routes/session/rows.ts
  45. 3 3
      packages/tui/test/cli/cmd/tui/notifications.test.ts
  46. 16 16
      packages/tui/test/cli/tui/data.test.tsx
  47. 1 1
      packages/ui/src/components/provider-icon.tsx
  48. 22 1
      specs/v2/schema-changelog.md
  49. 1 1
      specs/v2/session.md

+ 1 - 1
packages/app/src/components/dialog-custom-provider.tsx

@@ -177,7 +177,7 @@ export function DialogCustomProvider(props: Props) {
     >
     >
       <div class="flex flex-col gap-6 px-2.5 pb-3 overflow-y-auto max-h-[60vh]">
       <div class="flex flex-col gap-6 px-2.5 pb-3 overflow-y-auto max-h-[60vh]">
         <div class="px-2.5 flex gap-4 items-center">
         <div class="px-2.5 flex gap-4 items-center">
-          <ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
+          <ProviderIcon id="session.synthetic" class="size-5 shrink-0 icon-strong-base" />
           <div class="text-16-medium text-text-strong">{language.t("provider.custom.title")}</div>
           <div class="text-16-medium text-text-strong">{language.t("provider.custom.title")}</div>
         </div>
         </div>
 
 

+ 1 - 1
packages/app/src/components/settings-providers.tsx

@@ -226,7 +226,7 @@ const SettingsProvidersContent: Component = () => {
             >
             >
               <div class="flex flex-col min-w-0">
               <div class="flex flex-col min-w-0">
                 <div class="flex flex-wrap items-center gap-x-3 gap-y-1">
                 <div class="flex flex-wrap items-center gap-x-3 gap-y-1">
-                  <ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
+                  <ProviderIcon id="session.synthetic" class="size-5 shrink-0 icon-strong-base" />
                   <span class="text-14-medium text-text-strong">{language.t("provider.custom.title")}</span>
                   <span class="text-14-medium text-text-strong">{language.t("provider.custom.title")}</span>
                   <Tag>{language.t("settings.providers.tag.custom")}</Tag>
                   <Tag>{language.t("settings.providers.tag.custom")}</Tag>
                 </div>
                 </div>

+ 1 - 1
packages/app/src/components/settings-v2/providers.tsx

@@ -223,7 +223,7 @@ export const SettingsProvidersV2: Component = () => {
             <div class="settings-v2-provider-row" data-component="custom-provider-section">
             <div class="settings-v2-provider-row" data-component="custom-provider-section">
               <div class="settings-v2-provider-lead">
               <div class="settings-v2-provider-lead">
                 <ProviderIcon
                 <ProviderIcon
-                  id="synthetic"
+                  id="session.synthetic"
                   width={PROVIDER_ICON_SIZE}
                   width={PROVIDER_ICON_SIZE}
                   height={PROVIDER_ICON_SIZE}
                   height={PROVIDER_ICON_SIZE}
                   class="settings-v2-provider-icon shrink-0"
                   class="settings-v2-provider-icon shrink-0"

+ 198 - 76
packages/client/src/promise/generated/types.ts

@@ -976,9 +976,27 @@ export type SessionContextOutput = {
         readonly metadata?: { readonly [x: string]: JsonValue }
         readonly metadata?: { readonly [x: string]: JsonValue }
         readonly time: { readonly created: number; readonly completed?: number }
         readonly time: { readonly created: number; readonly completed?: number }
         readonly type: "shell"
         readonly type: "shell"
-        readonly callID: string
-        readonly command: string
-        readonly output: string
+        readonly shell: {
+          readonly id: string
+          readonly status: "running" | "exited" | "timeout" | "killed"
+          readonly command: string
+          readonly cwd: string
+          readonly shell: string
+          readonly file: string
+          readonly pid?: number
+          readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
+          readonly metadata: { readonly [x: string]: JsonValue }
+          readonly time: {
+            readonly started: number | "Infinity" | "-Infinity" | "NaN"
+            readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
+          }
+        }
+        readonly output?: {
+          readonly output: string
+          readonly cursor: number
+          readonly size: number
+          readonly truncated: boolean
+        }
       }
       }
     | {
     | {
         readonly id: string
         readonly id: string
@@ -1109,7 +1127,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "agent.selected"
+          readonly type: "session.agent.selected"
           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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string; readonly agent: string }
           readonly data: { readonly sessionID: string; readonly agent: string }
@@ -1118,7 +1136,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "model.selected"
+          readonly type: "session.model.selected"
           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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1143,7 +1161,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "renamed"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string; readonly title: string }
           readonly data: { readonly sessionID: string; readonly title: string }
@@ -1152,7 +1170,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "forked"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string; readonly parentID: string; readonly from?: string }
           readonly data: { readonly sessionID: string; readonly parentID: string; readonly from?: string }
@@ -1161,7 +1179,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "prompt.promoted"
+          readonly type: "session.prompt.promoted"
           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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string; readonly inputID: string }
           readonly data: { readonly sessionID: string; readonly inputID: string }
@@ -1170,7 +1188,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "prompt.admitted"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1206,7 +1224,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "synthetic"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1220,7 +1238,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "skill.activated"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string; readonly name: string; readonly text: string }
           readonly data: { readonly sessionID: string; readonly name: string; readonly text: string }
@@ -1229,25 +1247,59 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "shell.started"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
-          readonly data: { readonly sessionID: string; readonly callID: string; readonly command: string }
+          readonly data: {
+            readonly sessionID: string
+            readonly shell: {
+              readonly id: string
+              readonly status: "running" | "exited" | "timeout" | "killed"
+              readonly command: string
+              readonly cwd: string
+              readonly shell: string
+              readonly file: string
+              readonly pid?: number
+              readonly exit?: number
+              readonly metadata: { readonly [x: string]: unknown }
+              readonly time: { readonly started: number; readonly completed?: number }
+            }
+          }
         }
         }
       | {
       | {
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "shell.ended"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
-          readonly data: { readonly sessionID: string; readonly callID: string; readonly output: string }
+          readonly data: {
+            readonly sessionID: string
+            readonly shell: {
+              readonly id: string
+              readonly status: "running" | "exited" | "timeout" | "killed"
+              readonly command: string
+              readonly cwd: string
+              readonly shell: string
+              readonly file: string
+              readonly pid?: number
+              readonly exit?: number
+              readonly metadata: { readonly [x: string]: unknown }
+              readonly time: { readonly started: number; readonly completed?: number }
+            }
+            readonly output: {
+              readonly output: string
+              readonly cursor: number
+              readonly size: number
+              readonly truncated: boolean
+            }
+          }
         }
         }
       | {
       | {
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "step.started"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1262,7 +1314,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "step.ended"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1284,7 +1336,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "step.failed"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1297,7 +1349,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "text.started"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string }
           readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string }
@@ -1306,7 +1358,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "text.ended"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1320,7 +1372,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "reasoning.started"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1334,7 +1386,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "reasoning.ended"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1349,7 +1401,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "tool.input.started"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1363,7 +1415,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "tool.input.ended"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1377,7 +1429,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "tool.called"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1396,7 +1448,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "tool.progress"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1414,7 +1466,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "tool.success"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1438,7 +1490,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "tool.failed"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1457,7 +1509,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "retried"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1477,7 +1529,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "compaction.started"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string; readonly reason: "auto" | "manual" }
           readonly data: { readonly sessionID: string; readonly reason: "auto" | "manual" }
@@ -1486,7 +1538,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "compaction.ended"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1500,7 +1552,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "revert.staged"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: {
           readonly data: {
@@ -1524,7 +1576,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "revert.cleared"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string }
           readonly data: { readonly sessionID: string }
@@ -1533,7 +1585,7 @@ export type SessionLogOutput =
           readonly id: string
           readonly id: string
           readonly created: number
           readonly created: number
           readonly metadata?: { readonly [x: string]: unknown }
           readonly metadata?: { readonly [x: string]: unknown }
-          readonly type: "revert.committed"
+          readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
           readonly data: { readonly sessionID: string; readonly messageID: string }
           readonly data: { readonly sessionID: string; readonly messageID: string }
@@ -1617,9 +1669,27 @@ export type SessionMessageOutput = {
         readonly metadata?: { readonly [x: string]: JsonValue }
         readonly metadata?: { readonly [x: string]: JsonValue }
         readonly time: { readonly created: number; readonly completed?: number }
         readonly time: { readonly created: number; readonly completed?: number }
         readonly type: "shell"
         readonly type: "shell"
-        readonly callID: string
-        readonly command: string
-        readonly output: string
+        readonly shell: {
+          readonly id: string
+          readonly status: "running" | "exited" | "timeout" | "killed"
+          readonly command: string
+          readonly cwd: string
+          readonly shell: string
+          readonly file: string
+          readonly pid?: number
+          readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
+          readonly metadata: { readonly [x: string]: JsonValue }
+          readonly time: {
+            readonly started: number | "Infinity" | "-Infinity" | "NaN"
+            readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
+          }
+        }
+        readonly output?: {
+          readonly output: string
+          readonly cursor: number
+          readonly size: number
+          readonly truncated: boolean
+        }
       }
       }
     | {
     | {
         readonly id: string
         readonly id: string
@@ -1798,9 +1868,27 @@ export type MessageListOutput = {
         readonly metadata?: { readonly [x: string]: JsonValue }
         readonly metadata?: { readonly [x: string]: JsonValue }
         readonly time: { readonly created: number; readonly completed?: number }
         readonly time: { readonly created: number; readonly completed?: number }
         readonly type: "shell"
         readonly type: "shell"
-        readonly callID: string
-        readonly command: string
-        readonly output: string
+        readonly shell: {
+          readonly id: string
+          readonly status: "running" | "exited" | "timeout" | "killed"
+          readonly command: string
+          readonly cwd: string
+          readonly shell: string
+          readonly file: string
+          readonly pid?: number
+          readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
+          readonly metadata: { readonly [x: string]: JsonValue }
+          readonly time: {
+            readonly started: number | "Infinity" | "-Infinity" | "NaN"
+            readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
+          }
+        }
+        readonly output?: {
+          readonly output: string
+          readonly cursor: number
+          readonly size: number
+          readonly truncated: boolean
+        }
       }
       }
     | {
     | {
         readonly id: string
         readonly id: string
@@ -4309,7 +4397,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "agent.selected"
+      readonly type: "session.agent.selected"
       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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly agent: string }
       readonly data: { readonly sessionID: string; readonly agent: string }
@@ -4318,7 +4406,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "model.selected"
+      readonly type: "session.model.selected"
       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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4343,7 +4431,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "renamed"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly title: string }
       readonly data: { readonly sessionID: string; readonly title: string }
@@ -4352,7 +4440,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "forked"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly parentID: string; readonly from?: string }
       readonly data: { readonly sessionID: string; readonly parentID: string; readonly from?: string }
@@ -4361,7 +4449,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "prompt.promoted"
+      readonly type: "session.prompt.promoted"
       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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly inputID: string }
       readonly data: { readonly sessionID: string; readonly inputID: string }
@@ -4370,7 +4458,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "prompt.admitted"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4397,7 +4485,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "execution.settled"
+      readonly type: "session.execution.settled"
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
         readonly sessionID: string
         readonly sessionID: string
@@ -4418,7 +4506,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "synthetic"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4432,7 +4520,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "skill.activated"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly name: string; readonly text: string }
       readonly data: { readonly sessionID: string; readonly name: string; readonly text: string }
@@ -4441,25 +4529,59 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "shell.started"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
-      readonly data: { readonly sessionID: string; readonly callID: string; readonly command: string }
+      readonly data: {
+        readonly sessionID: string
+        readonly shell: {
+          readonly id: string
+          readonly status: "running" | "exited" | "timeout" | "killed"
+          readonly command: string
+          readonly cwd: string
+          readonly shell: string
+          readonly file: string
+          readonly pid?: number
+          readonly exit?: number
+          readonly metadata: { readonly [x: string]: unknown }
+          readonly time: { readonly started: number; readonly completed?: number }
+        }
+      }
     }
     }
   | {
   | {
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "shell.ended"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
-      readonly data: { readonly sessionID: string; readonly callID: string; readonly output: string }
+      readonly data: {
+        readonly sessionID: string
+        readonly shell: {
+          readonly id: string
+          readonly status: "running" | "exited" | "timeout" | "killed"
+          readonly command: string
+          readonly cwd: string
+          readonly shell: string
+          readonly file: string
+          readonly pid?: number
+          readonly exit?: number
+          readonly metadata: { readonly [x: string]: unknown }
+          readonly time: { readonly started: number; readonly completed?: number }
+        }
+        readonly output: {
+          readonly output: string
+          readonly cursor: number
+          readonly size: number
+          readonly truncated: boolean
+        }
+      }
     }
     }
   | {
   | {
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "step.started"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4474,7 +4596,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "step.ended"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4496,7 +4618,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "step.failed"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4509,7 +4631,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "text.started"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string }
       readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string }
@@ -4518,7 +4640,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "text.delta"
+      readonly type: "session.text.delta"
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
         readonly sessionID: string
         readonly sessionID: string
@@ -4531,7 +4653,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "text.ended"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4545,7 +4667,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "reasoning.started"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4559,7 +4681,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "reasoning.delta"
+      readonly type: "session.reasoning.delta"
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
         readonly sessionID: string
         readonly sessionID: string
@@ -4572,7 +4694,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "reasoning.ended"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4587,7 +4709,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "tool.input.started"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4601,7 +4723,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "tool.input.delta"
+      readonly type: "session.tool.input.delta"
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
         readonly sessionID: string
         readonly sessionID: string
@@ -4614,7 +4736,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "tool.input.ended"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4628,7 +4750,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "tool.called"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4647,7 +4769,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "tool.progress"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4665,7 +4787,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "tool.success"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4689,7 +4811,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "tool.failed"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4708,7 +4830,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "retried"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4728,7 +4850,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "compaction.started"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly reason: "auto" | "manual" }
       readonly data: { readonly sessionID: string; readonly reason: "auto" | "manual" }
@@ -4737,7 +4859,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "compaction.delta"
+      readonly type: "session.compaction.delta"
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly text: string }
       readonly data: { readonly sessionID: string; readonly text: string }
     }
     }
@@ -4745,7 +4867,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "compaction.ended"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4759,7 +4881,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "revert.staged"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: {
       readonly data: {
@@ -4783,7 +4905,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "revert.cleared"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string }
       readonly data: { readonly sessionID: string }
@@ -4792,7 +4914,7 @@ export type EventSubscribeOutput =
       readonly id: string
       readonly id: string
       readonly created: number
       readonly created: number
       readonly metadata?: { readonly [x: string]: unknown }
       readonly metadata?: { readonly [x: string]: unknown }
-      readonly type: "revert.committed"
+      readonly type: "session.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 location?: { readonly directory: string; readonly workspaceID?: string }
       readonly data: { readonly sessionID: string; readonly messageID: string }
       readonly data: { readonly sessionID: string; readonly messageID: string }

+ 5 - 5
packages/client/test/effect.test.ts

@@ -45,9 +45,9 @@ test("event.subscribe exposes and decodes the native Effect event stream", async
     return yield* client.event.subscribe().pipe(Stream.runCollect)
     return yield* client.event.subscribe().pipe(Stream.runCollect)
   }).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
   }).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
 
 
-  expect(Array.from(events).map((event) => event.type)).toEqual(["server.connected", "model.selected"])
+  expect(Array.from(events).map((event) => event.type)).toEqual(["server.connected", "session.model.selected"])
   const durable = events[1]
   const durable = events[1]
-  if (durable?.type !== "model.selected") throw new Error("Expected model event")
+  if (durable?.type !== "session.model.selected") throw new Error("Expected model event")
   expect(DateTime.toEpochMillis(durable.created)).toBe(1_717_171_717_000)
   expect(DateTime.toEpochMillis(durable.created)).toBe(1_717_171_717_000)
   expect(durable.durable).toEqual({ aggregateID: "ses_test", seq: 1, version: 1 })
   expect(durable.durable).toEqual({ aggregateID: "ses_test", seq: 1, version: 1 })
 })
 })
@@ -159,8 +159,8 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
   expect(result.context).toEqual([])
   expect(result.context).toEqual([])
   expect(logQueries[0]).toEqual({ after: "0" })
   expect(logQueries[0]).toEqual({ after: "0" })
   const logged = Array.from(result.log)
   const logged = Array.from(result.log)
-  expect(logged.map((item) => item.type)).toEqual(["model.selected", "log.synced"])
-  expect(logged[0]?.type === "model.selected" && DateTime.toEpochMillis(logged[0].created)).toBe(
+  expect(logged.map((item) => item.type)).toEqual(["session.model.selected", "log.synced"])
+  expect(logged[0]?.type === "session.model.selected" && DateTime.toEpochMillis(logged[0].created)).toBe(
     1_717_171_717_000,
     1_717_171_717_000,
   )
   )
   expect(logged.at(-1)).toEqual(synced)
   expect(logged.at(-1)).toEqual(synced)
@@ -228,7 +228,7 @@ const modelSwitchedMessage = {
 const modelSwitchedEvent = {
 const modelSwitchedEvent = {
   id: "evt_model",
   id: "evt_model",
   created: 1_717_171_717_000,
   created: 1_717_171_717_000,
-  type: "model.selected",
+  type: "session.model.selected",
   durable: { aggregateID: "ses_test", seq: 1, version: 1 },
   durable: { aggregateID: "ses_test", seq: 1, version: 1 },
   data: {
   data: {
     sessionID: "ses_test",
     sessionID: "ses_test",

+ 2 - 2
packages/client/test/promise.test.ts

@@ -160,7 +160,7 @@ test("event.subscribe exposes the Promise event stream wire projection", async (
   for await (const event of client.event.subscribe()) events.push(event)
   for await (const event of client.event.subscribe()) events.push(event)
 
 
   expect(events).toEqual([{ id: "evt_connected", created: 0, type: "server.connected", data: {} }, modelSwitchedEvent])
   expect(events).toEqual([{ id: "evt_connected", created: 0, type: "server.connected", data: {} }, modelSwitchedEvent])
-  expect(events[1]?.type === "model.selected" && events[1].created).toBe(1_717_171_717_000)
+  expect(events[1]?.type === "session.model.selected" && events[1].created).toBe(1_717_171_717_000)
 })
 })
 
 
 test("event.subscribe terminates on malformed Promise SSE data", async () => {
 test("event.subscribe terminates on malformed Promise SSE data", async () => {
@@ -329,7 +329,7 @@ const synced = { type: "log.synced", aggregateID: "ses_test", seq: 1 }
 const modelSwitchedEvent = {
 const modelSwitchedEvent = {
   id: "evt_model",
   id: "evt_model",
   created: 1_717_171_717_000,
   created: 1_717_171_717_000,
-  type: "model.selected",
+  type: "session.model.selected",
   durable: { aggregateID: "ses_test", seq: 1, version: 1 },
   durable: { aggregateID: "ses_test", seq: 1, version: 1 },
   data: {
   data: {
     sessionID: "ses_test",
     sessionID: "ses_test",

+ 1 - 0
packages/core/src/database/migration.gen.ts

@@ -43,5 +43,6 @@ export const migrations = (
     import("./migration/20260702134641_add_session_context_entry"),
     import("./migration/20260702134641_add_session_context_entry"),
     import("./migration/20260703090000_reset_v2_event_rename_sweep"),
     import("./migration/20260703090000_reset_v2_event_rename_sweep"),
     import("./migration/20260703181610_event_created_column"),
     import("./migration/20260703181610_event_created_column"),
+    import("./migration/20260703190000_reset_v2_shell_event_payloads"),
   ])
   ])
 ).map((module) => module.default) satisfies DatabaseMigration.Migration[]
 ).map((module) => module.default) satisfies DatabaseMigration.Migration[]

+ 14 - 0
packages/core/src/database/migration/20260703190000_reset_v2_shell_event_payloads.ts

@@ -0,0 +1,14 @@
+import { Effect } from "effect"
+import type { DatabaseMigration } from "../migration"
+
+export default {
+  id: "20260703190000_reset_v2_shell_event_payloads",
+  up(tx) {
+    return Effect.gen(function* () {
+      yield* tx.run(`DELETE FROM \`session_input\`;`)
+      yield* tx.run(`DELETE FROM \`session_message\`;`)
+      yield* tx.run(`DELETE FROM \`event\`;`)
+      yield* tx.run(`DELETE FROM \`event_sequence\`;`)
+    })
+  },
+} satisfies DatabaseMigration.Migration

+ 44 - 22
packages/core/src/session.ts

@@ -41,8 +41,8 @@ import type { EventLog } from "@opencode-ai/schema/event-log"
 import { SkillV2 } from "./skill"
 import { SkillV2 } from "./skill"
 import { Job } from "./job"
 import { Job } from "./job"
 import { CommandV2 } from "./command"
 import { CommandV2 } from "./command"
-import { Identifier } from "./util/identifier"
 import { Shell } from "./shell"
 import { Shell } from "./shell"
+import { Shell as ShellSchema } from "@opencode-ai/schema/shell"
 import { KeyedMutex } from "./effect/keyed-mutex"
 import { KeyedMutex } from "./effect/keyed-mutex"
 
 
 export const RevertState = Revert.State
 export const RevertState = Revert.State
@@ -272,19 +272,6 @@ const layer = Layer.effect(
         ),
         ),
       )
       )
 
 
-    // Session shell is user-initiated and synchronous at the API boundary, while
-    // the Location shell service owns process lifecycle and file-backed output.
-    const runShellCommand = (command: string, cwd: string) =>
-      Effect.gen(function* () {
-        const shell = yield* Shell.Service
-        const info = yield* shell.create({ command, cwd })
-        yield* shell.wait(info.id)
-        const output = yield* shell.output(info.id, { limit: SHELL_MAX_CAPTURE_BYTES })
-        return output.output || "(no output)"
-      }).pipe(
-        Effect.catchTag("Shell.NotFoundError", () => Effect.succeed("Shell command output is no longer available.")),
-      )
-
     const result = Service.of({
     const result = Service.of({
       create: Effect.fn("V2Session.create")(function* (input) {
       create: Effect.fn("V2Session.create")(function* (input) {
         const sessionID = input.id ?? SessionSchema.ID.create()
         const sessionID = input.id ?? SessionSchema.ID.create()
@@ -550,23 +537,38 @@ const layer = Layer.effect(
           Effect.gen(function* () {
           Effect.gen(function* () {
             activeShells.add(input.sessionID)
             activeShells.add(input.sessionID)
             if ((yield* execution.active).has(input.sessionID)) yield* execution.awaitIdle(input.sessionID)
             if ((yield* execution.active).has(input.sessionID)) yield* execution.awaitIdle(input.sessionID)
-            const callID = Identifier.ascending()
+            const started = yield* Effect.gen(function* () {
+              const shell = yield* Shell.Service
+              return yield* shell.create({ command: input.command, cwd: session.location.directory })
+            }).pipe(Effect.provide(locations.get(session.location)))
             yield* events.publish(
             yield* events.publish(
               SessionEvent.Shell.Started,
               SessionEvent.Shell.Started,
               {
               {
                 sessionID: input.sessionID,
                 sessionID: input.sessionID,
-                callID,
-                command: input.command,
+                shell: started,
               },
               },
               { id: input.id },
               { id: input.id },
             )
             )
-            const output = yield* runShellCommand(input.command, session.location.directory).pipe(
-              Effect.provide(locations.get(session.location)),
-            )
+            const completed = yield* Effect.gen(function* () {
+              const shell = yield* Shell.Service
+              const terminal = yield* shell.wait(started.id).pipe(
+                Effect.map((info) => ({ info, retained: true as const })),
+                Effect.catchTag("Shell.NotFoundError", () =>
+                  Effect.succeed({ info: synthesizeTerminalShellInfo(started), retained: false as const }),
+                ),
+              )
+              const output = terminal.retained
+                ? yield* shell
+                    .output(started.id, { limit: SHELL_MAX_CAPTURE_BYTES })
+                    .pipe(Effect.catchTag("Shell.NotFoundError", () => Effect.succeed(missingShellOutput())))
+                : missingShellOutput()
+              return { shell: terminal.info, output }
+            })
+              .pipe(Effect.provide(locations.get(session.location)))
             yield* events.publish(SessionEvent.Shell.Ended, {
             yield* events.publish(SessionEvent.Shell.Ended, {
               sessionID: input.sessionID,
               sessionID: input.sessionID,
-              callID,
-              output,
+              shell: completed.shell,
+              output: completed.output,
             })
             })
           }).pipe(
           }).pipe(
             Effect.ensuring(
             Effect.ensuring(
@@ -706,6 +708,26 @@ const layer = Layer.effect(
   }),
   }),
 )
 )
 
 
+function missingShellOutput() {
+  const output = "Shell command output is no longer available."
+  return {
+    output,
+    cursor: Buffer.byteLength(output),
+    size: Buffer.byteLength(output),
+    truncated: false,
+  }
+}
+
+function synthesizeTerminalShellInfo(started: ShellSchema.Info): ShellSchema.Info {
+  return {
+    ...started,
+    // The Shell record was removed before waiters could observe it; publish a terminal
+    // boundary instead of leaving the Session shell message permanently running.
+    status: "killed",
+    time: { ...started.time, completed: Date.now() },
+  }
+}
+
 const resolvePrompt = (input: PromptInput.Prompt) =>
 const resolvePrompt = (input: PromptInput.Prompt) =>
   Prompt.make({
   Prompt.make({
     text: input.text,
     text: input.text,

+ 1 - 1
packages/core/src/session/compaction.ts

@@ -129,7 +129,7 @@ const serialize = (message: SessionMessage.Message) => {
   if (message.type === "system") return `[System update]: ${message.text}`
   if (message.type === "system") return `[System update]: ${message.text}`
   if (message.type === "synthetic") return `[Synthetic context]: ${message.text}`
   if (message.type === "synthetic") return `[Synthetic context]: ${message.text}`
   if (message.type === "skill") return `[Skill activated: ${message.name}]\n${message.text}`
   if (message.type === "skill") return `[Skill activated: ${message.name}]\n${message.text}`
-  if (message.type === "shell") return `[Shell]: ${message.command}\n${truncate(message.output)}`
+  if (message.type === "shell") return `[Shell]: ${message.shell.command}\n${truncate(message.output?.output ?? "")}`
   return ""
   return ""
 }
 }
 
 

+ 54 - 52
packages/core/src/session/message-updater.ts

@@ -8,21 +8,25 @@ export type MemoryState = {
 }
 }
 
 
 export interface Adapter {
 export interface Adapter {
-  readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined>
-  readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect<SessionMessage.Assistant | undefined>
-  readonly getCurrentShell: (callID: string) => Effect.Effect<SessionMessage.Shell | undefined>
-  readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void>
-  readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void>
-  readonly appendMessage: (message: SessionMessage.Message) => Effect.Effect<void>
+  readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined, never, never>
+  readonly getAssistant: (
+    messageID: SessionMessage.ID,
+  ) => Effect.Effect<SessionMessage.Assistant | undefined, never, never>
+  readonly getShell: (
+    shellID: SessionMessage.Shell["shell"]["id"],
+  ) => Effect.Effect<SessionMessage.Shell | undefined, never, never>
+  readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void, never, never>
+  readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void, never, never>
+  readonly appendMessage: (message: SessionMessage.Message) => Effect.Effect<void, never, never>
 }
 }
 
 
 export function memory(state: MemoryState): Adapter {
 export function memory(state: MemoryState): Adapter {
   const assistantIndex = (messageID: SessionMessage.ID) =>
   const assistantIndex = (messageID: SessionMessage.ID) =>
     state.messages.findLastIndex((message) => message.id === messageID)
     state.messages.findLastIndex((message) => message.id === messageID)
+  const shellIndex = (messageID: SessionMessage.ID) =>
+    state.messages.findLastIndex((message) => message.id === messageID)
   // A newer step supersedes stale incomplete rows; never resume an older assistant projection.
   // A newer step supersedes stale incomplete rows; never resume an older assistant projection.
   const latestAssistantIndex = () => state.messages.findLastIndex((message) => message.type === "assistant")
   const latestAssistantIndex = () => state.messages.findLastIndex((message) => message.type === "assistant")
-  const activeShellIndex = (callID: string) =>
-    state.messages.findLastIndex((message) => message.type === "shell" && message.callID === callID)
 
 
   return {
   return {
     getCurrentAssistant() {
     getCurrentAssistant() {
@@ -41,12 +45,11 @@ export function memory(state: MemoryState): Adapter {
         return assistant?.type === "assistant" ? assistant : undefined
         return assistant?.type === "assistant" ? assistant : undefined
       })
       })
     },
     },
-    getCurrentShell(callID) {
+    getShell(shellID) {
       return Effect.sync(() => {
       return Effect.sync(() => {
-        const index = activeShellIndex(callID)
-        if (index < 0) return
-        const shell = state.messages[index]
-        return shell?.type === "shell" ? shell : undefined
+        return state.messages.find((message): message is SessionMessage.Shell => {
+          return message.type === "shell" && message.shell.id === shellID
+        })
       })
       })
     },
     },
     updateAssistant(assistant) {
     updateAssistant(assistant) {
@@ -60,7 +63,7 @@ export function memory(state: MemoryState): Adapter {
     },
     },
     updateShell(shell) {
     updateShell(shell) {
       return Effect.sync(() => {
       return Effect.sync(() => {
-        const index = activeShellIndex(shell.callID)
+        const index = shellIndex(shell.id)
         if (index < 0) return
         if (index < 0) return
         const current = state.messages[index]
         const current = state.messages[index]
         if (current?.type !== "shell") return
         if (current?.type !== "shell") return
@@ -100,7 +103,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
 
 
   return Effect.gen(function* () {
   return Effect.gen(function* () {
     yield* SessionEvent.All.match(event, {
     yield* SessionEvent.All.match(event, {
-      "agent.selected": (event) => {
+      "session.agent.selected": (event) => {
         return adapter.appendMessage(
         return adapter.appendMessage(
           SessionMessage.AgentSelected.make({
           SessionMessage.AgentSelected.make({
             id: SessionMessage.ID.fromEvent(event.id),
             id: SessionMessage.ID.fromEvent(event.id),
@@ -111,7 +114,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }),
           }),
         )
         )
       },
       },
-      "model.selected": (event) => {
+      "session.model.selected": (event) => {
         return adapter.appendMessage(
         return adapter.appendMessage(
           SessionMessage.ModelSelected.make({
           SessionMessage.ModelSelected.make({
             id: SessionMessage.ID.fromEvent(event.id),
             id: SessionMessage.ID.fromEvent(event.id),
@@ -123,11 +126,11 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
         )
         )
       },
       },
       "session.moved": () => Effect.void,
       "session.moved": () => Effect.void,
-      renamed: () => Effect.void,
-      forked: () => Effect.void,
-      "prompt.promoted": () => Effect.void,
-      "prompt.admitted": () => Effect.void,
-      "execution.settled": () => Effect.void,
+      "session.renamed": () => Effect.void,
+      "session.forked": () => Effect.void,
+      "session.prompt.promoted": () => Effect.void,
+      "session.prompt.admitted": () => Effect.void,
+      "session.execution.settled": () => Effect.void,
       "session.context.updated": (event) =>
       "session.context.updated": (event) =>
         adapter.appendMessage(
         adapter.appendMessage(
           SessionMessage.System.make({
           SessionMessage.System.make({
@@ -137,7 +140,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
             time: { created: event.created },
             time: { created: event.created },
           }),
           }),
         ),
         ),
-      synthetic: (event) => {
+      "session.synthetic": (event) => {
         return adapter.appendMessage(
         return adapter.appendMessage(
           SessionMessage.Synthetic.make({
           SessionMessage.Synthetic.make({
             sessionID: event.data.sessionID,
             sessionID: event.data.sessionID,
@@ -150,7 +153,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }),
           }),
         )
         )
       },
       },
-      "skill.activated": (event) => {
+      "session.skill.activated": (event) => {
         return adapter.appendMessage(
         return adapter.appendMessage(
           SessionMessage.Skill.make({
           SessionMessage.Skill.make({
             id: SessionMessage.ID.fromEvent(event.id),
             id: SessionMessage.ID.fromEvent(event.id),
@@ -161,25 +164,24 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }),
           }),
         )
         )
       },
       },
-      "shell.started": (event) => {
+      "session.shell.started": (event) => {
         return adapter.appendMessage(
         return adapter.appendMessage(
           SessionMessage.Shell.make({
           SessionMessage.Shell.make({
             id: SessionMessage.ID.fromEvent(event.id),
             id: SessionMessage.ID.fromEvent(event.id),
             type: "shell",
             type: "shell",
             metadata: event.metadata,
             metadata: event.metadata,
-            callID: event.data.callID,
-            command: event.data.command,
-            output: "",
+            shell: event.data.shell,
             time: { created: event.created },
             time: { created: event.created },
           }),
           }),
         )
         )
       },
       },
-      "shell.ended": (event) => {
+      "session.shell.ended": (event) => {
         return Effect.gen(function* () {
         return Effect.gen(function* () {
-          const currentShell = yield* adapter.getCurrentShell(event.data.callID)
+          const currentShell = yield* adapter.getShell(event.data.shell.id)
           if (currentShell) {
           if (currentShell) {
             yield* adapter.updateShell(
             yield* adapter.updateShell(
               produce(currentShell, (draft) => {
               produce(currentShell, (draft) => {
+                draft.shell = castDraft(event.data.shell)
                 draft.output = event.data.output
                 draft.output = event.data.output
                 draft.time.completed = event.created
                 draft.time.completed = event.created
               }),
               }),
@@ -187,7 +189,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }
           }
         })
         })
       },
       },
-      "step.started": (event) => {
+      "session.step.started": (event) => {
         return Effect.gen(function* () {
         return Effect.gen(function* () {
           const currentAssistant = yield* adapter.getCurrentAssistant()
           const currentAssistant = yield* adapter.getCurrentAssistant()
           if (currentAssistant) {
           if (currentAssistant) {
@@ -210,7 +212,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           )
           )
         })
         })
       },
       },
-      "step.ended": (event) => {
+      "session.step.ended": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           draft.time.completed = event.created
           draft.time.completed = event.created
           draft.finish = event.data.finish
           draft.finish = event.data.finish
@@ -224,33 +226,33 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
             }
             }
         })
         })
       },
       },
-      "step.failed": (event) => {
+      "session.step.failed": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           draft.time.completed = event.created
           draft.time.completed = event.created
           draft.finish = "error"
           draft.finish = "error"
           draft.error = event.data.error
           draft.error = event.data.error
         })
         })
       },
       },
-      "text.started": (event) => {
+      "session.text.started": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           draft.content.push(
           draft.content.push(
             castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })),
             castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })),
           )
           )
         })
         })
       },
       },
-      "text.delta": (event) => {
+      "session.text.delta": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestText(draft, event.data.textID)
           const match = latestText(draft, event.data.textID)
           if (match) match.text += event.data.delta
           if (match) match.text += event.data.delta
         })
         })
       },
       },
-      "text.ended": (event) => {
+      "session.text.ended": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestText(draft, event.data.textID)
           const match = latestText(draft, event.data.textID)
           if (match) match.text = event.data.text
           if (match) match.text = event.data.text
         })
         })
       },
       },
-      "tool.input.started": (event) => {
+      "session.tool.input.started": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           draft.content.push(
           draft.content.push(
             castDraft(
             castDraft(
@@ -265,14 +267,14 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           )
           )
         })
         })
       },
       },
-      "tool.input.delta": () => Effect.void,
-      "tool.input.ended": (event) => {
+      "session.tool.input.delta": () => Effect.void,
+      "session.tool.input.ended": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestTool(draft, event.data.callID)
           const match = latestTool(draft, event.data.callID)
           if (match && match.state.status === "pending") match.state.input = event.data.text
           if (match && match.state.status === "pending") match.state.input = event.data.text
         })
         })
       },
       },
-      "tool.called": (event) => {
+      "session.tool.called": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestTool(draft, event.data.callID)
           const match = latestTool(draft, event.data.callID)
           if (match) {
           if (match) {
@@ -289,7 +291,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }
           }
         })
         })
       },
       },
-      "tool.progress": (event) => {
+      "session.tool.progress": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestTool(draft, event.data.callID)
           const match = latestTool(draft, event.data.callID)
           if (match && match.state.status === "running") {
           if (match && match.state.status === "running") {
@@ -298,7 +300,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }
           }
         })
         })
       },
       },
-      "tool.success": (event) => {
+      "session.tool.success": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestTool(draft, event.data.callID)
           const match = latestTool(draft, event.data.callID)
           if (match && match.state.status === "running") {
           if (match && match.state.status === "running") {
@@ -321,7 +323,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }
           }
         })
         })
       },
       },
-      "tool.failed": (event) => {
+      "session.tool.failed": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestTool(draft, event.data.callID)
           const match = latestTool(draft, event.data.callID)
           if (match && (match.state.status === "pending" || match.state.status === "running")) {
           if (match && (match.state.status === "pending" || match.state.status === "running")) {
@@ -344,7 +346,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }
           }
         })
         })
       },
       },
-      "reasoning.started": (event) => {
+      "session.reasoning.started": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           draft.content.push(
           draft.content.push(
             castDraft(
             castDraft(
@@ -359,13 +361,13 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           )
           )
         })
         })
       },
       },
-      "reasoning.delta": (event) => {
+      "session.reasoning.delta": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestReasoning(draft, event.data.reasoningID)
           const match = latestReasoning(draft, event.data.reasoningID)
           if (match) match.text += event.data.delta
           if (match) match.text += event.data.delta
         })
         })
       },
       },
-      "reasoning.ended": (event) => {
+      "session.reasoning.ended": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestReasoning(draft, event.data.reasoningID)
           const match = latestReasoning(draft, event.data.reasoningID)
           if (match) {
           if (match) {
@@ -375,10 +377,10 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }
           }
         })
         })
       },
       },
-      retried: () => Effect.void,
-      "compaction.started": () => Effect.void,
-      "compaction.delta": () => Effect.void,
-      "compaction.ended": (event) => {
+      "session.retried": () => Effect.void,
+      "session.compaction.started": () => Effect.void,
+      "session.compaction.delta": () => Effect.void,
+      "session.compaction.ended": (event) => {
         return adapter.appendMessage(
         return adapter.appendMessage(
           SessionMessage.Compaction.make({
           SessionMessage.Compaction.make({
             id: SessionMessage.ID.fromEvent(event.id),
             id: SessionMessage.ID.fromEvent(event.id),
@@ -391,9 +393,9 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }),
           }),
         )
         )
       },
       },
-      "revert.staged": () => Effect.void,
-      "revert.cleared": () => Effect.void,
-      "revert.committed": () => Effect.void,
+      "session.revert.staged": () => Effect.void,
+      "session.revert.cleared": () => Effect.void,
+      "session.revert.committed": () => Effect.void,
     })
     })
   })
   })
 }
 }

+ 14 - 7
packages/core/src/session/projector.ts

@@ -393,18 +393,25 @@ function run(db: DatabaseService, event: MessageEvent) {
           return message.type === "assistant" ? message : undefined
           return message.type === "assistant" ? message : undefined
         })
         })
       },
       },
-      getCurrentShell(callID) {
+      getShell(shellID) {
         return Effect.gen(function* () {
         return Effect.gen(function* () {
-          const rows = yield* db
+          const row = yield* db
             .select()
             .select()
             .from(SessionMessageTable)
             .from(SessionMessageTable)
-            .where(and(eq(SessionMessageTable.session_id, event.data.sessionID), eq(SessionMessageTable.type, "shell")))
+            .where(
+              and(
+                eq(SessionMessageTable.session_id, event.data.sessionID),
+                eq(SessionMessageTable.type, "shell"),
+                sql`json_extract(${SessionMessageTable.data}, '$.shell.id') = ${shellID}`,
+              ),
+            )
             .orderBy(desc(SessionMessageTable.seq))
             .orderBy(desc(SessionMessageTable.seq))
-            .all()
+            .limit(1)
+            .get()
             .pipe(Effect.orDie)
             .pipe(Effect.orDie)
-          return rows
-            .map(decodeRow)
-            .find((message): message is SessionMessage.Shell => message.type === "shell" && message.callID === callID)
+          if (!row) return
+          const message = decodeRow(row)
+          return message.type === "shell" ? message : undefined
         })
         })
       },
       },
       updateAssistant: updateMessage,
       updateAssistant: updateMessage,

+ 1 - 1
packages/core/src/session/runner/to-llm-message.ts

@@ -139,7 +139,7 @@ function toLLMMessage(message: SessionMessage.Message, model: Model): Message[]
         Message.make({
         Message.make({
           id: message.id,
           id: message.id,
           role: "user",
           role: "user",
-          content: `Shell command: ${message.command}\n\n${message.output}`,
+          content: `Shell command: ${message.shell.command}\n\n${message.output?.output ?? ""}`,
           metadata: message.metadata,
           metadata: message.metadata,
         }),
         }),
       ]
       ]

+ 10 - 8
packages/core/test/session-create.test.ts

@@ -213,7 +213,7 @@ describe("SessionV2.create", () => {
       expect(forkContext.map((message) => message.id)).not.toEqual(parentContext.map((message) => message.id))
       expect(forkContext.map((message) => message.id)).not.toEqual(parentContext.map((message) => message.id))
       expect(history).toHaveLength(1)
       expect(history).toHaveLength(1)
       expect(history[0]).toMatchObject({
       expect(history[0]).toMatchObject({
-        type: "forked",
+        type: "session.forked",
         durable: { seq: 0 },
         durable: { seq: 0 },
         data: { sessionID: forked.id, parentID: parent.id },
         data: { sessionID: forked.id, parentID: parent.id },
       })
       })
@@ -378,8 +378,8 @@ describe("SessionV2.create", () => {
       expect(
       expect(
         Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(2), Stream.runCollect)),
         Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(2), Stream.runCollect)),
       ).toMatchObject([
       ).toMatchObject([
-        { durable: { seq: 1 }, type: "prompt.admitted", data: { prompt: { text: "Hello" } } },
-        { durable: { seq: 2 }, type: "prompt.promoted" },
+        { durable: { seq: 1 }, type: "session.prompt.admitted", data: { prompt: { text: "Hello" } } },
+        { durable: { seq: 2 }, type: "session.prompt.promoted" },
       ])
       ])
     }),
     }),
   )
   )
@@ -494,8 +494,9 @@ describe("SessionV2.create", () => {
 
 
         const messages = yield* session.messages({ sessionID: created.id, order: "asc" })
         const messages = yield* session.messages({ sessionID: created.id, order: "asc" })
         const shell = messages.find((message): message is SessionMessage.Shell => message.type === "shell")
         const shell = messages.find((message): message is SessionMessage.Shell => message.type === "shell")
-        expect(shell).toMatchObject({ type: "shell", command: "echo hello" })
-        expect(shell?.output).toContain("hello")
+        expect(shell).toMatchObject({ type: "shell", shell: { command: "echo hello", status: "exited", exit: 0 } })
+        expect(shell?.output?.output).toContain("hello")
+        expect(shell?.output?.truncated).toBe(false)
         expect(shell?.time.completed).toBeDefined()
         expect(shell?.time.completed).toBeDefined()
       }),
       }),
     ),
     ),
@@ -513,7 +514,8 @@ describe("SessionV2.create", () => {
 
 
         const messages = yield* session.messages({ sessionID: created.id, order: "asc" })
         const messages = yield* session.messages({ sessionID: created.id, order: "asc" })
         const shell = messages.find((message): message is SessionMessage.Shell => message.type === "shell")
         const shell = messages.find((message): message is SessionMessage.Shell => message.type === "shell")
-        expect(shell).toMatchObject({ type: "shell", command: "false" })
+        expect(shell).toMatchObject({ type: "shell", shell: { command: "false", status: "exited" } })
+        expect(shell?.shell.exit).not.toBe(0)
         expect(shell?.time.completed).toBeDefined()
         expect(shell?.time.completed).toBeDefined()
       }),
       }),
     ),
     ),
@@ -529,7 +531,7 @@ describe("SessionV2.create", () => {
       expect(yield* session.get(created.id)).toMatchObject({ agent: "plan" })
       expect(yield* session.get(created.id)).toMatchObject({ agent: "plan" })
       expect(
       expect(
         Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(1), Stream.runCollect)),
         Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(1), Stream.runCollect)),
-      ).toMatchObject([{ type: "agent.selected", data: { agent: "plan" } }])
+      ).toMatchObject([{ type: "session.agent.selected", data: { agent: "plan" } }])
     }),
     }),
   )
   )
 
 
@@ -562,7 +564,7 @@ describe("SessionV2.create", () => {
       expect(yield* session.get(created.id)).toMatchObject({ model })
       expect(yield* session.get(created.id)).toMatchObject({ model })
       expect(
       expect(
         Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(1), Stream.runCollect)),
         Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(1), Stream.runCollect)),
-      ).toMatchObject([{ type: "model.selected", data: { model } }])
+      ).toMatchObject([{ type: "session.model.selected", data: { model } }])
     }),
     }),
   )
   )
 
 

+ 4 - 4
packages/core/test/session-log.test.ts

@@ -40,14 +40,14 @@ describe("SessionV2.log", () => {
       const session = yield* SessionV2.Service
       const session = yield* SessionV2.Service
       const events = yield* EventV2.Service
       const events = yield* EventV2.Service
       const created = yield* session.create({ location })
       const created = yield* session.create({ location })
-      yield* session.rename({ sessionID: created.id, title: "renamed" })
+      yield* session.rename({ sessionID: created.id, title: "session.renamed" })
 
 
       const items = Array.from(yield* Stream.runCollect(session.log({ sessionID: created.id })))
       const items = Array.from(yield* Stream.runCollect(session.log({ sessionID: created.id })))
       const watermark = (yield* events.sequences([created.id])).get(created.id)
       const watermark = (yield* events.sequences([created.id])).get(created.id)
 
 
       // Session creation commits a non-public durable event, so the marker's
       // Session creation commits a non-public durable event, so the marker's
       // seq covers more of the aggregate than the public events emitted.
       // seq covers more of the aggregate than the public events emitted.
-      expect(items.map((item) => item.type)).toEqual(["renamed", "log.synced"])
+      expect(items.map((item) => item.type)).toEqual(["session.renamed", "log.synced"])
       expect(items.at(-1)).toEqual({ type: "log.synced", aggregateID: created.id, seq: watermark })
       expect(items.at(-1)).toEqual({ type: "log.synced", aggregateID: created.id, seq: watermark })
     }),
     }),
   )
   )
@@ -64,7 +64,7 @@ describe("SessionV2.log", () => {
       yield* session.rename({ sessionID: created.id, title: "renamed live" })
       yield* session.rename({ sessionID: created.id, title: "renamed live" })
 
 
       const items = Array.from(yield* Fiber.join(fiber))
       const items = Array.from(yield* Fiber.join(fiber))
-      expect(items.map((item) => item.type)).toEqual(["log.synced", "renamed"])
+      expect(items.map((item) => item.type)).toEqual(["log.synced", "session.renamed"])
     }),
     }),
   )
   )
 
 
@@ -137,7 +137,7 @@ describe("SessionV2 watermarks", () => {
       const events = yield* EventV2.Service
       const events = yield* EventV2.Service
       const first = yield* session.create({ location })
       const first = yield* session.create({ location })
       const second = yield* session.create({ location })
       const second = yield* session.create({ location })
-      yield* session.rename({ sessionID: first.id, title: "renamed" })
+      yield* session.rename({ sessionID: first.id, title: "session.renamed" })
 
 
       const page = yield* session.list()
       const page = yield* session.list()
       const sequences = yield* events.sequences([first.id, second.id])
       const sequences = yield* events.sequences([first.id, second.id])

+ 25 - 6
packages/core/test/session-projector.test.ts

@@ -19,6 +19,7 @@ import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater
 import { SessionProjector } from "@opencode-ai/core/session/projector"
 import { SessionProjector } from "@opencode-ai/core/session/projector"
 import { SessionExecution } from "@opencode-ai/core/session/execution"
 import { SessionExecution } from "@opencode-ai/core/session/execution"
 import { SessionInput } from "@opencode-ai/core/session/input"
 import { SessionInput } from "@opencode-ai/core/session/input"
+import { Shell } from "@opencode-ai/schema/shell"
 import {
 import {
   SessionContextCheckpointTable,
   SessionContextCheckpointTable,
   SessionInputTable,
   SessionInputTable,
@@ -257,15 +258,32 @@ describe("SessionProjector", () => {
       })
       })
       yield* events.publish(SessionEvent.Shell.Started, {
       yield* events.publish(SessionEvent.Shell.Started, {
         sessionID,
         sessionID,
-        callID: "shell-1",
-        command: "pwd",
+        shell: Shell.Info.make({
+          id: Shell.ID.make("sh_projector"),
+          status: "running",
+          command: "pwd",
+          cwd: "/project",
+          shell: "/bin/sh",
+          file: "/tmp/sh_projector.out",
+          metadata: {},
+          time: { started: 0 },
+        }),
       })
       })
       yield* events.publish(SessionEvent.Shell.Ended, {
       yield* events.publish(SessionEvent.Shell.Ended, {
         sessionID,
         sessionID,
-        callID: "shell-1",
-        output: "/project",
+        shell: Shell.Info.make({
+          id: Shell.ID.make("sh_projector"),
+          status: "exited",
+          command: "pwd",
+          cwd: "/project",
+          shell: "/bin/sh",
+          file: "/tmp/sh_projector.out",
+          exit: 0,
+          metadata: {},
+          time: { started: 0, completed: 1 },
+        }),
+        output: { output: "/project", cursor: 8, size: 8, truncated: false },
       })
       })
-      const compactionID = SessionMessage.ID.create()
       yield* events.publish(SessionEvent.Compaction.Started, {
       yield* events.publish(SessionEvent.Compaction.Started, {
         sessionID,
         sessionID,
         reason: "manual",
         reason: "manual",
@@ -320,7 +338,8 @@ describe("SessionProjector", () => {
         metadata: { source: "projector-test" },
         metadata: { source: "projector-test" },
       })
       })
       expect(messages.find((message) => message.type === "shell")).toMatchObject({
       expect(messages.find((message) => message.type === "shell")).toMatchObject({
-        output: "/project",
+        shell: { command: "pwd", status: "exited", exit: 0 },
+        output: { output: "/project", truncated: false },
         time: { completed: DateTime.makeUnsafe(0) },
         time: { completed: DateTime.makeUnsafe(0) },
       })
       })
       expect(messages.find((message) => message.type === "compaction")).toMatchObject({
       expect(messages.find((message) => message.type === "compaction")).toMatchObject({

+ 5 - 5
packages/core/test/session-prompt.test.ts

@@ -256,16 +256,16 @@ describe("SessionV2.prompt", () => {
       const streamed = Array.from(yield* Fiber.join(fiber))
       const streamed = Array.from(yield* Fiber.join(fiber))
 
 
       expect(streamed.map((event): [number | undefined, string] => [event.durable?.seq, event.type])).toEqual([
       expect(streamed.map((event): [number | undefined, string] => [event.durable?.seq, event.type])).toEqual([
-        [0, "prompt.admitted"],
-        [1, "prompt.admitted"],
-        [2, "prompt.promoted"],
-        [3, "prompt.promoted"],
+        [0, "session.prompt.admitted"],
+        [1, "session.prompt.admitted"],
+        [2, "session.prompt.promoted"],
+        [3, "session.prompt.promoted"],
       ])
       ])
       expect(
       expect(
         Array.from(
         Array.from(
           yield* publicEvents({ sessionID, after: streamed[0].durable?.seq }).pipe(Stream.take(1), Stream.runCollect),
           yield* publicEvents({ sessionID, after: streamed[0].durable?.seq }).pipe(Stream.take(1), Stream.runCollect),
         ).map((event): [number | undefined, string] => [event.durable?.seq, event.type]),
         ).map((event): [number | undefined, string] => [event.durable?.seq, event.type]),
-      ).toEqual([[1, "prompt.admitted"]])
+      ).toEqual([[1, "session.prompt.admitted"]])
     }),
     }),
   )
   )
 
 

+ 13 - 3
packages/core/test/session-runner-message.test.ts

@@ -7,6 +7,7 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
 import { AgentAttachment, FileAttachment } from "@opencode-ai/core/session/prompt"
 import { AgentAttachment, FileAttachment } from "@opencode-ai/core/session/prompt"
 import { toLLMMessages } from "@opencode-ai/core/session/runner/to-llm-message"
 import { toLLMMessages } from "@opencode-ai/core/session/runner/to-llm-message"
 import { SessionV2 } from "@opencode-ai/core/session"
 import { SessionV2 } from "@opencode-ai/core/session"
+import { Shell } from "@opencode-ai/schema/shell"
 import { DateTime } from "effect"
 import { DateTime } from "effect"
 
 
 const created = DateTime.makeUnsafe(0)
 const created = DateTime.makeUnsafe(0)
@@ -87,9 +88,18 @@ describe("toLLMMessages", () => {
         SessionMessage.Shell.make({
         SessionMessage.Shell.make({
           id: id("shell"),
           id: id("shell"),
           type: "shell",
           type: "shell",
-          callID: "shell-1",
-          command: "pwd",
-          output: "/project",
+          shell: Shell.Info.make({
+            id: Shell.ID.make("sh_test"),
+            status: "exited",
+            command: "pwd",
+            cwd: "/project",
+            shell: "/bin/sh",
+            file: "/tmp/sh_test.out",
+            exit: 0,
+            metadata: {},
+            time: { started: 0, completed: 0 },
+          }),
+          output: { output: "/project", cursor: 8, size: 8, truncated: false },
           time: { created, completed: created },
           time: { created, completed: created },
         }),
         }),
         SessionMessage.Compaction.make({
         SessionMessage.Compaction.make({

+ 6 - 6
packages/core/test/session-runner-recorded.test.ts

@@ -193,12 +193,12 @@ describe("SessionRunnerLLM recorded", () => {
           .orderBy(EventTable.seq)
           .orderBy(EventTable.seq)
           .all()).map((event) => event.type),
           .all()).map((event) => event.type),
       ).toEqual([
       ).toEqual([
-        "prompt.admitted.1",
-        "prompt.promoted.1",
-        "step.started.1",
-        "text.started.1",
-        "text.ended.1",
-        "step.ended.1",
+        "session.prompt.admitted.1",
+        "session.prompt.promoted.1",
+        "session.step.started.1",
+        "session.text.started.1",
+        "session.text.ended.1",
+        "session.step.ended.1",
       ])
       ])
     }),
     }),
   )
   )

+ 4 - 4
packages/core/test/session-runner-tool-events.test.ts

@@ -76,7 +76,7 @@ test("local tool success serializes media base64 once and reconstructs from stru
   await Effect.runPromise(publisher.publish(call))
   await Effect.runPromise(publisher.publish(call))
   await Effect.runPromise(publisher.publish(result))
   await Effect.runPromise(publisher.publish(result))
 
 
-  const success = published.find((event) => event.type === "tool.success.1")
+  const success = published.find((event) => event.type === "session.tool.success.1")
   expect(success).toBeDefined()
   expect(success).toBeDefined()
   const serialized = JSON.stringify(success)
   const serialized = JSON.stringify(success)
   expect(serialized.split(base64)).toHaveLength(2)
   expect(serialized.split(base64)).toHaveLength(2)
@@ -94,7 +94,7 @@ test("provider-executed success retains its compatibility result", async () => {
   const { published, publisher } = capture()
   const { published, publisher } = capture()
   await Effect.runPromise(publisher.publish(LLMEvent.toolCall({ ...call, providerExecuted: true })))
   await Effect.runPromise(publisher.publish(LLMEvent.toolCall({ ...call, providerExecuted: true })))
   await Effect.runPromise(publisher.publish(LLMEvent.toolResult({ ...result, providerExecuted: true })))
   await Effect.runPromise(publisher.publish(LLMEvent.toolResult({ ...result, providerExecuted: true })))
-  const success = published.find((event) => event.type === "tool.success.1")
+  const success = published.find((event) => event.type === "session.tool.success.1")
   expect(success?.data).toHaveProperty("result")
   expect(success?.data).toHaveProperty("result")
 })
 })
 
 
@@ -110,8 +110,8 @@ test("binary failure emits no success event", async () => {
       }),
       }),
     ),
     ),
   )
   )
-  expect(published.some((event) => event.type === "tool.success.1")).toBe(false)
-  expect(published.some((event) => event.type === "tool.failed.1")).toBe(true)
+  expect(published.some((event) => event.type === "session.tool.success.1")).toBe(false)
+  expect(published.some((event) => event.type === "session.tool.failed.1")).toBe(true)
 })
 })
 
 
 test("old success event data containing result still decodes", () => {
 test("old success event data containing result still decodes", () => {

+ 3 - 3
packages/core/test/session-runner.test.ts

@@ -2985,7 +2985,7 @@ describe("SessionRunnerLLM", () => {
         { type: "user", text: "Interrupt provider" },
         { type: "user", text: "Interrupt provider" },
         { type: "assistant", finish: "error", error: { type: "unknown", message: "Step interrupted" } },
         { type: "assistant", finish: "error", error: { type: "unknown", message: "Step interrupted" } },
       ])
       ])
-      expect(yield* recordedEventTypes(sessionID)).toContain("step.failed.1")
+      expect(yield* recordedEventTypes(sessionID)).toContain("session.step.failed.1")
       yield* session.interrupt(sessionID)
       yield* session.interrupt(sessionID)
     }),
     }),
   )
   )
@@ -3029,8 +3029,8 @@ describe("SessionRunnerLLM", () => {
         },
         },
       ])
       ])
       const eventTypes = yield* recordedEventTypes(sessionID)
       const eventTypes = yield* recordedEventTypes(sessionID)
-      expect(eventTypes).toContain("step.failed.1")
-      expect(eventTypes).not.toContain("step.ended.1")
+      expect(eventTypes).toContain("session.step.failed.1")
+      expect(eventTypes).not.toContain("session.step.ended.1")
     }),
     }),
   )
   )
 
 

+ 15 - 15
packages/opencode/src/cli/cmd/run/noninteractive.ts

@@ -158,14 +158,14 @@ export async function runNonInteractivePrompt(input: Input) {
       if (!("sessionID" in event.data) || event.data.sessionID !== input.sessionID) continue
       if (!("sessionID" in event.data) || event.data.sessionID !== input.sessionID) continue
       const time = toMillis(event.created)
       const time = toMillis(event.created)
 
 
-      if (event.type === "prompt.promoted") {
+      if (event.type === "session.prompt.promoted") {
         if (event.data.inputID === messageID) {
         if (event.data.inputID === messageID) {
           promoted = true
           promoted = true
           continue
           continue
         }
         }
       }
       }
       if (
       if (
-        event.type === "execution.settled" &&
+        event.type === "session.execution.settled" &&
         event.data.outcome === "interrupted" &&
         event.data.outcome === "interrupted" &&
         (interrupted || permissionRejected || questionRejected || formCancelled)
         (interrupted || permissionRejected || questionRejected || formCancelled)
       ) {
       ) {
@@ -173,7 +173,7 @@ export async function runNonInteractivePrompt(input: Input) {
       }
       }
       if (!promoted) continue
       if (!promoted) continue
 
 
-      if (event.type === "step.started") {
+      if (event.type === "session.step.started") {
         const part: StepStartPart = {
         const part: StepStartPart = {
           id: partID(event.id),
           id: partID(event.id),
           sessionID: input.sessionID,
           sessionID: input.sessionID,
@@ -189,11 +189,11 @@ export async function runNonInteractivePrompt(input: Input) {
         continue
         continue
       }
       }
 
 
-      if (event.type === "text.started") {
+      if (event.type === "session.text.started") {
         starts.set(event.data.textID, { id: partID(event.id), timestamp: time })
         starts.set(event.data.textID, { id: partID(event.id), timestamp: time })
         continue
         continue
       }
       }
-      if (event.type === "text.ended") {
+      if (event.type === "session.text.ended") {
         const started = starts.get(event.data.textID)
         const started = starts.get(event.data.textID)
         const part: TextPart = {
         const part: TextPart = {
           id: started?.id ?? partID(event.id),
           id: started?.id ?? partID(event.id),
@@ -207,11 +207,11 @@ export async function runNonInteractivePrompt(input: Input) {
         continue
         continue
       }
       }
 
 
-      if (event.type === "reasoning.started") {
+      if (event.type === "session.reasoning.started") {
         starts.set(event.data.reasoningID, { id: partID(event.id), timestamp: time })
         starts.set(event.data.reasoningID, { id: partID(event.id), timestamp: time })
         continue
         continue
       }
       }
-      if (event.type === "reasoning.ended" && input.thinking) {
+      if (event.type === "session.reasoning.ended" && input.thinking) {
         const started = starts.get(event.data.reasoningID)
         const started = starts.get(event.data.reasoningID)
         const part: ReasoningPart = {
         const part: ReasoningPart = {
           id: started?.id ?? partID(event.id),
           id: started?.id ?? partID(event.id),
@@ -236,7 +236,7 @@ export async function runNonInteractivePrompt(input: Input) {
         continue
         continue
       }
       }
 
 
-      if (event.type === "tool.input.started") {
+      if (event.type === "session.tool.input.started") {
         tools.set(event.data.callID, {
         tools.set(event.data.callID, {
           id: partID(event.id),
           id: partID(event.id),
           timestamp: time,
           timestamp: time,
@@ -246,12 +246,12 @@ export async function runNonInteractivePrompt(input: Input) {
         })
         })
         continue
         continue
       }
       }
-      if (event.type === "tool.input.ended") {
+      if (event.type === "session.tool.input.ended") {
         const current = tools.get(event.data.callID)
         const current = tools.get(event.data.callID)
         if (current) current.raw = event.data.text
         if (current) current.raw = event.data.text
         continue
         continue
       }
       }
-      if (event.type === "tool.called") {
+      if (event.type === "session.tool.called") {
         const current = tools.get(event.data.callID)
         const current = tools.get(event.data.callID)
         tools.set(event.data.callID, {
         tools.set(event.data.callID, {
           id: current?.id ?? partID(event.id),
           id: current?.id ?? partID(event.id),
@@ -264,7 +264,7 @@ export async function runNonInteractivePrompt(input: Input) {
         })
         })
         continue
         continue
       }
       }
-      if (event.type === "tool.success") {
+      if (event.type === "session.tool.success") {
         const current = tools.get(event.data.callID) ?? fallbackTool(event)
         const current = tools.get(event.data.callID) ?? fallbackTool(event)
         const part: ToolPart = {
         const part: ToolPart = {
           id: current.id,
           id: current.id,
@@ -297,7 +297,7 @@ export async function runNonInteractivePrompt(input: Input) {
         if (!emit("tool_use", time, { part })) await input.renderTool(part)
         if (!emit("tool_use", time, { part })) await input.renderTool(part)
         continue
         continue
       }
       }
-      if (event.type === "tool.failed") {
+      if (event.type === "session.tool.failed") {
         const current = tools.get(event.data.callID) ?? fallbackTool(event)
         const current = tools.get(event.data.callID) ?? fallbackTool(event)
         const error = event.data.error.message
         const error = event.data.error.message
         const part: ToolPart = {
         const part: ToolPart = {
@@ -328,7 +328,7 @@ export async function runNonInteractivePrompt(input: Input) {
         continue
         continue
       }
       }
 
 
-      if (event.type === "step.ended") {
+      if (event.type === "session.step.ended") {
         const part: StepFinishPart = {
         const part: StepFinishPart = {
           id: partID(event.id),
           id: partID(event.id),
           sessionID: input.sessionID,
           sessionID: input.sessionID,
@@ -342,14 +342,14 @@ export async function runNonInteractivePrompt(input: Input) {
         emit("step_finish", time, { part })
         emit("step_finish", time, { part })
         continue
         continue
       }
       }
-      if (event.type === "step.failed") {
+      if (event.type === "session.step.failed") {
         if (interrupted || permissionRejected || questionRejected || formCancelled) continue
         if (interrupted || permissionRejected || questionRejected || formCancelled) continue
         emittedError = true
         emittedError = true
         process.exitCode = 1
         process.exitCode = 1
         if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message)
         if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message)
         continue
         continue
       }
       }
-      if (event.type === "execution.settled") {
+      if (event.type === "session.execution.settled") {
         if (event.data.outcome === "failure" && !emittedError && !questionRejected && !formCancelled) {
         if (event.data.outcome === "failure" && !emittedError && !questionRejected && !formCancelled) {
           emittedError = true
           emittedError = true
           process.exitCode = 1
           process.exitCode = 1

+ 15 - 15
packages/opencode/src/cli/cmd/run/session-data.ts

@@ -62,7 +62,7 @@ type SessionCommit = StreamCommit
 // - sent:   part ID → byte offset of last flushed text (for incremental output)
 // - sent:   part ID → byte offset of last flushed text (for incremental output)
 // - visible: part ID → rendered text for an active part after display transforms
 // - visible: part ID → rendered text for an active part after display transforms
 // - end:    part IDs whose time.end has arrived (part is finished)
 // - end:    part IDs whose time.end has arrived (part is finished)
-// - shell:  shell call ID → chosen transcript source for direct shell calls
+// - shell:  shell ID → chosen transcript source for direct shell calls
 // - echo:   message ID → bash outputs to strip from the next assistant chunk
 // - echo:   message ID → bash outputs to strip from the next assistant chunk
 type ShellCall = {
 type ShellCall = {
   source: "shell" | "tool"
   source: "shell" | "tool"
@@ -607,12 +607,12 @@ function toolCommit(
   }
   }
 }
 }
 
 
-function shellPartID(callID: string): string {
-  return `shell:${callID}`
+function shellPartID(shellID: string): string {
+  return `shell:${shellID}`
 }
 }
 
 
-function claimShell(data: SessionData, callID: string, source: ShellCall["source"], command?: string): ShellCall {
-  const current = data.shell.get(callID)
+function claimShell(data: SessionData, shellID: string, source: ShellCall["source"], command?: string): ShellCall {
+  const current = data.shell.get(shellID)
   if (current) {
   if (current) {
     if (command && !current.command) {
     if (command && !current.command) {
       current.command = command
       current.command = command
@@ -625,7 +625,7 @@ function claimShell(data: SessionData, callID: string, source: ShellCall["source
     source,
     source,
     ...(command ? { command } : {}),
     ...(command ? { command } : {}),
   } satisfies ShellCall
   } satisfies ShellCall
-  data.shell.set(callID, next)
+  data.shell.set(shellID, next)
   return next
   return next
 }
 }
 
 
@@ -728,37 +728,37 @@ export function reduceSessionData(input: SessionDataInput): SessionDataOutput {
   const data = input.data
   const data = input.data
   const event = input.event
   const event = input.event
 
 
-  if (event.type === "shell.started") {
+  if (event.type === "session.shell.started") {
     if (event.properties.sessionID !== input.sessionID) {
     if (event.properties.sessionID !== input.sessionID) {
       return out(data, commits)
       return out(data, commits)
     }
     }
 
 
-    const shell = claimShell(data, event.properties.callID, "shell", event.properties.command)
+    const shell = claimShell(data, event.properties.shell.id, "shell", event.properties.shell.command)
     if (shell.source !== "shell") {
     if (shell.source !== "shell") {
       return out(data, commits)
       return out(data, commits)
     }
     }
 
 
-    const partID = shellPartID(event.properties.callID)
+    const partID = shellPartID(event.properties.shell.id)
     if (data.ids.has(partID) || data.tools.has(partID)) {
     if (data.ids.has(partID) || data.tools.has(partID)) {
       return out(data, commits, patch({ status: "running shell" }))
       return out(data, commits, patch({ status: "running shell" }))
     }
     }
 
 
     data.tools.add(partID)
     data.tools.add(partID)
-    commits.push(startShell(event.properties.callID, shell.command ?? event.properties.command))
+    commits.push(startShell(event.properties.shell.id, shell.command ?? event.properties.shell.command))
     return out(data, commits, patch({ status: "running shell" }))
     return out(data, commits, patch({ status: "running shell" }))
   }
   }
 
 
-  if (event.type === "shell.ended") {
+  if (event.type === "session.shell.ended") {
     if (event.properties.sessionID !== input.sessionID) {
     if (event.properties.sessionID !== input.sessionID) {
       return out(data, commits)
       return out(data, commits)
     }
     }
 
 
-    const shell = claimShell(data, event.properties.callID, "shell")
+    const shell = claimShell(data, event.properties.shell.id, "shell")
     if (shell.source !== "shell") {
     if (shell.source !== "shell") {
       return out(data, commits)
       return out(data, commits)
     }
     }
 
 
-    const partID = shellPartID(event.properties.callID)
+    const partID = shellPartID(event.properties.shell.id)
     const seen = data.tools.has(partID)
     const seen = data.tools.has(partID)
     const command = shell.command ?? ""
     const command = shell.command ?? ""
     data.tools.delete(partID)
     data.tools.delete(partID)
@@ -767,11 +767,11 @@ export function reduceSessionData(input: SessionDataInput): SessionDataOutput {
     }
     }
 
 
     if (!seen && command) {
     if (!seen && command) {
-      commits.push(startShell(event.properties.callID, command))
+      commits.push(startShell(event.properties.shell.id, command))
     }
     }
 
 
     data.ids.add(partID)
     data.ids.add(partID)
-    commits.push(doneShell(event.properties.callID, command, event.properties.output))
+    commits.push(doneShell(event.properties.shell.id, command, event.properties.output.output))
     return out(data, commits)
     return out(data, commits)
   }
   }
 
 

+ 15 - 15
packages/opencode/src/cli/cmd/run/stream-v2.subagent.ts

@@ -424,21 +424,21 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
   }
   }
 
 
   const reduce = (child: ChildState, event: V2Event) => {
   const reduce = (child: ChildState, event: V2Event) => {
-    if (event.type === "prompt.promoted") {
+    if (event.type === "session.prompt.promoted") {
       if (userFrame(child, event.data.inputID, "")) {
       if (userFrame(child, event.data.inputID, "")) {
         touch(child, event.created)
         touch(child, event.created)
         notifyDetail(child)
         notifyDetail(child)
       }
       }
       return
       return
     }
     }
-    if (event.type === "step.started") {
+    if (event.type === "session.step.started") {
       touch(child, event.created)
       touch(child, event.created)
       if (child.label === FALLBACK_LABEL && event.data.agent) child.label = Locale.titlecase(event.data.agent)
       if (child.label === FALLBACK_LABEL && event.data.agent) child.label = Locale.titlecase(event.data.agent)
       if (child.status !== "running") child.status = "running"
       if (child.status !== "running") child.status = "running"
       input.emit()
       input.emit()
       return
       return
     }
     }
-    if (event.type === "text.delta") {
+    if (event.type === "session.text.delta") {
       const projected = child.projectedText.get(event.data.textID)
       const projected = child.projectedText.get(event.data.textID)
       const covered = projected?.indexOf(event.data.delta) ?? -1
       const covered = projected?.indexOf(event.data.delta) ?? -1
       if (projected && covered >= 0) {
       if (projected && covered >= 0) {
@@ -459,7 +459,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
       notifyDetail(child)
       notifyDetail(child)
       return
       return
     }
     }
-    if (event.type === "text.ended") {
+    if (event.type === "session.text.ended") {
       child.text.set(event.data.textID, event.data.text)
       child.text.set(event.data.textID, event.data.text)
       child.projectedText.delete(event.data.textID)
       child.projectedText.delete(event.data.textID)
       setFrame(child, `text:${event.data.textID}`, {
       setFrame(child, `text:${event.data.textID}`, {
@@ -474,7 +474,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
       notifyDetail(child)
       notifyDetail(child)
       return
       return
     }
     }
-    if (event.type === "reasoning.delta") {
+    if (event.type === "session.reasoning.delta") {
       const projected = child.projectedReasoning.get(event.data.reasoningID)
       const projected = child.projectedReasoning.get(event.data.reasoningID)
       const covered = projected?.indexOf(event.data.delta) ?? -1
       const covered = projected?.indexOf(event.data.delta) ?? -1
       if (projected && covered >= 0) {
       if (projected && covered >= 0) {
@@ -495,7 +495,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
       notifyDetail(child)
       notifyDetail(child)
       return
       return
     }
     }
-    if (event.type === "reasoning.ended") {
+    if (event.type === "session.reasoning.ended") {
       child.reasoning.set(event.data.reasoningID, event.data.text)
       child.reasoning.set(event.data.reasoningID, event.data.text)
       child.projectedReasoning.delete(event.data.reasoningID)
       child.projectedReasoning.delete(event.data.reasoningID)
       if (!input.thinking) return
       if (!input.thinking) return
@@ -510,11 +510,11 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
       notifyDetail(child)
       notifyDetail(child)
       return
       return
     }
     }
-    if (event.type === "tool.input.started") {
+    if (event.type === "session.tool.input.started") {
       child.tools.set(event.data.callID, { name: event.data.name, input: {}, started: event.created })
       child.tools.set(event.data.callID, { name: event.data.name, input: {}, started: event.created })
       return
       return
     }
     }
-    if (event.type === "tool.called") {
+    if (event.type === "session.tool.called") {
       const current = child.tools.get(event.data.callID)
       const current = child.tools.get(event.data.callID)
       child.tools.set(event.data.callID, {
       child.tools.set(event.data.callID, {
         name: event.data.tool,
         name: event.data.tool,
@@ -537,10 +537,10 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
       notifyDetail(child)
       notifyDetail(child)
       return
       return
     }
     }
-    if (event.type === "tool.success" || event.type === "tool.failed") {
+    if (event.type === "session.tool.success" || event.type === "session.tool.failed") {
       if (child.finishedTools.has(event.data.callID)) return
       if (child.finishedTools.has(event.data.callID)) return
       const current = child.tools.get(event.data.callID)
       const current = child.tools.get(event.data.callID)
-      const failed = event.type === "tool.failed"
+      const failed = event.type === "session.tool.failed"
       childTool(
       childTool(
         child,
         child,
         {
         {
@@ -577,7 +577,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
       notifyDetail(child)
       notifyDetail(child)
       return
       return
     }
     }
-    if (event.type === "step.failed") {
+    if (event.type === "session.step.failed") {
       setFrame(child, `error:step:${event.data.assistantMessageID}`, {
       setFrame(child, `error:step:${event.data.assistantMessageID}`, {
         kind: "error",
         kind: "error",
         source: "system",
         source: "system",
@@ -589,7 +589,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
       notifyDetail(child)
       notifyDetail(child)
       return
       return
     }
     }
-    if (event.type === "execution.settled") {
+    if (event.type === "session.execution.settled") {
       child.status =
       child.status =
         event.data.outcome === "success" ? "completed" : event.data.outcome === "interrupted" ? "cancelled" : "error"
         event.data.outcome === "success" ? "completed" : event.data.outcome === "interrupted" ? "cancelled" : "error"
       touch(child, event.created)
       touch(child, event.created)
@@ -613,15 +613,15 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
 
 
   return {
   return {
     main(event) {
     main(event) {
-      if (event.type === "tool.called") {
+      if (event.type === "session.tool.called") {
         if (event.data.tool === "subagent") pendingCalls.set(event.data.callID, event.data.input)
         if (event.data.tool === "subagent") pendingCalls.set(event.data.callID, event.data.input)
         return
         return
       }
       }
-      if (event.type === "tool.failed") {
+      if (event.type === "session.tool.failed") {
         pendingCalls.delete(event.data.callID)
         pendingCalls.delete(event.data.callID)
         return
         return
       }
       }
-      if (event.type !== "tool.success") return
+      if (event.type !== "session.tool.success") return
       const pending = pendingCalls.get(event.data.callID)
       const pending = pendingCalls.get(event.data.callID)
       pendingCalls.delete(event.data.callID)
       pendingCalls.delete(event.data.callID)
       const found = childSessionID(record(event.data.structured))
       const found = childSessionID(record(event.data.structured))

+ 77 - 48
packages/opencode/src/cli/cmd/run/stream-v2.transport.ts

@@ -213,7 +213,9 @@ function promptAgents(next: SessionTurnInput) {
       ? [
       ? [
           {
           {
             name: part.name,
             name: part.name,
-            source: part.source ? { start: part.source.start, end: part.source.end, text: part.source.value } : undefined,
+            source: part.source
+              ? { start: part.source.start, end: part.source.end, text: part.source.value }
+              : undefined,
           },
           },
         ]
         ]
       : [],
       : [],
@@ -404,27 +406,39 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       return
       return
     }
     }
     if (message.type === "shell") {
     if (message.type === "shell") {
-      state.shellCommands.set(message.callID, message.command)
+      state.shellCommands.set(message.shell.id, message.shell.command)
       const completed = message.time.completed !== undefined
       const completed = message.time.completed !== undefined
       if (!render) {
       if (!render) {
         // Suppressed history: mark settled shells rendered so live redelivery
         // Suppressed history: mark settled shells rendered so live redelivery
         // stays silent. A still-running shell stays unmarked and renders in
         // stays silent. A still-running shell stays unmarked and renders in
         // full when its live shell.ended event arrives.
         // full when its live shell.ended event arrives.
         if (completed) {
         if (completed) {
-          state.shellStarted.add(message.callID)
-          state.shellEnded.add(message.callID)
+          state.shellStarted.add(message.shell.id)
+          state.shellEnded.add(message.shell.id)
         }
         }
         return
         return
       }
       }
-      if (!state.shellStarted.has(message.callID)) {
-        state.shellStarted.add(message.callID)
-        write([shellCommit(message.callID, message.command, { text: "running shell", phase: "start", toolState: "running" })])
+      if (!state.shellStarted.has(message.shell.id)) {
+        state.shellStarted.add(message.shell.id)
+        write([
+          shellCommit(message.shell.id, message.shell.command, {
+            text: "running shell",
+            phase: "start",
+            toolState: "running",
+          }),
+        ])
       }
       }
-      if (completed && !state.shellEnded.has(message.callID)) {
-        state.shellEnded.add(message.callID)
-        write([shellCommit(message.callID, message.command, { text: message.output, phase: "progress", toolState: "completed" })])
+      if (completed && message.output && !state.shellEnded.has(message.shell.id)) {
+        state.shellEnded.add(message.shell.id)
+        write([
+          shellCommit(message.shell.id, message.shell.command, {
+            text: message.output.output,
+            phase: "progress",
+            toolState: "completed",
+          }),
+        ])
       }
       }
-      if (completed && state.shellWait?.callID === message.callID) state.shellWait.resolve()
+      if (completed && state.shellWait?.callID === message.shell.id) state.shellWait.resolve()
       return
       return
     }
     }
     if (message.type !== "assistant") return
     if (message.type !== "assistant") return
@@ -515,17 +529,17 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
     }
     }
     input.trace?.write("recv.event", event)
     input.trace?.write("recv.event", event)
     subagents.main(event)
     subagents.main(event)
-    if (event.type === "prompt.promoted") {
+    if (event.type === "session.prompt.promoted") {
       if (state.wait?.messageID === event.data.inputID) state.wait.promoted = true
       if (state.wait?.messageID === event.data.inputID) state.wait.promoted = true
       state.messageIDs.add(event.data.inputID)
       state.messageIDs.add(event.data.inputID)
       write([], { phase: "running", status: "waiting for assistant" })
       write([], { phase: "running", status: "waiting for assistant" })
       return
       return
     }
     }
-    if (event.type === "step.started") {
+    if (event.type === "session.step.started") {
       write([], { phase: "running", status: "assistant responding" })
       write([], { phase: "running", status: "assistant responding" })
       return
       return
     }
     }
-    if (event.type === "skill.activated") {
+    if (event.type === "session.skill.activated") {
       const messageID = event.id.replace(/^evt_/, "msg_")
       const messageID = event.id.replace(/^evt_/, "msg_")
       if (state.wait) state.wait.promoted = true
       if (state.wait) state.wait.promoted = true
       if (state.skillMessages.has(messageID)) return
       if (state.skillMessages.has(messageID)) return
@@ -533,38 +547,56 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       write([skillCommit(messageID, event.data.name)])
       write([skillCommit(messageID, event.data.name)])
       return
       return
     }
     }
-    if (event.type === "shell.started") {
-      state.shellCommands.set(event.data.callID, event.data.command)
+    if (event.type === "session.shell.started") {
+      state.shellCommands.set(event.data.shell.id, event.data.shell.command)
       const wait = state.shellWait
       const wait = state.shellWait
-      if (wait && wait.callID === undefined) wait.callID = event.data.callID
-      if (state.shellStarted.has(event.data.callID)) return
-      state.shellStarted.add(event.data.callID)
-      write([shellCommit(event.data.callID, event.data.command, { text: "running shell", phase: "start", toolState: "running" })], {
-        phase: "running",
-        status: "running shell",
-      })
+      if (wait && wait.callID === undefined) wait.callID = event.data.shell.id
+      if (state.shellStarted.has(event.data.shell.id)) return
+      state.shellStarted.add(event.data.shell.id)
+      write(
+        [
+          shellCommit(event.data.shell.id, event.data.shell.command, {
+            text: "running shell",
+            phase: "start",
+            toolState: "running",
+          }),
+        ],
+        {
+          phase: "running",
+          status: "running shell",
+        },
+      )
       return
       return
     }
     }
-    if (event.type === "shell.ended") {
-      const command = state.shellCommands.get(event.data.callID) ?? ""
+    if (event.type === "session.shell.ended") {
+      const command = state.shellCommands.get(event.data.shell.id) ?? event.data.shell.command
       const commits: StreamCommit[] = []
       const commits: StreamCommit[] = []
-      if (!state.shellStarted.has(event.data.callID)) {
-        state.shellStarted.add(event.data.callID)
-        if (command) commits.push(shellCommit(event.data.callID, command, { text: "running shell", phase: "start", toolState: "running" }))
+      if (!state.shellStarted.has(event.data.shell.id)) {
+        state.shellStarted.add(event.data.shell.id)
+        if (command)
+          commits.push(
+            shellCommit(event.data.shell.id, command, { text: "running shell", phase: "start", toolState: "running" }),
+          )
       }
       }
-      if (!state.shellEnded.has(event.data.callID)) {
-        state.shellEnded.add(event.data.callID)
-        commits.push(shellCommit(event.data.callID, command, { text: event.data.output, phase: "progress", toolState: "completed" }))
+      if (!state.shellEnded.has(event.data.shell.id)) {
+        state.shellEnded.add(event.data.shell.id)
+        commits.push(
+          shellCommit(event.data.shell.id, command, {
+            text: event.data.output.output,
+            phase: "progress",
+            toolState: "completed",
+          }),
+        )
       }
       }
       const wait = state.shellWait
       const wait = state.shellWait
       // An unset callID means shell.started has not been observed yet (event
       // An unset callID means shell.started has not been observed yet (event
       // delivery lag); mini serializes its own shells, so adopt this ended.
       // delivery lag); mini serializes its own shells, so adopt this ended.
-      const owned = wait !== undefined && (wait.callID === undefined || wait.callID === event.data.callID)
+      const owned = wait !== undefined && (wait.callID === undefined || wait.callID === event.data.shell.id)
       write(commits, owned || state.wait ? undefined : { phase: "idle", status: "" })
       write(commits, owned || state.wait ? undefined : { phase: "idle", status: "" })
       if (owned) wait.resolve()
       if (owned) wait.resolve()
       return
       return
     }
     }
-    if (event.type === "text.delta") {
+    if (event.type === "session.text.delta") {
       const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
       const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
       const projected = state.projectedText.get(key)
       const projected = state.projectedText.get(key)
       const covered = projected?.indexOf(event.data.delta) ?? -1
       const covered = projected?.indexOf(event.data.delta) ?? -1
@@ -586,7 +618,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       ])
       ])
       return
       return
     }
     }
-    if (event.type === "text.ended") {
+    if (event.type === "session.text.ended") {
       const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
       const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
       const previous = state.text.get(key) ?? ""
       const previous = state.text.get(key) ?? ""
       if (event.data.text.length > previous.length)
       if (event.data.text.length > previous.length)
@@ -604,7 +636,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       state.projectedText.delete(key)
       state.projectedText.delete(key)
       return
       return
     }
     }
-    if (event.type === "reasoning.delta") {
+    if (event.type === "session.reasoning.delta") {
       const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
       const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
       const projected = state.projectedReasoning.get(key)
       const projected = state.projectedReasoning.get(key)
       const covered = projected?.indexOf(event.data.delta) ?? -1
       const covered = projected?.indexOf(event.data.delta) ?? -1
@@ -627,7 +659,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
         ])
         ])
       return
       return
     }
     }
-    if (event.type === "reasoning.ended") {
+    if (event.type === "session.reasoning.ended") {
       const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
       const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
       const previous = state.reasoning.get(key) ?? ""
       const previous = state.reasoning.get(key) ?? ""
       if (input.thinking && event.data.text.length > previous.length)
       if (input.thinking && event.data.text.length > previous.length)
@@ -645,7 +677,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       state.projectedReasoning.delete(key)
       state.projectedReasoning.delete(key)
       return
       return
     }
     }
-    if (event.type === "tool.input.started") {
+    if (event.type === "session.tool.input.started") {
       state.tools.set(event.data.callID, {
       state.tools.set(event.data.callID, {
         messageID: event.data.assistantMessageID,
         messageID: event.data.assistantMessageID,
         name: event.data.name,
         name: event.data.name,
@@ -655,7 +687,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       })
       })
       return
       return
     }
     }
-    if (event.type === "tool.called") {
+    if (event.type === "session.tool.called") {
       if (state.finishedTools.has(event.data.callID)) return
       if (state.finishedTools.has(event.data.callID)) return
       const current = state.tools.get(event.data.callID)
       const current = state.tools.get(event.data.callID)
       const item: SessionMessageAssistantTool = {
       const item: SessionMessageAssistantTool = {
@@ -669,10 +701,10 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       renderTool(event.data.assistantMessageID, item)
       renderTool(event.data.assistantMessageID, item)
       return
       return
     }
     }
-    if (event.type === "tool.progress") return
-    if (event.type === "tool.success" || event.type === "tool.failed") {
+    if (event.type === "session.tool.progress") return
+    if (event.type === "session.tool.success" || event.type === "session.tool.failed") {
       const current = state.tools.get(event.data.callID)
       const current = state.tools.get(event.data.callID)
-      const failed = event.type === "tool.failed"
+      const failed = event.type === "session.tool.failed"
       const item: SessionMessageAssistantTool = {
       const item: SessionMessageAssistantTool = {
         type: "tool",
         type: "tool",
         id: event.data.callID,
         id: event.data.callID,
@@ -720,7 +752,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       syncBlockers()
       syncBlockers()
       return
       return
     }
     }
-    if (event.type === "step.ended") {
+    if (event.type === "session.step.ended") {
       const total =
       const total =
         event.data.tokens.input +
         event.data.tokens.input +
         event.data.tokens.output +
         event.data.tokens.output +
@@ -734,13 +766,13 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
       })
       })
       return
       return
     }
     }
-    if (event.type === "step.failed") {
+    if (event.type === "session.step.failed") {
       state.errors.add(event.data.assistantMessageID)
       state.errors.add(event.data.assistantMessageID)
       if (state.wait) state.wait.failureRendered = true
       if (state.wait) state.wait.failureRendered = true
       write([{ kind: "error", source: "system", text: errorMessage(event.data.error), phase: "start" }])
       write([{ kind: "error", source: "system", text: errorMessage(event.data.error), phase: "start" }])
       return
       return
     }
     }
-    if (event.type === "execution.settled") {
+    if (event.type === "session.execution.settled") {
       write([], { phase: "idle", status: "" })
       write([], { phase: "idle", status: "" })
       const current = state.wait
       const current = state.wait
       if (!current || (!current.promoted && !current.interrupted)) return
       if (!current || (!current.promoted && !current.interrupted)) return
@@ -977,10 +1009,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
               sessionID: input.sessionID,
               sessionID: input.sessionID,
               id: messageID,
               id: messageID,
               prompt: {
               prompt: {
-                text: [
-                  next.prompt.text,
-                  ...prepared.flatMap((file) => (file.text ? [file.text] : [])),
-                ].join("\n\n"),
+                text: [next.prompt.text, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
                 files: attachments.length ? attachments : undefined,
                 files: attachments.length ? attachments : undefined,
                 agents: agents.length ? agents : undefined,
                 agents: agents.length ? agents : undefined,
               },
               },

+ 2 - 2
packages/opencode/test/cli/run/noninteractive.test.ts

@@ -25,7 +25,7 @@ function prompted(inputID: string): V2Event {
   return {
   return {
     id: "evt_prompted",
     id: "evt_prompted",
     created: 0,
     created: 0,
-    type: "prompt.promoted",
+    type: "session.prompt.promoted",
     durable: { aggregateID: "ses_1", seq: 0, version: 1 },
     durable: { aggregateID: "ses_1", seq: 0, version: 1 },
     data: { sessionID: "ses_1", inputID },
     data: { sessionID: "ses_1", inputID },
   }
   }
@@ -35,7 +35,7 @@ function settled(outcome: "success" | "interrupted" = "success"): V2Event {
   return {
   return {
     id: "evt_settled",
     id: "evt_settled",
     created: 0,
     created: 0,
-    type: "execution.settled",
+    type: "session.execution.settled",
     data: { sessionID: "ses_1", outcome },
     data: { sessionID: "ses_1", outcome },
   }
   }
 }
 }

+ 6 - 6
packages/opencode/test/cli/run/session-data.test.ts

@@ -329,7 +329,7 @@ describe("run session data", () => {
   test("renders direct shell mode from first-class shell events", () => {
   test("renders direct shell mode from first-class shell events", () => {
     let data = createSessionData()
     let data = createSessionData()
     const started = reduce(data, {
     const started = reduce(data, {
-      type: "shell.started",
+      type: "session.shell.started",
       properties: {
       properties: {
         sessionID: "session-1",
         sessionID: "session-1",
         timestamp: 1,
         timestamp: 1,
@@ -353,7 +353,7 @@ describe("run session data", () => {
 
 
     data = started.data
     data = started.data
     const ended = reduce(data, {
     const ended = reduce(data, {
-      type: "shell.ended",
+      type: "session.shell.ended",
       properties: {
       properties: {
         sessionID: "session-1",
         sessionID: "session-1",
         timestamp: 2,
         timestamp: 2,
@@ -380,7 +380,7 @@ describe("run session data", () => {
 
 
   test("suppresses legacy bash part updates once shell events claim the call", () => {
   test("suppresses legacy bash part updates once shell events claim the call", () => {
     let data = reduce(createSessionData(), {
     let data = reduce(createSessionData(), {
-      type: "shell.started",
+      type: "session.shell.started",
       properties: {
       properties: {
         sessionID: "session-1",
         sessionID: "session-1",
         timestamp: 1,
         timestamp: 1,
@@ -409,7 +409,7 @@ describe("run session data", () => {
     ).toEqual([])
     ).toEqual([])
 
 
     data = reduce(data, {
     data = reduce(data, {
-      type: "shell.ended",
+      type: "session.shell.ended",
       properties: {
       properties: {
         sessionID: "session-1",
         sessionID: "session-1",
         timestamp: 2,
         timestamp: 2,
@@ -463,7 +463,7 @@ describe("run session data", () => {
 
 
     expect(
     expect(
       reduce(data, {
       reduce(data, {
-        type: "shell.started",
+        type: "session.shell.started",
         properties: {
         properties: {
           sessionID: "session-1",
           sessionID: "session-1",
           timestamp: 1,
           timestamp: 1,
@@ -497,7 +497,7 @@ describe("run session data", () => {
 
 
     expect(
     expect(
       reduce(data, {
       reduce(data, {
-        type: "shell.ended",
+        type: "session.shell.ended",
         properties: {
         properties: {
           sessionID: "session-1",
           sessionID: "session-1",
           timestamp: 2,
           timestamp: 2,

+ 94 - 45
packages/opencode/test/cli/run/stream-v2.transport.test.ts

@@ -200,7 +200,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_prompted",
       id: "evt_prompted",
       created: 0,
       created: 0,
-      type: "prompt.promoted",
+      type: "session.prompt.promoted",
       durable: durable("ses_1"),
       durable: durable("ses_1"),
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
@@ -210,7 +210,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_text",
       id: "evt_text",
       created: 0,
       created: 0,
-      type: "text.delta",
+      type: "session.text.delta",
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
         assistantMessageID: "msg_assistant",
         assistantMessageID: "msg_assistant",
@@ -221,7 +221,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_settled",
       id: "evt_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_1", outcome: "success" },
       data: { sessionID: "ses_1", outcome: "success" },
     })
     })
     await turn
     await turn
@@ -259,7 +259,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_prompted",
           id: "evt_prompted",
           created: 0,
           created: 0,
-          type: "prompt.promoted",
+          type: "session.prompt.promoted",
           durable: durable("ses_1"),
           durable: durable("ses_1"),
           data: {
           data: {
             sessionID: "ses_1",
             sessionID: "ses_1",
@@ -269,7 +269,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_settled",
           id: "evt_settled",
           created: 0,
           created: 0,
-          type: "execution.settled",
+          type: "session.execution.settled",
           data: { sessionID: "ses_1", outcome: "success" },
           data: { sessionID: "ses_1", outcome: "success" },
         })
         })
       })
       })
@@ -353,7 +353,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_prompted",
           id: "evt_prompted",
           created: 0,
           created: 0,
-          type: "prompt.promoted",
+          type: "session.prompt.promoted",
           durable: durable("ses_1"),
           durable: durable("ses_1"),
           data: {
           data: {
             sessionID: "ses_1",
             sessionID: "ses_1",
@@ -363,7 +363,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_settled",
           id: "evt_settled",
           created: 0,
           created: 0,
-          type: "execution.settled",
+          type: "session.execution.settled",
           data: { sessionID: "ses_1", outcome: "success" },
           data: { sessionID: "ses_1", outcome: "success" },
         })
         })
       })
       })
@@ -450,7 +450,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_prompted",
           id: "evt_prompted",
           created: 0,
           created: 0,
-          type: "prompt.promoted",
+          type: "session.prompt.promoted",
           durable: durable("ses_1"),
           durable: durable("ses_1"),
           data: {
           data: {
             sessionID: "ses_1",
             sessionID: "ses_1",
@@ -460,7 +460,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_settled",
           id: "evt_settled",
           created: 0,
           created: 0,
-          type: "execution.settled",
+          type: "session.execution.settled",
           data: { sessionID: "ses_1", outcome: "success" },
           data: { sessionID: "ses_1", outcome: "success" },
         })
         })
       })
       })
@@ -724,7 +724,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_text",
       id: "evt_text",
       created: 0,
       created: 0,
-      type: "text.delta",
+      type: "session.text.delta",
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
         assistantMessageID: "msg_assistant",
         assistantMessageID: "msg_assistant",
@@ -809,7 +809,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_reasoning",
       id: "evt_reasoning",
       created: 0,
       created: 0,
-      type: "reasoning.ended",
+      type: "session.reasoning.ended",
       durable: durable("ses_1"),
       durable: durable("ses_1"),
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
@@ -865,7 +865,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_settled",
       id: "evt_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_1", outcome: "success" },
       data: { sessionID: "ses_1", outcome: "success" },
     })
     })
     await turn
     await turn
@@ -921,7 +921,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_prompted",
       id: "evt_prompted",
       created: 0,
       created: 0,
-      type: "prompt.promoted",
+      type: "session.prompt.promoted",
       durable: durable("ses_1"),
       durable: durable("ses_1"),
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
@@ -931,7 +931,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_settled",
       id: "evt_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_1", outcome: "success" },
       data: { sessionID: "ses_1", outcome: "success" },
     })
     })
     await turn
     await turn
@@ -981,7 +981,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_prompted",
       id: "evt_prompted",
       created: 0,
       created: 0,
-      type: "prompt.promoted",
+      type: "session.prompt.promoted",
       durable: durable("ses_1"),
       durable: durable("ses_1"),
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
@@ -993,7 +993,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_settled",
       id: "evt_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_1", outcome: "success" },
       data: { sessionID: "ses_1", outcome: "success" },
     })
     })
     await turn
     await turn
@@ -1021,16 +1021,42 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_shell_start",
           id: "evt_shell_start",
           created: 0,
           created: 0,
-          type: "shell.started",
+          type: "session.shell.started",
           durable: durable("ses_1"),
           durable: durable("ses_1"),
-          data: { sessionID: "ses_1", callID: "call_shell", command: "ls" },
+          data: {
+            sessionID: "ses_1",
+            shell: {
+              id: "sh_shell",
+              status: "running",
+              command: "ls",
+              cwd: "/tmp",
+              shell: "/bin/sh",
+              file: "/tmp/opencode-shell",
+              metadata: {},
+              time: { started: 0 },
+            },
+          },
         })
         })
         events.push({
         events.push({
           id: "evt_shell_end",
           id: "evt_shell_end",
           created: 0,
           created: 0,
-          type: "shell.ended",
+          type: "session.shell.ended",
           durable: durable("ses_1", 1),
           durable: durable("ses_1", 1),
-          data: { sessionID: "ses_1", callID: "call_shell", output: "file.txt" },
+          data: {
+            sessionID: "ses_1",
+            shell: {
+              id: "sh_shell",
+              status: "exited",
+              command: "ls",
+              cwd: "/tmp",
+              shell: "/bin/sh",
+              file: "/tmp/opencode-shell",
+              exit: 0,
+              metadata: {},
+              time: { started: 0, completed: 1 },
+            },
+            output: { output: "file.txt", cursor: 8, size: 8, truncated: false },
+          },
         })
         })
       })
       })
       return ok(undefined) as never
       return ok(undefined) as never
@@ -1047,8 +1073,8 @@ describe("V2 mini transport", () => {
 
 
     expect(request).toMatchObject({ sessionID: "ses_1", command: "ls" })
     expect(request).toMatchObject({ sessionID: "ses_1", command: "ls" })
     expect(ui.commits.filter((item) => item.shell)).toMatchObject([
     expect(ui.commits.filter((item) => item.shell)).toMatchObject([
-      { phase: "start", tool: "bash", toolState: "running", shell: { callID: "call_shell", command: "ls" } },
-      { phase: "progress", text: "file.txt", toolState: "completed", shell: { callID: "call_shell", command: "ls" } },
+      { phase: "start", tool: "bash", toolState: "running", shell: { callID: "sh_shell", command: "ls" } },
+      { phase: "progress", text: "file.txt", toolState: "completed", shell: { callID: "sh_shell", command: "ls" } },
     ])
     ])
     expect(ui.events).toContainEqual({ type: "stream.patch", patch: { phase: "running", status: "running shell" } })
     expect(ui.events).toContainEqual({ type: "stream.patch", patch: { phase: "running", status: "running shell" } })
     await transport.close()
     await transport.close()
@@ -1107,9 +1133,18 @@ describe("V2 mini transport", () => {
           {
           {
             id: "msg_shell",
             id: "msg_shell",
             type: "shell" as const,
             type: "shell" as const,
-            callID: "call_1",
-            command: "ls",
-            output: "file.txt",
+            shell: {
+              id: "sh_1",
+              status: "exited",
+              command: "ls",
+              cwd: "/tmp",
+              shell: "/bin/sh",
+              file: "/tmp/opencode-shell",
+              exit: 0,
+              metadata: {},
+              time: { started: 0, completed: 1 },
+            },
+            output: { output: "file.txt", cursor: 8, size: 8, truncated: false },
             time: { created: 1, completed: 2 },
             time: { created: 1, completed: 2 },
           },
           },
         ],
         ],
@@ -1127,15 +1162,29 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_shell_end",
       id: "evt_shell_end",
       created: 0,
       created: 0,
-      type: "shell.ended",
+      type: "session.shell.ended",
       durable: durable("ses_1", 1),
       durable: durable("ses_1", 1),
-      data: { sessionID: "ses_1", callID: "call_1", output: "file.txt" },
+      data: {
+        sessionID: "ses_1",
+        shell: {
+          id: "sh_1",
+          status: "exited",
+          command: "ls",
+          cwd: "/tmp",
+          shell: "/bin/sh",
+          file: "/tmp/opencode-shell",
+          exit: 0,
+          metadata: {},
+          time: { started: 0, completed: 1 },
+        },
+        output: { output: "file.txt", cursor: 8, size: 8, truncated: false },
+      },
     })
     })
     await Bun.sleep(0)
     await Bun.sleep(0)
     await Bun.sleep(0)
     await Bun.sleep(0)
 
 
     expect(ui.commits.filter((item) => item.shell)).toMatchObject([
     expect(ui.commits.filter((item) => item.shell)).toMatchObject([
-      { phase: "start", shell: { callID: "call_1", command: "ls" } },
+      { phase: "start", shell: { callID: "sh_1", command: "ls" } },
       { phase: "progress", text: "file.txt", toolState: "completed" },
       { phase: "progress", text: "file.txt", toolState: "completed" },
     ])
     ])
     await transport.close()
     await transport.close()
@@ -1160,7 +1209,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_prompted",
           id: "evt_prompted",
           created: 0,
           created: 0,
-          type: "prompt.promoted",
+          type: "session.prompt.promoted",
           durable: durable("ses_1"),
           durable: durable("ses_1"),
           data: {
           data: {
             sessionID: "ses_1",
             sessionID: "ses_1",
@@ -1170,7 +1219,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_settled",
           id: "evt_settled",
           created: 0,
           created: 0,
-          type: "execution.settled",
+          type: "session.execution.settled",
           data: { sessionID: "ses_1", outcome: "success" },
           data: { sessionID: "ses_1", outcome: "success" },
         })
         })
       })
       })
@@ -1236,7 +1285,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_skill",
           id: "evt_skill",
           created: 0,
           created: 0,
-          type: "skill.activated",
+          type: "session.skill.activated",
           durable: durable("ses_1"),
           durable: durable("ses_1"),
           data: {
           data: {
             sessionID: "ses_1",
             sessionID: "ses_1",
@@ -1247,7 +1296,7 @@ describe("V2 mini transport", () => {
         events.push({
         events.push({
           id: "evt_settled",
           id: "evt_settled",
           created: 0,
           created: 0,
-          type: "execution.settled",
+          type: "session.execution.settled",
           data: { sessionID: "ses_1", outcome: "success" },
           data: { sessionID: "ses_1", outcome: "success" },
         })
         })
       })
       })
@@ -1317,7 +1366,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_unrelated_settled",
       id: "evt_unrelated_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_1", outcome: "success" },
       data: { sessionID: "ses_1", outcome: "success" },
     })
     })
     await Bun.sleep(0)
     await Bun.sleep(0)
@@ -1327,7 +1376,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_skill",
       id: "evt_skill",
       created: 0,
       created: 0,
-      type: "skill.activated",
+      type: "session.skill.activated",
       durable: durable("ses_1"),
       durable: durable("ses_1"),
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
@@ -1338,7 +1387,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_skill_settled",
       id: "evt_skill_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_1", outcome: "success" },
       data: { sessionID: "ses_1", outcome: "success" },
     })
     })
     await turn
     await turn
@@ -1376,7 +1425,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_skill",
       id: "evt_skill",
       created: 0,
       created: 0,
-      type: "skill.activated",
+      type: "session.skill.activated",
       durable: durable("ses_1"),
       durable: durable("ses_1"),
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
@@ -1438,7 +1487,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_child_step",
       id: "evt_child_step",
       created: 0,
       created: 0,
-      type: "step.started",
+      type: "session.step.started",
       durable: durable("ses_child"),
       durable: durable("ses_child"),
       data: {
       data: {
         sessionID: "ses_child",
         sessionID: "ses_child",
@@ -1456,7 +1505,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_child_text",
       id: "evt_child_text",
       created: 0,
       created: 0,
-      type: "text.delta",
+      type: "session.text.delta",
       data: {
       data: {
         sessionID: "ses_child",
         sessionID: "ses_child",
         assistantMessageID: "msg_child_a",
         assistantMessageID: "msg_child_a",
@@ -1470,7 +1519,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_child_settled",
       id: "evt_child_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_child", outcome: "success" },
       data: { sessionID: "ses_child", outcome: "success" },
     })
     })
     while (!states().some((state) => state.tabs.some((tab) => tab.status === "completed"))) await Bun.sleep(0)
     while (!states().some((state) => state.tabs.some((tab) => tab.status === "completed"))) await Bun.sleep(0)
@@ -1515,7 +1564,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_child_step",
       id: "evt_child_step",
       created: 0,
       created: 0,
-      type: "step.started",
+      type: "session.step.started",
       durable: durable("ses_child"),
       durable: durable("ses_child"),
       data: {
       data: {
         sessionID: "ses_child",
         sessionID: "ses_child",
@@ -1527,7 +1576,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_child_settled",
       id: "evt_child_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_child", outcome: "interrupted" },
       data: { sessionID: "ses_child", outcome: "interrupted" },
     })
     })
     await Bun.sleep(0)
     await Bun.sleep(0)
@@ -1574,7 +1623,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_child_step",
       id: "evt_child_step",
       created: 0,
       created: 0,
-      type: "step.started",
+      type: "session.step.started",
       durable: durable("ses_child"),
       durable: durable("ses_child"),
       data: {
       data: {
         sessionID: "ses_child",
         sessionID: "ses_child",
@@ -1587,7 +1636,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_parent_call",
       id: "evt_parent_call",
       created: 0,
       created: 0,
-      type: "tool.called",
+      type: "session.tool.called",
       durable: durable("ses_1"),
       durable: durable("ses_1"),
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
@@ -1601,7 +1650,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_parent_success",
       id: "evt_parent_success",
       created: 0,
       created: 0,
-      type: "tool.success",
+      type: "session.tool.success",
       durable: durable("ses_1", 1),
       durable: durable("ses_1", 1),
       data: {
       data: {
         sessionID: "ses_1",
         sessionID: "ses_1",
@@ -1616,7 +1665,7 @@ describe("V2 mini transport", () => {
     events.push({
     events.push({
       id: "evt_child_settled",
       id: "evt_child_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: { sessionID: "ses_child", outcome: "interrupted" },
       data: { sessionID: "ses_child", outcome: "interrupted" },
     })
     })
     while (!states().some((state) => state.tabs.some((tab) => tab.status === "cancelled"))) await Bun.sleep(0)
     while (!states().some((state) => state.tabs.some((tab) => tab.status === "cancelled"))) await Bun.sleep(0)

+ 2 - 2
packages/opencode/test/server/httpapi-pty.test.ts

@@ -99,10 +99,10 @@ describe("pty HttpApi bridge", () => {
       const updated = await app().request(PtyPaths.update.replace(":ptyID", info.id), {
       const updated = await app().request(PtyPaths.update.replace(":ptyID", info.id), {
         method: "PUT",
         method: "PUT",
         headers: { ...headers, "content-type": "application/json" },
         headers: { ...headers, "content-type": "application/json" },
-        body: JSON.stringify({ title: "renamed", size: { cols: 80, rows: 24 } }),
+        body: JSON.stringify({ title: "session.renamed", size: { cols: 80, rows: 24 } }),
       })
       })
       expect(updated.status).toBe(200)
       expect(updated.status).toBe(200)
-      expect(await updated.json()).toMatchObject({ id: info.id, title: "renamed" })
+      expect(await updated.json()).toMatchObject({ id: info.id, title: "session.renamed" })
     } finally {
     } finally {
       await app().request(PtyPaths.remove.replace(":ptyID", info.id), { method: "DELETE", headers })
       await app().request(PtyPaths.remove.replace(":ptyID", info.id), { method: "DELETE", headers })
     }
     }

+ 1 - 1
packages/opencode/test/server/httpapi-sdk.test.ts

@@ -573,7 +573,7 @@ describe("HttpApi SDK", () => {
         const child = yield* capture(() => sdk.session.create({ title: "child", parentID }))
         const child = yield* capture(() => sdk.session.create({ title: "child", parentID }))
         const childID = String(record(child.data).id)
         const childID = String(record(child.data).id)
         const get = yield* capture(() => sdk.session.get({ sessionID: parentID }))
         const get = yield* capture(() => sdk.session.get({ sessionID: parentID }))
-        const update = yield* capture(() => sdk.session.update({ sessionID: parentID, title: "renamed" }))
+        const update = yield* capture(() => sdk.session.update({ sessionID: parentID, title: "session.renamed" }))
         const roots = yield* capture(() => sdk.session.list({ roots: true, limit: 10 }))
         const roots = yield* capture(() => sdk.session.list({ roots: true, limit: 10 }))
         const all = yield* capture(() => sdk.session.list({ roots: false, limit: 10 }))
         const all = yield* capture(() => sdk.session.list({ roots: false, limit: 10 }))
         const children = yield* capture(() => sdk.session.children({ sessionID: parentID }))
         const children = yield* capture(() => sdk.session.children({ sessionID: parentID }))

+ 2 - 2
packages/opencode/test/tool/apply_patch.test.ts

@@ -271,7 +271,7 @@ describe("tool.apply_patch freeform", () => {
 
 
       yield* execute({ patchText }, ctx)
       yield* execute({ patchText }, ctx)
 
 
-      const moved = path.join(test.directory, "renamed", "dir", "name.txt")
+      const moved = path.join(test.directory, "session.renamed", "dir", "name.txt")
       yield* expectReadFailure(original)
       yield* expectReadFailure(original)
       expect(yield* readText(moved)).toBe("new content\n")
       expect(yield* readText(moved)).toBe("new content\n")
     }),
     }),
@@ -282,7 +282,7 @@ describe("tool.apply_patch freeform", () => {
       const test = yield* TestInstance
       const test = yield* TestInstance
       const { ctx } = makeCtx()
       const { ctx } = makeCtx()
       const original = path.join(test.directory, "old", "name.txt")
       const original = path.join(test.directory, "old", "name.txt")
-      const destination = path.join(test.directory, "renamed", "dir", "name.txt")
+      const destination = path.join(test.directory, "session.renamed", "dir", "name.txt")
       yield* makeDir(path.dirname(original))
       yield* makeDir(path.dirname(original))
       yield* makeDir(path.dirname(destination))
       yield* makeDir(path.dirname(destination))
       yield* writeText(original, "from\n")
       yield* writeText(original, "from\n")

+ 2 - 2
packages/opencode/test/tool/fixtures/models-api.json

@@ -79593,8 +79593,8 @@
       }
       }
     }
     }
   },
   },
-  "synthetic": {
-    "id": "synthetic",
+  "session.synthetic": {
+    "id": "session.synthetic",
     "env": ["SYNTHETIC_API_KEY"],
     "env": ["SYNTHETIC_API_KEY"],
     "npm": "@ai-sdk/openai-compatible",
     "npm": "@ai-sdk/openai-compatible",
     "api": "https://api.synthetic.new/openai/v1",
     "api": "https://api.synthetic.new/openai/v1",

+ 13 - 13
packages/opencode/test/v2/session-message-updater.test.ts

@@ -22,7 +22,7 @@ test.skip("step snapshots carry over to assistant messages", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "step.started",
+      type: "session.step.started",
       durable: durable(sessionID),
       durable: durable(sessionID),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -44,7 +44,7 @@ test.skip("step snapshots carry over to assistant messages", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "step.ended",
+      type: "session.step.ended",
       durable: durable(sessionID, 1, 2),
       durable: durable(sessionID, 1, 2),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -77,7 +77,7 @@ test.skip("text ended populates assistant text content", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "step.started",
+      type: "session.step.started",
       durable: durable(sessionID),
       durable: durable(sessionID),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -96,7 +96,7 @@ test.skip("text ended populates assistant text content", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "text.started",
+      type: "session.text.started",
       durable: durable(sessionID, 1),
       durable: durable(sessionID, 1),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -110,7 +110,7 @@ test.skip("text ended populates assistant text content", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "text.ended",
+      type: "session.text.ended",
       durable: durable(sessionID, 2),
       durable: durable(sessionID, 2),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -136,7 +136,7 @@ test.skip("tool completion stores completed timestamp", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "step.started",
+      type: "session.step.started",
       durable: durable(sessionID),
       durable: durable(sessionID),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -155,7 +155,7 @@ test.skip("tool completion stores completed timestamp", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "tool.input.started",
+      type: "session.tool.input.started",
       durable: durable(sessionID, 1),
       durable: durable(sessionID, 1),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -170,7 +170,7 @@ test.skip("tool completion stores completed timestamp", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "tool.called",
+      type: "session.tool.called",
       durable: durable(sessionID, 2),
       durable: durable(sessionID, 2),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -187,7 +187,7 @@ test.skip("tool completion stores completed timestamp", () => {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "tool.success",
+      type: "session.tool.success",
       durable: durable(sessionID, 3),
       durable: durable(sessionID, 3),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -218,7 +218,7 @@ test("compaction events reduce to compaction message only when completed", () =>
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id,
       id,
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "compaction.started",
+      type: "session.compaction.started",
       durable: durable(sessionID),
       durable: durable(sessionID),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -233,7 +233,7 @@ test("compaction events reduce to compaction message only when completed", () =>
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "compaction.delta",
+      type: "session.compaction.delta",
       data: {
       data: {
         sessionID,
         sessionID,
         text: "hello ",
         text: "hello ",
@@ -245,7 +245,7 @@ test("compaction events reduce to compaction message only when completed", () =>
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: EventV2.ID.create(),
       id: EventV2.ID.create(),
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "compaction.delta",
+      type: "session.compaction.delta",
       data: {
       data: {
         sessionID,
         sessionID,
         text: "summary",
         text: "summary",
@@ -257,7 +257,7 @@ test("compaction events reduce to compaction message only when completed", () =>
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
     SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
       id: endedID,
       id: endedID,
       created: DateTime.makeUnsafe(0),
       created: DateTime.makeUnsafe(0),
-      type: "compaction.ended",
+      type: "session.compaction.ended",
       durable: durable(sessionID, 1),
       durable: durable(sessionID, 1),
       data: {
       data: {
         sessionID,
         sessionID,

+ 38 - 38
packages/schema/src/session-event.ts

@@ -12,6 +12,7 @@ import { SessionID } from "./session-id.js"
 import { Location } from "./location.js"
 import { Location } from "./location.js"
 import { SessionMessage } from "./session-message.js"
 import { SessionMessage } from "./session-message.js"
 import { Revert } from "./revert.js"
 import { Revert } from "./revert.js"
+import { Shell as ShellSchema } from "./shell.js"
 
 
 export { FileAttachment }
 export { FileAttachment }
 
 
@@ -51,7 +52,7 @@ export const UnknownError = SessionMessage.UnknownError
 export type UnknownError = SessionMessage.UnknownError
 export type UnknownError = SessionMessage.UnknownError
 
 
 export const AgentSelected = Event.durable({
 export const AgentSelected = Event.durable({
-  type: "agent.selected",
+  type: "session.agent.selected",
   ...options,
   ...options,
   schema: {
   schema: {
     ...Base,
     ...Base,
@@ -61,7 +62,7 @@ export const AgentSelected = Event.durable({
 export type AgentSelected = typeof AgentSelected.Type
 export type AgentSelected = typeof AgentSelected.Type
 
 
 export const ModelSelected = Event.durable({
 export const ModelSelected = Event.durable({
-  type: "model.selected",
+  type: "session.model.selected",
   ...options,
   ...options,
   schema: {
   schema: {
     ...Base,
     ...Base,
@@ -82,7 +83,7 @@ export const Moved = Event.durable({
 export type Moved = typeof Moved.Type
 export type Moved = typeof Moved.Type
 
 
 export const Renamed = Event.durable({
 export const Renamed = Event.durable({
-  type: "renamed",
+  type: "session.renamed",
   ...options,
   ...options,
   schema: {
   schema: {
     ...Base,
     ...Base,
@@ -92,7 +93,7 @@ export const Renamed = Event.durable({
 export type Renamed = typeof Renamed.Type
 export type Renamed = typeof Renamed.Type
 
 
 export const Forked = Event.durable({
 export const Forked = Event.durable({
-  type: "forked",
+  type: "session.forked",
   ...options,
   ...options,
   schema: {
   schema: {
     ...Base,
     ...Base,
@@ -103,7 +104,7 @@ export const Forked = Event.durable({
 export type Forked = typeof Forked.Type
 export type Forked = typeof Forked.Type
 
 
 export const PromptPromoted = Event.durable({
 export const PromptPromoted = Event.durable({
-  type: "prompt.promoted",
+  type: "session.prompt.promoted",
   ...options,
   ...options,
   schema: {
   schema: {
     sessionID: SessionID,
     sessionID: SessionID,
@@ -113,14 +114,14 @@ export const PromptPromoted = Event.durable({
 export type PromptPromoted = typeof PromptPromoted.Type
 export type PromptPromoted = typeof PromptPromoted.Type
 
 
 export const PromptAdmitted = Event.durable({
 export const PromptAdmitted = Event.durable({
-  type: "prompt.admitted",
+  type: "session.prompt.admitted",
   ...options,
   ...options,
   schema: PromptFields,
   schema: PromptFields,
 })
 })
 export type PromptAdmitted = typeof PromptAdmitted.Type
 export type PromptAdmitted = typeof PromptAdmitted.Type
 
 
 export const ExecutionSettled = Event.ephemeral({
 export const ExecutionSettled = Event.ephemeral({
-  type: "execution.settled",
+  type: "session.execution.settled",
   schema: {
   schema: {
     ...Base,
     ...Base,
     outcome: Schema.Literals(["success", "failure", "interrupted"]),
     outcome: Schema.Literals(["success", "failure", "interrupted"]),
@@ -140,7 +141,7 @@ export const ContextUpdated = Event.durable({
 export type ContextUpdated = typeof ContextUpdated.Type
 export type ContextUpdated = typeof ContextUpdated.Type
 
 
 export const Synthetic = Event.durable({
 export const Synthetic = Event.durable({
-  type: "synthetic",
+  type: "session.synthetic",
   ...options,
   ...options,
   schema: {
   schema: {
     ...Base,
     ...Base,
@@ -153,7 +154,7 @@ export type Synthetic = typeof Synthetic.Type
 
 
 export namespace Skill {
 export namespace Skill {
   export const Activated = Event.durable({
   export const Activated = Event.durable({
-    type: "skill.activated",
+    type: "session.skill.activated",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -166,23 +167,22 @@ export namespace Skill {
 
 
 export namespace Shell {
 export namespace Shell {
   export const Started = Event.durable({
   export const Started = Event.durable({
-    type: "shell.started",
+    type: "session.shell.started",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
-      callID: Schema.String,
-      command: Schema.String,
+      shell: ShellSchema.Info,
     },
     },
   })
   })
   export type Started = typeof Started.Type
   export type Started = typeof Started.Type
 
 
   export const Ended = Event.durable({
   export const Ended = Event.durable({
-    type: "shell.ended",
+    type: "session.shell.ended",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
-      callID: Schema.String,
-      output: Schema.String,
+      shell: ShellSchema.Info,
+      output: ShellSchema.Output,
     },
     },
   })
   })
   export type Ended = typeof Ended.Type
   export type Ended = typeof Ended.Type
@@ -190,7 +190,7 @@ export namespace Shell {
 
 
 export namespace Step {
 export namespace Step {
   export const Started = Event.durable({
   export const Started = Event.durable({
-    type: "step.started",
+    type: "session.step.started",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -203,7 +203,7 @@ export namespace Step {
   export type Started = typeof Started.Type
   export type Started = typeof Started.Type
 
 
   export const Ended = Event.durable({
   export const Ended = Event.durable({
-    type: "step.ended",
+    type: "session.step.ended",
     ...stepSettlementOptions,
     ...stepSettlementOptions,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -226,7 +226,7 @@ export namespace Step {
   export type Ended = typeof Ended.Type
   export type Ended = typeof Ended.Type
 
 
   export const Failed = Event.durable({
   export const Failed = Event.durable({
-    type: "step.failed",
+    type: "session.step.failed",
     ...stepSettlementOptions,
     ...stepSettlementOptions,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -239,7 +239,7 @@ export namespace Step {
 
 
 export namespace Text {
 export namespace Text {
   export const Started = Event.durable({
   export const Started = Event.durable({
-    type: "text.started",
+    type: "session.text.started",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -251,7 +251,7 @@ export namespace Text {
 
 
   // Stream fragments are live-only; Text.Ended is the replayable full-value boundary.
   // Stream fragments are live-only; Text.Ended is the replayable full-value boundary.
   export const Delta = Event.ephemeral({
   export const Delta = Event.ephemeral({
-    type: "text.delta",
+    type: "session.text.delta",
     schema: {
     schema: {
       ...Base,
       ...Base,
       assistantMessageID: SessionMessage.ID,
       assistantMessageID: SessionMessage.ID,
@@ -262,7 +262,7 @@ export namespace Text {
   export type Delta = typeof Delta.Type
   export type Delta = typeof Delta.Type
 
 
   export const Ended = Event.durable({
   export const Ended = Event.durable({
-    type: "text.ended",
+    type: "session.text.ended",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -276,7 +276,7 @@ export namespace Text {
 
 
 export namespace Reasoning {
 export namespace Reasoning {
   export const Started = Event.durable({
   export const Started = Event.durable({
-    type: "reasoning.started",
+    type: "session.reasoning.started",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -289,7 +289,7 @@ export namespace Reasoning {
 
 
   // Stream fragments are live-only; Reasoning.Ended is the replayable full-value boundary.
   // Stream fragments are live-only; Reasoning.Ended is the replayable full-value boundary.
   export const Delta = Event.ephemeral({
   export const Delta = Event.ephemeral({
-    type: "reasoning.delta",
+    type: "session.reasoning.delta",
     schema: {
     schema: {
       ...Base,
       ...Base,
       assistantMessageID: SessionMessage.ID,
       assistantMessageID: SessionMessage.ID,
@@ -300,7 +300,7 @@ export namespace Reasoning {
   export type Delta = typeof Delta.Type
   export type Delta = typeof Delta.Type
 
 
   export const Ended = Event.durable({
   export const Ended = Event.durable({
-    type: "reasoning.ended",
+    type: "session.reasoning.ended",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -322,7 +322,7 @@ export namespace Tool {
 
 
   export namespace Input {
   export namespace Input {
     export const Started = Event.durable({
     export const Started = Event.durable({
-      type: "tool.input.started",
+      type: "session.tool.input.started",
       ...options,
       ...options,
       schema: {
       schema: {
         ...ToolBase,
         ...ToolBase,
@@ -333,7 +333,7 @@ export namespace Tool {
 
 
     // Stream fragments are live-only; Input.Ended is the replayable raw-input boundary.
     // Stream fragments are live-only; Input.Ended is the replayable raw-input boundary.
     export const Delta = Event.ephemeral({
     export const Delta = Event.ephemeral({
-      type: "tool.input.delta",
+      type: "session.tool.input.delta",
       schema: {
       schema: {
         ...ToolBase,
         ...ToolBase,
         delta: Schema.String,
         delta: Schema.String,
@@ -342,7 +342,7 @@ export namespace Tool {
     export type Delta = typeof Delta.Type
     export type Delta = typeof Delta.Type
 
 
     export const Ended = Event.durable({
     export const Ended = Event.durable({
-      type: "tool.input.ended",
+      type: "session.tool.input.ended",
       ...options,
       ...options,
       schema: {
       schema: {
         ...ToolBase,
         ...ToolBase,
@@ -353,7 +353,7 @@ export namespace Tool {
   }
   }
 
 
   export const Called = Event.durable({
   export const Called = Event.durable({
-    type: "tool.called",
+    type: "session.tool.called",
     ...options,
     ...options,
     schema: {
     schema: {
       ...ToolBase,
       ...ToolBase,
@@ -372,7 +372,7 @@ export namespace Tool {
    * transitions or at a bounded cadence, not persist every stdout/stderr chunk.
    * transitions or at a bounded cadence, not persist every stdout/stderr chunk.
    */
    */
   export const Progress = Event.durable({
   export const Progress = Event.durable({
-    type: "tool.progress",
+    type: "session.tool.progress",
     ...options,
     ...options,
     schema: {
     schema: {
       ...ToolBase,
       ...ToolBase,
@@ -383,7 +383,7 @@ export namespace Tool {
   export type Progress = typeof Progress.Type
   export type Progress = typeof Progress.Type
 
 
   export const Success = Event.durable({
   export const Success = Event.durable({
-    type: "tool.success",
+    type: "session.tool.success",
     ...options,
     ...options,
     schema: {
     schema: {
       ...ToolBase,
       ...ToolBase,
@@ -400,7 +400,7 @@ export namespace Tool {
   export type Success = typeof Success.Type
   export type Success = typeof Success.Type
 
 
   export const Failed = Event.durable({
   export const Failed = Event.durable({
-    type: "tool.failed",
+    type: "session.tool.failed",
     ...options,
     ...options,
     schema: {
     schema: {
       ...ToolBase,
       ...ToolBase,
@@ -428,7 +428,7 @@ export const RetryError = Schema.Struct({
 export interface RetryError extends Schema.Schema.Type<typeof RetryError> {}
 export interface RetryError extends Schema.Schema.Type<typeof RetryError> {}
 
 
 export const Retried = Event.durable({
 export const Retried = Event.durable({
-  type: "retried",
+  type: "session.retried",
   ...options,
   ...options,
   schema: {
   schema: {
     ...Base,
     ...Base,
@@ -440,7 +440,7 @@ export type Retried = typeof Retried.Type
 
 
 export namespace Compaction {
 export namespace Compaction {
   export const Started = Event.durable({
   export const Started = Event.durable({
-    type: "compaction.started",
+    type: "session.compaction.started",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -450,7 +450,7 @@ export namespace Compaction {
   export type Started = typeof Started.Type
   export type Started = typeof Started.Type
 
 
   export const Delta = Event.ephemeral({
   export const Delta = Event.ephemeral({
-    type: "compaction.delta",
+    type: "session.compaction.delta",
     schema: {
     schema: {
       ...Base,
       ...Base,
       text: Schema.String,
       text: Schema.String,
@@ -459,7 +459,7 @@ export namespace Compaction {
   export type Delta = typeof Delta.Type
   export type Delta = typeof Delta.Type
 
 
   export const Ended = Event.durable({
   export const Ended = Event.durable({
-    type: "compaction.ended",
+    type: "session.compaction.ended",
     ...options,
     ...options,
     schema: {
     schema: {
       ...Base,
       ...Base,
@@ -473,13 +473,13 @@ export namespace Compaction {
 
 
 export namespace RevertEvent {
 export namespace RevertEvent {
   export const Staged = Event.durable({
   export const Staged = Event.durable({
-    type: "revert.staged",
+    type: "session.revert.staged",
     ...options,
     ...options,
     schema: { ...Base, revert: Revert.State },
     schema: { ...Base, revert: Revert.State },
   })
   })
-  export const Cleared = Event.durable({ type: "revert.cleared", ...options, schema: Base })
+  export const Cleared = Event.durable({ type: "session.revert.cleared", ...options, schema: Base })
   export const Committed = Event.durable({
   export const Committed = Event.durable({
-    type: "revert.committed",
+    type: "session.revert.committed",
     ...options,
     ...options,
     schema: { ...Base, messageID: SessionMessage.ID },
     schema: { ...Base, messageID: SessionMessage.ID },
   })
   })

+ 3 - 3
packages/schema/src/session-message.ts

@@ -9,6 +9,7 @@ import { DateTimeUtcFromMillis, RelativePath, statics } from "./schema.js"
 import { SessionID } from "./session-id.js"
 import { SessionID } from "./session-id.js"
 import { ascending } from "./identifier.js"
 import { ascending } from "./identifier.js"
 import { Event } from "./event.js"
 import { Event } from "./event.js"
+import { Shell as ShellSchema } from "./shell.js"
 
 
 export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
 export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
   Schema.brand("Session.Message.ID"),
   Schema.brand("Session.Message.ID"),
@@ -82,9 +83,8 @@ export interface Shell extends Schema.Schema.Type<typeof Shell> {}
 export const Shell = Schema.Struct({
 export const Shell = Schema.Struct({
   ...Base,
   ...Base,
   type: Schema.Literal("shell"),
   type: Schema.Literal("shell"),
-  callID: Schema.String,
-  command: Schema.String,
-  output: Schema.String,
+  shell: ShellSchema.Info,
+  output: ShellSchema.Output.pipe(optional),
   time: Schema.Struct({
   time: Schema.Struct({
     created: DateTimeUtcFromMillis,
     created: DateTimeUtcFromMillis,
     completed: DateTimeUtcFromMillis.pipe(optional),
     completed: DateTimeUtcFromMillis.pipe(optional),

+ 32 - 32
packages/schema/test/event-manifest.test.ts

@@ -53,7 +53,7 @@ describe("public event manifest", () => {
     expect(Session.Event.Definitions).toBe(SessionEvent.Definitions)
     expect(Session.Event.Definitions).toBe(SessionEvent.Definitions)
     expect(Workspace.Event).toBe(WorkspaceEvent)
     expect(Workspace.Event).toBe(WorkspaceEvent)
     expect(Workspace.Event.Definitions).toBe(WorkspaceEvent.Definitions)
     expect(Workspace.Event.Definitions).toBe(WorkspaceEvent.Definitions)
-    expect(EventManifest.Latest.get("step.ended")).toBe(SessionEvent.Step.Ended)
+    expect(EventManifest.Latest.get("session.step.ended")).toBe(SessionEvent.Step.Ended)
     expect(EventManifest.Latest.get("todo.updated")).toBe(SessionTodo.Event.Updated)
     expect(EventManifest.Latest.get("todo.updated")).toBe(SessionTodo.Event.Updated)
     expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated)
     expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated)
     expect(EventManifest.Latest.get("project.updated")).toBe(Project.Event.Updated)
     expect(EventManifest.Latest.get("project.updated")).toBe(Project.Event.Updated)
@@ -74,8 +74,8 @@ describe("public event manifest", () => {
       SessionV1.Event.Diff,
       SessionV1.Event.Diff,
       SessionV1.Event.Error,
       SessionV1.Event.Error,
     ])
     ])
-    expect(EventManifest.Durable.get("step.ended.1")).toBe(SessionEvent.Step.Ended)
-    expect(EventManifest.Durable.has("step.ended.2")).toBe(false)
+    expect(EventManifest.Durable.get("session.step.ended.1")).toBe(SessionEvent.Step.Ended)
+    expect(EventManifest.Durable.has("session.step.ended.2")).toBe(false)
   })
   })
 
 
   test("derives durable definitions from explicit definition durability", () => {
   test("derives durable definitions from explicit definition durability", () => {
@@ -88,37 +88,37 @@ describe("public event manifest", () => {
         "message.removed.1",
         "message.removed.1",
         "message.part.updated.1",
         "message.part.updated.1",
         "message.part.removed.1",
         "message.part.removed.1",
-        "agent.selected.1",
-        "model.selected.1",
+        "session.agent.selected.1",
+        "session.model.selected.1",
         "session.moved.1",
         "session.moved.1",
-        "renamed.1",
-        "forked.1",
-        "prompt.promoted.1",
-        "prompt.admitted.1",
+        "session.renamed.1",
+        "session.forked.1",
+        "session.prompt.promoted.1",
+        "session.prompt.admitted.1",
         "session.context.updated.1",
         "session.context.updated.1",
-        "synthetic.1",
-        "skill.activated.1",
-        "shell.started.1",
-        "shell.ended.1",
-        "step.started.1",
-        "step.ended.1",
-        "step.failed.1",
-        "text.started.1",
-        "text.ended.1",
-        "tool.input.started.1",
-        "tool.input.ended.1",
-        "tool.called.1",
-        "tool.progress.1",
-        "tool.success.1",
-        "tool.failed.1",
-        "reasoning.started.1",
-        "reasoning.ended.1",
-        "retried.1",
-        "compaction.started.1",
-        "compaction.ended.1",
-        "revert.staged.1",
-        "revert.cleared.1",
-        "revert.committed.1",
+        "session.synthetic.1",
+        "session.skill.activated.1",
+        "session.shell.started.1",
+        "session.shell.ended.1",
+        "session.step.started.1",
+        "session.step.ended.1",
+        "session.step.failed.1",
+        "session.text.started.1",
+        "session.text.ended.1",
+        "session.tool.input.started.1",
+        "session.tool.input.ended.1",
+        "session.tool.called.1",
+        "session.tool.progress.1",
+        "session.tool.success.1",
+        "session.tool.failed.1",
+        "session.reasoning.started.1",
+        "session.reasoning.ended.1",
+        "session.retried.1",
+        "session.compaction.started.1",
+        "session.compaction.ended.1",
+        "session.revert.staged.1",
+        "session.revert.cleared.1",
+        "session.revert.committed.1",
       ].toSorted(),
       ].toSorted(),
     )
     )
     expect(SessionEvent.DurableDefinitions).toEqual(
     expect(SessionEvent.DurableDefinitions).toEqual(

+ 6 - 6
packages/sdk-next/test/embedded.test.ts

@@ -78,7 +78,7 @@ it.live(
           prompt: fixture.sdk.Prompt.make({ text: "Promote this input" }),
           prompt: fixture.sdk.Prompt.make({ text: "Promote this input" }),
         })
         })
         const prompted = yield* opencode.sessions.log({ sessionID: id, follow: true }).pipe(
         const prompted = yield* opencode.sessions.log({ sessionID: id, follow: true }).pipe(
-          Stream.filter((event) => event.type === "prompt.promoted" && event.data.inputID === wake.id),
+          Stream.filter((event) => event.type === "session.prompt.promoted" && event.data.inputID === wake.id),
           Stream.runHead,
           Stream.runHead,
           Effect.timeout("10 seconds"),
           Effect.timeout("10 seconds"),
           Effect.map(Option.getOrThrow),
           Effect.map(Option.getOrThrow),
@@ -119,7 +119,7 @@ it.live(
         expect(page.data.some((session) => session.id === id)).toBe(true)
         expect(page.data.some((session) => session.id === id)).toBe(true)
         expect(active).toEqual({ data: {}, watermarks: {} })
         expect(active).toEqual({ data: {}, watermarks: {} })
         expect(admitted.sessionID).toBe(id)
         expect(admitted.sessionID).toBe(id)
-        expect(prompted.type).toBe("prompt.promoted")
+        expect(prompted.type).toBe("session.prompt.promoted")
         expect(wakeContext).toContainEqual(expect.objectContaining({ id: wake.id, type: "user" }))
         expect(wakeContext).toContainEqual(expect.objectContaining({ id: wake.id, type: "user" }))
         expect(contextEntries).toEqual([
         expect(contextEntries).toEqual([
           { key: "deploy-target", value: "production" },
           { key: "deploy-target", value: "production" },
@@ -127,7 +127,7 @@ it.live(
         ])
         ])
         expect(remainingContextEntries).toEqual([{ key: "deploy-target", value: "production" }])
         expect(remainingContextEntries).toEqual([{ key: "deploy-target", value: "production" }])
         expect(context.some((message) => message.type === "model-switched")).toBe(true)
         expect(context.some((message) => message.type === "model-switched")).toBe(true)
-        expect(event).toMatchObject({ type: "model.selected", durable: { seq: 1 } })
+        expect(event).toMatchObject({ type: "session.model.selected", durable: { seq: 1 } })
         expect(message).toEqual(modelMessage)
         expect(message).toEqual(modelMessage)
         expect(missing.map((error) => error._tag)).toEqual([
         expect(missing.map((error) => error._tag)).toEqual([
           "SessionNotFoundError",
           "SessionNotFoundError",
@@ -149,13 +149,13 @@ it.live(
         const opencode = yield* fixture.sdk.OpenCode.create()
         const opencode = yield* fixture.sdk.OpenCode.create()
         const id = sessionID(fixture)
         const id = sessionID(fixture)
         const connected = yield* Latch.make(false)
         const connected = yield* Latch.make(false)
-        const prompted = yield* Deferred.make<Extract<OpenCodeEvent, { type: "prompt.promoted" }>>()
+        const prompted = yield* Deferred.make<Extract<OpenCodeEvent, { type: "session.prompt.promoted" }>>()
 
 
         yield* opencode.events.subscribe().pipe(
         yield* opencode.events.subscribe().pipe(
           Stream.runForEach((event) =>
           Stream.runForEach((event) =>
             event.type === "server.connected"
             event.type === "server.connected"
               ? connected.open
               ? connected.open
-              : event.type === "prompt.promoted" && event.data.sessionID === id
+              : event.type === "session.prompt.promoted" && event.data.sessionID === id
                 ? Deferred.succeed(prompted, event).pipe(Effect.asVoid)
                 ? Deferred.succeed(prompted, event).pipe(Effect.asVoid)
                 : Effect.void,
                 : Effect.void,
           ),
           ),
@@ -191,7 +191,7 @@ it.live(
           Stream.runForEach((notification: OpenCodeEvent) =>
           Stream.runForEach((notification: OpenCodeEvent) =>
             notification.type === "server.connected"
             notification.type === "server.connected"
               ? ready.open
               ? ready.open
-              : notification.type === "agent.selected" && notification.data.sessionID === id
+              : notification.type === "session.agent.selected" && notification.data.sessionID === id
                 ? event.open
                 ? event.open
                 : Effect.void,
                 : Effect.void,
           )
           )

+ 2 - 2
packages/sdk/js/script/build.ts

@@ -60,7 +60,7 @@ if (schemas) {
   visit({ ...document, components: { ...document.components, schemas: undefined } })
   visit({ ...document, components: { ...document.components, schemas: undefined } })
   for (const name of Object.keys(schemas)) {
   for (const name of Object.keys(schemas)) {
     if (
     if (
-      /^(AgentSelected|ModelSelected|SessionMoved|Renamed|Forked|PromptPromoted|PromptAdmitted|ExecutionSettled|ContextUpdated|Synthetic|SkillActivated|ShellStarted|ShellEnded|StepStarted|StepEnded|StepFailed|TextStarted|TextDelta|TextEnded|ReasoningStarted|ReasoningDelta|ReasoningEnded|ToolInputStarted|ToolInputDelta|ToolInputEnded|ToolCalled|ToolProgress|ToolSuccess|ToolFailed|Retried|CompactionStarted|CompactionDelta|CompactionEnded|RevertStaged|RevertCleared|RevertCommitted)1$/.test(
+      /^(SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionSettled|SessionContextUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionReasoningStarted|SessionReasoningDelta|SessionReasoningEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetried|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)1$/.test(
         name,
         name,
       ) &&
       ) &&
       !reachable.has(name)
       !reachable.has(name)
@@ -100,7 +100,7 @@ await createClient({
 const generatedTypesPath = "./src/v2/gen/types.gen.ts"
 const generatedTypesPath = "./src/v2/gen/types.gen.ts"
 const generatedTypes = await Bun.file(generatedTypesPath).text()
 const generatedTypes = await Bun.file(generatedTypesPath).text()
 if (
 if (
-  /export type (AgentSelected|ModelSelected|SessionMoved|Renamed|Forked|PromptPromoted|PromptAdmitted|ExecutionSettled|ContextUpdated|Synthetic|SkillActivated|ShellStarted|ShellEnded|StepStarted|StepEnded|StepFailed|TextStarted|TextDelta|TextEnded|ReasoningStarted|ReasoningDelta|ReasoningEnded|ToolInputStarted|ToolInputDelta|ToolInputEnded|ToolCalled|ToolProgress|ToolSuccess|ToolFailed|Retried|CompactionStarted|CompactionDelta|CompactionEnded|RevertStaged|RevertCleared|RevertCommitted)1 =/.test(
+  /export type (SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionSettled|SessionContextUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionReasoningStarted|SessionReasoningDelta|SessionReasoningEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetried|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)1 =/.test(
     generatedTypes,
     generatedTypes,
   )
   )
 ) {
 ) {

Разница между файлами не показана из-за своего большого размера
+ 233 - 307
packages/sdk/js/src/v2/gen/types.gen.ts


+ 3 - 3
packages/sdk/openapi.json

@@ -16410,7 +16410,7 @@
           "text": {
           "text": {
             "type": "string"
             "type": "string"
           },
           },
-          "synthetic": {
+          "session.synthetic": {
             "type": "boolean"
             "type": "boolean"
           },
           },
           "ignored": {
           "ignored": {
@@ -23071,7 +23071,7 @@
           "text": {
           "text": {
             "type": "string"
             "type": "string"
           },
           },
-          "synthetic": {
+          "session.synthetic": {
             "type": "boolean"
             "type": "boolean"
           },
           },
           "ignored": {
           "ignored": {
@@ -27479,7 +27479,7 @@
           },
           },
           "type": {
           "type": {
             "type": "string",
             "type": "string",
-            "enum": ["synthetic"]
+            "enum": ["session.synthetic"]
           }
           }
         },
         },
         "required": ["id", "time", "sessionID", "text", "type"],
         "required": ["id", "time", "sessionID", "text", "type"],

+ 38 - 39
packages/tui/src/context/data.tsx

@@ -126,8 +126,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
         const item = position === undefined ? undefined : messages[position]
         const item = position === undefined ? undefined : messages[position]
         return item?.type === "assistant" ? item : undefined
         return item?.type === "assistant" ? item : undefined
       },
       },
-      activeShell(messages: SessionMessage[], callID: string) {
-        const item = messages.findLast((item) => item.type === "shell" && item.callID === callID)
+      shell(messages: SessionMessage[], shellID: string) {
+        const item = messages.findLast((item) => item.type === "shell" && item.shell.id === shellID)
         return item?.type === "shell" ? item : undefined
         return item?.type === "shell" ? item : undefined
       },
       },
       latestTool(assistant: SessionMessageAssistant | undefined, callID?: string) {
       latestTool(assistant: SessionMessageAssistant | undefined, callID?: string) {
@@ -220,7 +220,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
         case "skill.updated":
         case "skill.updated":
           void result.location.skill.refresh(event.location)
           void result.location.skill.refresh(event.location)
           break
           break
-        case "agent.selected":
+        case "session.agent.selected":
           if (store.session.info[event.data.sessionID])
           if (store.session.info[event.data.sessionID])
             setStore("session", "info", event.data.sessionID, "agent", event.data.agent)
             setStore("session", "info", event.data.sessionID, "agent", event.data.agent)
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
@@ -232,7 +232,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             })
             })
           })
           })
           break
           break
-        case "model.selected":
+        case "session.model.selected":
           if (store.session.info[event.data.sessionID])
           if (store.session.info[event.data.sessionID])
             setStore("session", "info", event.data.sessionID, "model", event.data.model)
             setStore("session", "info", event.data.sessionID, "model", event.data.model)
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
@@ -244,11 +244,11 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             })
             })
           })
           })
           break
           break
-        case "renamed":
+        case "session.renamed":
           if (store.session.info[event.data.sessionID])
           if (store.session.info[event.data.sessionID])
             setStore("session", "info", event.data.sessionID, "title", event.data.title)
             setStore("session", "info", event.data.sessionID, "title", event.data.title)
           break
           break
-        case "prompt.promoted": {
+        case "session.prompt.promoted": {
           setStore("session", "status", event.data.sessionID, "running")
           setStore("session", "status", event.data.sessionID, "running")
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const position = index.get(event.data.inputID)
             const position = index.get(event.data.inputID)
@@ -264,7 +264,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           })
           })
           break
           break
         }
         }
-        case "prompt.admitted":
+        case "session.prompt.admitted":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             message.append(draft, index, {
             message.append(draft, index, {
               id: event.data.inputID,
               id: event.data.inputID,
@@ -287,7 +287,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             })
             })
           })
           })
           break
           break
-        case "synthetic":
+        case "session.synthetic":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             message.append(draft, index, {
             message.append(draft, index, {
               id: messageIDFromEvent(event.id),
               id: messageIDFromEvent(event.id),
@@ -299,29 +299,28 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             })
             })
           })
           })
           break
           break
-        case "shell.started":
+        case "session.shell.started":
           setStore("session", "status", event.data.sessionID, "running")
           setStore("session", "status", event.data.sessionID, "running")
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             message.append(draft, index, {
             message.append(draft, index, {
               id: messageIDFromEvent(event.id),
               id: messageIDFromEvent(event.id),
               type: "shell",
               type: "shell",
-              callID: event.data.callID,
-              command: event.data.command,
-              output: "",
+              shell: event.data.shell,
               time: { created: event.created },
               time: { created: event.created },
             })
             })
           })
           })
           break
           break
-        case "shell.ended":
+        case "session.shell.ended":
           setStore("session", "status", event.data.sessionID, "idle")
           setStore("session", "status", event.data.sessionID, "idle")
-          message.update(event.data.sessionID, (draft, index) => {
-            const match = message.activeShell(draft, event.data.callID)
+          message.update(event.data.sessionID, (draft) => {
+            const match = message.shell(draft, event.data.shell.id)
             if (!match) return
             if (!match) return
+            match.shell = event.data.shell
             match.output = event.data.output
             match.output = event.data.output
             match.time.completed = event.created
             match.time.completed = event.created
           })
           })
           break
           break
-        case "step.started":
+        case "session.step.started":
           setStore("session", "status", event.data.sessionID, "running")
           setStore("session", "status", event.data.sessionID, "running")
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             if (index.has(event.data.assistantMessageID)) return
             if (index.has(event.data.assistantMessageID)) return
@@ -338,7 +337,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             })
             })
           })
           })
           break
           break
-        case "step.ended":
+        case "session.step.ended":
           setStore("session", "status", event.data.sessionID, "running")
           setStore("session", "status", event.data.sessionID, "running")
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID)
             const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID)
@@ -351,7 +350,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
               currentAssistant.snapshot = { ...currentAssistant.snapshot, end: event.data.snapshot }
               currentAssistant.snapshot = { ...currentAssistant.snapshot, end: event.data.snapshot }
           })
           })
           break
           break
-        case "step.failed":
+        case "session.step.failed":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID)
             const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID)
             if (!currentAssistant) return
             if (!currentAssistant) return
@@ -360,7 +359,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             currentAssistant.error = event.data.error
             currentAssistant.error = event.data.error
           })
           })
           break
           break
-        case "text.started":
+        case "session.text.started":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
             message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
               type: "text",
               type: "text",
@@ -369,7 +368,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             })
             })
           })
           })
           break
           break
-        case "text.delta":
+        case "session.text.delta":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestText(
             const match = message.latestText(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -378,7 +377,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             if (match) match.text += event.data.delta
             if (match) match.text += event.data.delta
           })
           })
           break
           break
-        case "text.ended":
+        case "session.text.ended":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestText(
             const match = message.latestText(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -387,7 +386,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             if (match) match.text = event.data.text
             if (match) match.text = event.data.text
           })
           })
           break
           break
-        case "tool.input.started":
+        case "session.tool.input.started":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
             message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
               type: "tool",
               type: "tool",
@@ -398,7 +397,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             })
             })
           })
           })
           break
           break
-        case "tool.input.delta":
+        case "session.tool.input.delta":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestTool(
             const match = message.latestTool(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -407,7 +406,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             if (match?.state.status === "pending") match.state.input += event.data.delta
             if (match?.state.status === "pending") match.state.input += event.data.delta
           })
           })
           break
           break
-        case "tool.input.ended":
+        case "session.tool.input.ended":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestTool(
             const match = message.latestTool(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -416,7 +415,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             if (match?.state.status === "pending") match.state.input = event.data.text
             if (match?.state.status === "pending") match.state.input = event.data.text
           })
           })
           break
           break
-        case "tool.called":
+        case "session.tool.called":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestTool(
             const match = message.latestTool(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -428,7 +427,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             match.state = { status: "running", input: event.data.input, structured: {}, content: [] }
             match.state = { status: "running", input: event.data.input, structured: {}, content: [] }
           })
           })
           break
           break
-        case "tool.progress":
+        case "session.tool.progress":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestTool(
             const match = message.latestTool(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -439,7 +438,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             match.state.content = [...event.data.content]
             match.state.content = [...event.data.content]
           })
           })
           break
           break
-        case "tool.success":
+        case "session.tool.success":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestTool(
             const match = message.latestTool(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -461,7 +460,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             match.time.completed = event.created
             match.time.completed = event.created
           })
           })
           break
           break
-        case "tool.failed":
+        case "session.tool.failed":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestTool(
             const match = message.latestTool(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -484,7 +483,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             match.time.completed = event.created
             match.time.completed = event.created
           })
           })
           break
           break
-        case "reasoning.started":
+        case "session.reasoning.started":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
             message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
               type: "reasoning",
               type: "reasoning",
@@ -495,7 +494,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             })
             })
           })
           })
           break
           break
-        case "reasoning.delta":
+        case "session.reasoning.delta":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestReasoning(
             const match = message.latestReasoning(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -504,7 +503,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             if (match) match.text += event.data.delta
             if (match) match.text += event.data.delta
           })
           })
           break
           break
-        case "reasoning.ended":
+        case "session.reasoning.ended":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             const match = message.latestReasoning(
             const match = message.latestReasoning(
               message.assistant(draft, index, event.data.assistantMessageID),
               message.assistant(draft, index, event.data.assistantMessageID),
@@ -517,25 +516,25 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             }
             }
           })
           })
           break
           break
-        case "retried":
-        case "compaction.started":
+        case "session.retried":
+        case "session.compaction.started":
           setStore("session", "status", event.data.sessionID, "running")
           setStore("session", "status", event.data.sessionID, "running")
           break
           break
-        case "execution.settled":
+        case "session.execution.settled":
           setStore("session", "status", event.data.sessionID, "idle")
           setStore("session", "status", event.data.sessionID, "idle")
           break
           break
-        case "revert.staged":
+        case "session.revert.staged":
           if (store.session.info[event.data.sessionID])
           if (store.session.info[event.data.sessionID])
             setStore("session", "info", event.data.sessionID, "revert", event.data.revert)
             setStore("session", "info", event.data.sessionID, "revert", event.data.revert)
           break
           break
-        case "revert.cleared":
-        case "revert.committed":
+        case "session.revert.cleared":
+        case "session.revert.committed":
           if (store.session.info[event.data.sessionID])
           if (store.session.info[event.data.sessionID])
             setStore("session", "info", event.data.sessionID, "revert", undefined)
             setStore("session", "info", event.data.sessionID, "revert", undefined)
           break
           break
-        case "compaction.delta":
+        case "session.compaction.delta":
           break
           break
-        case "compaction.ended":
+        case "session.compaction.ended":
           message.update(event.data.sessionID, (draft, index) => {
           message.update(event.data.sessionID, (draft, index) => {
             message.append(draft, index, {
             message.append(draft, index, {
               id: messageIDFromEvent(event.id),
               id: messageIDFromEvent(event.id),

+ 8 - 8
packages/tui/src/feature-plugins/system/notifications.ts

@@ -74,17 +74,17 @@ const tui: TuiPlugin = async (api) => {
     notify(api, sessionID, "Session done", session?.parentID ? "subagent_done" : "done")
     notify(api, sessionID, "Session done", session?.parentID ? "subagent_done" : "done")
   }
   }
 
 
-  api.event.on("prompt.promoted", (event) => started(event.data.sessionID))
-  api.event.on("shell.started", (event) => started(event.data.sessionID))
-  api.event.on("step.started", (event) => started(event.data.sessionID))
-  api.event.on("retried", (event) => started(event.data.sessionID))
-  api.event.on("compaction.started", (event) => started(event.data.sessionID))
-  api.event.on("shell.ended", (event) => ended(event.data.sessionID))
-  api.event.on("step.ended", (event) => {
+  api.event.on("session.prompt.promoted", (event) => started(event.data.sessionID))
+  api.event.on("session.shell.started", (event) => started(event.data.sessionID))
+  api.event.on("session.step.started", (event) => started(event.data.sessionID))
+  api.event.on("session.retried", (event) => started(event.data.sessionID))
+  api.event.on("session.compaction.started", (event) => started(event.data.sessionID))
+  api.event.on("session.shell.ended", (event) => ended(event.data.sessionID))
+  api.event.on("session.step.ended", (event) => {
     if (event.data.finish === "tool-calls") return
     if (event.data.finish === "tool-calls") return
     ended(event.data.sessionID)
     ended(event.data.sessionID)
   })
   })
-  api.event.on("step.failed", (event) => {
+  api.event.on("session.step.failed", (event) => {
     const sessionID = event.data.sessionID
     const sessionID = event.data.sessionID
     if (!active.has(sessionID)) return
     if (!active.has(sessionID)) return
     errored.add(sessionID)
     errored.add(sessionID)

+ 7 - 11
packages/tui/src/routes/session/index.tsx

@@ -1346,7 +1346,7 @@ function RevertMessage(props: {
 
 
 function ShellMessage(props: { message: Extract<SessionMessage, { type: "shell" }> }) {
 function ShellMessage(props: { message: Extract<SessionMessage, { type: "shell" }> }) {
   const { theme } = useTheme()
   const { theme } = useTheme()
-  const output = createMemo(() => stripAnsi(props.message.output.trim()))
+  const output = createMemo(() => stripAnsi(props.message.output?.output.trim() ?? ""))
 
 
   return (
   return (
     <box
     <box
@@ -1359,7 +1359,7 @@ function ShellMessage(props: { message: Extract<SessionMessage, { type: "shell"
       customBorderChars={SplitBorder.customBorderChars}
       customBorderChars={SplitBorder.customBorderChars}
       borderColor={theme.background}
       borderColor={theme.background}
     >
     >
-      <text fg={theme.text}>$ {props.message.command}</text>
+      <text fg={theme.text}>$ {props.message.shell.command}</text>
       <Show when={output()}>
       <Show when={output()}>
         <text fg={theme.textMuted}>{output()}</text>
         <text fg={theme.textMuted}>{output()}</text>
       </Show>
       </Show>
@@ -1408,13 +1408,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
         >
         >
           <text fg={theme.text}>{props.message.text}</text>
           <text fg={theme.text}>{props.message.text}</text>
           <Show when={files().length}>
           <Show when={files().length}>
-            <box
-              flexDirection="row"
-              paddingBottom={metadataVisible() ? 1 : 0}
-              paddingTop={1}
-              gap={1}
-              flexWrap="wrap"
-            >
+            <box flexDirection="row" paddingBottom={metadataVisible() ? 1 : 0} paddingTop={1} gap={1} flexWrap="wrap">
               <For each={files()}>
               <For each={files()}>
                 {(file) => {
                 {(file) => {
                   const directory = file.mime === "application/x-directory"
                   const directory = file.mime === "application/x-directory"
@@ -1744,7 +1738,8 @@ function ToolPart(props: { part: SessionMessageAssistantTool }) {
       return Boolean(shellID && data.shell.get(shellID))
       return Boolean(shellID && data.shell.get(shellID))
     }
     }
     if (display() === "subagent") {
     if (display() === "subagent") {
-      const sessionID = stringValue(props.part.state.structured.sessionID) ?? stringValue(props.part.state.structured.sessionId)
+      const sessionID =
+        stringValue(props.part.state.structured.sessionID) ?? stringValue(props.part.state.structured.sessionId)
       return Boolean(sessionID && data.session.status(sessionID) === "running")
       return Boolean(sessionID && data.session.status(sessionID) === "running")
     }
     }
     return false
     return false
@@ -2660,7 +2655,8 @@ function formatSessionTranscript(
 ) {
 ) {
   const body = messages.flatMap((message) => {
   const body = messages.flatMap((message) => {
     if (message.type === "user") return [`## User\n\n${message.text}`]
     if (message.type === "user") return [`## User\n\n${message.text}`]
-    if (message.type === "shell") return [`## Shell\n\n\`\`\`\n$ ${message.command}\n${message.output}\n\`\`\``]
+    if (message.type === "shell")
+      return [`## Shell\n\n\`\`\`\n$ ${message.shell.command}\n${message.output?.output ?? ""}\n\`\`\``]
     if (message.type !== "assistant") return []
     if (message.type !== "assistant") return []
     const content = message.content.flatMap((item) => {
     const content = message.content.flatMap((item) => {
       if (item.type === "text") return [item.text]
       if (item.type === "text") return [item.text]

+ 14 - 14
packages/tui/src/routes/session/rows.ts

@@ -133,42 +133,42 @@ export function createSessionRows(sessionID: Accessor<string>) {
     if (event.data.sessionID === sessionID()) appendMessage(event.data.inputID)
     if (event.data.sessionID === sessionID()) appendMessage(event.data.inputID)
   }
   }
   const subscriptions = [
   const subscriptions = [
-    data.on("prompt.admitted", input),
-    data.on("prompt.promoted", input),
+    data.on("session.prompt.admitted", input),
+    data.on("session.prompt.promoted", input),
     data.on("session.context.updated", message),
     data.on("session.context.updated", message),
-    data.on("synthetic", (event) => {
+    data.on("session.synthetic", (event) => {
       if (event.data.sessionID === sessionID() && event.data.description?.trim())
       if (event.data.sessionID === sessionID() && event.data.description?.trim())
         appendMessage(event.id.replace(/^evt_/, "msg_"))
         appendMessage(event.id.replace(/^evt_/, "msg_"))
     }),
     }),
-    data.on("shell.started", message),
-    data.on("agent.selected", message),
-    data.on("model.selected", message),
-    data.on("compaction.ended", message),
-    data.on("text.delta", (event) => {
+    data.on("session.shell.started", message),
+    data.on("session.agent.selected", message),
+    data.on("session.model.selected", message),
+    data.on("session.compaction.ended", message),
+    data.on("session.text.delta", (event) => {
       if (event.data.sessionID === sessionID())
       if (event.data.sessionID === sessionID())
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.textID })
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.textID })
     }),
     }),
-    data.on("text.ended", (event) => {
+    data.on("session.text.ended", (event) => {
       if (event.data.sessionID === sessionID() && event.data.text.trim())
       if (event.data.sessionID === sessionID() && event.data.text.trim())
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.textID })
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.textID })
     }),
     }),
-    data.on("reasoning.delta", (event) => {
+    data.on("session.reasoning.delta", (event) => {
       if (event.data.sessionID === sessionID())
       if (event.data.sessionID === sessionID())
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.reasoningID })
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.reasoningID })
     }),
     }),
-    data.on("reasoning.ended", (event) => {
+    data.on("session.reasoning.ended", (event) => {
       if (event.data.sessionID === sessionID() && event.data.text.trim())
       if (event.data.sessionID === sessionID() && event.data.text.trim())
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.reasoningID })
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.reasoningID })
     }),
     }),
-    data.on("tool.input.started", (event) => {
+    data.on("session.tool.input.started", (event) => {
       if (event.data.sessionID === sessionID())
       if (event.data.sessionID === sessionID())
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.callID }, event.data.name)
         appendPart({ messageID: event.data.assistantMessageID, partID: event.data.callID }, event.data.name)
     }),
     }),
-    data.on("step.ended", (event) => {
+    data.on("session.step.ended", (event) => {
       if (event.data.sessionID !== sessionID() || ["tool-calls", "unknown"].includes(event.data.finish)) return
       if (event.data.sessionID !== sessionID() || ["tool-calls", "unknown"].includes(event.data.finish)) return
       appendFooter(event.data.assistantMessageID)
       appendFooter(event.data.assistantMessageID)
     }),
     }),
-    data.on("step.failed", (event) => {
+    data.on("session.step.failed", (event) => {
       if (event.data.sessionID === sessionID()) appendFooter(event.data.assistantMessageID)
       if (event.data.sessionID === sessionID()) appendFooter(event.data.assistantMessageID)
     }),
     }),
   ]
   ]

+ 3 - 3
packages/tui/test/cli/cmd/tui/notifications.test.ts

@@ -91,7 +91,7 @@ function stepStarted(id: string, sessionID = "session"): V2Event {
   return {
   return {
     id,
     id,
     created: 0,
     created: 0,
-    type: "step.started",
+    type: "session.step.started",
     durable: durable(sessionID),
     durable: durable(sessionID),
     data: {
     data: {
       sessionID,
       sessionID,
@@ -106,7 +106,7 @@ function stepEnded(id: string, sessionID = "session", finish = "stop"): V2Event
   return {
   return {
     id,
     id,
     created: 0,
     created: 0,
-    type: "step.ended",
+    type: "session.step.ended",
     durable: durable(sessionID),
     durable: durable(sessionID),
     data: {
     data: {
       sessionID,
       sessionID,
@@ -122,7 +122,7 @@ function stepFailed(id: string, sessionID = "session"): V2Event {
   return {
   return {
     id,
     id,
     created: 0,
     created: 0,
-    type: "step.failed",
+    type: "session.step.failed",
     durable: durable(sessionID),
     durable: durable(sessionID),
     data: {
     data: {
       sessionID,
       sessionID,

+ 16 - 16
packages/tui/test/cli/tui/data.test.tsx

@@ -269,7 +269,7 @@ test("tracks session status from active sessions and execution events", async ()
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_step_started",
       id: "evt_step_started",
       created: 0,
       created: 0,
-      type: "step.started",
+      type: "session.step.started",
       durable: durable("session-live"),
       durable: durable("session-live"),
       data: {
       data: {
         sessionID: "session-live",
         sessionID: "session-live",
@@ -283,7 +283,7 @@ test("tracks session status from active sessions and execution events", async ()
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_step_ended",
       id: "evt_step_ended",
       created: 0,
       created: 0,
-      type: "step.ended",
+      type: "session.step.ended",
       durable: durable("session-live", 1, 2),
       durable: durable("session-live", 1, 2),
       data: {
       data: {
         sessionID: "session-live",
         sessionID: "session-live",
@@ -302,7 +302,7 @@ test("tracks session status from active sessions and execution events", async ()
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_execution_settled",
       id: "evt_execution_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: {
       data: {
         sessionID: "session-live",
         sessionID: "session-live",
         outcome: "success",
         outcome: "success",
@@ -313,7 +313,7 @@ test("tracks session status from active sessions and execution events", async ()
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_failed_step_started",
       id: "evt_failed_step_started",
       created: 0,
       created: 0,
-      type: "step.started",
+      type: "session.step.started",
       durable: durable("session-failed"),
       durable: durable("session-failed"),
       data: {
       data: {
         sessionID: "session-failed",
         sessionID: "session-failed",
@@ -327,7 +327,7 @@ test("tracks session status from active sessions and execution events", async ()
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_step_failed",
       id: "evt_step_failed",
       created: 0,
       created: 0,
-      type: "step.failed",
+      type: "session.step.failed",
       durable: durable("session-failed", 1, 2),
       durable: durable("session-failed", 1, 2),
       data: {
       data: {
         sessionID: "session-failed",
         sessionID: "session-failed",
@@ -344,7 +344,7 @@ test("tracks session status from active sessions and execution events", async ()
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_failed_execution_settled",
       id: "evt_failed_execution_settled",
       created: 0,
       created: 0,
-      type: "execution.settled",
+      type: "session.execution.settled",
       data: {
       data: {
         sessionID: "session-failed",
         sessionID: "session-failed",
         outcome: "failure",
         outcome: "failure",
@@ -797,14 +797,14 @@ test("settles pending tools when a live failure arrives", async () => {
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_agent_1",
       id: "evt_agent_1",
       created: 0,
       created: 0,
-      type: "agent.selected",
+      type: "session.agent.selected",
       durable: durable("session-1"),
       durable: durable("session-1"),
       data: { sessionID: "session-1", agent: "build" },
       data: { sessionID: "session-1", agent: "build" },
     })
     })
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_model_1",
       id: "evt_model_1",
       created: 0,
       created: 0,
-      type: "model.selected",
+      type: "session.model.selected",
       durable: durable("session-1", 1),
       durable: durable("session-1", 1),
       data: {
       data: {
         sessionID: "session-1",
         sessionID: "session-1",
@@ -814,7 +814,7 @@ test("settles pending tools when a live failure arrives", async () => {
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_step_started_1",
       id: "evt_step_started_1",
       created: 0,
       created: 0,
-      type: "step.started",
+      type: "session.step.started",
       durable: durable("session-1", 2),
       durable: durable("session-1", 2),
       data: {
       data: {
         sessionID: "session-1",
         sessionID: "session-1",
@@ -826,7 +826,7 @@ test("settles pending tools when a live failure arrives", async () => {
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_input_1",
       id: "evt_input_1",
       created: 0,
       created: 0,
-      type: "tool.input.started",
+      type: "session.tool.input.started",
       durable: durable("session-1", 3),
       durable: durable("session-1", 3),
       data: {
       data: {
         sessionID: "session-1",
         sessionID: "session-1",
@@ -838,7 +838,7 @@ test("settles pending tools when a live failure arrives", async () => {
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_called_1",
       id: "evt_called_1",
       created: 0,
       created: 0,
-      type: "tool.called",
+      type: "session.tool.called",
       durable: durable("session-1", 4),
       durable: durable("session-1", 4),
       data: {
       data: {
         sessionID: "session-1",
         sessionID: "session-1",
@@ -852,7 +852,7 @@ test("settles pending tools when a live failure arrives", async () => {
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_failed_1",
       id: "evt_failed_1",
       created: 0,
       created: 0,
-      type: "tool.failed",
+      type: "session.tool.failed",
       durable: durable("session-1", 5),
       durable: durable("session-1", 5),
       data: {
       data: {
         sessionID: "session-1",
         sessionID: "session-1",
@@ -942,7 +942,7 @@ test("renders admitted prompts immediately with queued marker and clears when pr
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_admitted_1",
       id: "evt_admitted_1",
       created: 0,
       created: 0,
-      type: "prompt.admitted",
+      type: "session.prompt.admitted",
       durable: durable(sessionID),
       durable: durable(sessionID),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -961,7 +961,7 @@ test("renders admitted prompts immediately with queued marker and clears when pr
     emitEvent(events, {
     emitEvent(events, {
       id: "evt_prompted_1",
       id: "evt_prompted_1",
       created: 0,
       created: 0,
-      type: "prompt.promoted",
+      type: "session.prompt.promoted",
       durable: durable(sessionID, 1),
       durable: durable(sessionID, 1),
       data: {
       data: {
         sessionID,
         sessionID,
@@ -969,8 +969,8 @@ test("renders admitted prompts immediately with queued marker and clears when pr
       },
       },
     })
     })
 
 
-    await wait(() => received.at(-1) === "prompt.promoted")
-    expect(received.slice(-2)).toEqual(["prompt.admitted", "prompt.promoted"])
+    await wait(() => received.at(-1) === "session.prompt.promoted")
+    expect(received.slice(-2)).toEqual(["session.prompt.admitted", "session.prompt.promoted"])
     unsubscribe()
     unsubscribe()
     const message = sync.session.message.list(sessionID)?.[0]
     const message = sync.session.message.list(sessionID)?.[0]
     expect(message?.type).toBe("user")
     expect(message?.type).toBe("user")

+ 1 - 1
packages/ui/src/components/provider-icon.tsx

@@ -9,7 +9,7 @@ export type ProviderIconProps = JSX.SVGElementTags["svg"] & {
 
 
 export const ProviderIcon: Component<ProviderIconProps> = (props) => {
 export const ProviderIcon: Component<ProviderIconProps> = (props) => {
   const [local, rest] = splitProps(props, ["id", "class", "classList"])
   const [local, rest] = splitProps(props, ["id", "class", "classList"])
-  const resolved = createMemo(() => (iconNames.includes(local.id as IconName) ? local.id : "synthetic"))
+  const resolved = createMemo(() => (iconNames.includes(local.id as IconName) ? local.id : "session.synthetic"))
   return (
   return (
     <svg
     <svg
       data-component="provider-icon"
       data-component="provider-icon"

+ 22 - 1
specs/v2/schema-changelog.md

@@ -908,7 +908,7 @@ Compatibility:
 ## 2026-07-03: Normalize Session Event Names And Envelope Time
 ## 2026-07-03: Normalize Session Event Names And Envelope Time
 
 
 - Drop the experimental `session.next.` prefix from current Session event names.
 - Drop the experimental `session.next.` prefix from current Session event names.
-- Rename `agent.switched` to `agent.selected`, `model.switched` to `model.selected`, and `prompted` to `prompt.promoted`; add `prompt.admitted` as the durable prompt admission record.
+- Rename `agent.switched` to `session.agent.selected`, `model.switched` to `session.model.selected`, and `prompted` to `session.prompt.promoted`; add `session.prompt.admitted` as the durable prompt admission record.
 - Add an envelope-level `created` timestamp stored on each event row; payload-level `timestamp` fields are removed.
 - Add an envelope-level `created` timestamp stored on each event row; payload-level `timestamp` fields are removed.
 - Remove projection-only `messageID` fields from selected/message-producing events; projected message IDs derive from the event ID. `revert.committed.messageID` remains, and `forked.messageID` is now `forked.from`.
 - Remove projection-only `messageID` fields from selected/message-producing events; projected message IDs derive from the event ID. `revert.committed.messageID` remains, and `forked.messageID` is now `forked.from`.
 - Rename `session.moved.subdirectory` to `subpath` and normalize event-related schema identifiers.
 - Rename `session.moved.subdirectory` to `subpath` and normalize event-related schema identifiers.
@@ -918,3 +918,24 @@ Compatibility:
 - V2 durable events and projections are experimental and are reset by `20260703090000_reset_v2_event_rename_sweep`; existing V2 event rows, event sequences, projected session messages, and admitted inputs are wiped.
 - V2 durable events and projections are experimental and are reset by `20260703090000_reset_v2_event_rename_sweep`; existing V2 event rows, event sequences, projected session messages, and admitted inputs are wiped.
 - All renamed durable event types restart at version 1 under their normalized names.
 - All renamed durable event types restart at version 1 under their normalized names.
 - Generated Promise, Effect, and legacy JavaScript SDK surfaces were regenerated from the normalized schemas.
 - Generated Promise, Effect, and legacy JavaScript SDK surfaces were regenerated from the normalized schemas.
+
+## 2026-07-03: Restore Session Event Prefix
+
+- Restore the `session.` prefix on current Session event names; the prior rename intended to drop only the `.next.` segment from `session.next.*` names.
+- Payloads are unchanged. For example, `step.ended` is now `session.step.ended`, while already-correct names such as `session.moved` and `session.context.updated` are unchanged.
+
+Compatibility:
+
+- Covered by the existing `20260703190000_reset_v2_shell_event_payloads` V2 reset; no additional migration or column change is required.
+
+## 2026-07-03: Align Session Shell Payloads With Shell Service
+
+- Change durable `session.shell.started` to carry `shell: Shell.Info`.
+- Change durable `session.shell.ended` to carry the final `shell: Shell.Info` snapshot and structured `output: Shell.Output`.
+- Project `SessionMessage.Shell` with the same nested Shell service objects so consumers can render command, exit status, truncation, and paged output consistently.
+- Remove the transitional `callID` correlation field from shell events and projected shell messages; shell records now key by `shell.id`.
+- Publish `session.shell.started` after the shell process is created so `Shell.Info` reflects the real process. A failed spawn publishes no shell events; the failure surfaces as an error on the shell request itself, so the durable ledger never records a `session.shell.started` without a process that existed.
+
+Compatibility:
+
+- V2 durable events and projections are experimental and are reset by `20260703190000_reset_v2_shell_event_payloads`; existing V2 event rows, event sequences, projected session messages, and admitted inputs are wiped.

+ 1 - 1
specs/v2/session.md

@@ -114,7 +114,7 @@ Before each step, the runner estimates the complete model-visible request and co
 
 
 Compaction keeps the full transcript durable while replacing its active model representation with one hidden checkpoint containing a structured rolling summary and token-bounded serialized recent context. Provider-native assistant, reasoning, and tool messages never survive across the boundary, avoiding signature and encrypted-reasoning failures when the earlier prefix changes.
 Compaction keeps the full transcript durable while replacing its active model representation with one hidden checkpoint containing a structured rolling summary and token-bounded serialized recent context. Provider-native assistant, reasoning, and tool messages never survive across the boundary, avoiding signature and encrypted-reasoning failures when the earlier prefix changes.
 
 
-`compaction.started.1` durably identifies the attempt. Compaction deltas are live-only progress. `compaction.ended.1` durably stores the final summary and serialized recent context; only this completed event projects a model-visible compaction message. On the next physical attempt, the runner observes that completed compaction and directly renders a fresh Context Epoch baseline. A failed or interrupted attempt therefore leaves the previous history boundary active.
+`session.compaction.started.1` durably identifies the attempt. Compaction deltas are live-only progress. `session.compaction.ended.1` durably stores the final summary and serialized recent context; only this completed event projects a model-visible compaction message. On the next physical attempt, the runner observes that completed compaction and directly renders a fresh Context Epoch baseline. A failed or interrupted attempt therefore leaves the previous history boundary active.
 
 
 Repeated compactions update the previous structured summary with newly compacted messages. The runner then reloads projected history and executes the original pending step.
 Repeated compactions update the previous structured summary with newly compacted messages. The runner then reloads projected history and executes the original pending step.
 
 

Некоторые файлы не были показаны из-за большого количества измененных файлов