refactor(schema): rename V2 session events and normalize payloads (#35217)
This commit is contained in:
parent
4790a2772c
commit
394e0b9045
63 changed files with 1888 additions and 1989 deletions
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"id": "22e57fed-b9b8-4e94-a3b4-f94bece680a8",
|
||||
"id": "96e9fe64-d810-4102-8f79-3317a88bb6d2",
|
||||
"prevIds": [
|
||||
"f14a9b18-8207-487e-a3d3-227e629ba9ad"
|
||||
"22e57fed-b9b8-4e94-a3b4-f94bece680a8"
|
||||
],
|
||||
"ddl": [
|
||||
{
|
||||
|
|
@ -526,6 +526,16 @@
|
|||
"entityType": "columns",
|
||||
"table": "event"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "created",
|
||||
"entityType": "columns",
|
||||
"table": "event"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
|
|
|
|||
|
|
@ -106,8 +106,7 @@ const layer = Layer.effect(
|
|||
yield* events.publish(SessionEvent.Moved, {
|
||||
sessionID: input.sessionID,
|
||||
location: Location.Ref.make({ directory }),
|
||||
subdirectory: RelativePath.make(path.relative(destination.directory, directory).replaceAll("\\", "/")),
|
||||
timestamp: yield* DateTime.now,
|
||||
subpath: RelativePath.make(path.relative(destination.directory, directory).replaceAll("\\", "/")),
|
||||
})
|
||||
|
||||
if (patch) {
|
||||
|
|
|
|||
2
packages/core/src/database/migration.gen.ts
generated
2
packages/core/src/database/migration.gen.ts
generated
|
|
@ -41,5 +41,7 @@ export const migrations = (
|
|||
import("./migration/20260622170816_reset_v2_session_state"),
|
||||
import("./migration/20260622202450_simplify_session_input"),
|
||||
import("./migration/20260702134641_add_session_context_entry"),
|
||||
import("./migration/20260703090000_reset_v2_event_rename_sweep"),
|
||||
import("./migration/20260703181610_event_created_column"),
|
||||
])
|
||||
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import { Effect } from "effect"
|
||||
import type { DatabaseMigration } from "../migration"
|
||||
|
||||
export default {
|
||||
id: "20260703090000_reset_v2_event_rename_sweep",
|
||||
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\`;`)
|
||||
// `created` column is added by the generated 20260703181610_event_created_column
|
||||
// migration, which runs after this wipe (NOT NULL without default is safe on the
|
||||
// emptied table).
|
||||
})
|
||||
},
|
||||
} satisfies DatabaseMigration.Migration
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Effect } from "effect"
|
||||
import type { DatabaseMigration } from "../migration"
|
||||
|
||||
export default {
|
||||
id: "20260703181610_event_created_column",
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
yield* tx.run(`ALTER TABLE \`event\` ADD \`created\` integer NOT NULL;`)
|
||||
})
|
||||
},
|
||||
} satisfies DatabaseMigration.Migration
|
||||
|
|
@ -81,6 +81,7 @@ export default {
|
|||
\`id\` text PRIMARY KEY,
|
||||
\`aggregate_id\` text NOT NULL,
|
||||
\`seq\` integer NOT NULL,
|
||||
\`created\` integer NOT NULL,
|
||||
\`type\` text NOT NULL,
|
||||
\`data\` text NOT NULL,
|
||||
CONSTRAINT \`fk_event_aggregate_id_event_sequence_aggregate_id_fk\` FOREIGN KEY (\`aggregate_id\`) REFERENCES \`event_sequence\`(\`aggregate_id\`) ON DELETE CASCADE
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as EventV2 from "./event"
|
||||
|
||||
import { Cause, Context, Effect, Layer, Option, PubSub, Queue, Schema, Stream } from "effect"
|
||||
import { Cause, Context, DateTime, Effect, Layer, Option, PubSub, Queue, Schema, Stream } from "effect"
|
||||
import { Event } from "@opencode-ai/schema/event"
|
||||
import type { Data, Definition, Payload } from "@opencode-ai/schema/event"
|
||||
import type { EventLog } from "@opencode-ai/schema/event-log"
|
||||
|
|
@ -55,6 +55,7 @@ export const reserveSequence = Effect.fn("EventV2.reserveSequence")(function* (
|
|||
export type SerializedEvent = {
|
||||
readonly id: ID
|
||||
readonly type: string
|
||||
readonly created?: DateTime.Utc
|
||||
readonly seq: number
|
||||
readonly aggregateID: string
|
||||
readonly data: Record<string, unknown>
|
||||
|
|
@ -81,6 +82,7 @@ const decodeSerializedEvent = (event: SerializedEvent): Payload => {
|
|||
}
|
||||
return {
|
||||
id: event.id,
|
||||
created: event.created ?? DateTime.makeUnsafe(0),
|
||||
type: definition.type,
|
||||
durable: envelope(event.aggregateID, event.seq, definition.durable.version),
|
||||
data: Schema.decodeUnknownSync(definition.data)(event.data),
|
||||
|
|
@ -295,6 +297,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||
if (
|
||||
stored?.id === event.id &&
|
||||
stored.type === versionedType(definition.type, durable.version) &&
|
||||
stored.created === DateTime.toEpochMillis(event.created ?? DateTime.makeUnsafe(0)) &&
|
||||
isDeepStrictEqual(stored.data, encoded)
|
||||
) {
|
||||
if (input.ownerID && row?.ownerID == null) {
|
||||
|
|
@ -366,6 +369,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||
id: event.id,
|
||||
aggregate_id: aggregateID,
|
||||
seq,
|
||||
created: DateTime.toEpochMillis(event.created ?? DateTime.makeUnsafe(0)),
|
||||
type: versionedType(definition.type, durable.version),
|
||||
data: encoded,
|
||||
},
|
||||
|
|
@ -471,6 +475,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||
definition,
|
||||
{
|
||||
id: options?.id ?? ID.create(),
|
||||
created: yield* DateTime.now,
|
||||
...(options?.metadata ? { metadata: options.metadata } : {}),
|
||||
type: definition.type,
|
||||
...(location ? { location } : {}),
|
||||
|
|
@ -494,6 +499,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||
} else {
|
||||
const payload = {
|
||||
id: event.id,
|
||||
created: event.created ?? DateTime.makeUnsafe(0),
|
||||
type: definition.type,
|
||||
data: Schema.decodeUnknownSync(definition.data)(event.data),
|
||||
} as Payload
|
||||
|
|
@ -610,6 +616,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||
return [
|
||||
decodeSerializedEvent({
|
||||
id: event.id,
|
||||
created: DateTime.makeUnsafe(event.created),
|
||||
aggregateID: event.aggregate_id,
|
||||
seq: event.seq,
|
||||
type: event.type,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const EventTable = sqliteTable(
|
|||
.notNull()
|
||||
.references(() => EventSequenceTable.aggregate_id, { onDelete: "cascade" }),
|
||||
seq: integer().notNull(),
|
||||
created: integer().notNull(),
|
||||
type: text().notNull(),
|
||||
data: text({ mode: "json" }).$type<Record<string, unknown>>().notNull(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -363,8 +363,7 @@ const layer = Layer.effect(
|
|||
yield* events.publish(SessionEvent.Forked, {
|
||||
sessionID,
|
||||
parentID: parent.id,
|
||||
messageID: input.messageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
from: input.messageID,
|
||||
})
|
||||
return yield* result.get(sessionID).pipe(Effect.orDie)
|
||||
}),
|
||||
|
|
@ -551,16 +550,13 @@ const layer = Layer.effect(
|
|||
Effect.gen(function* () {
|
||||
activeShells.add(input.sessionID)
|
||||
if ((yield* execution.active).has(input.sessionID)) yield* execution.awaitIdle(input.sessionID)
|
||||
const messageID = SessionMessage.ID.create()
|
||||
const callID = Identifier.ascending()
|
||||
yield* events.publish(
|
||||
SessionEvent.Shell.Started,
|
||||
{
|
||||
sessionID: input.sessionID,
|
||||
messageID,
|
||||
callID,
|
||||
command: input.command,
|
||||
timestamp: yield* DateTime.now,
|
||||
},
|
||||
{ id: input.id },
|
||||
)
|
||||
|
|
@ -571,7 +567,6 @@ const layer = Layer.effect(
|
|||
sessionID: input.sessionID,
|
||||
callID,
|
||||
output,
|
||||
timestamp: yield* DateTime.now,
|
||||
})
|
||||
}).pipe(
|
||||
Effect.ensuring(
|
||||
|
|
@ -590,8 +585,6 @@ const layer = Layer.effect(
|
|||
if (!skill) return yield* new SkillNotFoundError({ skill: input.skill })
|
||||
yield* events.publish(SessionEvent.Skill.Activated, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.id ?? SessionMessage.ID.create(),
|
||||
timestamp: yield* DateTime.now,
|
||||
name: skill.name,
|
||||
text: skill.content,
|
||||
})
|
||||
|
|
@ -602,10 +595,8 @@ const layer = Layer.effect(
|
|||
}),
|
||||
switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
|
||||
yield* result.get(input.sessionID)
|
||||
yield* events.publish(SessionEvent.AgentSwitched, {
|
||||
yield* events.publish(SessionEvent.AgentSelected, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: yield* DateTime.now,
|
||||
agent: input.agent,
|
||||
})
|
||||
}),
|
||||
|
|
@ -617,10 +608,8 @@ const layer = Layer.effect(
|
|||
(session.model.variant ?? "default") === (input.model.variant ?? "default")
|
||||
)
|
||||
return
|
||||
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||
yield* events.publish(SessionEvent.ModelSelected, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: yield* DateTime.now,
|
||||
model: input.model,
|
||||
})
|
||||
}),
|
||||
|
|
@ -628,7 +617,6 @@ const layer = Layer.effect(
|
|||
yield* result.get(input.sessionID)
|
||||
yield* events.publish(SessionEvent.Renamed, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
title: input.title,
|
||||
})
|
||||
}),
|
||||
|
|
@ -676,8 +664,6 @@ const layer = Layer.effect(
|
|||
yield* result.get(input.sessionID)
|
||||
yield* events.publish(SessionEvent.Synthetic, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: yield* DateTime.now,
|
||||
text: input.text,
|
||||
description: input.description,
|
||||
metadata: input.metadata,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { EventV2 } from "../event"
|
|||
import { makeLocationNode } from "../effect/app-node"
|
||||
import { llmClient } from "../effect/app-node-platform"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionMessage } from "./message"
|
||||
import type { SessionMessage } from "./message"
|
||||
import { SessionRunnerModel } from "./runner/model"
|
||||
import { SessionSchema } from "./schema"
|
||||
import { Token } from "../util/token"
|
||||
|
|
@ -206,11 +206,8 @@ const make = (dependencies: Dependencies) => {
|
|||
const summaryPrompt = buildPrompt({ previousSummary: input.previousSummary, context: input.context })
|
||||
const summaryOutput = Math.min(output || SUMMARY_OUTPUT_TOKENS, SUMMARY_OUTPUT_TOKENS)
|
||||
if (Token.estimate(summaryPrompt) > context - summaryOutput) return false
|
||||
const messageID = SessionMessage.ID.create()
|
||||
yield* dependencies.events.publish(SessionEvent.Compaction.Started, {
|
||||
sessionID: input.sessionID,
|
||||
messageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
reason: input.reason,
|
||||
})
|
||||
|
||||
|
|
@ -238,8 +235,6 @@ const make = (dependencies: Dependencies) => {
|
|||
if (!summarized || failed || !summary.trim()) return false
|
||||
yield* dependencies.events.publish(SessionEvent.Compaction.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
messageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
reason: input.reason,
|
||||
text: summary,
|
||||
recent: input.recent,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
export * as SessionContextCheckpoint from "./context-checkpoint"
|
||||
|
||||
import { eq } from "drizzle-orm"
|
||||
import { DateTime, Effect, Option, Schema } from "effect"
|
||||
import { Effect, Option, Schema } from "effect"
|
||||
import type { Database } from "../database/database"
|
||||
import { EventV2 } from "../event"
|
||||
import { SystemContext } from "../system-context/index"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionHistory } from "./history"
|
||||
import { SessionMessage } from "./message"
|
||||
import { SessionSchema } from "./schema"
|
||||
import { SessionContextCheckpointTable } from "./sql"
|
||||
|
||||
|
|
@ -50,7 +49,7 @@ export const prepare = Effect.fn("SessionContextCheckpoint.prepare")(function* (
|
|||
|
||||
yield* events.publish(
|
||||
SessionEvent.ContextUpdated,
|
||||
{ sessionID, messageID: SessionMessage.ID.create(), timestamp: yield* DateTime.now, text: result.text },
|
||||
{ sessionID, text: result.text },
|
||||
{ commit: () => advance(db, sessionID, result.applied).pipe(Effect.orDie) },
|
||||
)
|
||||
return { baseline: stored.baseline, baselineSeq: stored.baseline_seq }
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ const layer = Layer.effect(
|
|||
Exit.isFailure(exit) && !Cause.hasInterrupts(exit.cause) ? Cause.squash(exit.cause) : undefined
|
||||
yield* events.publish(SessionEvent.ExecutionSettled, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
outcome: Exit.isSuccess(exit) ? "success" : Cause.hasInterrupts(exit.cause) ? "interrupted" : "failure",
|
||||
error:
|
||||
failure !== undefined
|
||||
|
|
|
|||
|
|
@ -50,12 +50,10 @@ export const admit = Effect.fn("SessionInput.admit")(function* (
|
|||
) {
|
||||
const existing = yield* find(db, input.id)
|
||||
if (existing !== undefined) return existing
|
||||
const timestamp = yield* DateTime.now
|
||||
return yield* events
|
||||
.publish(SessionEvent.PromptAdmitted, {
|
||||
messageID: input.id,
|
||||
inputID: input.id,
|
||||
sessionID: input.sessionID,
|
||||
timestamp,
|
||||
prompt: input.prompt,
|
||||
delivery: input.delivery,
|
||||
})
|
||||
|
|
@ -70,7 +68,7 @@ export const admit = Effect.fn("SessionInput.admit")(function* (
|
|||
sessionID: input.sessionID,
|
||||
prompt: input.prompt,
|
||||
delivery: input.delivery,
|
||||
timeCreated: timestamp,
|
||||
timeCreated: event.created,
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
@ -115,14 +113,11 @@ export const projectAdmitted = Effect.fn("SessionInput.projectAdmitted")(functio
|
|||
if (!stored) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
})
|
||||
|
||||
export const projectPrompted = Effect.fn("SessionInput.projectPrompted")(function* (
|
||||
export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromoted")(function* (
|
||||
db: DatabaseService,
|
||||
input: {
|
||||
readonly id: SessionMessage.ID
|
||||
readonly sessionID: SessionSchema.ID
|
||||
readonly prompt: Prompt
|
||||
readonly delivery: Delivery
|
||||
readonly timeCreated: DateTime.Utc
|
||||
readonly promotedSeq: number
|
||||
},
|
||||
) {
|
||||
|
|
@ -141,15 +136,16 @@ export const projectPrompted = Effect.fn("SessionInput.projectPrompted")(functio
|
|||
.pipe(Effect.orDie)
|
||||
if (updated) {
|
||||
const stored = fromRow(updated)
|
||||
if (!matchesProjection(stored, input)) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
return
|
||||
if (stored.sessionID !== input.sessionID) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
return stored
|
||||
}
|
||||
|
||||
// Every Prompted event is published from an admitted inbox row, so a missing or
|
||||
// Every PromptPromoted event is published from an admitted inbox row, so a missing or
|
||||
// divergent row on replay is an invariant violation.
|
||||
const stored = yield* find(db, input.id)
|
||||
if (!stored || !matchesProjection(stored, input) || stored.promotedSeq !== input.promotedSeq)
|
||||
if (!stored || stored.sessionID !== input.sessionID || stored.promotedSeq !== input.promotedSeq)
|
||||
return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
return stored
|
||||
})
|
||||
|
||||
export const hasPending = Effect.fn("SessionInput.hasPending")(function* (
|
||||
|
|
@ -206,12 +202,9 @@ const publish = Effect.fn("SessionInput.publish")(function* (
|
|||
for (const row of rows) {
|
||||
const id = SessionMessage.ID.make(row.id)
|
||||
yield* events
|
||||
.publish(SessionEvent.Prompted, {
|
||||
.publish(SessionEvent.PromptPromoted, {
|
||||
sessionID,
|
||||
timestamp: DateTime.makeUnsafe(row.time_created),
|
||||
messageID: id,
|
||||
prompt: decodePrompt(row.prompt),
|
||||
delivery: row.delivery,
|
||||
inputID: id,
|
||||
})
|
||||
.pipe(
|
||||
Effect.catchDefect((defect) =>
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ const layer = Layer.effect(
|
|||
const files = yield* Effect.forEach(
|
||||
toInject,
|
||||
(path) =>
|
||||
fs.readFileStringSafe(path).pipe(
|
||||
Effect.map((content) => (content === undefined ? undefined : { path, content })),
|
||||
),
|
||||
fs
|
||||
.readFileStringSafe(path)
|
||||
.pipe(Effect.map((content) => (content === undefined ? undefined : { path, content }))),
|
||||
{ concurrency: "unbounded" },
|
||||
)
|
||||
const readable = files.filter((file): file is { path: string; content: string } => file !== undefined)
|
||||
|
|
@ -74,8 +74,6 @@ const layer = Layer.effect(
|
|||
// metadata so it survives across Location layer restarts.
|
||||
yield* events.publish(SessionEvent.Synthetic, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: yield* DateTime.now,
|
||||
text: readable.map((file) => `Instructions from: ${file.path}\n${file.content}`).join("\n\n"),
|
||||
description: `Loaded ${readable.map((file) => describePath(root, file.path)).join(", ")}`,
|
||||
metadata: { instruction: { paths: readable.map((file) => file.path) } },
|
||||
|
|
|
|||
|
|
@ -100,112 +100,100 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
|
||||
return Effect.gen(function* () {
|
||||
yield* SessionEvent.All.match(event, {
|
||||
"session.next.agent.switched": (event) => {
|
||||
"agent.selected": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.AgentSwitched.make({
|
||||
id: event.data.messageID,
|
||||
SessionMessage.AgentSelected.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "agent-switched",
|
||||
metadata: event.metadata,
|
||||
agent: event.data.agent,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
},
|
||||
"session.next.model.switched": (event) => {
|
||||
"model.selected": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.ModelSwitched.make({
|
||||
id: event.data.messageID,
|
||||
SessionMessage.ModelSelected.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "model-switched",
|
||||
metadata: event.metadata,
|
||||
model: event.data.model,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
},
|
||||
"session.next.moved": () => Effect.void,
|
||||
"session.next.renamed": () => Effect.void,
|
||||
"session.next.forked": () => Effect.void,
|
||||
"session.next.prompted": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.User.make({
|
||||
id: event.data.messageID,
|
||||
type: "user",
|
||||
metadata: event.metadata,
|
||||
text: event.data.prompt.text,
|
||||
files: event.data.prompt.files,
|
||||
agents: event.data.prompt.agents,
|
||||
time: { created: event.data.timestamp },
|
||||
}),
|
||||
)
|
||||
},
|
||||
"session.next.prompt.admitted": () => Effect.void,
|
||||
"session.next.execution.settled": () => Effect.void,
|
||||
"session.next.context.updated": (event) =>
|
||||
"session.moved": () => Effect.void,
|
||||
renamed: () => Effect.void,
|
||||
forked: () => Effect.void,
|
||||
"prompt.promoted": () => Effect.void,
|
||||
"prompt.admitted": () => Effect.void,
|
||||
"execution.settled": () => Effect.void,
|
||||
"session.context.updated": (event) =>
|
||||
adapter.appendMessage(
|
||||
SessionMessage.System.make({
|
||||
id: event.data.messageID,
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "system",
|
||||
text: event.data.text,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
),
|
||||
"session.next.synthetic": (event) => {
|
||||
synthetic: (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.Synthetic.make({
|
||||
sessionID: event.data.sessionID,
|
||||
text: event.data.text,
|
||||
description: event.data.description,
|
||||
metadata: event.data.metadata,
|
||||
id: event.data.messageID,
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "synthetic",
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
},
|
||||
"session.next.skill.activated": (event) => {
|
||||
"skill.activated": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.Skill.make({
|
||||
id: event.data.messageID,
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "skill",
|
||||
name: event.data.name,
|
||||
text: event.data.text,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
},
|
||||
"session.next.shell.started": (event) => {
|
||||
"shell.started": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.Shell.make({
|
||||
id: event.data.messageID,
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "shell",
|
||||
metadata: event.metadata,
|
||||
callID: event.data.callID,
|
||||
command: event.data.command,
|
||||
output: "",
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
},
|
||||
"session.next.shell.ended": (event) => {
|
||||
"shell.ended": (event) => {
|
||||
return Effect.gen(function* () {
|
||||
const currentShell = yield* adapter.getCurrentShell(event.data.callID)
|
||||
if (currentShell) {
|
||||
yield* adapter.updateShell(
|
||||
produce(currentShell, (draft) => {
|
||||
draft.output = event.data.output
|
||||
draft.time.completed = event.data.timestamp
|
||||
draft.time.completed = event.created
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
"session.next.step.started": (event) => {
|
||||
"step.started": (event) => {
|
||||
return Effect.gen(function* () {
|
||||
const currentAssistant = yield* adapter.getCurrentAssistant()
|
||||
if (currentAssistant) {
|
||||
yield* adapter.updateAssistant(
|
||||
produce(currentAssistant, (draft) => {
|
||||
draft.time.completed = event.data.timestamp
|
||||
draft.time.completed = event.created
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
@ -215,16 +203,16 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
type: "assistant",
|
||||
agent: event.data.agent,
|
||||
model: event.data.model,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
content: [],
|
||||
snapshot: event.data.snapshot ? { start: event.data.snapshot } : undefined,
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
"session.next.step.ended": (event) => {
|
||||
"step.ended": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.time.completed = event.data.timestamp
|
||||
draft.time.completed = event.created
|
||||
draft.finish = event.data.finish
|
||||
draft.cost = event.data.cost
|
||||
draft.tokens = event.data.tokens
|
||||
|
|
@ -236,33 +224,33 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
}
|
||||
})
|
||||
},
|
||||
"session.next.step.failed": (event) => {
|
||||
"step.failed": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.time.completed = event.data.timestamp
|
||||
draft.time.completed = event.created
|
||||
draft.finish = "error"
|
||||
draft.error = event.data.error
|
||||
})
|
||||
},
|
||||
"session.next.text.started": (event) => {
|
||||
"text.started": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })),
|
||||
)
|
||||
})
|
||||
},
|
||||
"session.next.text.delta": (event) => {
|
||||
"text.delta": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestText(draft, event.data.textID)
|
||||
if (match) match.text += event.data.delta
|
||||
})
|
||||
},
|
||||
"session.next.text.ended": (event) => {
|
||||
"text.ended": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestText(draft, event.data.textID)
|
||||
if (match) match.text = event.data.text
|
||||
})
|
||||
},
|
||||
"session.next.tool.input.started": (event) => {
|
||||
"tool.input.started": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
castDraft(
|
||||
|
|
@ -270,26 +258,26 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
type: "tool",
|
||||
id: event.data.callID,
|
||||
name: event.data.name,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
state: SessionMessage.ToolStatePending.make({ status: "pending", input: "" }),
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
},
|
||||
"session.next.tool.input.delta": () => Effect.void,
|
||||
"session.next.tool.input.ended": (event) => {
|
||||
"tool.input.delta": () => Effect.void,
|
||||
"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
|
||||
})
|
||||
},
|
||||
"session.next.tool.called": (event) => {
|
||||
"tool.called": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
if (match) {
|
||||
match.provider = event.data.provider
|
||||
match.time.ran = event.data.timestamp
|
||||
match.time.ran = event.created
|
||||
match.state = castDraft(
|
||||
SessionMessage.ToolStateRunning.make({
|
||||
status: "running",
|
||||
|
|
@ -301,7 +289,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
}
|
||||
})
|
||||
},
|
||||
"session.next.tool.progress": (event) => {
|
||||
"tool.progress": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
if (match && match.state.status === "running") {
|
||||
|
|
@ -310,7 +298,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
}
|
||||
})
|
||||
},
|
||||
"session.next.tool.success": (event) => {
|
||||
"tool.success": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
if (match && match.state.status === "running") {
|
||||
|
|
@ -319,7 +307,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
metadata: match.provider?.metadata,
|
||||
resultMetadata: event.data.provider.metadata,
|
||||
}
|
||||
match.time.completed = event.data.timestamp
|
||||
match.time.completed = event.created
|
||||
match.state = castDraft(
|
||||
SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
|
|
@ -333,7 +321,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
}
|
||||
})
|
||||
},
|
||||
"session.next.tool.failed": (event) => {
|
||||
"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")) {
|
||||
|
|
@ -342,7 +330,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
metadata: match.provider?.metadata,
|
||||
resultMetadata: event.data.provider.metadata,
|
||||
}
|
||||
match.time.completed = event.data.timestamp
|
||||
match.time.completed = event.created
|
||||
match.state = castDraft(
|
||||
SessionMessage.ToolStateError.make({
|
||||
status: "error",
|
||||
|
|
@ -356,7 +344,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
}
|
||||
})
|
||||
},
|
||||
"session.next.reasoning.started": (event) => {
|
||||
"reasoning.started": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
castDraft(
|
||||
|
|
@ -365,47 +353,47 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
id: event.data.reasoningID,
|
||||
text: "",
|
||||
providerMetadata: event.data.providerMetadata,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
},
|
||||
"session.next.reasoning.delta": (event) => {
|
||||
"reasoning.delta": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestReasoning(draft, event.data.reasoningID)
|
||||
if (match) match.text += event.data.delta
|
||||
})
|
||||
},
|
||||
"session.next.reasoning.ended": (event) => {
|
||||
"reasoning.ended": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestReasoning(draft, event.data.reasoningID)
|
||||
if (match) {
|
||||
match.text = event.data.text
|
||||
match.time = { created: match.time?.created ?? event.data.timestamp, completed: event.data.timestamp }
|
||||
match.time = { created: match.time?.created ?? event.created, completed: event.created }
|
||||
if (event.data.providerMetadata !== undefined) match.providerMetadata = event.data.providerMetadata
|
||||
}
|
||||
})
|
||||
},
|
||||
"session.next.retried": () => Effect.void,
|
||||
"session.next.compaction.started": () => Effect.void,
|
||||
"session.next.compaction.delta": () => Effect.void,
|
||||
"session.next.compaction.ended": (event) => {
|
||||
retried: () => Effect.void,
|
||||
"compaction.started": () => Effect.void,
|
||||
"compaction.delta": () => Effect.void,
|
||||
"compaction.ended": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.Compaction.make({
|
||||
id: event.data.messageID,
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "compaction",
|
||||
metadata: event.metadata,
|
||||
reason: event.data.reason,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
},
|
||||
"session.next.revert.staged": () => Effect.void,
|
||||
"session.next.revert.cleared": () => Effect.void,
|
||||
"session.next.revert.committed": () => Effect.void,
|
||||
"revert.staged": () => Effect.void,
|
||||
"revert.cleared": () => Effect.void,
|
||||
"revert.committed": () => Effect.void,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,21 +158,17 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
|||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!parent) return yield* Effect.die(new Error(`Fork parent session not found: ${event.data.parentID}`))
|
||||
const boundary = event.data.messageID
|
||||
const boundary = event.data.from
|
||||
? yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionMessageTable.session_id, event.data.parentID),
|
||||
eq(SessionMessageTable.id, event.data.messageID),
|
||||
),
|
||||
and(eq(SessionMessageTable.session_id, event.data.parentID), eq(SessionMessageTable.id, event.data.from)),
|
||||
)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
: undefined
|
||||
if (event.data.messageID && !boundary)
|
||||
return yield* Effect.die(new Error(`Fork boundary message not found: ${event.data.messageID}`))
|
||||
if (event.data.from && !boundary) return yield* Effect.die(new Error(`Fork boundary message not found: ${event.data.from}`))
|
||||
const copied = yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
|
|
@ -208,8 +204,8 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
|||
tokens_reasoning: 0,
|
||||
tokens_cache_read: 0,
|
||||
tokens_cache_write: 0,
|
||||
time_created: DateTime.toEpochMillis(event.data.timestamp),
|
||||
time_updated: DateTime.toEpochMillis(event.data.timestamp),
|
||||
time_created: DateTime.toEpochMillis(event.created),
|
||||
time_updated: DateTime.toEpochMillis(event.created),
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.returning({ sessionID: SessionTable.id })
|
||||
|
|
@ -474,9 +470,9 @@ const layer = Layer.effectDiscard(
|
|||
.update(SessionTable)
|
||||
.set({
|
||||
directory: event.data.location.directory,
|
||||
path: event.data.subdirectory,
|
||||
path: event.data.subpath,
|
||||
workspace_id: event.data.location.workspaceID ? WorkspaceV2.ID.make(event.data.location.workspaceID) : null,
|
||||
time_updated: DateTime.toEpochMillis(event.data.timestamp),
|
||||
time_updated: DateTime.toEpochMillis(event.created),
|
||||
})
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
|
|
@ -556,19 +552,19 @@ const layer = Layer.effectDiscard(
|
|||
if (next) yield* applyUsage(db, sessionID, next)
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.AgentSwitched, (event) =>
|
||||
yield* events.project(SessionEvent.AgentSelected, (event) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
.set({ agent: event.data.agent, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
|
||||
.set({ agent: event.data.agent, time_updated: DateTime.toEpochMillis(event.created) })
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie, Effect.andThen(run(db, event))),
|
||||
)
|
||||
yield* events.project(SessionEvent.ModelSwitched, (event) =>
|
||||
yield* events.project(SessionEvent.ModelSelected, (event) =>
|
||||
Effect.gen(function* () {
|
||||
yield* db
|
||||
.update(SessionTable)
|
||||
.set({ model: event.data.model, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
|
||||
.set({ model: event.data.model, time_updated: DateTime.toEpochMillis(event.created) })
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
|
|
@ -578,25 +574,30 @@ const layer = Layer.effectDiscard(
|
|||
yield* events.project(SessionEvent.Renamed, (event) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
.set({ title: event.data.title, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
|
||||
.set({ title: event.data.title, time_updated: DateTime.toEpochMillis(event.created) })
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie),
|
||||
)
|
||||
yield* events.project(SessionEvent.Forked, (event) => projectFork(db, event))
|
||||
yield* events.project(SessionEvent.Prompted, (event) =>
|
||||
yield* events.project(SessionEvent.PromptPromoted, (event) =>
|
||||
Effect.gen(function* () {
|
||||
if (event.durable === undefined)
|
||||
return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
yield* SessionInput.projectPrompted(db, {
|
||||
id: event.data.messageID,
|
||||
const input = yield* SessionInput.projectPromptPromoted(db, {
|
||||
id: event.data.inputID,
|
||||
sessionID: event.data.sessionID,
|
||||
prompt: event.data.prompt,
|
||||
delivery: event.data.delivery,
|
||||
timeCreated: event.data.timestamp,
|
||||
promotedSeq: event.durable.seq,
|
||||
})
|
||||
yield* run(db, event)
|
||||
yield* insertMessage(db, event, {
|
||||
id: input.id,
|
||||
type: "user",
|
||||
metadata: event.metadata,
|
||||
text: input.prompt.text,
|
||||
files: input.prompt.files,
|
||||
agents: input.prompt.agents,
|
||||
time: { created: event.created },
|
||||
})
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.PromptAdmitted, (event) =>
|
||||
|
|
@ -605,11 +606,11 @@ const layer = Layer.effectDiscard(
|
|||
return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
yield* SessionInput.projectAdmitted(db, {
|
||||
admittedSeq: event.durable.seq,
|
||||
id: event.data.messageID,
|
||||
id: event.data.inputID,
|
||||
sessionID: event.data.sessionID,
|
||||
prompt: event.data.prompt,
|
||||
delivery: event.data.delivery,
|
||||
timeCreated: event.data.timestamp,
|
||||
timeCreated: event.created,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
@ -617,11 +618,11 @@ const layer = Layer.effectDiscard(
|
|||
yield* events.project(SessionEvent.Synthetic, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Skill.Activated, (event) =>
|
||||
insertMessage(db, event, {
|
||||
id: event.data.messageID,
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "skill",
|
||||
name: event.data.name,
|
||||
text: event.data.text,
|
||||
time: { created: event.data.timestamp },
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.Shell.Started, (event) => run(db, event))
|
||||
|
|
@ -646,7 +647,7 @@ const layer = Layer.effectDiscard(
|
|||
.update(SessionTable)
|
||||
.set({
|
||||
revert: { ...event.data.revert, files: event.data.revert.files ? [...event.data.revert.files] : undefined },
|
||||
time_updated: DateTime.toEpochMillis(event.data.timestamp),
|
||||
time_updated: DateTime.toEpochMillis(event.created),
|
||||
})
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
|
|
@ -655,7 +656,7 @@ const layer = Layer.effectDiscard(
|
|||
yield* events.project(SessionEvent.RevertEvent.Cleared, (event) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
.set({ revert: null, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
|
||||
.set({ revert: null, time_updated: DateTime.toEpochMillis(event.created) })
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie, Effect.asVoid),
|
||||
|
|
@ -693,7 +694,7 @@ const layer = Layer.effectDiscard(
|
|||
.pipe(Effect.orDie)
|
||||
yield* db
|
||||
.update(SessionTable)
|
||||
.set({ revert: null, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
|
||||
.set({ revert: null, time_updated: DateTime.toEpochMillis(event.created) })
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ export const stage = Effect.fn("SessionRevert.stage")(function* (input: {
|
|||
} satisfies SessionSchema.Info["revert"]
|
||||
yield* events.publish(SessionEvent.RevertEvent.Staged, {
|
||||
sessionID: input.session.id,
|
||||
timestamp: yield* DateTime.now,
|
||||
revert,
|
||||
})
|
||||
return revert
|
||||
|
|
@ -106,7 +105,6 @@ export const clear = Effect.fn("SessionRevert.clear")(function* (session: Sessio
|
|||
const events = yield* EventV2.Service
|
||||
yield* events.publish(SessionEvent.RevertEvent.Cleared, {
|
||||
sessionID: session.id,
|
||||
timestamp: yield* DateTime.now,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -116,6 +114,5 @@ export const commit = Effect.fn("SessionRevert.commit")(function* (session: Sess
|
|||
yield* events.publish(SessionEvent.RevertEvent.Committed, {
|
||||
sessionID: session.id,
|
||||
messageID: session.revert.messageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ const layer = Layer.effect(
|
|||
if (tool.type !== "tool" || (tool.state.status !== "pending" && tool.state.status !== "running")) continue
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID: message.id,
|
||||
callID: tool.id,
|
||||
error: { type: "unknown", message: "Tool execution interrupted" },
|
||||
|
|
@ -297,7 +296,6 @@ const layer = Layer.effect(
|
|||
yield* serialized(
|
||||
events.publish(SessionEvent.Step.Ended, {
|
||||
sessionID: session.id,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID: yield* publisher.startAssistant(),
|
||||
finish: settlement.finish,
|
||||
cost: 0,
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
...input,
|
||||
assistantMessageID,
|
||||
timestamp: yield* timestamp,
|
||||
snapshot: input.snapshot,
|
||||
})
|
||||
return assistantMessageID
|
||||
|
|
@ -123,7 +122,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
timestamp: yield* timestamp,
|
||||
textID,
|
||||
text: value,
|
||||
})
|
||||
|
|
@ -134,7 +132,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* events.publish(SessionEvent.Reasoning.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
timestamp: yield* timestamp,
|
||||
reasoningID,
|
||||
text: value,
|
||||
providerMetadata,
|
||||
|
|
@ -147,7 +144,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
if (!tool) return yield* Effect.die(new Error(`Tool input end before start: ${callID}`))
|
||||
yield* events.publish(SessionEvent.Tool.Input.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID,
|
||||
text: value,
|
||||
|
|
@ -176,7 +172,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* toolInput.start(event.id)
|
||||
yield* events.publish(SessionEvent.Tool.Input.Started, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID,
|
||||
callID: event.id,
|
||||
name: event.name,
|
||||
|
|
@ -204,7 +199,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
assistantFailed = true
|
||||
yield* events.publish(SessionEvent.Step.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID,
|
||||
error: { type: "unknown", message },
|
||||
})
|
||||
|
|
@ -219,7 +213,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
tool.settled = true
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID,
|
||||
error: { type: "unknown", message },
|
||||
|
|
@ -248,7 +241,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* events.publish(SessionEvent.Text.Started, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* startAssistant(),
|
||||
timestamp: yield* timestamp,
|
||||
textID: event.id,
|
||||
})
|
||||
return
|
||||
|
|
@ -257,7 +249,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* events.publish(SessionEvent.Text.Delta, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
timestamp: yield* timestamp,
|
||||
textID: event.id,
|
||||
delta: event.text,
|
||||
})
|
||||
|
|
@ -270,7 +261,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* events.publish(SessionEvent.Reasoning.Started, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* startAssistant(),
|
||||
timestamp: yield* timestamp,
|
||||
reasoningID: event.id,
|
||||
providerMetadata: event.providerMetadata,
|
||||
})
|
||||
|
|
@ -280,7 +270,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* events.publish(SessionEvent.Reasoning.Delta, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
timestamp: yield* timestamp,
|
||||
reasoningID: event.id,
|
||||
delta: event.text,
|
||||
})
|
||||
|
|
@ -300,7 +289,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* toolInput.append(event.id, event.text)
|
||||
yield* events.publish(SessionEvent.Tool.Input.Delta, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID: event.id,
|
||||
delta: event.text,
|
||||
|
|
@ -322,7 +310,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
tool.providerMetadata = event.providerMetadata
|
||||
yield* events.publish(SessionEvent.Tool.Called, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID: event.id,
|
||||
tool: event.name,
|
||||
|
|
@ -352,7 +339,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
if ("error" in result) {
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID: event.id,
|
||||
error: result.error,
|
||||
|
|
@ -363,7 +349,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
}
|
||||
yield* events.publish(SessionEvent.Tool.Success, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID: event.id,
|
||||
...result,
|
||||
|
|
@ -382,7 +367,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
tool.settled = true
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID: event.id,
|
||||
error: { type: "unknown", message: event.message },
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ const make = (dependencies: Dependencies) => {
|
|||
if (!firstUser) return
|
||||
const agent = yield* dependencies.agents.get(AgentV2.ID.make("title"))
|
||||
if (!agent) return
|
||||
const resolved = yield* (agent.model
|
||||
? dependencies.models.resolve({ ...session, model: agent.model })
|
||||
: dependencies.models.resolve(session)
|
||||
const resolved = yield* (
|
||||
agent.model
|
||||
? dependencies.models.resolve({ ...session, model: agent.model })
|
||||
: dependencies.models.resolve(session)
|
||||
).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
if (!resolved) return
|
||||
const chunks: string[] = []
|
||||
|
|
@ -76,7 +77,6 @@ const make = (dependencies: Dependencies) => {
|
|||
if (!title) return
|
||||
yield* dependencies.events.publish(SessionEvent.Renamed, {
|
||||
sessionID: session.id,
|
||||
timestamp: yield* DateTime.now,
|
||||
title: truncate(title),
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ describe("DatabaseMigration", () => {
|
|||
)
|
||||
yield* db.run(sql`INSERT INTO event_sequence (aggregate_id, seq) VALUES ('session', 9)`)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO event (id, aggregate_id, seq, type, data) VALUES ('event', 'session', 9, 'session.updated.1', '{}')`,
|
||||
sql`INSERT INTO event (id, aggregate_id, seq, type, data, created) VALUES ('event', 'session', 9, 'session.updated.1', '{}', 1)`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO session_input (id, session_id, prompt, delivery, admitted_seq, time_created) VALUES ('input', 'session', '{}', 'steer', 9, 1)`,
|
||||
|
|
|
|||
|
|
@ -554,6 +554,7 @@ describe("EventV2", () => {
|
|||
|
||||
yield* events.replay({
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -573,6 +574,7 @@ describe("EventV2", () => {
|
|||
|
||||
yield* events.replay({
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -609,6 +611,7 @@ describe("EventV2", () => {
|
|||
const exit = yield* events
|
||||
.replay({
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 1,
|
||||
aggregateID: envelopeAggregateID,
|
||||
|
|
@ -642,6 +645,7 @@ describe("EventV2", () => {
|
|||
|
||||
yield* events.replay({
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -650,6 +654,7 @@ describe("EventV2", () => {
|
|||
const exit = yield* events
|
||||
.replay({
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 5,
|
||||
aggregateID,
|
||||
|
|
@ -674,13 +679,14 @@ describe("EventV2", () => {
|
|||
|
||||
yield* events.replay({
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(SessionEvent.ContextUpdated.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
data: { sessionID: aggregateID, messageID: "msg_context", timestamp: 0, text: "context" },
|
||||
data: { sessionID: aggregateID, text: "context" },
|
||||
})
|
||||
|
||||
expect(received[0]?.data.timestamp).toEqual(DateTime.makeUnsafe(0))
|
||||
expect(received[0]?.created).toEqual(DateTime.makeUnsafe(0))
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -690,6 +696,7 @@ describe("EventV2", () => {
|
|||
const exit = yield* events
|
||||
.replay({
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "unknown.event.1",
|
||||
seq: 0,
|
||||
aggregateID: EventV2.ID.create(),
|
||||
|
|
@ -708,6 +715,7 @@ describe("EventV2", () => {
|
|||
const source = yield* events.replayAll([
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -715,6 +723,7 @@ describe("EventV2", () => {
|
|||
},
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 1,
|
||||
aggregateID,
|
||||
|
|
@ -735,6 +744,7 @@ describe("EventV2", () => {
|
|||
const one = yield* events.replayAll([
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -742,6 +752,7 @@ describe("EventV2", () => {
|
|||
},
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 1,
|
||||
aggregateID,
|
||||
|
|
@ -751,6 +762,7 @@ describe("EventV2", () => {
|
|||
const two = yield* events.replayAll([
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 2,
|
||||
aggregateID,
|
||||
|
|
@ -758,6 +770,7 @@ describe("EventV2", () => {
|
|||
},
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 3,
|
||||
aggregateID,
|
||||
|
|
@ -793,6 +806,7 @@ describe("EventV2", () => {
|
|||
yield* events.replay(
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 1,
|
||||
aggregateID,
|
||||
|
|
@ -812,6 +826,7 @@ describe("EventV2", () => {
|
|||
const id = EventV2.ID.create()
|
||||
const replayed = {
|
||||
id,
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -833,6 +848,7 @@ describe("EventV2", () => {
|
|||
const published = yield* events.publish(DurableMessage, durableData(aggregateID, "owned"))
|
||||
const replayed = {
|
||||
id: published.id,
|
||||
created: published.created,
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: published.durable!.seq,
|
||||
aggregateID,
|
||||
|
|
@ -867,6 +883,7 @@ describe("EventV2", () => {
|
|||
yield* events.replay(
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -895,6 +912,7 @@ describe("EventV2", () => {
|
|||
yield* events.replay(
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 1,
|
||||
aggregateID,
|
||||
|
|
@ -905,6 +923,7 @@ describe("EventV2", () => {
|
|||
yield* events.replay(
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 2,
|
||||
aggregateID,
|
||||
|
|
@ -937,6 +956,7 @@ describe("EventV2", () => {
|
|||
yield* events.replay(
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -949,6 +969,7 @@ describe("EventV2", () => {
|
|||
.replay(
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 1,
|
||||
aggregateID,
|
||||
|
|
@ -970,6 +991,7 @@ describe("EventV2", () => {
|
|||
yield* events.listen((event) => Effect.sync(() => received.push(event)))
|
||||
const replayed = {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -990,6 +1012,7 @@ describe("EventV2", () => {
|
|||
const aggregateID = Session.ID.create()
|
||||
const replayed = {
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -1014,6 +1037,7 @@ describe("EventV2", () => {
|
|||
const id = EventV2.ID.create()
|
||||
yield* events.replay({
|
||||
id,
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -1023,6 +1047,7 @@ describe("EventV2", () => {
|
|||
const exit = yield* events
|
||||
.replay({
|
||||
id,
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 1,
|
||||
aggregateID,
|
||||
|
|
@ -1045,6 +1070,7 @@ describe("EventV2", () => {
|
|||
yield* events.replay(
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
@ -1055,6 +1081,7 @@ describe("EventV2", () => {
|
|||
yield* events.replay(
|
||||
{
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 1,
|
||||
aggregateID,
|
||||
|
|
@ -1116,6 +1143,7 @@ describe("EventV2", () => {
|
|||
|
||||
yield* events.replay({
|
||||
id: EventV2.ID.create(),
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
||||
seq: 0,
|
||||
aggregateID,
|
||||
|
|
|
|||
|
|
@ -85,17 +85,13 @@ describe("SessionV2.compact", () => {
|
|||
const prompt = Prompt.make({ text: "Please compact this session history." })
|
||||
yield* events.publish(SessionEvent.PromptAdmitted, {
|
||||
sessionID: created.id,
|
||||
messageID,
|
||||
timestamp: DateTime.makeUnsafe(0),
|
||||
inputID: messageID,
|
||||
prompt,
|
||||
delivery: "steer",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Prompted, {
|
||||
yield* events.publish(SessionEvent.PromptPromoted, {
|
||||
sessionID: created.id,
|
||||
messageID,
|
||||
timestamp: DateTime.makeUnsafe(0),
|
||||
prompt,
|
||||
delivery: "steer",
|
||||
inputID: messageID,
|
||||
})
|
||||
|
||||
yield* session.compact({ sessionID: created.id })
|
||||
|
|
|
|||
|
|
@ -197,8 +197,6 @@ describe("SessionV2.create", () => {
|
|||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||
yield* events.publish(SessionEvent.Synthetic, {
|
||||
sessionID: parent.id,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: yield* DateTime.now,
|
||||
text: "parent note",
|
||||
})
|
||||
|
||||
|
|
@ -215,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: "session.next.forked",
|
||||
type: "forked",
|
||||
durable: { seq: 0 },
|
||||
data: { sessionID: forked.id, parentID: parent.id },
|
||||
})
|
||||
|
|
@ -267,7 +265,7 @@ describe("SessionV2.create", () => {
|
|||
const history = Array.from(yield* Stream.runCollect(logEvents(session, forked.id)))
|
||||
expect(context).toMatchObject([{ text: "First" }])
|
||||
expect(context[0]?.id).not.toBe(first.id)
|
||||
expect(history[0]).toMatchObject({ data: { messageID: second.id } })
|
||||
expect(history[0]).toMatchObject({ data: { from: second.id } })
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -380,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: "session.next.prompt.admitted", data: { prompt: { text: "Hello" } } },
|
||||
{ durable: { seq: 2 }, type: "session.next.prompted" },
|
||||
{ durable: { seq: 1 }, type: "prompt.admitted", data: { prompt: { text: "Hello" } } },
|
||||
{ durable: { seq: 2 }, type: "prompt.promoted" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
|
@ -406,6 +404,7 @@ describe("SessionV2.create", () => {
|
|||
.all()
|
||||
.pipe(Effect.orDie)).map((event) => ({
|
||||
id: event.id,
|
||||
created: DateTime.makeUnsafe(event.created),
|
||||
aggregateID: event.aggregate_id,
|
||||
seq: event.seq,
|
||||
type: event.type,
|
||||
|
|
@ -466,7 +465,7 @@ describe("SessionV2.create", () => {
|
|||
).toEqual([
|
||||
[0, EventV2.versionedType(SessionV1.Event.Created.type, 1)],
|
||||
[1, EventV2.versionedType(SessionEvent.PromptAdmitted.type, 1)],
|
||||
[2, EventV2.versionedType(SessionEvent.Prompted.type, 1)],
|
||||
[2, EventV2.versionedType(SessionEvent.PromptPromoted.type, 1)],
|
||||
])
|
||||
}).pipe(Effect.provide(Layer.fresh(targetLayer)))
|
||||
}),
|
||||
|
|
@ -530,7 +529,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: "session.next.agent.switched", data: { agent: "plan" } }])
|
||||
).toMatchObject([{ type: "agent.selected", data: { agent: "plan" } }])
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -563,7 +562,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: "session.next.model.switched", data: { model } }])
|
||||
).toMatchObject([{ type: "model.selected", data: { model } }])
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -133,8 +133,6 @@ const seedSynthetic = (sessionID: SessionV2.ID, paths: string[]) =>
|
|||
const events = yield* EventV2.Service
|
||||
yield* events.publish(SessionEvent.Synthetic, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: yield* DateTime.now,
|
||||
text: `Instructions from: ${paths[0]}\nprior`,
|
||||
description: `Loaded ${paths[0]}`,
|
||||
metadata: { instruction: { paths } },
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ describe("SessionV2.log", () => {
|
|||
|
||||
// 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(["session.next.renamed", "log.synced"])
|
||||
expect(items.map((item) => item.type)).toEqual(["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", "session.next.renamed"])
|
||||
expect(items.map((item) => item.type)).toEqual(["log.synced", "renamed"])
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ describe("SessionProjector", () => {
|
|||
const events = yield* EventV2.Service
|
||||
yield* events.publish(SessionEvent.RevertEvent.Staged, {
|
||||
sessionID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
revert: { messageID: boundary, snapshot: Snapshot.ID.make("tree"), diff: "patch", files: [] },
|
||||
})
|
||||
expect((yield* db.select({ revert: SessionTable.revert }).from(SessionTable).get())?.revert).toMatchObject({
|
||||
|
|
@ -87,17 +86,15 @@ describe("SessionProjector", () => {
|
|||
snapshot: "tree",
|
||||
files: [],
|
||||
})
|
||||
yield* events.publish(SessionEvent.RevertEvent.Cleared, { sessionID, timestamp: DateTime.makeUnsafe(2) })
|
||||
yield* events.publish(SessionEvent.RevertEvent.Cleared, { sessionID })
|
||||
expect((yield* db.select({ revert: SessionTable.revert }).from(SessionTable).get())?.revert).toBeNull()
|
||||
yield* events.publish(SessionEvent.RevertEvent.Staged, {
|
||||
sessionID,
|
||||
timestamp: DateTime.makeUnsafe(3),
|
||||
revert: { messageID: boundary, files: [] },
|
||||
})
|
||||
yield* events.publish(SessionEvent.RevertEvent.Committed, {
|
||||
sessionID,
|
||||
messageID: boundary,
|
||||
timestamp: DateTime.makeUnsafe(4),
|
||||
})
|
||||
expect(
|
||||
(yield* db.select({ id: SessionMessageTable.id }).from(SessionMessageTable).all()).map((row) => row.id),
|
||||
|
|
@ -131,37 +128,29 @@ describe("SessionProjector", () => {
|
|||
|
||||
yield* events.publish(SessionEvent.PromptAdmitted, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.make("msg_first"),
|
||||
timestamp: created,
|
||||
inputID: SessionMessage.ID.make("msg_first"),
|
||||
prompt: Prompt.make({ text: "first" }),
|
||||
delivery: "steer",
|
||||
})
|
||||
yield* events.publish(
|
||||
SessionEvent.Prompted,
|
||||
SessionEvent.PromptPromoted,
|
||||
{
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.make("msg_first"),
|
||||
timestamp: created,
|
||||
prompt: Prompt.make({ text: "first" }),
|
||||
delivery: "steer",
|
||||
inputID: SessionMessage.ID.make("msg_first"),
|
||||
},
|
||||
{ id: EventV2.ID.make("evt_z") },
|
||||
)
|
||||
yield* events.publish(SessionEvent.PromptAdmitted, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.make("msg_second"),
|
||||
timestamp: created,
|
||||
inputID: SessionMessage.ID.make("msg_second"),
|
||||
prompt: Prompt.make({ text: "second" }),
|
||||
delivery: "steer",
|
||||
})
|
||||
yield* events.publish(
|
||||
SessionEvent.Prompted,
|
||||
SessionEvent.PromptPromoted,
|
||||
{
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.make("msg_second"),
|
||||
timestamp: created,
|
||||
prompt: Prompt.make({ text: "second" }),
|
||||
delivery: "steer",
|
||||
inputID: SessionMessage.ID.make("msg_second"),
|
||||
},
|
||||
{ id: EventV2.ID.make("evt_a") },
|
||||
)
|
||||
|
|
@ -190,7 +179,7 @@ describe("SessionProjector", () => {
|
|||
}).pipe(Effect.provide(sessionsLayer)),
|
||||
)
|
||||
|
||||
it.effect("marks an inbox row promoted with the Prompted event sequence", () =>
|
||||
it.effect("marks an inbox row promoted with the PromptPromoted event sequence", () =>
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
yield* db
|
||||
|
|
@ -220,12 +209,9 @@ describe("SessionProjector", () => {
|
|||
})
|
||||
if (!admitted) return yield* Effect.die("Prompt admission failed")
|
||||
|
||||
const event = yield* events.publish(SessionEvent.Prompted, {
|
||||
const event = yield* events.publish(SessionEvent.PromptPromoted, {
|
||||
sessionID,
|
||||
timestamp: admitted.timeCreated,
|
||||
messageID: id,
|
||||
prompt: Prompt.make({ text: "promote me" }),
|
||||
delivery: "steer",
|
||||
inputID: id,
|
||||
})
|
||||
|
||||
expect(
|
||||
|
|
@ -256,49 +242,36 @@ describe("SessionProjector", () => {
|
|||
.pipe(Effect.orDie)
|
||||
const events = yield* EventV2.Service
|
||||
|
||||
yield* events.publish(SessionEvent.AgentSwitched, {
|
||||
yield* events.publish(SessionEvent.AgentSelected, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: created,
|
||||
agent: "build",
|
||||
})
|
||||
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||
yield* events.publish(SessionEvent.ModelSelected, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: created,
|
||||
model,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Synthetic, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: created,
|
||||
text: "synthetic context",
|
||||
metadata: { source: "projector-test" },
|
||||
})
|
||||
yield* events.publish(SessionEvent.Shell.Started, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: created,
|
||||
callID: "shell-1",
|
||||
command: "pwd",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Shell.Ended, {
|
||||
sessionID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
callID: "shell-1",
|
||||
output: "/project",
|
||||
})
|
||||
const compactionID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.Compaction.Started, {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: created,
|
||||
reason: "manual",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Compaction.Delta, {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: created,
|
||||
text: "partial",
|
||||
})
|
||||
expect(
|
||||
|
|
@ -319,8 +292,6 @@ describe("SessionProjector", () => {
|
|||
).toEqual([])
|
||||
yield* events.publish(SessionEvent.Compaction.Ended, {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
reason: "manual",
|
||||
text: "summary",
|
||||
recent: "recent context",
|
||||
|
|
@ -350,7 +321,7 @@ describe("SessionProjector", () => {
|
|||
})
|
||||
expect(messages.find((message) => message.type === "shell")).toMatchObject({
|
||||
output: "/project",
|
||||
time: { completed: DateTime.makeUnsafe(1) },
|
||||
time: { completed: DateTime.makeUnsafe(0) },
|
||||
})
|
||||
expect(messages.find((message) => message.type === "compaction")).toMatchObject({
|
||||
summary: "summary",
|
||||
|
|
@ -388,13 +359,20 @@ describe("SessionProjector", () => {
|
|||
.pipe(Effect.orDie)
|
||||
const events = yield* EventV2.Service
|
||||
const id = SessionMessage.ID.make("msg_creator_collision")
|
||||
const {
|
||||
id: _,
|
||||
type,
|
||||
...data
|
||||
} = encodeMessage({ id, sessionID, type: "synthetic", text: "existing", time: { created } })
|
||||
yield* db
|
||||
.insert(SessionMessageTable)
|
||||
.values({ id, session_id: sessionID, type, seq: 0, time_created: 0, data })
|
||||
.run()
|
||||
|
||||
yield* events.publish(SessionEvent.Synthetic, { sessionID, messageID: id, timestamp: created, text: "keep me" })
|
||||
const exit = yield* events
|
||||
.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: id,
|
||||
timestamp: created,
|
||||
agent: "build",
|
||||
model,
|
||||
})
|
||||
|
|
@ -464,7 +442,6 @@ describe("SessionProjector", () => {
|
|||
const service = yield* EventV2.Service
|
||||
yield* service.publish(SessionEvent.Step.Ended, {
|
||||
sessionID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
assistantMessageID: SessionMessage.ID.make("msg_assistant_2"),
|
||||
finish: "stop",
|
||||
cost: 0,
|
||||
|
|
@ -485,7 +462,7 @@ describe("SessionProjector", () => {
|
|||
expect(messages[1]).toMatchObject({
|
||||
type: "assistant",
|
||||
finish: "stop",
|
||||
time: { completed: DateTime.makeUnsafe(1) },
|
||||
time: { completed: DateTime.makeUnsafe(0) },
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
@ -526,7 +503,6 @@ describe("SessionProjector", () => {
|
|||
yield* service.publish(SessionEvent.Text.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: SessionMessage.ID.make("msg_assistant_completed"),
|
||||
timestamp: DateTime.makeUnsafe(3),
|
||||
textID: "text-stale",
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -201,7 +201,6 @@ describe("SessionV2.prompt", () => {
|
|||
yield* db.insert(SessionMessageTable).values(assistantRow(stale, 100)).run().pipe(Effect.orDie)
|
||||
yield* events.publish(SessionEvent.RevertEvent.Staged, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
revert: { messageID: boundary.id, files: [] },
|
||||
})
|
||||
expect((yield* session.get(sessionID)).revert?.messageID).toBe(boundary.id)
|
||||
|
|
@ -257,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, "session.next.prompt.admitted"],
|
||||
[1, "session.next.prompt.admitted"],
|
||||
[2, "session.next.prompted"],
|
||||
[3, "session.next.prompted"],
|
||||
[0, "prompt.admitted"],
|
||||
[1, "prompt.admitted"],
|
||||
[2, "prompt.promoted"],
|
||||
[3, "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, "session.next.prompt.admitted"]])
|
||||
).toEqual([[1, "prompt.admitted"]])
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -429,7 +428,7 @@ describe("SessionV2.prompt", () => {
|
|||
{ concurrency: "unbounded" },
|
||||
)
|
||||
|
||||
expect(yield* eventCount(EventV2.versionedType(SessionEvent.Prompted.type, 1))).toBe(1)
|
||||
expect(yield* eventCount(EventV2.versionedType(SessionEvent.PromptPromoted.type, 1))).toBe(1)
|
||||
expect(yield* admitted(messageID)).toMatchObject({ promotedSeq: 1 })
|
||||
expect(yield* session.messages({ sessionID })).toMatchObject([
|
||||
{ id: messageID, type: "user", text: "Promote once" },
|
||||
|
|
@ -467,6 +466,7 @@ describe("SessionV2.prompt", () => {
|
|||
yield* events.replayAll(
|
||||
recorded.map((event) => ({
|
||||
id: event.id,
|
||||
created: DateTime.makeUnsafe(event.created),
|
||||
aggregateID: event.aggregate_id,
|
||||
seq: event.seq,
|
||||
type: event.type,
|
||||
|
|
@ -514,13 +514,23 @@ describe("SessionV2.prompt", () => {
|
|||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
yield* events.publish(SessionEvent.Synthetic, {
|
||||
const { db } = yield* Database.Service
|
||||
const {
|
||||
id: _,
|
||||
type,
|
||||
...data
|
||||
} = encodeMessage({
|
||||
id: messageID,
|
||||
sessionID,
|
||||
messageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
type: "synthetic",
|
||||
text: "Existing history",
|
||||
time: { created: DateTime.makeUnsafe(0) },
|
||||
})
|
||||
yield* db
|
||||
.insert(SessionMessageTable)
|
||||
.values({ id: messageID, session_id: sessionID, type, seq: 0, time_created: 0, data })
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
const failure = yield* session
|
||||
.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "Conflicting prompt" }), resume: false })
|
||||
|
|
|
|||
|
|
@ -51,13 +51,13 @@ describe("toLLMMessages", () => {
|
|||
const file = FileAttachment.make({ uri: "data:image/png;base64,aGVsbG8=", mime: "image/png", name: "hello.png" })
|
||||
const messages = toLLMMessages(
|
||||
[
|
||||
SessionMessage.AgentSwitched.make({
|
||||
SessionMessage.AgentSelected.make({
|
||||
id: id("agent"),
|
||||
type: "agent-switched",
|
||||
agent: "build",
|
||||
time: { created },
|
||||
}),
|
||||
SessionMessage.ModelSwitched.make({
|
||||
SessionMessage.ModelSelected.make({
|
||||
id: id("model"),
|
||||
type: "model-switched",
|
||||
model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") },
|
||||
|
|
|
|||
|
|
@ -193,12 +193,12 @@ describe("SessionRunnerLLM recorded", () => {
|
|||
.orderBy(EventTable.seq)
|
||||
.all()).map((event) => event.type),
|
||||
).toEqual([
|
||||
"session.next.prompt.admitted.1",
|
||||
"session.next.prompted.1",
|
||||
"session.next.step.started.1",
|
||||
"session.next.text.started.1",
|
||||
"session.next.text.ended.1",
|
||||
"session.next.step.ended.2",
|
||||
"prompt.admitted.1",
|
||||
"prompt.promoted.1",
|
||||
"step.started.1",
|
||||
"text.started.1",
|
||||
"text.ended.1",
|
||||
"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 === "session.next.tool.success.1")
|
||||
const success = published.find((event) => event.type === "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 === "session.next.tool.success.1")
|
||||
const success = published.find((event) => event.type === "tool.success.1")
|
||||
expect(success?.data).toHaveProperty("result")
|
||||
})
|
||||
|
||||
|
|
@ -110,14 +110,13 @@ test("binary failure emits no success event", async () => {
|
|||
}),
|
||||
),
|
||||
)
|
||||
expect(published.some((event) => event.type === "session.next.tool.success.1")).toBe(false)
|
||||
expect(published.some((event) => event.type === "session.next.tool.failed.1")).toBe(true)
|
||||
expect(published.some((event) => event.type === "tool.success.1")).toBe(false)
|
||||
expect(published.some((event) => event.type === "tool.failed.1")).toBe(true)
|
||||
})
|
||||
|
||||
test("old success event data containing result still decodes", () => {
|
||||
const decoded = Schema.decodeUnknownSync(SessionEvent.Tool.Success.data)({
|
||||
sessionID,
|
||||
timestamp: Date.now(),
|
||||
assistantMessageID: SessionMessage.ID.create(),
|
||||
callID: "call-old",
|
||||
structured: { type: "media", mime: "image/png" },
|
||||
|
|
@ -133,6 +132,6 @@ test("step finish records settlement without publishing step ended", async () =>
|
|||
await Effect.runPromise(publisher.publish(LLMEvent.stepStart({ index: 0 })))
|
||||
await Effect.runPromise(publisher.publish(LLMEvent.stepFinish({ index: 0, reason: "stop" })))
|
||||
|
||||
expect(published.some((event) => event.type === "session.next.step.ended.2")).toBe(false)
|
||||
expect(published.some((event) => event.type === "step.ended.2")).toBe(false)
|
||||
expect(publisher.stepSettlement()).toMatchObject({ finish: "stop" })
|
||||
})
|
||||
|
|
|
|||
|
|
@ -423,6 +423,7 @@ const replaySessionProjection = (id: SessionV2.ID) =>
|
|||
yield* events.replayAll(
|
||||
recorded.map((event) => ({
|
||||
id: event.id,
|
||||
created: DateTime.makeUnsafe(event.created),
|
||||
aggregateID: event.aggregate_id,
|
||||
seq: event.seq,
|
||||
type: event.type,
|
||||
|
|
@ -740,7 +741,6 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
yield* events.publish(SessionEvent.Moved, {
|
||||
sessionID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
location: Location.Ref.make({ directory: AbsolutePath.make("/moved") }),
|
||||
})
|
||||
expect(
|
||||
|
|
@ -848,7 +848,7 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* db
|
||||
.select({ id: EventTable.id })
|
||||
.from(EventTable)
|
||||
.where(eq(EventTable.type, "session.next.context.updated.1"))
|
||||
.where(eq(EventTable.type, "session.context.updated.1"))
|
||||
.all()
|
||||
.pipe(Effect.orDie),
|
||||
).toHaveLength(1)
|
||||
|
|
@ -1010,10 +1010,8 @@ describe("SessionRunnerLLM", () => {
|
|||
response = []
|
||||
yield* session.resume(sessionID)
|
||||
skillBaselines.set(AgentV2.ID.make("reviewer"), "Reviewer skills")
|
||||
yield* events.publish(SessionEvent.AgentSwitched, {
|
||||
yield* events.publish(SessionEvent.AgentSelected, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
agent: "reviewer",
|
||||
})
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
||||
|
|
@ -1039,10 +1037,8 @@ describe("SessionRunnerLLM", () => {
|
|||
if (switched) return Effect.void
|
||||
switched = true
|
||||
return events
|
||||
.publish(SessionEvent.AgentSwitched, {
|
||||
.publish(SessionEvent.AgentSelected, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
agent: "reviewer",
|
||||
})
|
||||
.pipe(Effect.asVoid)
|
||||
|
|
@ -1069,10 +1065,8 @@ describe("SessionRunnerLLM", () => {
|
|||
if (switched) return Effect.void
|
||||
switched = true
|
||||
return events
|
||||
.publish(SessionEvent.ModelSwitched, {
|
||||
.publish(SessionEvent.ModelSelected, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
|
||||
})
|
||||
.pipe(Effect.asVoid)
|
||||
|
|
@ -1175,10 +1169,8 @@ describe("SessionRunnerLLM", () => {
|
|||
systemBaseline = "Changed context"
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
||||
yield* session.resume(sessionID)
|
||||
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||
yield* events.publish(SessionEvent.ModelSelected, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
|
||||
})
|
||||
systemBaseline = "Replacement context"
|
||||
|
|
@ -1217,10 +1209,8 @@ describe("SessionRunnerLLM", () => {
|
|||
requests.length = 0
|
||||
response = []
|
||||
yield* session.resume(sessionID)
|
||||
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||
yield* events.publish(SessionEvent.ModelSelected, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
|
||||
})
|
||||
systemUnavailable = true
|
||||
|
|
@ -1252,14 +1242,10 @@ describe("SessionRunnerLLM", () => {
|
|||
const compactionID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.Compaction.Started, {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
reason: "manual",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Compaction.Ended, {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(2),
|
||||
reason: "manual",
|
||||
text: "summary",
|
||||
recent: "",
|
||||
|
|
@ -1482,14 +1468,10 @@ describe("SessionRunnerLLM", () => {
|
|||
const compactionID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.Compaction.Started, {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
reason: "manual",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Compaction.Ended, {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(2),
|
||||
reason: "manual",
|
||||
text: "summary",
|
||||
recent: "",
|
||||
|
|
@ -1686,10 +1668,8 @@ describe("SessionRunnerLLM", () => {
|
|||
toolExecutionsReady = 1
|
||||
const run = yield* Effect.forkChild(session.resume(sessionID))
|
||||
yield* Deferred.await(toolExecutionsStarted)
|
||||
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||
yield* events.publish(SessionEvent.ModelSelected, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
|
||||
})
|
||||
systemBaseline = "Replacement context"
|
||||
|
|
@ -2403,27 +2383,23 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
agent: "build",
|
||||
model: { id: ModelV2.ID.make("fake-model"), providerID: ProviderV2.ID.make("fake") },
|
||||
})
|
||||
yield* events.publish(SessionEvent.Tool.Input.Started, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID,
|
||||
callID: "call-interrupted",
|
||||
name: "echo",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Tool.Input.Ended, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID,
|
||||
callID: "call-interrupted",
|
||||
text: '{"text":"stale"}',
|
||||
})
|
||||
yield* events.publish(SessionEvent.Tool.Called, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID,
|
||||
callID: "call-interrupted",
|
||||
tool: "echo",
|
||||
|
|
@ -2467,27 +2443,23 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
agent: "build",
|
||||
model: { id: ModelV2.ID.make("fake-model"), providerID: ProviderV2.ID.make("fake") },
|
||||
})
|
||||
yield* events.publish(SessionEvent.Tool.Input.Started, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID,
|
||||
callID: "call-hosted-interrupted",
|
||||
name: "web_search",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Tool.Input.Ended, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID,
|
||||
callID: "call-hosted-interrupted",
|
||||
text: '{"query":"stale"}',
|
||||
})
|
||||
yield* events.publish(SessionEvent.Tool.Called, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID,
|
||||
callID: "call-hosted-interrupted",
|
||||
tool: "web_search",
|
||||
|
|
@ -2527,13 +2499,11 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
agent: "build",
|
||||
model: { id: ModelV2.ID.make("fake-model"), providerID: ProviderV2.ID.make("fake") },
|
||||
})
|
||||
yield* events.publish(SessionEvent.Tool.Input.Started, {
|
||||
sessionID,
|
||||
timestamp: yield* DateTime.now,
|
||||
assistantMessageID,
|
||||
callID: "call-pending-interrupted",
|
||||
name: "echo",
|
||||
|
|
@ -2578,7 +2548,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const events = yield* EventV2.Service
|
||||
const defect = new Error("fail after prompt promotion")
|
||||
let fail = true
|
||||
yield* events.project(SessionEvent.Prompted, () => (fail ? Effect.die(defect) : Effect.void))
|
||||
yield* events.project(SessionEvent.PromptPromoted, () => (fail ? Effect.die(defect) : Effect.void))
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Recover promoted input" }), resume: false })
|
||||
|
||||
expect(yield* session.resume(sessionID).pipe(Effect.catchDefect(Effect.succeed))).toBe(defect)
|
||||
|
|
@ -2603,7 +2573,9 @@ describe("SessionRunnerLLM", () => {
|
|||
const session = yield* SessionV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
yield* events.listen((event) =>
|
||||
event.type === SessionEvent.Prompted.type ? Effect.die("fail after prompt promotion commits") : Effect.void,
|
||||
event.type === SessionEvent.PromptPromoted.type
|
||||
? Effect.die("fail after prompt promotion commits")
|
||||
: Effect.void,
|
||||
)
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
|
|
@ -3013,7 +2985,7 @@ describe("SessionRunnerLLM", () => {
|
|||
{ type: "user", text: "Interrupt provider" },
|
||||
{ type: "assistant", finish: "error", error: { type: "unknown", message: "Provider turn interrupted" } },
|
||||
])
|
||||
expect(yield* recordedEventTypes(sessionID)).toContain("session.next.step.failed.2")
|
||||
expect(yield* recordedEventTypes(sessionID)).toContain("step.failed.1")
|
||||
yield* session.interrupt(sessionID)
|
||||
}),
|
||||
)
|
||||
|
|
@ -3057,8 +3029,8 @@ describe("SessionRunnerLLM", () => {
|
|||
},
|
||||
])
|
||||
const eventTypes = yield* recordedEventTypes(sessionID)
|
||||
expect(eventTypes).toContain("session.next.step.failed.2")
|
||||
expect(eventTypes).not.toContain("session.next.step.ended.2")
|
||||
expect(eventTypes).toContain("step.failed.1")
|
||||
expect(eventTypes).not.toContain("step.ended.1")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -86,17 +86,13 @@ const prompt = (sessionID: SessionV2.ID, text: string) =>
|
|||
const messageID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.PromptAdmitted, {
|
||||
sessionID,
|
||||
messageID,
|
||||
timestamp: DateTime.makeUnsafe(0),
|
||||
inputID: messageID,
|
||||
prompt: Prompt.make({ text }),
|
||||
delivery: "steer",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Prompted, {
|
||||
yield* events.publish(SessionEvent.PromptPromoted, {
|
||||
sessionID,
|
||||
messageID,
|
||||
timestamp: DateTime.makeUnsafe(0),
|
||||
prompt: Prompt.make({ text }),
|
||||
delivery: "steer",
|
||||
inputID: messageID,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ describe("Tool.Progress", () => {
|
|||
yield* service.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp,
|
||||
agent: "build",
|
||||
model,
|
||||
})
|
||||
|
|
@ -69,14 +68,12 @@ describe("Tool.Progress", () => {
|
|||
Effect.gen(function* () {
|
||||
yield* service.publish(SessionEvent.Tool.Input.Started, {
|
||||
sessionID,
|
||||
timestamp,
|
||||
assistantMessageID,
|
||||
callID,
|
||||
name: "bash",
|
||||
})
|
||||
yield* service.publish(SessionEvent.Tool.Called, {
|
||||
sessionID,
|
||||
timestamp,
|
||||
assistantMessageID,
|
||||
callID,
|
||||
tool: "bash",
|
||||
|
|
@ -92,7 +89,6 @@ describe("Tool.Progress", () => {
|
|||
|
||||
yield* service.publish(SessionEvent.Tool.Progress, {
|
||||
sessionID,
|
||||
timestamp,
|
||||
assistantMessageID,
|
||||
callID: "call-success",
|
||||
structured: { phase: "checkpoint" },
|
||||
|
|
@ -104,7 +100,6 @@ describe("Tool.Progress", () => {
|
|||
|
||||
const success = yield* service.publish(SessionEvent.Tool.Success, {
|
||||
sessionID,
|
||||
timestamp,
|
||||
assistantMessageID,
|
||||
callID: "call-success",
|
||||
structured: { phase: "done" },
|
||||
|
|
@ -118,7 +113,6 @@ describe("Tool.Progress", () => {
|
|||
yield* start("call-failed")
|
||||
yield* service.publish(SessionEvent.Tool.Progress, {
|
||||
sessionID,
|
||||
timestamp,
|
||||
assistantMessageID,
|
||||
callID: "call-failed",
|
||||
structured: { phase: "checkpoint" },
|
||||
|
|
@ -126,7 +120,6 @@ describe("Tool.Progress", () => {
|
|||
})
|
||||
const failed = yield* service.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID,
|
||||
timestamp,
|
||||
assistantMessageID,
|
||||
callID: "call-failed",
|
||||
error: { type: "unknown", message: "boom" },
|
||||
|
|
|
|||
|
|
@ -148,8 +148,8 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
[coreSessionInput.Admitted, SessionInput.Admitted],
|
||||
[coreSessionMessage.ID, SessionMessage.ID],
|
||||
[coreSessionMessage.UnknownError, SessionMessage.UnknownError],
|
||||
[coreSessionMessage.AgentSwitched, SessionMessage.AgentSwitched],
|
||||
[coreSessionMessage.ModelSwitched, SessionMessage.ModelSwitched],
|
||||
[coreSessionMessage.AgentSelected, SessionMessage.AgentSelected],
|
||||
[coreSessionMessage.ModelSelected, SessionMessage.ModelSelected],
|
||||
[coreSessionMessage.User, SessionMessage.User],
|
||||
[coreSessionMessage.Synthetic, SessionMessage.Synthetic],
|
||||
[coreSessionMessage.System, SessionMessage.System],
|
||||
|
|
|
|||
|
|
@ -79,27 +79,23 @@ const executionNode = makeGlobalNode({
|
|||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID: id,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
agent: session.agent ?? AgentV2.ID.make("code"),
|
||||
model: sessionModel,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Text.Started, {
|
||||
sessionID: id,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
textID,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID: id,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
textID,
|
||||
text: "ok",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Step.Ended, {
|
||||
sessionID: id,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
finish: "stop",
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
|
|
|
|||
|
|
@ -53,27 +53,23 @@ const executionNode = makeGlobalNode({
|
|||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
agent: AgentV2.ID.make("reviewer"),
|
||||
model: childModel,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Text.Started, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
textID,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
textID,
|
||||
text: childText,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Step.Ended, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
finish: "stop",
|
||||
cost: 0,
|
||||
tokens,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue