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
|
|
@ -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,
|
||||
}),
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue