refactor(schema): session shell payloads and event prefix restore (#35229)
This commit is contained in:
parent
64e4f6f91b
commit
650d774372
49 changed files with 1521 additions and 1200 deletions
|
|
@ -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="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>
|
||||
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ const SettingsProvidersContent: Component = () => {
|
|||
>
|
||||
<div class="flex flex-col min-w-0">
|
||||
<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>
|
||||
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ export const SettingsProvidersV2: Component = () => {
|
|||
<div class="settings-v2-provider-row" data-component="custom-provider-section">
|
||||
<div class="settings-v2-provider-lead">
|
||||
<ProviderIcon
|
||||
id="synthetic"
|
||||
id="session.synthetic"
|
||||
width={PROVIDER_ICON_SIZE}
|
||||
height={PROVIDER_ICON_SIZE}
|
||||
class="settings-v2-provider-icon shrink-0"
|
||||
|
|
|
|||
|
|
@ -976,9 +976,27 @@ export type SessionContextOutput = {
|
|||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
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
|
||||
|
|
@ -1109,7 +1127,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly agent: string }
|
||||
|
|
@ -1118,7 +1136,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1143,7 +1161,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly title: string }
|
||||
|
|
@ -1152,7 +1170,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly parentID: string; readonly from?: string }
|
||||
|
|
@ -1161,7 +1179,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly inputID: string }
|
||||
|
|
@ -1170,7 +1188,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1206,7 +1224,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1220,7 +1238,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly name: string; readonly text: string }
|
||||
|
|
@ -1229,25 +1247,59 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 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 created: number
|
||||
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 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 created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1262,7 +1314,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1284,7 +1336,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1297,7 +1349,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string }
|
||||
|
|
@ -1306,7 +1358,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1320,7 +1372,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1334,7 +1386,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1349,7 +1401,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1363,7 +1415,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1377,7 +1429,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1396,7 +1448,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1414,7 +1466,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1438,7 +1490,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1457,7 +1509,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1477,7 +1529,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly reason: "auto" | "manual" }
|
||||
|
|
@ -1486,7 +1538,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1500,7 +1552,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -1524,7 +1576,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string }
|
||||
|
|
@ -1533,7 +1585,7 @@ export type SessionLogOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly messageID: string }
|
||||
|
|
@ -1617,9 +1669,27 @@ export type SessionMessageOutput = {
|
|||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
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
|
||||
|
|
@ -1798,9 +1868,27 @@ export type MessageListOutput = {
|
|||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
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
|
||||
|
|
@ -4309,7 +4397,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly agent: string }
|
||||
|
|
@ -4318,7 +4406,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4343,7 +4431,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly title: string }
|
||||
|
|
@ -4352,7 +4440,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly parentID: string; readonly from?: string }
|
||||
|
|
@ -4361,7 +4449,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly inputID: string }
|
||||
|
|
@ -4370,7 +4458,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4397,7 +4485,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "execution.settled"
|
||||
readonly type: "session.execution.settled"
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
|
|
@ -4418,7 +4506,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4432,7 +4520,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly name: string; readonly text: string }
|
||||
|
|
@ -4441,25 +4529,59 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 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 created: number
|
||||
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 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 created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4474,7 +4596,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4496,7 +4618,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4509,7 +4631,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string }
|
||||
|
|
@ -4518,7 +4640,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "text.delta"
|
||||
readonly type: "session.text.delta"
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
|
|
@ -4531,7 +4653,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4545,7 +4667,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4559,7 +4681,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "reasoning.delta"
|
||||
readonly type: "session.reasoning.delta"
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
|
|
@ -4572,7 +4694,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4587,7 +4709,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4601,7 +4723,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 data: {
|
||||
readonly sessionID: string
|
||||
|
|
@ -4614,7 +4736,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4628,7 +4750,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4647,7 +4769,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4665,7 +4787,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4689,7 +4811,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4708,7 +4830,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4728,7 +4850,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly reason: "auto" | "manual" }
|
||||
|
|
@ -4737,7 +4859,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "compaction.delta"
|
||||
readonly type: "session.compaction.delta"
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly text: string }
|
||||
}
|
||||
|
|
@ -4745,7 +4867,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4759,7 +4881,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
|
|
@ -4783,7 +4905,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string }
|
||||
|
|
@ -4792,7 +4914,7 @@ export type EventSubscribeOutput =
|
|||
readonly id: string
|
||||
readonly created: number
|
||||
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 location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly messageID: string }
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ test("event.subscribe exposes and decodes the native Effect event stream", async
|
|||
return yield* client.event.subscribe().pipe(Stream.runCollect)
|
||||
}).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]
|
||||
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(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(logQueries[0]).toEqual({ after: "0" })
|
||||
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,
|
||||
)
|
||||
expect(logged.at(-1)).toEqual(synced)
|
||||
|
|
@ -228,7 +228,7 @@ const modelSwitchedMessage = {
|
|||
const modelSwitchedEvent = {
|
||||
id: "evt_model",
|
||||
created: 1_717_171_717_000,
|
||||
type: "model.selected",
|
||||
type: "session.model.selected",
|
||||
durable: { aggregateID: "ses_test", seq: 1, version: 1 },
|
||||
data: {
|
||||
sessionID: "ses_test",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
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 () => {
|
||||
|
|
@ -329,7 +329,7 @@ const synced = { type: "log.synced", aggregateID: "ses_test", seq: 1 }
|
|||
const modelSwitchedEvent = {
|
||||
id: "evt_model",
|
||||
created: 1_717_171_717_000,
|
||||
type: "model.selected",
|
||||
type: "session.model.selected",
|
||||
durable: { aggregateID: "ses_test", seq: 1, version: 1 },
|
||||
data: {
|
||||
sessionID: "ses_test",
|
||||
|
|
|
|||
1
packages/core/src/database/migration.gen.ts
generated
1
packages/core/src/database/migration.gen.ts
generated
|
|
@ -43,5 +43,6 @@ export const migrations = (
|
|||
import("./migration/20260702134641_add_session_context_entry"),
|
||||
import("./migration/20260703090000_reset_v2_event_rename_sweep"),
|
||||
import("./migration/20260703181610_event_created_column"),
|
||||
import("./migration/20260703190000_reset_v2_shell_event_payloads"),
|
||||
])
|
||||
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -41,8 +41,8 @@ import type { EventLog } from "@opencode-ai/schema/event-log"
|
|||
import { SkillV2 } from "./skill"
|
||||
import { Job } from "./job"
|
||||
import { CommandV2 } from "./command"
|
||||
import { Identifier } from "./util/identifier"
|
||||
import { Shell } from "./shell"
|
||||
import { Shell as ShellSchema } from "@opencode-ai/schema/shell"
|
||||
import { KeyedMutex } from "./effect/keyed-mutex"
|
||||
|
||||
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({
|
||||
create: Effect.fn("V2Session.create")(function* (input) {
|
||||
const sessionID = input.id ?? SessionSchema.ID.create()
|
||||
|
|
@ -550,23 +537,38 @@ const layer = Layer.effect(
|
|||
Effect.gen(function* () {
|
||||
activeShells.add(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(
|
||||
SessionEvent.Shell.Started,
|
||||
{
|
||||
sessionID: input.sessionID,
|
||||
callID,
|
||||
command: input.command,
|
||||
shell: started,
|
||||
},
|
||||
{ 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, {
|
||||
sessionID: input.sessionID,
|
||||
callID,
|
||||
output,
|
||||
shell: completed.shell,
|
||||
output: completed.output,
|
||||
})
|
||||
}).pipe(
|
||||
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) =>
|
||||
Prompt.make({
|
||||
text: input.text,
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ const serialize = (message: SessionMessage.Message) => {
|
|||
if (message.type === "system") return `[System update]: ${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 === "shell") return `[Shell]: ${message.command}\n${truncate(message.output)}`
|
||||
if (message.type === "shell") return `[Shell]: ${message.shell.command}\n${truncate(message.output?.output ?? "")}`
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,21 +8,25 @@ export type MemoryState = {
|
|||
}
|
||||
|
||||
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 {
|
||||
const assistantIndex = (messageID: SessionMessage.ID) =>
|
||||
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.
|
||||
const latestAssistantIndex = () => state.messages.findLastIndex((message) => message.type === "assistant")
|
||||
const activeShellIndex = (callID: string) =>
|
||||
state.messages.findLastIndex((message) => message.type === "shell" && message.callID === callID)
|
||||
|
||||
return {
|
||||
getCurrentAssistant() {
|
||||
|
|
@ -41,12 +45,11 @@ export function memory(state: MemoryState): Adapter {
|
|||
return assistant?.type === "assistant" ? assistant : undefined
|
||||
})
|
||||
},
|
||||
getCurrentShell(callID) {
|
||||
getShell(shellID) {
|
||||
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) {
|
||||
|
|
@ -60,7 +63,7 @@ export function memory(state: MemoryState): Adapter {
|
|||
},
|
||||
updateShell(shell) {
|
||||
return Effect.sync(() => {
|
||||
const index = activeShellIndex(shell.callID)
|
||||
const index = shellIndex(shell.id)
|
||||
if (index < 0) return
|
||||
const current = state.messages[index]
|
||||
if (current?.type !== "shell") return
|
||||
|
|
@ -100,7 +103,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
|
||||
return Effect.gen(function* () {
|
||||
yield* SessionEvent.All.match(event, {
|
||||
"agent.selected": (event) => {
|
||||
"session.agent.selected": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.AgentSelected.make({
|
||||
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(
|
||||
SessionMessage.ModelSelected.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
|
|
@ -123,11 +126,11 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
)
|
||||
},
|
||||
"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) =>
|
||||
adapter.appendMessage(
|
||||
SessionMessage.System.make({
|
||||
|
|
@ -137,7 +140,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
time: { created: event.created },
|
||||
}),
|
||||
),
|
||||
synthetic: (event) => {
|
||||
"session.synthetic": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.Synthetic.make({
|
||||
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(
|
||||
SessionMessage.Skill.make({
|
||||
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(
|
||||
SessionMessage.Shell.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "shell",
|
||||
metadata: event.metadata,
|
||||
callID: event.data.callID,
|
||||
command: event.data.command,
|
||||
output: "",
|
||||
shell: event.data.shell,
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
},
|
||||
"shell.ended": (event) => {
|
||||
"session.shell.ended": (event) => {
|
||||
return Effect.gen(function* () {
|
||||
const currentShell = yield* adapter.getCurrentShell(event.data.callID)
|
||||
const currentShell = yield* adapter.getShell(event.data.shell.id)
|
||||
if (currentShell) {
|
||||
yield* adapter.updateShell(
|
||||
produce(currentShell, (draft) => {
|
||||
draft.shell = castDraft(event.data.shell)
|
||||
draft.output = event.data.output
|
||||
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* () {
|
||||
const currentAssistant = yield* adapter.getCurrentAssistant()
|
||||
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) => {
|
||||
draft.time.completed = event.created
|
||||
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) => {
|
||||
draft.time.completed = event.created
|
||||
draft.finish = "error"
|
||||
draft.error = event.data.error
|
||||
})
|
||||
},
|
||||
"text.started": (event) => {
|
||||
"session.text.started": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })),
|
||||
)
|
||||
})
|
||||
},
|
||||
"text.delta": (event) => {
|
||||
"session.text.delta": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestText(draft, event.data.textID)
|
||||
if (match) match.text += event.data.delta
|
||||
})
|
||||
},
|
||||
"text.ended": (event) => {
|
||||
"session.text.ended": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestText(draft, event.data.textID)
|
||||
if (match) match.text = event.data.text
|
||||
})
|
||||
},
|
||||
"tool.input.started": (event) => {
|
||||
"session.tool.input.started": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
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) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
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) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
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) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
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) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
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) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
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) => {
|
||||
draft.content.push(
|
||||
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) => {
|
||||
const match = latestReasoning(draft, event.data.reasoningID)
|
||||
if (match) match.text += event.data.delta
|
||||
})
|
||||
},
|
||||
"reasoning.ended": (event) => {
|
||||
"session.reasoning.ended": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestReasoning(draft, event.data.reasoningID)
|
||||
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(
|
||||
SessionMessage.Compaction.make({
|
||||
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,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -393,18 +393,25 @@ function run(db: DatabaseService, event: MessageEvent) {
|
|||
return message.type === "assistant" ? message : undefined
|
||||
})
|
||||
},
|
||||
getCurrentShell(callID) {
|
||||
getShell(shellID) {
|
||||
return Effect.gen(function* () {
|
||||
const rows = yield* db
|
||||
const row = yield* db
|
||||
.select()
|
||||
.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))
|
||||
.all()
|
||||
.limit(1)
|
||||
.get()
|
||||
.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,
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ function toLLMMessage(message: SessionMessage.Message, model: Model): Message[]
|
|||
Message.make({
|
||||
id: message.id,
|
||||
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,
|
||||
}),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ describe("SessionV2.create", () => {
|
|||
expect(forkContext.map((message) => message.id)).not.toEqual(parentContext.map((message) => message.id))
|
||||
expect(history).toHaveLength(1)
|
||||
expect(history[0]).toMatchObject({
|
||||
type: "forked",
|
||||
type: "session.forked",
|
||||
durable: { seq: 0 },
|
||||
data: { sessionID: forked.id, parentID: parent.id },
|
||||
})
|
||||
|
|
@ -378,8 +378,8 @@ describe("SessionV2.create", () => {
|
|||
expect(
|
||||
Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(2), Stream.runCollect)),
|
||||
).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 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()
|
||||
}),
|
||||
),
|
||||
|
|
@ -513,7 +514,8 @@ describe("SessionV2.create", () => {
|
|||
|
||||
const messages = yield* session.messages({ sessionID: created.id, order: "asc" })
|
||||
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()
|
||||
}),
|
||||
),
|
||||
|
|
@ -529,7 +531,7 @@ describe("SessionV2.create", () => {
|
|||
expect(yield* session.get(created.id)).toMatchObject({ agent: "plan" })
|
||||
expect(
|
||||
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(
|
||||
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 } }])
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -40,14 +40,14 @@ describe("SessionV2.log", () => {
|
|||
const session = yield* SessionV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
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 watermark = (yield* events.sequences([created.id])).get(created.id)
|
||||
|
||||
// Session creation commits a non-public durable event, so the marker's
|
||||
// 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 })
|
||||
}),
|
||||
)
|
||||
|
|
@ -64,7 +64,7 @@ describe("SessionV2.log", () => {
|
|||
yield* session.rename({ sessionID: created.id, title: "renamed live" })
|
||||
|
||||
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 first = 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 sequences = yield* events.sequences([first.id, second.id])
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater
|
|||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||
import { SessionInput } from "@opencode-ai/core/session/input"
|
||||
import { Shell } from "@opencode-ai/schema/shell"
|
||||
import {
|
||||
SessionContextCheckpointTable,
|
||||
SessionInputTable,
|
||||
|
|
@ -257,15 +258,32 @@ describe("SessionProjector", () => {
|
|||
})
|
||||
yield* events.publish(SessionEvent.Shell.Started, {
|
||||
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, {
|
||||
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, {
|
||||
sessionID,
|
||||
reason: "manual",
|
||||
|
|
@ -320,7 +338,8 @@ describe("SessionProjector", () => {
|
|||
metadata: { source: "projector-test" },
|
||||
})
|
||||
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) },
|
||||
})
|
||||
expect(messages.find((message) => message.type === "compaction")).toMatchObject({
|
||||
|
|
|
|||
|
|
@ -256,16 +256,16 @@ describe("SessionV2.prompt", () => {
|
|||
const streamed = Array.from(yield* Fiber.join(fiber))
|
||||
|
||||
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(
|
||||
Array.from(
|
||||
yield* publicEvents({ sessionID, after: streamed[0].durable?.seq }).pipe(Stream.take(1), Stream.runCollect),
|
||||
).map((event): [number | undefined, string] => [event.durable?.seq, event.type]),
|
||||
).toEqual([[1, "prompt.admitted"]])
|
||||
).toEqual([[1, "session.prompt.admitted"]])
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
|
|||
import { AgentAttachment, FileAttachment } from "@opencode-ai/core/session/prompt"
|
||||
import { toLLMMessages } from "@opencode-ai/core/session/runner/to-llm-message"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Shell } from "@opencode-ai/schema/shell"
|
||||
import { DateTime } from "effect"
|
||||
|
||||
const created = DateTime.makeUnsafe(0)
|
||||
|
|
@ -87,9 +88,18 @@ describe("toLLMMessages", () => {
|
|||
SessionMessage.Shell.make({
|
||||
id: id("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 },
|
||||
}),
|
||||
SessionMessage.Compaction.make({
|
||||
|
|
|
|||
|
|
@ -193,12 +193,12 @@ describe("SessionRunnerLLM recorded", () => {
|
|||
.orderBy(EventTable.seq)
|
||||
.all()).map((event) => event.type),
|
||||
).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",
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(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()
|
||||
const serialized = JSON.stringify(success)
|
||||
expect(serialized.split(base64)).toHaveLength(2)
|
||||
|
|
@ -94,7 +94,7 @@ test("provider-executed success retains its compatibility result", async () => {
|
|||
const { published, publisher } = capture()
|
||||
await Effect.runPromise(publisher.publish(LLMEvent.toolCall({ ...call, 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")
|
||||
})
|
||||
|
||||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -2985,7 +2985,7 @@ describe("SessionRunnerLLM", () => {
|
|||
{ type: "user", text: "Interrupt provider" },
|
||||
{ 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)
|
||||
}),
|
||||
)
|
||||
|
|
@ -3029,8 +3029,8 @@ describe("SessionRunnerLLM", () => {
|
|||
},
|
||||
])
|
||||
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")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -158,14 +158,14 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
if (!("sessionID" in event.data) || event.data.sessionID !== input.sessionID) continue
|
||||
const time = toMillis(event.created)
|
||||
|
||||
if (event.type === "prompt.promoted") {
|
||||
if (event.type === "session.prompt.promoted") {
|
||||
if (event.data.inputID === messageID) {
|
||||
promoted = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (
|
||||
event.type === "execution.settled" &&
|
||||
event.type === "session.execution.settled" &&
|
||||
event.data.outcome === "interrupted" &&
|
||||
(interrupted || permissionRejected || questionRejected || formCancelled)
|
||||
) {
|
||||
|
|
@ -173,7 +173,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
}
|
||||
if (!promoted) continue
|
||||
|
||||
if (event.type === "step.started") {
|
||||
if (event.type === "session.step.started") {
|
||||
const part: StepStartPart = {
|
||||
id: partID(event.id),
|
||||
sessionID: input.sessionID,
|
||||
|
|
@ -189,11 +189,11 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
|
||||
if (event.type === "text.started") {
|
||||
if (event.type === "session.text.started") {
|
||||
starts.set(event.data.textID, { id: partID(event.id), timestamp: time })
|
||||
continue
|
||||
}
|
||||
if (event.type === "text.ended") {
|
||||
if (event.type === "session.text.ended") {
|
||||
const started = starts.get(event.data.textID)
|
||||
const part: TextPart = {
|
||||
id: started?.id ?? partID(event.id),
|
||||
|
|
@ -207,11 +207,11 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
|
||||
if (event.type === "reasoning.started") {
|
||||
if (event.type === "session.reasoning.started") {
|
||||
starts.set(event.data.reasoningID, { id: partID(event.id), timestamp: time })
|
||||
continue
|
||||
}
|
||||
if (event.type === "reasoning.ended" && input.thinking) {
|
||||
if (event.type === "session.reasoning.ended" && input.thinking) {
|
||||
const started = starts.get(event.data.reasoningID)
|
||||
const part: ReasoningPart = {
|
||||
id: started?.id ?? partID(event.id),
|
||||
|
|
@ -236,7 +236,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
|
||||
if (event.type === "tool.input.started") {
|
||||
if (event.type === "session.tool.input.started") {
|
||||
tools.set(event.data.callID, {
|
||||
id: partID(event.id),
|
||||
timestamp: time,
|
||||
|
|
@ -246,12 +246,12 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
})
|
||||
continue
|
||||
}
|
||||
if (event.type === "tool.input.ended") {
|
||||
if (event.type === "session.tool.input.ended") {
|
||||
const current = tools.get(event.data.callID)
|
||||
if (current) current.raw = event.data.text
|
||||
continue
|
||||
}
|
||||
if (event.type === "tool.called") {
|
||||
if (event.type === "session.tool.called") {
|
||||
const current = tools.get(event.data.callID)
|
||||
tools.set(event.data.callID, {
|
||||
id: current?.id ?? partID(event.id),
|
||||
|
|
@ -264,7 +264,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
})
|
||||
continue
|
||||
}
|
||||
if (event.type === "tool.success") {
|
||||
if (event.type === "session.tool.success") {
|
||||
const current = tools.get(event.data.callID) ?? fallbackTool(event)
|
||||
const part: ToolPart = {
|
||||
id: current.id,
|
||||
|
|
@ -297,7 +297,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
if (!emit("tool_use", time, { part })) await input.renderTool(part)
|
||||
continue
|
||||
}
|
||||
if (event.type === "tool.failed") {
|
||||
if (event.type === "session.tool.failed") {
|
||||
const current = tools.get(event.data.callID) ?? fallbackTool(event)
|
||||
const error = event.data.error.message
|
||||
const part: ToolPart = {
|
||||
|
|
@ -328,7 +328,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
|
||||
if (event.type === "step.ended") {
|
||||
if (event.type === "session.step.ended") {
|
||||
const part: StepFinishPart = {
|
||||
id: partID(event.id),
|
||||
sessionID: input.sessionID,
|
||||
|
|
@ -342,14 +342,14 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
emit("step_finish", time, { part })
|
||||
continue
|
||||
}
|
||||
if (event.type === "step.failed") {
|
||||
if (event.type === "session.step.failed") {
|
||||
if (interrupted || permissionRejected || questionRejected || formCancelled) continue
|
||||
emittedError = true
|
||||
process.exitCode = 1
|
||||
if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message)
|
||||
continue
|
||||
}
|
||||
if (event.type === "execution.settled") {
|
||||
if (event.type === "session.execution.settled") {
|
||||
if (event.data.outcome === "failure" && !emittedError && !questionRejected && !formCancelled) {
|
||||
emittedError = true
|
||||
process.exitCode = 1
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ type SessionCommit = StreamCommit
|
|||
// - sent: part ID → byte offset of last flushed text (for incremental output)
|
||||
// - visible: part ID → rendered text for an active part after display transforms
|
||||
// - 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
|
||||
type ShellCall = {
|
||||
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 (command && !current.command) {
|
||||
current.command = command
|
||||
|
|
@ -625,7 +625,7 @@ function claimShell(data: SessionData, callID: string, source: ShellCall["source
|
|||
source,
|
||||
...(command ? { command } : {}),
|
||||
} satisfies ShellCall
|
||||
data.shell.set(callID, next)
|
||||
data.shell.set(shellID, next)
|
||||
return next
|
||||
}
|
||||
|
||||
|
|
@ -728,37 +728,37 @@ export function reduceSessionData(input: SessionDataInput): SessionDataOutput {
|
|||
const data = input.data
|
||||
const event = input.event
|
||||
|
||||
if (event.type === "shell.started") {
|
||||
if (event.type === "session.shell.started") {
|
||||
if (event.properties.sessionID !== input.sessionID) {
|
||||
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") {
|
||||
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)) {
|
||||
return out(data, commits, patch({ status: "running shell" }))
|
||||
}
|
||||
|
||||
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" }))
|
||||
}
|
||||
|
||||
if (event.type === "shell.ended") {
|
||||
if (event.type === "session.shell.ended") {
|
||||
if (event.properties.sessionID !== input.sessionID) {
|
||||
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") {
|
||||
return out(data, commits)
|
||||
}
|
||||
|
||||
const partID = shellPartID(event.properties.callID)
|
||||
const partID = shellPartID(event.properties.shell.id)
|
||||
const seen = data.tools.has(partID)
|
||||
const command = shell.command ?? ""
|
||||
data.tools.delete(partID)
|
||||
|
|
@ -767,11 +767,11 @@ export function reduceSessionData(input: SessionDataInput): SessionDataOutput {
|
|||
}
|
||||
|
||||
if (!seen && command) {
|
||||
commits.push(startShell(event.properties.callID, command))
|
||||
commits.push(startShell(event.properties.shell.id, command))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -424,21 +424,21 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
}
|
||||
|
||||
const reduce = (child: ChildState, event: V2Event) => {
|
||||
if (event.type === "prompt.promoted") {
|
||||
if (event.type === "session.prompt.promoted") {
|
||||
if (userFrame(child, event.data.inputID, "")) {
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (event.type === "step.started") {
|
||||
if (event.type === "session.step.started") {
|
||||
touch(child, event.created)
|
||||
if (child.label === FALLBACK_LABEL && event.data.agent) child.label = Locale.titlecase(event.data.agent)
|
||||
if (child.status !== "running") child.status = "running"
|
||||
input.emit()
|
||||
return
|
||||
}
|
||||
if (event.type === "text.delta") {
|
||||
if (event.type === "session.text.delta") {
|
||||
const projected = child.projectedText.get(event.data.textID)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
if (projected && covered >= 0) {
|
||||
|
|
@ -459,7 +459,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "text.ended") {
|
||||
if (event.type === "session.text.ended") {
|
||||
child.text.set(event.data.textID, event.data.text)
|
||||
child.projectedText.delete(event.data.textID)
|
||||
setFrame(child, `text:${event.data.textID}`, {
|
||||
|
|
@ -474,7 +474,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "reasoning.delta") {
|
||||
if (event.type === "session.reasoning.delta") {
|
||||
const projected = child.projectedReasoning.get(event.data.reasoningID)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
if (projected && covered >= 0) {
|
||||
|
|
@ -495,7 +495,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "reasoning.ended") {
|
||||
if (event.type === "session.reasoning.ended") {
|
||||
child.reasoning.set(event.data.reasoningID, event.data.text)
|
||||
child.projectedReasoning.delete(event.data.reasoningID)
|
||||
if (!input.thinking) return
|
||||
|
|
@ -510,11 +510,11 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
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 })
|
||||
return
|
||||
}
|
||||
if (event.type === "tool.called") {
|
||||
if (event.type === "session.tool.called") {
|
||||
const current = child.tools.get(event.data.callID)
|
||||
child.tools.set(event.data.callID, {
|
||||
name: event.data.tool,
|
||||
|
|
@ -537,10 +537,10 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
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
|
||||
const current = child.tools.get(event.data.callID)
|
||||
const failed = event.type === "tool.failed"
|
||||
const failed = event.type === "session.tool.failed"
|
||||
childTool(
|
||||
child,
|
||||
{
|
||||
|
|
@ -577,7 +577,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "step.failed") {
|
||||
if (event.type === "session.step.failed") {
|
||||
setFrame(child, `error:step:${event.data.assistantMessageID}`, {
|
||||
kind: "error",
|
||||
source: "system",
|
||||
|
|
@ -589,7 +589,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "execution.settled") {
|
||||
if (event.type === "session.execution.settled") {
|
||||
child.status =
|
||||
event.data.outcome === "success" ? "completed" : event.data.outcome === "interrupted" ? "cancelled" : "error"
|
||||
touch(child, event.created)
|
||||
|
|
@ -613,15 +613,15 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
|
||||
return {
|
||||
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)
|
||||
return
|
||||
}
|
||||
if (event.type === "tool.failed") {
|
||||
if (event.type === "session.tool.failed") {
|
||||
pendingCalls.delete(event.data.callID)
|
||||
return
|
||||
}
|
||||
if (event.type !== "tool.success") return
|
||||
if (event.type !== "session.tool.success") return
|
||||
const pending = pendingCalls.get(event.data.callID)
|
||||
pendingCalls.delete(event.data.callID)
|
||||
const found = childSessionID(record(event.data.structured))
|
||||
|
|
|
|||
|
|
@ -213,7 +213,9 @@ function promptAgents(next: SessionTurnInput) {
|
|||
? [
|
||||
{
|
||||
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
|
||||
}
|
||||
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
|
||||
if (!render) {
|
||||
// Suppressed history: mark settled shells rendered so live redelivery
|
||||
// stays silent. A still-running shell stays unmarked and renders in
|
||||
// full when its live shell.ended event arrives.
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
if (message.type !== "assistant") return
|
||||
|
|
@ -515,17 +529,17 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
}
|
||||
input.trace?.write("recv.event", 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
|
||||
state.messageIDs.add(event.data.inputID)
|
||||
write([], { phase: "running", status: "waiting for assistant" })
|
||||
return
|
||||
}
|
||||
if (event.type === "step.started") {
|
||||
if (event.type === "session.step.started") {
|
||||
write([], { phase: "running", status: "assistant responding" })
|
||||
return
|
||||
}
|
||||
if (event.type === "skill.activated") {
|
||||
if (event.type === "session.skill.activated") {
|
||||
const messageID = event.id.replace(/^evt_/, "msg_")
|
||||
if (state.wait) state.wait.promoted = true
|
||||
if (state.skillMessages.has(messageID)) return
|
||||
|
|
@ -533,38 +547,56 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
write([skillCommit(messageID, event.data.name)])
|
||||
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
|
||||
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
|
||||
}
|
||||
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[] = []
|
||||
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
|
||||
// An unset callID means shell.started has not been observed yet (event
|
||||
// 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: "" })
|
||||
if (owned) wait.resolve()
|
||||
return
|
||||
}
|
||||
if (event.type === "text.delta") {
|
||||
if (event.type === "session.text.delta") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
|
||||
const projected = state.projectedText.get(key)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
|
|
@ -586,7 +618,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
])
|
||||
return
|
||||
}
|
||||
if (event.type === "text.ended") {
|
||||
if (event.type === "session.text.ended") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
|
||||
const previous = state.text.get(key) ?? ""
|
||||
if (event.data.text.length > previous.length)
|
||||
|
|
@ -604,7 +636,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
state.projectedText.delete(key)
|
||||
return
|
||||
}
|
||||
if (event.type === "reasoning.delta") {
|
||||
if (event.type === "session.reasoning.delta") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
|
||||
const projected = state.projectedReasoning.get(key)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
|
|
@ -627,7 +659,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
])
|
||||
return
|
||||
}
|
||||
if (event.type === "reasoning.ended") {
|
||||
if (event.type === "session.reasoning.ended") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
|
||||
const previous = state.reasoning.get(key) ?? ""
|
||||
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)
|
||||
return
|
||||
}
|
||||
if (event.type === "tool.input.started") {
|
||||
if (event.type === "session.tool.input.started") {
|
||||
state.tools.set(event.data.callID, {
|
||||
messageID: event.data.assistantMessageID,
|
||||
name: event.data.name,
|
||||
|
|
@ -655,7 +687,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
})
|
||||
return
|
||||
}
|
||||
if (event.type === "tool.called") {
|
||||
if (event.type === "session.tool.called") {
|
||||
if (state.finishedTools.has(event.data.callID)) return
|
||||
const current = state.tools.get(event.data.callID)
|
||||
const item: SessionMessageAssistantTool = {
|
||||
|
|
@ -669,10 +701,10 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
renderTool(event.data.assistantMessageID, item)
|
||||
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 failed = event.type === "tool.failed"
|
||||
const failed = event.type === "session.tool.failed"
|
||||
const item: SessionMessageAssistantTool = {
|
||||
type: "tool",
|
||||
id: event.data.callID,
|
||||
|
|
@ -720,7 +752,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
syncBlockers()
|
||||
return
|
||||
}
|
||||
if (event.type === "step.ended") {
|
||||
if (event.type === "session.step.ended") {
|
||||
const total =
|
||||
event.data.tokens.input +
|
||||
event.data.tokens.output +
|
||||
|
|
@ -734,13 +766,13 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
})
|
||||
return
|
||||
}
|
||||
if (event.type === "step.failed") {
|
||||
if (event.type === "session.step.failed") {
|
||||
state.errors.add(event.data.assistantMessageID)
|
||||
if (state.wait) state.wait.failureRendered = true
|
||||
write([{ kind: "error", source: "system", text: errorMessage(event.data.error), phase: "start" }])
|
||||
return
|
||||
}
|
||||
if (event.type === "execution.settled") {
|
||||
if (event.type === "session.execution.settled") {
|
||||
write([], { phase: "idle", status: "" })
|
||||
const current = state.wait
|
||||
if (!current || (!current.promoted && !current.interrupted)) return
|
||||
|
|
@ -977,10 +1009,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
sessionID: input.sessionID,
|
||||
id: messageID,
|
||||
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,
|
||||
agents: agents.length ? agents : undefined,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ function prompted(inputID: string): V2Event {
|
|||
return {
|
||||
id: "evt_prompted",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: { aggregateID: "ses_1", seq: 0, version: 1 },
|
||||
data: { sessionID: "ses_1", inputID },
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ function settled(outcome: "success" | "interrupted" = "success"): V2Event {
|
|||
return {
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome },
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ describe("run session data", () => {
|
|||
test("renders direct shell mode from first-class shell events", () => {
|
||||
let data = createSessionData()
|
||||
const started = reduce(data, {
|
||||
type: "shell.started",
|
||||
type: "session.shell.started",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 1,
|
||||
|
|
@ -353,7 +353,7 @@ describe("run session data", () => {
|
|||
|
||||
data = started.data
|
||||
const ended = reduce(data, {
|
||||
type: "shell.ended",
|
||||
type: "session.shell.ended",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 2,
|
||||
|
|
@ -380,7 +380,7 @@ describe("run session data", () => {
|
|||
|
||||
test("suppresses legacy bash part updates once shell events claim the call", () => {
|
||||
let data = reduce(createSessionData(), {
|
||||
type: "shell.started",
|
||||
type: "session.shell.started",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 1,
|
||||
|
|
@ -409,7 +409,7 @@ describe("run session data", () => {
|
|||
).toEqual([])
|
||||
|
||||
data = reduce(data, {
|
||||
type: "shell.ended",
|
||||
type: "session.shell.ended",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 2,
|
||||
|
|
@ -463,7 +463,7 @@ describe("run session data", () => {
|
|||
|
||||
expect(
|
||||
reduce(data, {
|
||||
type: "shell.started",
|
||||
type: "session.shell.started",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 1,
|
||||
|
|
@ -497,7 +497,7 @@ describe("run session data", () => {
|
|||
|
||||
expect(
|
||||
reduce(data, {
|
||||
type: "shell.ended",
|
||||
type: "session.shell.ended",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 2,
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_prompted",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -210,7 +210,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_text",
|
||||
created: 0,
|
||||
type: "text.delta",
|
||||
type: "session.text.delta",
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
|
|
@ -221,7 +221,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
await turn
|
||||
|
|
@ -259,7 +259,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_prompted",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -269,7 +269,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
})
|
||||
|
|
@ -353,7 +353,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_prompted",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -363,7 +363,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
})
|
||||
|
|
@ -450,7 +450,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_prompted",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -460,7 +460,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
})
|
||||
|
|
@ -724,7 +724,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_text",
|
||||
created: 0,
|
||||
type: "text.delta",
|
||||
type: "session.text.delta",
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
|
|
@ -809,7 +809,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_reasoning",
|
||||
created: 0,
|
||||
type: "reasoning.ended",
|
||||
type: "session.reasoning.ended",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -865,7 +865,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
await turn
|
||||
|
|
@ -921,7 +921,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_prompted",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -931,7 +931,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
await turn
|
||||
|
|
@ -981,7 +981,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_prompted",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -993,7 +993,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
await turn
|
||||
|
|
@ -1021,16 +1021,42 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_shell_start",
|
||||
created: 0,
|
||||
type: "shell.started",
|
||||
type: "session.shell.started",
|
||||
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({
|
||||
id: "evt_shell_end",
|
||||
created: 0,
|
||||
type: "shell.ended",
|
||||
type: "session.shell.ended",
|
||||
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
|
||||
|
|
@ -1047,8 +1073,8 @@ describe("V2 mini transport", () => {
|
|||
|
||||
expect(request).toMatchObject({ sessionID: "ses_1", command: "ls" })
|
||||
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" } })
|
||||
await transport.close()
|
||||
|
|
@ -1107,9 +1133,18 @@ describe("V2 mini transport", () => {
|
|||
{
|
||||
id: "msg_shell",
|
||||
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 },
|
||||
},
|
||||
],
|
||||
|
|
@ -1127,15 +1162,29 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_shell_end",
|
||||
created: 0,
|
||||
type: "shell.ended",
|
||||
type: "session.shell.ended",
|
||||
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)
|
||||
|
||||
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" },
|
||||
])
|
||||
await transport.close()
|
||||
|
|
@ -1160,7 +1209,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_prompted",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -1170,7 +1219,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
})
|
||||
|
|
@ -1236,7 +1285,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_skill",
|
||||
created: 0,
|
||||
type: "skill.activated",
|
||||
type: "session.skill.activated",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -1247,7 +1296,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
})
|
||||
|
|
@ -1317,7 +1366,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_unrelated_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
await Bun.sleep(0)
|
||||
|
|
@ -1327,7 +1376,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_skill",
|
||||
created: 0,
|
||||
type: "skill.activated",
|
||||
type: "session.skill.activated",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -1338,7 +1387,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_skill_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
})
|
||||
await turn
|
||||
|
|
@ -1376,7 +1425,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_skill",
|
||||
created: 0,
|
||||
type: "skill.activated",
|
||||
type: "session.skill.activated",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -1438,7 +1487,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_child_step",
|
||||
created: 0,
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable("ses_child"),
|
||||
data: {
|
||||
sessionID: "ses_child",
|
||||
|
|
@ -1456,7 +1505,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_child_text",
|
||||
created: 0,
|
||||
type: "text.delta",
|
||||
type: "session.text.delta",
|
||||
data: {
|
||||
sessionID: "ses_child",
|
||||
assistantMessageID: "msg_child_a",
|
||||
|
|
@ -1470,7 +1519,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_child_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_child", outcome: "success" },
|
||||
})
|
||||
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({
|
||||
id: "evt_child_step",
|
||||
created: 0,
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable("ses_child"),
|
||||
data: {
|
||||
sessionID: "ses_child",
|
||||
|
|
@ -1527,7 +1576,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_child_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_child", outcome: "interrupted" },
|
||||
})
|
||||
await Bun.sleep(0)
|
||||
|
|
@ -1574,7 +1623,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_child_step",
|
||||
created: 0,
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable("ses_child"),
|
||||
data: {
|
||||
sessionID: "ses_child",
|
||||
|
|
@ -1587,7 +1636,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_parent_call",
|
||||
created: 0,
|
||||
type: "tool.called",
|
||||
type: "session.tool.called",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -1601,7 +1650,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_parent_success",
|
||||
created: 0,
|
||||
type: "tool.success",
|
||||
type: "session.tool.success",
|
||||
durable: durable("ses_1", 1),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
|
|
@ -1616,7 +1665,7 @@ describe("V2 mini transport", () => {
|
|||
events.push({
|
||||
id: "evt_child_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_child", outcome: "interrupted" },
|
||||
})
|
||||
while (!states().some((state) => state.tabs.some((tab) => tab.status === "cancelled"))) await Bun.sleep(0)
|
||||
|
|
|
|||
|
|
@ -99,10 +99,10 @@ describe("pty HttpApi bridge", () => {
|
|||
const updated = await app().request(PtyPaths.update.replace(":ptyID", info.id), {
|
||||
method: "PUT",
|
||||
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(await updated.json()).toMatchObject({ id: info.id, title: "renamed" })
|
||||
expect(await updated.json()).toMatchObject({ id: info.id, title: "session.renamed" })
|
||||
} finally {
|
||||
await app().request(PtyPaths.remove.replace(":ptyID", info.id), { method: "DELETE", headers })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -573,7 +573,7 @@ describe("HttpApi SDK", () => {
|
|||
const child = yield* capture(() => sdk.session.create({ title: "child", parentID }))
|
||||
const childID = String(record(child.data).id)
|
||||
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 all = yield* capture(() => sdk.session.list({ roots: false, limit: 10 }))
|
||||
const children = yield* capture(() => sdk.session.children({ sessionID: parentID }))
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ describe("tool.apply_patch freeform", () => {
|
|||
|
||||
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)
|
||||
expect(yield* readText(moved)).toBe("new content\n")
|
||||
}),
|
||||
|
|
@ -282,7 +282,7 @@ describe("tool.apply_patch freeform", () => {
|
|||
const test = yield* TestInstance
|
||||
const { ctx } = makeCtx()
|
||||
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(destination))
|
||||
yield* writeText(original, "from\n")
|
||||
|
|
|
|||
|
|
@ -79593,8 +79593,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"synthetic": {
|
||||
"id": "synthetic",
|
||||
"session.synthetic": {
|
||||
"id": "session.synthetic",
|
||||
"env": ["SYNTHETIC_API_KEY"],
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"api": "https://api.synthetic.new/openai/v1",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ test.skip("step snapshots carry over to assistant messages", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -44,7 +44,7 @@ test.skip("step snapshots carry over to assistant messages", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "step.ended",
|
||||
type: "session.step.ended",
|
||||
durable: durable(sessionID, 1, 2),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -77,7 +77,7 @@ test.skip("text ended populates assistant text content", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -96,7 +96,7 @@ test.skip("text ended populates assistant text content", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "text.started",
|
||||
type: "session.text.started",
|
||||
durable: durable(sessionID, 1),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -110,7 +110,7 @@ test.skip("text ended populates assistant text content", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "text.ended",
|
||||
type: "session.text.ended",
|
||||
durable: durable(sessionID, 2),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -136,7 +136,7 @@ test.skip("tool completion stores completed timestamp", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -155,7 +155,7 @@ test.skip("tool completion stores completed timestamp", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "tool.input.started",
|
||||
type: "session.tool.input.started",
|
||||
durable: durable(sessionID, 1),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -170,7 +170,7 @@ test.skip("tool completion stores completed timestamp", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "tool.called",
|
||||
type: "session.tool.called",
|
||||
durable: durable(sessionID, 2),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -187,7 +187,7 @@ test.skip("tool completion stores completed timestamp", () => {
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "tool.success",
|
||||
type: "session.tool.success",
|
||||
durable: durable(sessionID, 3),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -218,7 +218,7 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id,
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "compaction.started",
|
||||
type: "session.compaction.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -233,7 +233,7 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "compaction.delta",
|
||||
type: "session.compaction.delta",
|
||||
data: {
|
||||
sessionID,
|
||||
text: "hello ",
|
||||
|
|
@ -245,7 +245,7 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "compaction.delta",
|
||||
type: "session.compaction.delta",
|
||||
data: {
|
||||
sessionID,
|
||||
text: "summary",
|
||||
|
|
@ -257,7 +257,7 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: endedID,
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "compaction.ended",
|
||||
type: "session.compaction.ended",
|
||||
durable: durable(sessionID, 1),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { SessionID } from "./session-id.js"
|
|||
import { Location } from "./location.js"
|
||||
import { SessionMessage } from "./session-message.js"
|
||||
import { Revert } from "./revert.js"
|
||||
import { Shell as ShellSchema } from "./shell.js"
|
||||
|
||||
export { FileAttachment }
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ export const UnknownError = SessionMessage.UnknownError
|
|||
export type UnknownError = SessionMessage.UnknownError
|
||||
|
||||
export const AgentSelected = Event.durable({
|
||||
type: "agent.selected",
|
||||
type: "session.agent.selected",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -61,7 +62,7 @@ export const AgentSelected = Event.durable({
|
|||
export type AgentSelected = typeof AgentSelected.Type
|
||||
|
||||
export const ModelSelected = Event.durable({
|
||||
type: "model.selected",
|
||||
type: "session.model.selected",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -82,7 +83,7 @@ export const Moved = Event.durable({
|
|||
export type Moved = typeof Moved.Type
|
||||
|
||||
export const Renamed = Event.durable({
|
||||
type: "renamed",
|
||||
type: "session.renamed",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -92,7 +93,7 @@ export const Renamed = Event.durable({
|
|||
export type Renamed = typeof Renamed.Type
|
||||
|
||||
export const Forked = Event.durable({
|
||||
type: "forked",
|
||||
type: "session.forked",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -103,7 +104,7 @@ export const Forked = Event.durable({
|
|||
export type Forked = typeof Forked.Type
|
||||
|
||||
export const PromptPromoted = Event.durable({
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
...options,
|
||||
schema: {
|
||||
sessionID: SessionID,
|
||||
|
|
@ -113,14 +114,14 @@ export const PromptPromoted = Event.durable({
|
|||
export type PromptPromoted = typeof PromptPromoted.Type
|
||||
|
||||
export const PromptAdmitted = Event.durable({
|
||||
type: "prompt.admitted",
|
||||
type: "session.prompt.admitted",
|
||||
...options,
|
||||
schema: PromptFields,
|
||||
})
|
||||
export type PromptAdmitted = typeof PromptAdmitted.Type
|
||||
|
||||
export const ExecutionSettled = Event.ephemeral({
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
schema: {
|
||||
...Base,
|
||||
outcome: Schema.Literals(["success", "failure", "interrupted"]),
|
||||
|
|
@ -140,7 +141,7 @@ export const ContextUpdated = Event.durable({
|
|||
export type ContextUpdated = typeof ContextUpdated.Type
|
||||
|
||||
export const Synthetic = Event.durable({
|
||||
type: "synthetic",
|
||||
type: "session.synthetic",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -153,7 +154,7 @@ export type Synthetic = typeof Synthetic.Type
|
|||
|
||||
export namespace Skill {
|
||||
export const Activated = Event.durable({
|
||||
type: "skill.activated",
|
||||
type: "session.skill.activated",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -166,23 +167,22 @@ export namespace Skill {
|
|||
|
||||
export namespace Shell {
|
||||
export const Started = Event.durable({
|
||||
type: "shell.started",
|
||||
type: "session.shell.started",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
callID: Schema.String,
|
||||
command: Schema.String,
|
||||
shell: ShellSchema.Info,
|
||||
},
|
||||
})
|
||||
export type Started = typeof Started.Type
|
||||
|
||||
export const Ended = Event.durable({
|
||||
type: "shell.ended",
|
||||
type: "session.shell.ended",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
callID: Schema.String,
|
||||
output: Schema.String,
|
||||
shell: ShellSchema.Info,
|
||||
output: ShellSchema.Output,
|
||||
},
|
||||
})
|
||||
export type Ended = typeof Ended.Type
|
||||
|
|
@ -190,7 +190,7 @@ export namespace Shell {
|
|||
|
||||
export namespace Step {
|
||||
export const Started = Event.durable({
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -203,7 +203,7 @@ export namespace Step {
|
|||
export type Started = typeof Started.Type
|
||||
|
||||
export const Ended = Event.durable({
|
||||
type: "step.ended",
|
||||
type: "session.step.ended",
|
||||
...stepSettlementOptions,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -226,7 +226,7 @@ export namespace Step {
|
|||
export type Ended = typeof Ended.Type
|
||||
|
||||
export const Failed = Event.durable({
|
||||
type: "step.failed",
|
||||
type: "session.step.failed",
|
||||
...stepSettlementOptions,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -239,7 +239,7 @@ export namespace Step {
|
|||
|
||||
export namespace Text {
|
||||
export const Started = Event.durable({
|
||||
type: "text.started",
|
||||
type: "session.text.started",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -251,7 +251,7 @@ export namespace Text {
|
|||
|
||||
// Stream fragments are live-only; Text.Ended is the replayable full-value boundary.
|
||||
export const Delta = Event.ephemeral({
|
||||
type: "text.delta",
|
||||
type: "session.text.delta",
|
||||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
|
|
@ -262,7 +262,7 @@ export namespace Text {
|
|||
export type Delta = typeof Delta.Type
|
||||
|
||||
export const Ended = Event.durable({
|
||||
type: "text.ended",
|
||||
type: "session.text.ended",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -276,7 +276,7 @@ export namespace Text {
|
|||
|
||||
export namespace Reasoning {
|
||||
export const Started = Event.durable({
|
||||
type: "reasoning.started",
|
||||
type: "session.reasoning.started",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -289,7 +289,7 @@ export namespace Reasoning {
|
|||
|
||||
// Stream fragments are live-only; Reasoning.Ended is the replayable full-value boundary.
|
||||
export const Delta = Event.ephemeral({
|
||||
type: "reasoning.delta",
|
||||
type: "session.reasoning.delta",
|
||||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
|
|
@ -300,7 +300,7 @@ export namespace Reasoning {
|
|||
export type Delta = typeof Delta.Type
|
||||
|
||||
export const Ended = Event.durable({
|
||||
type: "reasoning.ended",
|
||||
type: "session.reasoning.ended",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -322,7 +322,7 @@ export namespace Tool {
|
|||
|
||||
export namespace Input {
|
||||
export const Started = Event.durable({
|
||||
type: "tool.input.started",
|
||||
type: "session.tool.input.started",
|
||||
...options,
|
||||
schema: {
|
||||
...ToolBase,
|
||||
|
|
@ -333,7 +333,7 @@ export namespace Tool {
|
|||
|
||||
// Stream fragments are live-only; Input.Ended is the replayable raw-input boundary.
|
||||
export const Delta = Event.ephemeral({
|
||||
type: "tool.input.delta",
|
||||
type: "session.tool.input.delta",
|
||||
schema: {
|
||||
...ToolBase,
|
||||
delta: Schema.String,
|
||||
|
|
@ -342,7 +342,7 @@ export namespace Tool {
|
|||
export type Delta = typeof Delta.Type
|
||||
|
||||
export const Ended = Event.durable({
|
||||
type: "tool.input.ended",
|
||||
type: "session.tool.input.ended",
|
||||
...options,
|
||||
schema: {
|
||||
...ToolBase,
|
||||
|
|
@ -353,7 +353,7 @@ export namespace Tool {
|
|||
}
|
||||
|
||||
export const Called = Event.durable({
|
||||
type: "tool.called",
|
||||
type: "session.tool.called",
|
||||
...options,
|
||||
schema: {
|
||||
...ToolBase,
|
||||
|
|
@ -372,7 +372,7 @@ export namespace Tool {
|
|||
* transitions or at a bounded cadence, not persist every stdout/stderr chunk.
|
||||
*/
|
||||
export const Progress = Event.durable({
|
||||
type: "tool.progress",
|
||||
type: "session.tool.progress",
|
||||
...options,
|
||||
schema: {
|
||||
...ToolBase,
|
||||
|
|
@ -383,7 +383,7 @@ export namespace Tool {
|
|||
export type Progress = typeof Progress.Type
|
||||
|
||||
export const Success = Event.durable({
|
||||
type: "tool.success",
|
||||
type: "session.tool.success",
|
||||
...options,
|
||||
schema: {
|
||||
...ToolBase,
|
||||
|
|
@ -400,7 +400,7 @@ export namespace Tool {
|
|||
export type Success = typeof Success.Type
|
||||
|
||||
export const Failed = Event.durable({
|
||||
type: "tool.failed",
|
||||
type: "session.tool.failed",
|
||||
...options,
|
||||
schema: {
|
||||
...ToolBase,
|
||||
|
|
@ -428,7 +428,7 @@ export const RetryError = Schema.Struct({
|
|||
export interface RetryError extends Schema.Schema.Type<typeof RetryError> {}
|
||||
|
||||
export const Retried = Event.durable({
|
||||
type: "retried",
|
||||
type: "session.retried",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -440,7 +440,7 @@ export type Retried = typeof Retried.Type
|
|||
|
||||
export namespace Compaction {
|
||||
export const Started = Event.durable({
|
||||
type: "compaction.started",
|
||||
type: "session.compaction.started",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -450,7 +450,7 @@ export namespace Compaction {
|
|||
export type Started = typeof Started.Type
|
||||
|
||||
export const Delta = Event.ephemeral({
|
||||
type: "compaction.delta",
|
||||
type: "session.compaction.delta",
|
||||
schema: {
|
||||
...Base,
|
||||
text: Schema.String,
|
||||
|
|
@ -459,7 +459,7 @@ export namespace Compaction {
|
|||
export type Delta = typeof Delta.Type
|
||||
|
||||
export const Ended = Event.durable({
|
||||
type: "compaction.ended",
|
||||
type: "session.compaction.ended",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
|
|
@ -473,13 +473,13 @@ export namespace Compaction {
|
|||
|
||||
export namespace RevertEvent {
|
||||
export const Staged = Event.durable({
|
||||
type: "revert.staged",
|
||||
type: "session.revert.staged",
|
||||
...options,
|
||||
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({
|
||||
type: "revert.committed",
|
||||
type: "session.revert.committed",
|
||||
...options,
|
||||
schema: { ...Base, messageID: SessionMessage.ID },
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { DateTimeUtcFromMillis, RelativePath, statics } from "./schema.js"
|
|||
import { SessionID } from "./session-id.js"
|
||||
import { ascending } from "./identifier.js"
|
||||
import { Event } from "./event.js"
|
||||
import { Shell as ShellSchema } from "./shell.js"
|
||||
|
||||
export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
|
||||
Schema.brand("Session.Message.ID"),
|
||||
|
|
@ -82,9 +83,8 @@ export interface Shell extends Schema.Schema.Type<typeof Shell> {}
|
|||
export const Shell = Schema.Struct({
|
||||
...Base,
|
||||
type: Schema.Literal("shell"),
|
||||
callID: Schema.String,
|
||||
command: Schema.String,
|
||||
output: Schema.String,
|
||||
shell: ShellSchema.Info,
|
||||
output: ShellSchema.Output.pipe(optional),
|
||||
time: Schema.Struct({
|
||||
created: DateTimeUtcFromMillis,
|
||||
completed: DateTimeUtcFromMillis.pipe(optional),
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ describe("public event manifest", () => {
|
|||
expect(Session.Event.Definitions).toBe(SessionEvent.Definitions)
|
||||
expect(Workspace.Event).toBe(WorkspaceEvent)
|
||||
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("agent.updated")).toBe(Agent.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.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", () => {
|
||||
|
|
@ -88,37 +88,37 @@ describe("public event manifest", () => {
|
|||
"message.removed.1",
|
||||
"message.part.updated.1",
|
||||
"message.part.removed.1",
|
||||
"agent.selected.1",
|
||||
"model.selected.1",
|
||||
"session.agent.selected.1",
|
||||
"session.model.selected.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",
|
||||
"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(),
|
||||
)
|
||||
expect(SessionEvent.DurableDefinitions).toEqual(
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ it.live(
|
|||
prompt: fixture.sdk.Prompt.make({ text: "Promote this input" }),
|
||||
})
|
||||
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,
|
||||
Effect.timeout("10 seconds"),
|
||||
Effect.map(Option.getOrThrow),
|
||||
|
|
@ -119,7 +119,7 @@ it.live(
|
|||
expect(page.data.some((session) => session.id === id)).toBe(true)
|
||||
expect(active).toEqual({ data: {}, watermarks: {} })
|
||||
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(contextEntries).toEqual([
|
||||
{ key: "deploy-target", value: "production" },
|
||||
|
|
@ -127,7 +127,7 @@ it.live(
|
|||
])
|
||||
expect(remainingContextEntries).toEqual([{ key: "deploy-target", value: "production" }])
|
||||
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(missing.map((error) => error._tag)).toEqual([
|
||||
"SessionNotFoundError",
|
||||
|
|
@ -149,13 +149,13 @@ it.live(
|
|||
const opencode = yield* fixture.sdk.OpenCode.create()
|
||||
const id = sessionID(fixture)
|
||||
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(
|
||||
Stream.runForEach((event) =>
|
||||
event.type === "server.connected"
|
||||
? 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)
|
||||
: Effect.void,
|
||||
),
|
||||
|
|
@ -191,7 +191,7 @@ it.live(
|
|||
Stream.runForEach((notification: OpenCodeEvent) =>
|
||||
notification.type === "server.connected"
|
||||
? ready.open
|
||||
: notification.type === "agent.selected" && notification.data.sessionID === id
|
||||
: notification.type === "session.agent.selected" && notification.data.sessionID === id
|
||||
? event.open
|
||||
: Effect.void,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ if (schemas) {
|
|||
visit({ ...document, components: { ...document.components, schemas: undefined } })
|
||||
for (const name of Object.keys(schemas)) {
|
||||
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,
|
||||
) &&
|
||||
!reachable.has(name)
|
||||
|
|
@ -100,7 +100,7 @@ await createClient({
|
|||
const generatedTypesPath = "./src/v2/gen/types.gen.ts"
|
||||
const generatedTypes = await Bun.file(generatedTypesPath).text()
|
||||
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,
|
||||
)
|
||||
) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -16410,7 +16410,7 @@
|
|||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"synthetic": {
|
||||
"session.synthetic": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ignored": {
|
||||
|
|
@ -23071,7 +23071,7 @@
|
|||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"synthetic": {
|
||||
"session.synthetic": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ignored": {
|
||||
|
|
@ -27479,7 +27479,7 @@
|
|||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["synthetic"]
|
||||
"enum": ["session.synthetic"]
|
||||
}
|
||||
},
|
||||
"required": ["id", "time", "sessionID", "text", "type"],
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
const item = position === undefined ? undefined : messages[position]
|
||||
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
|
||||
},
|
||||
latestTool(assistant: SessionMessageAssistant | undefined, callID?: string) {
|
||||
|
|
@ -220,7 +220,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
case "skill.updated":
|
||||
void result.location.skill.refresh(event.location)
|
||||
break
|
||||
case "agent.selected":
|
||||
case "session.agent.selected":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
setStore("session", "info", event.data.sessionID, "agent", event.data.agent)
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
|
|
@ -232,7 +232,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
})
|
||||
break
|
||||
case "model.selected":
|
||||
case "session.model.selected":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
setStore("session", "info", event.data.sessionID, "model", event.data.model)
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
|
|
@ -244,11 +244,11 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
})
|
||||
break
|
||||
case "renamed":
|
||||
case "session.renamed":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
setStore("session", "info", event.data.sessionID, "title", event.data.title)
|
||||
break
|
||||
case "prompt.promoted": {
|
||||
case "session.prompt.promoted": {
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const position = index.get(event.data.inputID)
|
||||
|
|
@ -264,7 +264,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
break
|
||||
}
|
||||
case "prompt.admitted":
|
||||
case "session.prompt.admitted":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.append(draft, index, {
|
||||
id: event.data.inputID,
|
||||
|
|
@ -287,7 +287,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
})
|
||||
break
|
||||
case "synthetic":
|
||||
case "session.synthetic":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.append(draft, index, {
|
||||
id: messageIDFromEvent(event.id),
|
||||
|
|
@ -299,29 +299,28 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
})
|
||||
break
|
||||
case "shell.started":
|
||||
case "session.shell.started":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.append(draft, index, {
|
||||
id: messageIDFromEvent(event.id),
|
||||
type: "shell",
|
||||
callID: event.data.callID,
|
||||
command: event.data.command,
|
||||
output: "",
|
||||
shell: event.data.shell,
|
||||
time: { created: event.created },
|
||||
})
|
||||
})
|
||||
break
|
||||
case "shell.ended":
|
||||
case "session.shell.ended":
|
||||
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
|
||||
match.shell = event.data.shell
|
||||
match.output = event.data.output
|
||||
match.time.completed = event.created
|
||||
})
|
||||
break
|
||||
case "step.started":
|
||||
case "session.step.started":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
if (index.has(event.data.assistantMessageID)) return
|
||||
|
|
@ -338,7 +337,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
})
|
||||
break
|
||||
case "step.ended":
|
||||
case "session.step.ended":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
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 }
|
||||
})
|
||||
break
|
||||
case "step.failed":
|
||||
case "session.step.failed":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID)
|
||||
if (!currentAssistant) return
|
||||
|
|
@ -360,7 +359,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
currentAssistant.error = event.data.error
|
||||
})
|
||||
break
|
||||
case "text.started":
|
||||
case "session.text.started":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
|
||||
type: "text",
|
||||
|
|
@ -369,7 +368,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
})
|
||||
break
|
||||
case "text.delta":
|
||||
case "session.text.delta":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestText(
|
||||
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
|
||||
})
|
||||
break
|
||||
case "text.ended":
|
||||
case "session.text.ended":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestText(
|
||||
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
|
||||
})
|
||||
break
|
||||
case "tool.input.started":
|
||||
case "session.tool.input.started":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
|
||||
type: "tool",
|
||||
|
|
@ -398,7 +397,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
})
|
||||
break
|
||||
case "tool.input.delta":
|
||||
case "session.tool.input.delta":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestTool(
|
||||
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
|
||||
})
|
||||
break
|
||||
case "tool.input.ended":
|
||||
case "session.tool.input.ended":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestTool(
|
||||
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
|
||||
})
|
||||
break
|
||||
case "tool.called":
|
||||
case "session.tool.called":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestTool(
|
||||
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: [] }
|
||||
})
|
||||
break
|
||||
case "tool.progress":
|
||||
case "session.tool.progress":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestTool(
|
||||
message.assistant(draft, index, event.data.assistantMessageID),
|
||||
|
|
@ -439,7 +438,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
match.state.content = [...event.data.content]
|
||||
})
|
||||
break
|
||||
case "tool.success":
|
||||
case "session.tool.success":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestTool(
|
||||
message.assistant(draft, index, event.data.assistantMessageID),
|
||||
|
|
@ -461,7 +460,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
match.time.completed = event.created
|
||||
})
|
||||
break
|
||||
case "tool.failed":
|
||||
case "session.tool.failed":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestTool(
|
||||
message.assistant(draft, index, event.data.assistantMessageID),
|
||||
|
|
@ -484,7 +483,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
match.time.completed = event.created
|
||||
})
|
||||
break
|
||||
case "reasoning.started":
|
||||
case "session.reasoning.started":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
|
||||
type: "reasoning",
|
||||
|
|
@ -495,7 +494,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
})
|
||||
break
|
||||
case "reasoning.delta":
|
||||
case "session.reasoning.delta":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestReasoning(
|
||||
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
|
||||
})
|
||||
break
|
||||
case "reasoning.ended":
|
||||
case "session.reasoning.ended":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestReasoning(
|
||||
message.assistant(draft, index, event.data.assistantMessageID),
|
||||
|
|
@ -517,25 +516,25 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
}
|
||||
})
|
||||
break
|
||||
case "retried":
|
||||
case "compaction.started":
|
||||
case "session.retried":
|
||||
case "session.compaction.started":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
break
|
||||
case "execution.settled":
|
||||
case "session.execution.settled":
|
||||
setStore("session", "status", event.data.sessionID, "idle")
|
||||
break
|
||||
case "revert.staged":
|
||||
case "session.revert.staged":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
setStore("session", "info", event.data.sessionID, "revert", event.data.revert)
|
||||
break
|
||||
case "revert.cleared":
|
||||
case "revert.committed":
|
||||
case "session.revert.cleared":
|
||||
case "session.revert.committed":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
setStore("session", "info", event.data.sessionID, "revert", undefined)
|
||||
break
|
||||
case "compaction.delta":
|
||||
case "session.compaction.delta":
|
||||
break
|
||||
case "compaction.ended":
|
||||
case "session.compaction.ended":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.append(draft, index, {
|
||||
id: messageIDFromEvent(event.id),
|
||||
|
|
|
|||
|
|
@ -74,17 +74,17 @@ const tui: TuiPlugin = async (api) => {
|
|||
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
|
||||
ended(event.data.sessionID)
|
||||
})
|
||||
api.event.on("step.failed", (event) => {
|
||||
api.event.on("session.step.failed", (event) => {
|
||||
const sessionID = event.data.sessionID
|
||||
if (!active.has(sessionID)) return
|
||||
errored.add(sessionID)
|
||||
|
|
|
|||
|
|
@ -1346,7 +1346,7 @@ function RevertMessage(props: {
|
|||
|
||||
function ShellMessage(props: { message: Extract<SessionMessage, { type: "shell" }> }) {
|
||||
const { theme } = useTheme()
|
||||
const output = createMemo(() => stripAnsi(props.message.output.trim()))
|
||||
const output = createMemo(() => stripAnsi(props.message.output?.output.trim() ?? ""))
|
||||
|
||||
return (
|
||||
<box
|
||||
|
|
@ -1359,7 +1359,7 @@ function ShellMessage(props: { message: Extract<SessionMessage, { type: "shell"
|
|||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={theme.background}
|
||||
>
|
||||
<text fg={theme.text}>$ {props.message.command}</text>
|
||||
<text fg={theme.text}>$ {props.message.shell.command}</text>
|
||||
<Show when={output()}>
|
||||
<text fg={theme.textMuted}>{output()}</text>
|
||||
</Show>
|
||||
|
|
@ -1408,13 +1408,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
|||
>
|
||||
<text fg={theme.text}>{props.message.text}</text>
|
||||
<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()}>
|
||||
{(file) => {
|
||||
const directory = file.mime === "application/x-directory"
|
||||
|
|
@ -1744,7 +1738,8 @@ function ToolPart(props: { part: SessionMessageAssistantTool }) {
|
|||
return Boolean(shellID && data.shell.get(shellID))
|
||||
}
|
||||
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 false
|
||||
|
|
@ -2660,7 +2655,8 @@ function formatSessionTranscript(
|
|||
) {
|
||||
const body = messages.flatMap((message) => {
|
||||
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 []
|
||||
const content = message.content.flatMap((item) => {
|
||||
if (item.type === "text") return [item.text]
|
||||
|
|
|
|||
|
|
@ -133,42 +133,42 @@ export function createSessionRows(sessionID: Accessor<string>) {
|
|||
if (event.data.sessionID === sessionID()) appendMessage(event.data.inputID)
|
||||
}
|
||||
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("synthetic", (event) => {
|
||||
data.on("session.synthetic", (event) => {
|
||||
if (event.data.sessionID === sessionID() && event.data.description?.trim())
|
||||
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())
|
||||
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())
|
||||
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())
|
||||
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())
|
||||
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())
|
||||
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
|
||||
appendFooter(event.data.assistantMessageID)
|
||||
}),
|
||||
data.on("step.failed", (event) => {
|
||||
data.on("session.step.failed", (event) => {
|
||||
if (event.data.sessionID === sessionID()) appendFooter(event.data.assistantMessageID)
|
||||
}),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ function stepStarted(id: string, sessionID = "session"): V2Event {
|
|||
return {
|
||||
id,
|
||||
created: 0,
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -106,7 +106,7 @@ function stepEnded(id: string, sessionID = "session", finish = "stop"): V2Event
|
|||
return {
|
||||
id,
|
||||
created: 0,
|
||||
type: "step.ended",
|
||||
type: "session.step.ended",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -122,7 +122,7 @@ function stepFailed(id: string, sessionID = "session"): V2Event {
|
|||
return {
|
||||
id,
|
||||
created: 0,
|
||||
type: "step.failed",
|
||||
type: "session.step.failed",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ test("tracks session status from active sessions and execution events", async ()
|
|||
emitEvent(events, {
|
||||
id: "evt_step_started",
|
||||
created: 0,
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable("session-live"),
|
||||
data: {
|
||||
sessionID: "session-live",
|
||||
|
|
@ -283,7 +283,7 @@ test("tracks session status from active sessions and execution events", async ()
|
|||
emitEvent(events, {
|
||||
id: "evt_step_ended",
|
||||
created: 0,
|
||||
type: "step.ended",
|
||||
type: "session.step.ended",
|
||||
durable: durable("session-live", 1, 2),
|
||||
data: {
|
||||
sessionID: "session-live",
|
||||
|
|
@ -302,7 +302,7 @@ test("tracks session status from active sessions and execution events", async ()
|
|||
emitEvent(events, {
|
||||
id: "evt_execution_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: {
|
||||
sessionID: "session-live",
|
||||
outcome: "success",
|
||||
|
|
@ -313,7 +313,7 @@ test("tracks session status from active sessions and execution events", async ()
|
|||
emitEvent(events, {
|
||||
id: "evt_failed_step_started",
|
||||
created: 0,
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable("session-failed"),
|
||||
data: {
|
||||
sessionID: "session-failed",
|
||||
|
|
@ -327,7 +327,7 @@ test("tracks session status from active sessions and execution events", async ()
|
|||
emitEvent(events, {
|
||||
id: "evt_step_failed",
|
||||
created: 0,
|
||||
type: "step.failed",
|
||||
type: "session.step.failed",
|
||||
durable: durable("session-failed", 1, 2),
|
||||
data: {
|
||||
sessionID: "session-failed",
|
||||
|
|
@ -344,7 +344,7 @@ test("tracks session status from active sessions and execution events", async ()
|
|||
emitEvent(events, {
|
||||
id: "evt_failed_execution_settled",
|
||||
created: 0,
|
||||
type: "execution.settled",
|
||||
type: "session.execution.settled",
|
||||
data: {
|
||||
sessionID: "session-failed",
|
||||
outcome: "failure",
|
||||
|
|
@ -797,14 +797,14 @@ test("settles pending tools when a live failure arrives", async () => {
|
|||
emitEvent(events, {
|
||||
id: "evt_agent_1",
|
||||
created: 0,
|
||||
type: "agent.selected",
|
||||
type: "session.agent.selected",
|
||||
durable: durable("session-1"),
|
||||
data: { sessionID: "session-1", agent: "build" },
|
||||
})
|
||||
emitEvent(events, {
|
||||
id: "evt_model_1",
|
||||
created: 0,
|
||||
type: "model.selected",
|
||||
type: "session.model.selected",
|
||||
durable: durable("session-1", 1),
|
||||
data: {
|
||||
sessionID: "session-1",
|
||||
|
|
@ -814,7 +814,7 @@ test("settles pending tools when a live failure arrives", async () => {
|
|||
emitEvent(events, {
|
||||
id: "evt_step_started_1",
|
||||
created: 0,
|
||||
type: "step.started",
|
||||
type: "session.step.started",
|
||||
durable: durable("session-1", 2),
|
||||
data: {
|
||||
sessionID: "session-1",
|
||||
|
|
@ -826,7 +826,7 @@ test("settles pending tools when a live failure arrives", async () => {
|
|||
emitEvent(events, {
|
||||
id: "evt_input_1",
|
||||
created: 0,
|
||||
type: "tool.input.started",
|
||||
type: "session.tool.input.started",
|
||||
durable: durable("session-1", 3),
|
||||
data: {
|
||||
sessionID: "session-1",
|
||||
|
|
@ -838,7 +838,7 @@ test("settles pending tools when a live failure arrives", async () => {
|
|||
emitEvent(events, {
|
||||
id: "evt_called_1",
|
||||
created: 0,
|
||||
type: "tool.called",
|
||||
type: "session.tool.called",
|
||||
durable: durable("session-1", 4),
|
||||
data: {
|
||||
sessionID: "session-1",
|
||||
|
|
@ -852,7 +852,7 @@ test("settles pending tools when a live failure arrives", async () => {
|
|||
emitEvent(events, {
|
||||
id: "evt_failed_1",
|
||||
created: 0,
|
||||
type: "tool.failed",
|
||||
type: "session.tool.failed",
|
||||
durable: durable("session-1", 5),
|
||||
data: {
|
||||
sessionID: "session-1",
|
||||
|
|
@ -942,7 +942,7 @@ test("renders admitted prompts immediately with queued marker and clears when pr
|
|||
emitEvent(events, {
|
||||
id: "evt_admitted_1",
|
||||
created: 0,
|
||||
type: "prompt.admitted",
|
||||
type: "session.prompt.admitted",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
|
|
@ -961,7 +961,7 @@ test("renders admitted prompts immediately with queued marker and clears when pr
|
|||
emitEvent(events, {
|
||||
id: "evt_prompted_1",
|
||||
created: 0,
|
||||
type: "prompt.promoted",
|
||||
type: "session.prompt.promoted",
|
||||
durable: durable(sessionID, 1),
|
||||
data: {
|
||||
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()
|
||||
const message = sync.session.message.list(sessionID)?.[0]
|
||||
expect(message?.type).toBe("user")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export type ProviderIconProps = JSX.SVGElementTags["svg"] & {
|
|||
|
||||
export const ProviderIcon: Component<ProviderIconProps> = (props) => {
|
||||
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 (
|
||||
<svg
|
||||
data-component="provider-icon"
|
||||
|
|
|
|||
|
|
@ -908,7 +908,7 @@ Compatibility:
|
|||
## 2026-07-03: Normalize Session Event Names And Envelope Time
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
|
|
@ -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.
|
||||
- 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.
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -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.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.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue