@minpeter/pss-runtime 0.0.11 → 0.1.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +67 -5
  2. package/dist/agent-namespace.js +17 -0
  3. package/dist/agent-namespace.js.map +1 -0
  4. package/dist/agent-validation.js +35 -0
  5. package/dist/agent-validation.js.map +1 -0
  6. package/dist/agent.d.ts +11 -2
  7. package/dist/agent.js +79 -14
  8. package/dist/agent.js.map +1 -1
  9. package/dist/child-session-cleanups.js +61 -0
  10. package/dist/child-session-cleanups.js.map +1 -0
  11. package/dist/session/events.d.ts +21 -1
  12. package/dist/session/history.d.ts +1 -0
  13. package/dist/session/input-normalization.js +66 -0
  14. package/dist/session/input-normalization.js.map +1 -0
  15. package/dist/session/runtime-input.d.ts +1 -0
  16. package/dist/session/runtime-input.js +69 -0
  17. package/dist/session/runtime-input.js.map +1 -0
  18. package/dist/session/session-errors.js +23 -0
  19. package/dist/session/session-errors.js.map +1 -0
  20. package/dist/session/session-kill.js +23 -0
  21. package/dist/session/session-kill.js.map +1 -0
  22. package/dist/session/session-runtime-drain.js +22 -0
  23. package/dist/session/session-runtime-drain.js.map +1 -0
  24. package/dist/session/session-state.d.ts +1 -0
  25. package/dist/session/session-state.js +102 -0
  26. package/dist/session/session-state.js.map +1 -0
  27. package/dist/session/session-turn-error.js +35 -0
  28. package/dist/session/session-turn-error.js.map +1 -0
  29. package/dist/session/session.js +95 -240
  30. package/dist/session/session.js.map +1 -1
  31. package/dist/session/store/file.d.ts +1 -0
  32. package/dist/session/store/file.js +14 -0
  33. package/dist/session/store/file.js.map +1 -1
  34. package/dist/session/store/memory.d.ts +1 -0
  35. package/dist/session/store/memory.js +5 -0
  36. package/dist/session/store/memory.js.map +1 -1
  37. package/dist/session/store/types.d.ts +1 -0
  38. package/dist/subagent-job-cancel.js +28 -0
  39. package/dist/subagent-job-cancel.js.map +1 -0
  40. package/dist/subagent-job-output.js +63 -0
  41. package/dist/subagent-job-output.js.map +1 -0
  42. package/dist/subagent-jobs.js +151 -0
  43. package/dist/subagent-jobs.js.map +1 -0
  44. package/dist/subagent-prompt-schema.js +114 -0
  45. package/dist/subagent-prompt-schema.js.map +1 -0
  46. package/dist/subagent-run.js +111 -0
  47. package/dist/subagent-run.js.map +1 -0
  48. package/dist/subagents.js +92 -0
  49. package/dist/subagents.js.map +1 -0
  50. package/package.json +1 -1
@@ -1,36 +1,36 @@
1
1
  import { runAgentLoop } from "../agent-loop.js";
2
- import { ModelMessageHistory } from "./history.js";
2
+ import { normalizeAgentInput } from "./input-normalization.js";
3
3
  import { BufferedAgentRun } from "./run.js";
4
- import { decodeStoredSessionSnapshot, encodeSessionSnapshot } from "./snapshot.js";
4
+ import { addSteeringInput, closeRuntimeInput, createRuntimeInputState, hooksForRuntimeInput, withRuntimeInputWindow, withSteeringPlacement } from "./runtime-input.js";
5
+ import { errorMessage, runAfterTurnHook, sessionKilledError, sessionTerminalError } from "./session-errors.js";
6
+ import { closeKilledRuntimeInputs } from "./session-kill.js";
7
+ import { drainRuntimeInput } from "./session-runtime-drain.js";
8
+ import { SessionState } from "./session-state.js";
9
+ import { emitTurnErrorAfterRecovery } from "./session-turn-error.js";
5
10
  //#region src/session/session.ts
6
11
  var AgentSession = class {
7
12
  #hooks;
8
13
  #inputQueue = [];
9
14
  #llm;
10
- #persistence;
15
+ #pendingRuntimeInputs = [];
16
+ #state;
11
17
  #activeAbort;
12
18
  #activeRun;
13
19
  #activeRuntimeInput;
14
- #history = new ModelMessageHistory();
20
+ #deletePromise;
15
21
  #killed = false;
16
- #loadPromise;
17
- #loaded = false;
18
22
  #running = false;
19
23
  #runToCloseOnKill;
20
- #storeVersion;
21
24
  constructor(llm, persistence, hooks) {
22
25
  this.#hooks = hooks;
23
26
  this.#llm = llm;
24
- this.#persistence = persistence;
27
+ this.#state = new SessionState(persistence);
25
28
  }
26
29
  async send(input) {
27
- if (this.#killed) throw sessionKilledError();
28
- await this.#ensureLoaded();
29
- if (this.#killed) throw sessionKilledError();
30
- const runtimeInput = {
31
- pending: Promise.resolve(),
32
- queue: []
33
- };
30
+ if (this.#killed || this.#deletePromise) throw sessionTerminalError(this.#killed);
31
+ await this.#state.ensureLoaded();
32
+ if (this.#killed || this.#deletePromise) throw sessionTerminalError(this.#killed);
33
+ const runtimeInput = createRuntimeInputState(this.#pendingRuntimeInputs.splice(0));
34
34
  const acceptedInput = normalizeAgentInput(input);
35
35
  const run = new BufferedAgentRun();
36
36
  run.emit(acceptedInput);
@@ -49,57 +49,68 @@ var AgentSession = class {
49
49
  return run;
50
50
  }
51
51
  async steer(input) {
52
- if (this.#killed) throw sessionKilledError();
52
+ if (this.#killed || this.#deletePromise) throw sessionTerminalError(this.#killed);
53
53
  const runtimeInput = this.#activeRuntimeInput;
54
54
  const run = this.#activeRun;
55
55
  if (!(runtimeInput && run)) return this.send(input);
56
- await this.#addSteeringInput(runtimeInput, input);
56
+ await addSteeringInput(runtimeInput, input);
57
57
  return run;
58
58
  }
59
59
  interrupt() {
60
60
  this.#activeAbort?.abort();
61
61
  }
62
- kill() {
63
- if (this.#killed) return;
64
- this.#killed = true;
65
- const killedError = sessionKilledError();
66
- this.#activeAbort?.abort();
67
- this.#closeRuntimeInput(this.#activeRuntimeInput, killedError.message);
68
- const runToClose = this.#runToCloseOnKill ?? this.#activeRun;
69
- runToClose?.emit({
70
- type: "turn-error",
71
- message: killedError.message
62
+ delete() {
63
+ this.#deletePromise ??= this.#state.delete().then(() => this.kill(), (error) => {
64
+ this.#deletePromise = void 0;
65
+ throw error;
72
66
  });
73
- runToClose?.close(void 0, killedError.message);
74
- while (this.#inputQueue.length > 0) {
75
- const item = this.#inputQueue.shift();
76
- this.#closeRuntimeInput(item?.runtimeInput, killedError.message);
77
- item?.run.emit({
78
- type: "turn-error",
79
- message: killedError.message
67
+ return this.#deletePromise;
68
+ }
69
+ enqueueRuntimeInput(input, placement = "turn-start") {
70
+ if (this.#killed) return;
71
+ const runtimeInput = this.#activeRuntimeInput;
72
+ if (runtimeInput && !runtimeInput.closedReason) {
73
+ if (placement === "turn-start" && runtimeInput.placement !== placement) {
74
+ this.#enqueuePendingRuntimeInput({
75
+ input,
76
+ placement
77
+ });
78
+ return;
79
+ }
80
+ runtimeInput.queue.push({
81
+ input,
82
+ placement
80
83
  });
81
- item?.run.close(void 0, killedError.message);
84
+ return;
82
85
  }
86
+ this.#enqueuePendingRuntimeInput({
87
+ input,
88
+ placement
89
+ });
83
90
  }
84
- async #ensureLoaded() {
85
- if (this.#loaded) return;
86
- this.#loadPromise ??= this.#loadSessionState();
87
- try {
88
- await this.#loadPromise;
89
- } catch (error) {
90
- this.#loadPromise = void 0;
91
- throw error;
92
- }
91
+ emitObserverEvent(event) {
92
+ this.#activeRun?.emit(event);
93
93
  }
94
- async #loadSessionState() {
95
- if (this.#loaded) return;
96
- await this.#replaceWithStoredSession();
97
- this.#loaded = true;
94
+ #enqueuePendingRuntimeInput(input) {
95
+ const queuedTurn = this.#inputQueue[0];
96
+ if (input.placement === "turn-start" && queuedTurn) {
97
+ queuedTurn.runtimeInput.queue.push(input);
98
+ return;
99
+ }
100
+ this.#pendingRuntimeInputs.push(input);
98
101
  }
99
- async #replaceWithStoredSession() {
100
- const stored = await this.#persistence.store.load(this.#persistence.key);
101
- this.#storeVersion = stored?.version;
102
- this.#history = new ModelMessageHistory(decodeStoredSessionSnapshot(stored));
102
+ kill() {
103
+ if (this.#killed) return;
104
+ this.#killed = true;
105
+ const killedError = sessionKilledError();
106
+ this.#pendingRuntimeInputs.length = 0;
107
+ this.#activeAbort?.abort();
108
+ closeKilledRuntimeInputs({
109
+ activeRuntimeInput: this.#activeRuntimeInput,
110
+ inputQueue: this.#inputQueue,
111
+ message: killedError.message,
112
+ runToClose: this.#runToCloseOnKill ?? this.#activeRun
113
+ });
103
114
  }
104
115
  async #drainInputQueue() {
105
116
  if (this.#running) return;
@@ -119,79 +130,69 @@ var AgentSession = class {
119
130
  this.#activeRun = run;
120
131
  this.#activeRuntimeInput = runtimeInput;
121
132
  this.#runToCloseOnKill = run;
122
- const historySnapshot = this.#history.modelSnapshot();
133
+ const historySnapshot = this.#state.modelSnapshot();
123
134
  try {
124
- await this.#withSteeringPlacement(runtimeInput, "turn-start", async () => {
135
+ await withSteeringPlacement(runtimeInput, "turn-start", async () => {
125
136
  await this.#hooks?.beforeTurn?.({
126
- history: this.#history.modelSnapshot(),
137
+ history: this.#state.modelSnapshot(),
127
138
  input,
128
139
  signal: activeAbort.signal
129
140
  });
130
141
  });
131
- await this.#withRuntimeInputWindow(runtimeInput, "turn-start", async () => {
142
+ await withRuntimeInputWindow(runtimeInput, "turn-start", async () => {
132
143
  await run.emitBoundary({ type: "turn-start" });
133
144
  });
134
- this.#history.appendUserInput(input);
135
- await this.#commitHistory();
136
- await this.#drainRuntimeInput(run, runtimeInput, "turn-start");
145
+ this.#state.appendUserInput(input);
146
+ await this.#state.commit();
147
+ await drainRuntimeInput({
148
+ placement: "turn-start",
149
+ run,
150
+ runtimeInput,
151
+ state: this.#state
152
+ });
137
153
  const result = await runAgentLoop({
138
154
  emit: async (event) => {
139
155
  if (event.type === "step-start" || event.type === "step-end") {
140
- await this.#withRuntimeInputWindow(runtimeInput, event.type, async () => {
156
+ await withRuntimeInputWindow(runtimeInput, event.type, async () => {
141
157
  await run.emitBoundary(event);
142
158
  });
143
- const runtimeInputAdded = await this.#drainRuntimeInput(run, runtimeInput, event.type);
144
- if (event.type === "step-end") return { runtimeInputAdded };
145
- return;
159
+ const runtimeInputAdded = await drainRuntimeInput({
160
+ placement: event.type,
161
+ run,
162
+ runtimeInput,
163
+ state: this.#state
164
+ });
165
+ return event.type === "step-end" ? { runtimeInputAdded } : void 0;
146
166
  }
147
167
  run.emit(event);
148
168
  },
149
- history: this.#history,
150
- hooks: this.#hooksForRuntimeInput(runtimeInput),
169
+ history: this.#state.history,
170
+ hooks: hooksForRuntimeInput(this.#hooks, runtimeInput),
151
171
  llm: this.#llm,
152
172
  signal: activeAbort.signal
153
173
  });
154
- await this.#commitHistory();
174
+ await this.#state.commit();
155
175
  const terminalEvent = result === "aborted" ? "turn-abort" : "turn-end";
156
- this.#closeRuntimeInput(runtimeInput, terminalEvent);
176
+ closeRuntimeInput(runtimeInput, terminalEvent);
157
177
  this.#activeRuntimeInput = void 0;
158
178
  this.#activeRun = void 0;
159
179
  await runAfterTurnHook(this.#hooks, {
160
- history: this.#history.modelSnapshot(),
180
+ history: this.#state.modelSnapshot(),
161
181
  input,
162
182
  result,
163
183
  signal: activeAbort.signal
164
184
  });
165
185
  run.emit({ type: terminalEvent });
166
186
  } catch (error) {
167
- if (error instanceof SessionCommitConflictError) {
168
- run.emit({
169
- type: "turn-error",
170
- message: error.message
171
- });
172
- this.#closeRuntimeInput(runtimeInput, "a session commit conflict");
173
- this.#activeAbort = void 0;
174
- return;
175
- }
176
- this.#history.rollback(historySnapshot);
177
- try {
178
- await this.#commitHistory();
179
- } catch (rollbackError) {
180
- run.emit({
181
- type: "turn-error",
182
- message: `${errorMessage(error)}; history rollback persistence failed: ${errorMessage(rollbackError)}`
183
- });
184
- this.#closeRuntimeInput(runtimeInput, "turn-error");
185
- this.#activeAbort = void 0;
186
- return;
187
- }
188
- run.emit({
189
- type: "turn-error",
190
- message: errorMessage(error)
187
+ await emitTurnErrorAfterRecovery({
188
+ error: error instanceof Error ? error : new Error(String(error)),
189
+ historySnapshot,
190
+ run,
191
+ runtimeInput,
192
+ state: this.#state
191
193
  });
192
- this.#closeRuntimeInput(runtimeInput, "turn-error");
193
194
  } finally {
194
- this.#closeRuntimeInput(runtimeInput);
195
+ closeRuntimeInput(runtimeInput);
195
196
  this.#activeAbort = void 0;
196
197
  this.#activeRun = void 0;
197
198
  this.#activeRuntimeInput = void 0;
@@ -199,152 +200,6 @@ var AgentSession = class {
199
200
  run.close(void 0, runtimeInput.closedReason);
200
201
  }
201
202
  }
202
- #addSteeringInput(runtimeInput, input) {
203
- const next = runtimeInput.pending.then(() => {
204
- if (runtimeInput.closedReason) throw runtimeInputClosedError(runtimeInput.closedReason);
205
- runtimeInput.queue.push({
206
- input: normalizeAgentInput(input),
207
- placement: runtimeInput.steerPlacement ?? runtimeInput.placement ?? "step-end"
208
- });
209
- });
210
- runtimeInput.pending = next.catch(() => void 0);
211
- return next;
212
- }
213
- #closeRuntimeInput(runtimeInput, reason = "the run reached a terminal state") {
214
- if (!runtimeInput?.closedReason && runtimeInput) {
215
- runtimeInput.closedReason = reason;
216
- runtimeInput.placement = void 0;
217
- }
218
- }
219
- async #commitHistory() {
220
- const result = await this.#persistence.store.commit(this.#persistence.key, { state: encodeSessionSnapshot(this.#history.modelSnapshot()) }, { expectedVersion: this.#storeVersion ?? null });
221
- if (!result.ok) {
222
- await this.#replaceWithStoredSession();
223
- throw new SessionCommitConflictError(this.#persistence.key);
224
- }
225
- this.#storeVersion = result.version;
226
- }
227
- async #withRuntimeInputWindow(runtimeInput, placement, callback) {
228
- const previousSteerPlacement = runtimeInput.steerPlacement;
229
- runtimeInput.placement = placement;
230
- runtimeInput.steerPlacement = placement;
231
- try {
232
- return await callback();
233
- } finally {
234
- runtimeInput.placement = void 0;
235
- runtimeInput.steerPlacement = previousSteerPlacement;
236
- }
237
- }
238
- async #withSteeringPlacement(runtimeInput, placement, callback) {
239
- const previousSteerPlacement = runtimeInput.steerPlacement;
240
- runtimeInput.steerPlacement = placement;
241
- try {
242
- return await callback();
243
- } finally {
244
- runtimeInput.steerPlacement = previousSteerPlacement;
245
- }
246
- }
247
- #hooksForRuntimeInput(runtimeInput) {
248
- const hooks = this.#hooks;
249
- if (!hooks) return;
250
- return {
251
- ...hooks,
252
- afterStep: (context) => this.#withSteeringPlacement(runtimeInput, "step-end", async () => {
253
- await hooks.afterStep?.(context);
254
- }),
255
- beforeStep: (context) => this.#withSteeringPlacement(runtimeInput, "step-start", async () => {
256
- await hooks.beforeStep?.(context);
257
- })
258
- };
259
- }
260
- async #drainRuntimeInput(run, runtimeInput, placement) {
261
- let added = false;
262
- let next = shiftRuntimeInput(runtimeInput, placement);
263
- while (next) {
264
- added = true;
265
- run.emit({
266
- type: "runtime-input",
267
- input: next.input,
268
- placement
269
- });
270
- this.#history.appendUserInput(next.input);
271
- await this.#commitHistory();
272
- next = shiftRuntimeInput(runtimeInput, placement);
273
- }
274
- return added;
275
- }
276
- };
277
- function shiftRuntimeInput(runtimeInput, placement) {
278
- const index = runtimeInput.queue.findIndex((input) => input.placement === placement);
279
- if (index === -1) return;
280
- return runtimeInput.queue.splice(index, 1)[0];
281
- }
282
- async function runAfterTurnHook(hooks, context) {
283
- const hook = hooks?.afterTurn;
284
- if (!hook) return;
285
- await Promise.allSettled([Promise.resolve().then(() => hook(context))]);
286
- }
287
- function normalizeAgentInput(input) {
288
- if (typeof input === "string") return {
289
- type: "user-text",
290
- text: input
291
- };
292
- if (isStringArrayInput(input)) return {
293
- type: "user-text",
294
- text: structuredClone(input)
295
- };
296
- if (isArrayInput(input)) {
297
- assertUserMessageContent(input);
298
- return {
299
- type: "user-message",
300
- content: structuredClone(input)
301
- };
302
- }
303
- if (isUserMessage(input)) assertUserMessageContent(input.content);
304
- return structuredClone(input);
305
- }
306
- function isStringArrayInput(input) {
307
- return isArrayInput(input) && input.every((part) => typeof part === "string");
308
- }
309
- function isArrayInput(input) {
310
- return Array.isArray(input);
311
- }
312
- function isUserMessage(input) {
313
- return input.type === "user-message";
314
- }
315
- function assertUserMessageContent(input) {
316
- for (const part of input) if (!isUserMessageContentPart(part)) throw new TypeError("Agent input content parts must be { type: \"text\", text }, { type: \"image\", image }, or { type: \"file\", data, mediaType }.");
317
- }
318
- function isUserMessageContentPart(part) {
319
- if (part === null || typeof part !== "object" || !("type" in part)) return false;
320
- if (part.type === "text") return "text" in part && typeof part.text === "string";
321
- if (part.type === "image") return "image" in part && typeof part.image === "string" && (!("mediaType" in part) || typeof part.mediaType === "string");
322
- if (part.type === "file") return "data" in part && isUserMessageFileData(part.data) && "mediaType" in part && typeof part.mediaType === "string" && (!("filename" in part) || typeof part.filename === "string");
323
- return false;
324
- }
325
- function isUserMessageFileData(data) {
326
- if (typeof data === "string") return true;
327
- if (data === null || typeof data !== "object" || !("type" in data)) return false;
328
- if (data.type === "data") return "data" in data && typeof data.data === "string";
329
- if (data.type === "reference") return "reference" in data && data.reference !== null && typeof data.reference === "object" && Object.values(data.reference).every((value) => typeof value === "string");
330
- if (data.type === "text") return "text" in data && typeof data.text === "string";
331
- if (data.type === "url") return "url" in data && typeof data.url === "string";
332
- return false;
333
- }
334
- function errorMessage(error) {
335
- if (error instanceof Error) return error.message;
336
- return String(error);
337
- }
338
- function sessionKilledError() {
339
- return /* @__PURE__ */ new Error("Session killed");
340
- }
341
- function runtimeInputClosedError(reason) {
342
- return /* @__PURE__ */ new Error(`session.steer() cannot be used after ${reason}`);
343
- }
344
- var SessionCommitConflictError = class extends Error {
345
- constructor(key) {
346
- super(`Session ${JSON.stringify(key)} commit conflict`);
347
- }
348
203
  };
349
204
  //#endregion
350
205
  export { AgentSession };
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","names":["#hooks","#inputQueue","#llm","#persistence","#killed","#ensureLoaded","#drainInputQueue","#activeRuntimeInput","#activeRun","#addSteeringInput","#activeAbort","#closeRuntimeInput","#runToCloseOnKill","#loaded","#loadPromise","#loadSessionState","#replaceWithStoredSession","#storeVersion","#history","#running","#processQueuedInput","#withSteeringPlacement","#withRuntimeInputWindow","#commitHistory","#drainRuntimeInput","#hooksForRuntimeInput"],"sources":["../../src/session/session.ts"],"sourcesContent":["import { runAgentLoop } from \"../agent-loop\";\nimport type { AgentHooks } from \"../hooks\";\nimport type { Llm } from \"../llm\";\nimport type {\n RuntimeInput,\n UserMessage,\n UserMessageContentPart,\n} from \"./events\";\nimport { ModelMessageHistory } from \"./history\";\nimport type { AgentInput, UserInput } from \"./input\";\nimport type { AgentRun } from \"./run\";\nimport { BufferedAgentRun } from \"./run\";\nimport { decodeStoredSessionSnapshot, encodeSessionSnapshot } from \"./snapshot\";\nimport type { SessionStore } from \"./store/types\";\n\nexport type { AgentInput, SessionInput, UserInput } from \"./input\";\nexport type { AgentRun } from \"./run\";\n\ninterface SessionPersistenceOptions {\n readonly key: string;\n readonly store: SessionStore;\n}\n\ninterface QueuedInput {\n readonly input: UserInput;\n readonly run: BufferedAgentRun;\n readonly runtimeInput: RuntimeInputState;\n}\n\ntype RuntimeInputPlacement = RuntimeInput[\"placement\"];\nconst noBoundaryDecision = undefined;\n\ninterface QueuedRuntimeInput {\n readonly input: UserInput;\n readonly placement: RuntimeInputPlacement;\n}\n\ninterface RuntimeInputState {\n closedReason?: string;\n pending: Promise<void>;\n placement?: RuntimeInputPlacement;\n readonly queue: QueuedRuntimeInput[];\n steerPlacement?: RuntimeInputPlacement;\n}\n\nexport class AgentSession {\n readonly #hooks?: AgentHooks;\n readonly #inputQueue: QueuedInput[] = [];\n readonly #llm: Llm;\n readonly #persistence: SessionPersistenceOptions;\n #activeAbort?: AbortController;\n #activeRun?: BufferedAgentRun;\n #activeRuntimeInput?: RuntimeInputState;\n #history = new ModelMessageHistory();\n #killed = false;\n #loadPromise?: Promise<void>;\n #loaded = false;\n #running = false;\n #runToCloseOnKill?: BufferedAgentRun;\n #storeVersion: string | undefined;\n\n constructor(\n llm: Llm,\n persistence: SessionPersistenceOptions,\n hooks?: AgentHooks\n ) {\n this.#hooks = hooks;\n this.#llm = llm;\n this.#persistence = persistence;\n }\n\n async send(input: AgentInput): Promise<AgentRun> {\n if (this.#killed) {\n throw sessionKilledError();\n }\n\n await this.#ensureLoaded();\n\n if (this.#killed) {\n throw sessionKilledError();\n }\n\n const runtimeInput: RuntimeInputState = {\n pending: Promise.resolve(),\n queue: [],\n };\n const acceptedInput = normalizeAgentInput(input);\n const run = new BufferedAgentRun();\n run.emit(acceptedInput);\n this.#inputQueue.push({\n input: structuredClone(acceptedInput),\n run,\n runtimeInput,\n });\n this.#drainInputQueue().catch((error: unknown) => {\n run.emit({ type: \"turn-error\", message: errorMessage(error) });\n run.close();\n });\n return run;\n }\n\n async steer(input: AgentInput): Promise<AgentRun> {\n if (this.#killed) {\n throw sessionKilledError();\n }\n\n const runtimeInput = this.#activeRuntimeInput;\n const run = this.#activeRun;\n if (!(runtimeInput && run)) {\n return this.send(input);\n }\n\n await this.#addSteeringInput(runtimeInput, input);\n return run;\n }\n\n interrupt(): void {\n this.#activeAbort?.abort();\n }\n\n kill(): void {\n if (this.#killed) {\n return;\n }\n\n this.#killed = true;\n const killedError = sessionKilledError();\n this.#activeAbort?.abort();\n this.#closeRuntimeInput(this.#activeRuntimeInput, killedError.message);\n const runToClose = this.#runToCloseOnKill ?? this.#activeRun;\n runToClose?.emit({\n type: \"turn-error\",\n message: killedError.message,\n });\n runToClose?.close(undefined, killedError.message);\n\n while (this.#inputQueue.length > 0) {\n const item = this.#inputQueue.shift();\n this.#closeRuntimeInput(item?.runtimeInput, killedError.message);\n item?.run.emit({\n type: \"turn-error\",\n message: killedError.message,\n });\n item?.run.close(undefined, killedError.message);\n }\n }\n\n async #ensureLoaded(): Promise<void> {\n if (this.#loaded) {\n return;\n }\n\n this.#loadPromise ??= this.#loadSessionState();\n try {\n await this.#loadPromise;\n } catch (error) {\n this.#loadPromise = undefined;\n throw error;\n }\n }\n\n async #loadSessionState(): Promise<void> {\n if (this.#loaded) {\n return;\n }\n\n await this.#replaceWithStoredSession();\n this.#loaded = true;\n }\n\n async #replaceWithStoredSession(): Promise<void> {\n const stored = await this.#persistence.store.load(this.#persistence.key);\n this.#storeVersion = stored?.version;\n this.#history = new ModelMessageHistory(\n decodeStoredSessionSnapshot(stored)\n );\n }\n\n async #drainInputQueue(): Promise<void> {\n if (this.#running) {\n return;\n }\n\n this.#running = true;\n try {\n while (!this.#killed && this.#inputQueue.length > 0) {\n const item = this.#inputQueue.shift();\n if (item) {\n await this.#processQueuedInput(item);\n }\n }\n } finally {\n this.#running = false;\n }\n }\n\n async #processQueuedInput({\n input,\n run,\n runtimeInput,\n }: QueuedInput): Promise<void> {\n const activeAbort = new AbortController();\n this.#activeAbort = activeAbort;\n this.#activeRun = run;\n this.#activeRuntimeInput = runtimeInput;\n this.#runToCloseOnKill = run;\n const historySnapshot = this.#history.modelSnapshot();\n\n try {\n await this.#withSteeringPlacement(\n runtimeInput,\n \"turn-start\",\n async () => {\n await this.#hooks?.beforeTurn?.({\n history: this.#history.modelSnapshot(),\n input,\n signal: activeAbort.signal,\n });\n }\n );\n await this.#withRuntimeInputWindow(\n runtimeInput,\n \"turn-start\",\n async () => {\n await run.emitBoundary({ type: \"turn-start\" });\n }\n );\n this.#history.appendUserInput(input);\n await this.#commitHistory();\n await this.#drainRuntimeInput(run, runtimeInput, \"turn-start\");\n\n const result = await runAgentLoop({\n emit: async (event) => {\n if (event.type === \"step-start\" || event.type === \"step-end\") {\n await this.#withRuntimeInputWindow(\n runtimeInput,\n event.type,\n async () => {\n await run.emitBoundary(event);\n }\n );\n const runtimeInputAdded = await this.#drainRuntimeInput(\n run,\n runtimeInput,\n event.type\n );\n\n if (event.type === \"step-end\") {\n return { runtimeInputAdded };\n }\n return noBoundaryDecision;\n }\n\n run.emit(event);\n },\n history: this.#history,\n hooks: this.#hooksForRuntimeInput(runtimeInput),\n llm: this.#llm,\n signal: activeAbort.signal,\n });\n\n await this.#commitHistory();\n const terminalEvent = result === \"aborted\" ? \"turn-abort\" : \"turn-end\";\n this.#closeRuntimeInput(runtimeInput, terminalEvent);\n this.#activeRuntimeInput = undefined;\n this.#activeRun = undefined;\n await runAfterTurnHook(this.#hooks, {\n history: this.#history.modelSnapshot(),\n input,\n result,\n signal: activeAbort.signal,\n });\n run.emit({ type: terminalEvent });\n } catch (error) {\n if (error instanceof SessionCommitConflictError) {\n run.emit({ type: \"turn-error\", message: error.message });\n this.#closeRuntimeInput(runtimeInput, \"a session commit conflict\");\n this.#activeAbort = undefined;\n return;\n }\n\n this.#history.rollback(historySnapshot);\n try {\n await this.#commitHistory();\n } catch (rollbackError) {\n run.emit({\n type: \"turn-error\",\n message: `${errorMessage(error)}; history rollback persistence failed: ${errorMessage(\n rollbackError\n )}`,\n });\n this.#closeRuntimeInput(runtimeInput, \"turn-error\");\n this.#activeAbort = undefined;\n return;\n }\n run.emit({ type: \"turn-error\", message: errorMessage(error) });\n this.#closeRuntimeInput(runtimeInput, \"turn-error\");\n } finally {\n this.#closeRuntimeInput(runtimeInput);\n this.#activeAbort = undefined;\n this.#activeRun = undefined;\n this.#activeRuntimeInput = undefined;\n this.#runToCloseOnKill = undefined;\n run.close(undefined, runtimeInput.closedReason);\n }\n }\n\n #addSteeringInput(\n runtimeInput: RuntimeInputState,\n input: AgentInput\n ): Promise<void> {\n const next = runtimeInput.pending.then(() => {\n if (runtimeInput.closedReason) {\n throw runtimeInputClosedError(runtimeInput.closedReason);\n }\n\n runtimeInput.queue.push({\n input: normalizeAgentInput(input),\n placement:\n runtimeInput.steerPlacement ?? runtimeInput.placement ?? \"step-end\",\n });\n });\n runtimeInput.pending = next.catch(() => undefined);\n return next;\n }\n\n #closeRuntimeInput(\n runtimeInput: RuntimeInputState | undefined,\n reason = \"the run reached a terminal state\"\n ): void {\n if (!runtimeInput?.closedReason && runtimeInput) {\n runtimeInput.closedReason = reason;\n runtimeInput.placement = undefined;\n }\n }\n\n async #commitHistory(): Promise<void> {\n const result = await this.#persistence.store.commit(\n this.#persistence.key,\n {\n state: encodeSessionSnapshot(this.#history.modelSnapshot()),\n },\n { expectedVersion: this.#storeVersion ?? null }\n );\n\n if (!result.ok) {\n await this.#replaceWithStoredSession();\n throw new SessionCommitConflictError(this.#persistence.key);\n }\n\n this.#storeVersion = result.version;\n }\n\n async #withRuntimeInputWindow<T>(\n runtimeInput: RuntimeInputState,\n placement: RuntimeInputPlacement,\n callback: () => Promise<T>\n ): Promise<T> {\n const previousSteerPlacement = runtimeInput.steerPlacement;\n runtimeInput.placement = placement;\n runtimeInput.steerPlacement = placement;\n try {\n return await callback();\n } finally {\n runtimeInput.placement = undefined;\n runtimeInput.steerPlacement = previousSteerPlacement;\n }\n }\n\n async #withSteeringPlacement<T>(\n runtimeInput: RuntimeInputState,\n placement: RuntimeInputPlacement,\n callback: () => Promise<T>\n ): Promise<T> {\n const previousSteerPlacement = runtimeInput.steerPlacement;\n runtimeInput.steerPlacement = placement;\n try {\n return await callback();\n } finally {\n runtimeInput.steerPlacement = previousSteerPlacement;\n }\n }\n\n #hooksForRuntimeInput(\n runtimeInput: RuntimeInputState\n ): AgentHooks | undefined {\n const hooks = this.#hooks;\n if (!hooks) {\n return;\n }\n\n return {\n ...hooks,\n afterStep: (context) =>\n this.#withSteeringPlacement(runtimeInput, \"step-end\", async () => {\n await hooks.afterStep?.(context);\n }),\n beforeStep: (context) =>\n this.#withSteeringPlacement(runtimeInput, \"step-start\", async () => {\n await hooks.beforeStep?.(context);\n }),\n };\n }\n\n async #drainRuntimeInput(\n run: BufferedAgentRun,\n runtimeInput: RuntimeInputState,\n placement: RuntimeInputPlacement\n ): Promise<boolean> {\n let added = false;\n let next = shiftRuntimeInput(runtimeInput, placement);\n while (next) {\n added = true;\n run.emit({ type: \"runtime-input\", input: next.input, placement });\n this.#history.appendUserInput(next.input);\n await this.#commitHistory();\n next = shiftRuntimeInput(runtimeInput, placement);\n }\n\n return added;\n }\n}\n\nfunction shiftRuntimeInput(\n runtimeInput: RuntimeInputState,\n placement: RuntimeInputPlacement\n): QueuedRuntimeInput | undefined {\n const index = runtimeInput.queue.findIndex(\n (input) => input.placement === placement\n );\n if (index === -1) {\n return;\n }\n\n return runtimeInput.queue.splice(index, 1)[0];\n}\n\nasync function runAfterTurnHook(\n hooks: AgentHooks | undefined,\n context: Parameters<NonNullable<AgentHooks[\"afterTurn\"]>>[0]\n): Promise<void> {\n const hook = hooks?.afterTurn;\n if (!hook) {\n return;\n }\n\n await Promise.allSettled([Promise.resolve().then(() => hook(context))]);\n}\n\nexport function normalizeAgentInput(input: AgentInput): UserInput {\n if (typeof input === \"string\") {\n return {\n type: \"user-text\",\n text: input,\n };\n }\n\n if (isStringArrayInput(input)) {\n return {\n type: \"user-text\",\n text: structuredClone(input) as readonly string[],\n };\n }\n\n if (isArrayInput(input)) {\n assertUserMessageContent(input);\n return {\n type: \"user-message\",\n content: structuredClone(input) as readonly UserMessageContentPart[],\n };\n }\n\n if (isUserMessage(input)) {\n assertUserMessageContent(input.content);\n }\n\n return structuredClone(input);\n}\n\nfunction isStringArrayInput(input: AgentInput): input is readonly string[] {\n return isArrayInput(input) && input.every((part) => typeof part === \"string\");\n}\n\nfunction isArrayInput(\n input: AgentInput\n): input is readonly string[] | readonly UserMessageContentPart[] {\n return Array.isArray(input);\n}\n\nfunction isUserMessage(input: UserInput): input is UserMessage {\n return input.type === \"user-message\";\n}\n\nfunction assertUserMessageContent(\n input: readonly unknown[]\n): asserts input is readonly UserMessageContentPart[] {\n for (const part of input) {\n if (!isUserMessageContentPart(part)) {\n throw new TypeError(\n 'Agent input content parts must be { type: \"text\", text }, { type: \"image\", image }, or { type: \"file\", data, mediaType }.'\n );\n }\n }\n}\n\nfunction isUserMessageContentPart(\n part: unknown\n): part is UserMessageContentPart {\n if (part === null || typeof part !== \"object\" || !(\"type\" in part)) {\n return false;\n }\n\n if (part.type === \"text\") {\n return \"text\" in part && typeof part.text === \"string\";\n }\n\n if (part.type === \"image\") {\n return (\n \"image\" in part &&\n typeof part.image === \"string\" &&\n (!(\"mediaType\" in part) || typeof part.mediaType === \"string\")\n );\n }\n\n if (part.type === \"file\") {\n return (\n \"data\" in part &&\n isUserMessageFileData(part.data) &&\n \"mediaType\" in part &&\n typeof part.mediaType === \"string\" &&\n (!(\"filename\" in part) || typeof part.filename === \"string\")\n );\n }\n\n return false;\n}\n\nfunction isUserMessageFileData(data: unknown): boolean {\n if (typeof data === \"string\") {\n return true;\n }\n\n if (data === null || typeof data !== \"object\" || !(\"type\" in data)) {\n return false;\n }\n\n if (data.type === \"data\") {\n return \"data\" in data && typeof data.data === \"string\";\n }\n\n if (data.type === \"reference\") {\n return (\n \"reference\" in data &&\n data.reference !== null &&\n typeof data.reference === \"object\" &&\n Object.values(data.reference).every((value) => typeof value === \"string\")\n );\n }\n\n if (data.type === \"text\") {\n return \"text\" in data && typeof data.text === \"string\";\n }\n\n if (data.type === \"url\") {\n return \"url\" in data && typeof data.url === \"string\";\n }\n\n return false;\n}\n\nfunction errorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n\nfunction sessionKilledError(): Error {\n return new Error(\"Session killed\");\n}\n\nfunction runtimeInputClosedError(reason: string): Error {\n return new Error(`session.steer() cannot be used after ${reason}`);\n}\n\nclass SessionCommitConflictError extends Error {\n constructor(key: string) {\n super(`Session ${JSON.stringify(key)} commit conflict`);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session.js","names":["#hooks","#inputQueue","#llm","#pendingRuntimeInputs","#state","#killed","#deletePromise","#drainInputQueue","#activeRuntimeInput","#activeRun","#activeAbort","#enqueuePendingRuntimeInput","#runToCloseOnKill","#running","#processQueuedInput"],"sources":["../../src/session/session.ts"],"sourcesContent":["import { runAgentLoop } from \"../agent-loop\";\nimport type { AgentHooks } from \"../hooks\";\nimport type { Llm } from \"../llm\";\nimport type { AgentEvent } from \"./events\";\nimport type { AgentInput, UserInput } from \"./input\";\nimport { normalizeAgentInput } from \"./input-normalization\";\nimport { type AgentRun, BufferedAgentRun } from \"./run\";\nimport {\n addSteeringInput,\n closeRuntimeInput,\n createRuntimeInputState,\n hooksForRuntimeInput,\n type QueuedInput,\n type QueuedRuntimeInput,\n type RuntimeInputPlacement,\n type RuntimeInputState,\n withRuntimeInputWindow,\n withSteeringPlacement,\n} from \"./runtime-input\";\nimport {\n errorMessage,\n runAfterTurnHook,\n sessionKilledError,\n sessionTerminalError,\n} from \"./session-errors\";\nimport { closeKilledRuntimeInputs } from \"./session-kill\";\nimport { drainRuntimeInput } from \"./session-runtime-drain\";\nimport { type SessionPersistenceOptions, SessionState } from \"./session-state\";\nimport { emitTurnErrorAfterRecovery } from \"./session-turn-error\";\n\nexport type { AgentInput, SessionInput, UserInput } from \"./input\";\nexport type { AgentRun } from \"./run\";\n\nexport class AgentSession {\n readonly #hooks?: AgentHooks;\n readonly #inputQueue: QueuedInput[] = [];\n readonly #llm: Llm;\n readonly #pendingRuntimeInputs: QueuedRuntimeInput[] = [];\n readonly #state: SessionState;\n #activeAbort?: AbortController;\n #activeRun?: BufferedAgentRun;\n #activeRuntimeInput?: RuntimeInputState;\n #deletePromise?: Promise<void>;\n #killed = false;\n #running = false;\n #runToCloseOnKill?: BufferedAgentRun;\n\n constructor(\n llm: Llm,\n persistence: SessionPersistenceOptions,\n hooks?: AgentHooks\n ) {\n this.#hooks = hooks;\n this.#llm = llm;\n this.#state = new SessionState(persistence);\n }\n\n async send(input: AgentInput): Promise<AgentRun> {\n if (this.#killed || this.#deletePromise) {\n throw sessionTerminalError(this.#killed);\n }\n\n await this.#state.ensureLoaded();\n\n if (this.#killed || this.#deletePromise) {\n throw sessionTerminalError(this.#killed);\n }\n\n const runtimeInput = createRuntimeInputState(\n this.#pendingRuntimeInputs.splice(0)\n );\n const acceptedInput = normalizeAgentInput(input);\n const run = new BufferedAgentRun();\n run.emit(acceptedInput);\n this.#inputQueue.push({\n input: structuredClone(acceptedInput),\n run,\n runtimeInput,\n });\n this.#drainInputQueue().catch((error: unknown) => {\n run.emit({ type: \"turn-error\", message: errorMessage(error) });\n run.close();\n });\n return run;\n }\n\n async steer(input: AgentInput): Promise<AgentRun> {\n if (this.#killed || this.#deletePromise) {\n throw sessionTerminalError(this.#killed);\n }\n\n const runtimeInput = this.#activeRuntimeInput;\n const run = this.#activeRun;\n if (!(runtimeInput && run)) {\n return this.send(input);\n }\n\n await addSteeringInput(runtimeInput, input);\n return run;\n }\n\n interrupt(): void {\n this.#activeAbort?.abort();\n }\n\n delete(): Promise<void> {\n this.#deletePromise ??= this.#state.delete().then(\n () => this.kill(),\n (error: unknown) => {\n this.#deletePromise = undefined;\n throw error;\n }\n );\n return this.#deletePromise;\n }\n\n enqueueRuntimeInput(\n input: UserInput,\n placement: RuntimeInputPlacement = \"turn-start\"\n ): void {\n if (this.#killed) {\n return;\n }\n\n const runtimeInput = this.#activeRuntimeInput;\n if (runtimeInput && !runtimeInput.closedReason) {\n if (placement === \"turn-start\" && runtimeInput.placement !== placement) {\n this.#enqueuePendingRuntimeInput({ input, placement });\n return;\n }\n\n runtimeInput.queue.push({ input, placement });\n return;\n }\n\n this.#enqueuePendingRuntimeInput({ input, placement });\n }\n\n emitObserverEvent(event: AgentEvent): void {\n this.#activeRun?.emit(event);\n }\n\n #enqueuePendingRuntimeInput(input: QueuedRuntimeInput): void {\n const queuedTurn = this.#inputQueue[0];\n if (input.placement === \"turn-start\" && queuedTurn) {\n queuedTurn.runtimeInput.queue.push(input);\n return;\n }\n\n this.#pendingRuntimeInputs.push(input);\n }\n\n kill(): void {\n if (this.#killed) {\n return;\n }\n\n this.#killed = true;\n const killedError = sessionKilledError();\n this.#pendingRuntimeInputs.length = 0;\n this.#activeAbort?.abort();\n closeKilledRuntimeInputs({\n activeRuntimeInput: this.#activeRuntimeInput,\n inputQueue: this.#inputQueue,\n message: killedError.message,\n runToClose: this.#runToCloseOnKill ?? this.#activeRun,\n });\n }\n\n async #drainInputQueue(): Promise<void> {\n if (this.#running) {\n return;\n }\n\n this.#running = true;\n try {\n while (!this.#killed && this.#inputQueue.length > 0) {\n const item = this.#inputQueue.shift();\n if (item) {\n await this.#processQueuedInput(item);\n }\n }\n } finally {\n this.#running = false;\n }\n }\n\n async #processQueuedInput({\n input,\n run,\n runtimeInput,\n }: QueuedInput): Promise<void> {\n const activeAbort = new AbortController();\n this.#activeAbort = activeAbort;\n this.#activeRun = run;\n this.#activeRuntimeInput = runtimeInput;\n this.#runToCloseOnKill = run;\n const historySnapshot = this.#state.modelSnapshot();\n\n try {\n await withSteeringPlacement(runtimeInput, \"turn-start\", async () => {\n await this.#hooks?.beforeTurn?.({\n history: this.#state.modelSnapshot(),\n input,\n signal: activeAbort.signal,\n });\n });\n await withRuntimeInputWindow(runtimeInput, \"turn-start\", async () => {\n await run.emitBoundary({ type: \"turn-start\" });\n });\n this.#state.appendUserInput(input);\n await this.#state.commit();\n await drainRuntimeInput({\n placement: \"turn-start\",\n run,\n runtimeInput,\n state: this.#state,\n });\n\n const result = await runAgentLoop({\n emit: async (event) => {\n if (event.type === \"step-start\" || event.type === \"step-end\") {\n await withRuntimeInputWindow(runtimeInput, event.type, async () => {\n await run.emitBoundary(event);\n });\n const runtimeInputAdded = await drainRuntimeInput({\n placement: event.type,\n run,\n runtimeInput,\n state: this.#state,\n });\n\n return event.type === \"step-end\"\n ? { runtimeInputAdded }\n : undefined;\n }\n\n run.emit(event);\n },\n history: this.#state.history,\n hooks: hooksForRuntimeInput(this.#hooks, runtimeInput),\n llm: this.#llm,\n signal: activeAbort.signal,\n });\n\n await this.#state.commit();\n const terminalEvent = result === \"aborted\" ? \"turn-abort\" : \"turn-end\";\n closeRuntimeInput(runtimeInput, terminalEvent);\n this.#activeRuntimeInput = undefined;\n this.#activeRun = undefined;\n await runAfterTurnHook(this.#hooks, {\n history: this.#state.modelSnapshot(),\n input,\n result,\n signal: activeAbort.signal,\n });\n run.emit({ type: terminalEvent });\n } catch (error) {\n const turnError =\n error instanceof Error ? error : new Error(String(error));\n await emitTurnErrorAfterRecovery({\n error: turnError,\n historySnapshot,\n run,\n runtimeInput,\n state: this.#state,\n });\n } finally {\n closeRuntimeInput(runtimeInput);\n this.#activeAbort = undefined;\n this.#activeRun = undefined;\n this.#activeRuntimeInput = undefined;\n this.#runToCloseOnKill = undefined;\n run.close(undefined, runtimeInput.closedReason);\n }\n }\n}\n"],"mappings":"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"}
@@ -8,6 +8,7 @@ declare class FileSessionStore implements SessionStore {
8
8
  commit(key: string, next: SessionStoreCommit, options: {
9
9
  expectedVersion: string | null;
10
10
  }): Promise<CommitResult>;
11
+ delete(key: string): Promise<void>;
11
12
  }
12
13
  //#endregion
13
14
  export { FileSessionStore };
@@ -57,6 +57,20 @@ var FileSessionStore = class {
57
57
  });
58
58
  }
59
59
  }
60
+ async delete(key) {
61
+ const file = this.#fileForKey(key);
62
+ const lockDirectory = `${file}.lock`;
63
+ await mkdir(dirname(file), { recursive: true });
64
+ await acquireFileLock(lockDirectory);
65
+ try {
66
+ await rm(file, { force: true });
67
+ } finally {
68
+ await rm(lockDirectory, {
69
+ force: true,
70
+ recursive: true
71
+ });
72
+ }
73
+ }
60
74
  #fileForKey(key) {
61
75
  return join(this.#directory, `${Buffer.from(key).toString("base64url")}.json`);
62
76
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file.js","names":["#directory","#fileForKey"],"sources":["../../../src/session/store/file.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { setTimeout } from \"node:timers/promises\";\nimport type {\n CommitResult,\n SessionStore,\n SessionStoreCommit,\n StoredSession,\n} from \"./types\";\n\nconst LOCK_POLL_INTERVAL_MS = 10;\nconst LOCK_STALE_AFTER_MS = 30_000;\nconst LOCK_TIMEOUT_MS = 5000;\n\nexport class FileSessionStore implements SessionStore {\n readonly #directory: string;\n\n constructor(directory: string) {\n this.#directory = directory;\n }\n\n async load(key: string): Promise<StoredSession | null> {\n const file = this.#fileForKey(key);\n\n try {\n const parsed = JSON.parse(await readFile(file, \"utf8\")) as unknown;\n return parseStoredFileSession(parsed, file);\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return null;\n }\n if (error instanceof SyntaxError) {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: invalid JSON (${error.message})`\n );\n }\n throw error;\n }\n }\n\n async commit(\n key: string,\n next: SessionStoreCommit,\n options: { expectedVersion: string | null }\n ): Promise<CommitResult> {\n const file = this.#fileForKey(key);\n const lockDirectory = `${file}.lock`;\n await mkdir(dirname(file), { recursive: true });\n await acquireFileLock(lockDirectory);\n try {\n const current = await this.load(key);\n const currentVersion = current?.version ?? null;\n\n if (options.expectedVersion !== currentVersion) {\n return { ok: false, reason: \"conflict\" };\n }\n\n const version = String((Number(current?.version ?? \"0\") || 0) + 1);\n const payload: StoredSession = structuredClone({\n state: next.state,\n version,\n });\n const tempFile = `${file}.${process.pid}.${randomUUID()}.tmp`;\n\n try {\n await writeFile(\n tempFile,\n `${JSON.stringify(payload, null, 2)}\\n`,\n \"utf8\"\n );\n await rename(tempFile, file);\n } catch (error) {\n await rm(tempFile, { force: true }).catch(() => undefined);\n throw error;\n }\n\n return { ok: true, version };\n } finally {\n await rm(lockDirectory, { force: true, recursive: true });\n }\n }\n\n #fileForKey(key: string): string {\n return join(\n this.#directory,\n `${Buffer.from(key).toString(\"base64url\")}.json`\n );\n }\n}\n\nfunction parseStoredFileSession(value: unknown, file: string): StoredSession {\n if (value === null || typeof value !== \"object\") {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: expected an object`\n );\n }\n\n const candidate = value as Partial<StoredSession>;\n if (typeof candidate.version !== \"string\" || !(\"state\" in candidate)) {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: expected state and string version`\n );\n }\n\n return structuredClone({\n state: candidate.state,\n version: candidate.version,\n });\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error;\n}\n\nasync function acquireFileLock(lockDirectory: string): Promise<void> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < LOCK_TIMEOUT_MS) {\n try {\n await mkdir(lockDirectory);\n return;\n } catch (error) {\n if (!(isNodeError(error) && error.code === \"EEXIST\")) {\n throw error;\n }\n await removeStaleLock(lockDirectory);\n }\n\n await setTimeout(LOCK_POLL_INTERVAL_MS);\n }\n\n throw new Error(\n `Timed out waiting for FileSessionStore lock ${JSON.stringify(\n lockDirectory\n )}`\n );\n}\n\nasync function removeStaleLock(lockDirectory: string): Promise<void> {\n try {\n const stats = await stat(lockDirectory);\n if (Date.now() - stats.mtimeMs < LOCK_STALE_AFTER_MS) {\n return;\n }\n await rm(lockDirectory, { force: true, recursive: true });\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"file.js","names":["#directory","#fileForKey"],"sources":["../../../src/session/store/file.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { setTimeout } from \"node:timers/promises\";\nimport type {\n CommitResult,\n SessionStore,\n SessionStoreCommit,\n StoredSession,\n} from \"./types\";\n\nconst LOCK_POLL_INTERVAL_MS = 10;\nconst LOCK_STALE_AFTER_MS = 30_000;\nconst LOCK_TIMEOUT_MS = 5000;\n\nexport class FileSessionStore implements SessionStore {\n readonly #directory: string;\n\n constructor(directory: string) {\n this.#directory = directory;\n }\n\n async load(key: string): Promise<StoredSession | null> {\n const file = this.#fileForKey(key);\n\n try {\n const parsed = JSON.parse(await readFile(file, \"utf8\")) as unknown;\n return parseStoredFileSession(parsed, file);\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return null;\n }\n if (error instanceof SyntaxError) {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: invalid JSON (${error.message})`\n );\n }\n throw error;\n }\n }\n\n async commit(\n key: string,\n next: SessionStoreCommit,\n options: { expectedVersion: string | null }\n ): Promise<CommitResult> {\n const file = this.#fileForKey(key);\n const lockDirectory = `${file}.lock`;\n await mkdir(dirname(file), { recursive: true });\n await acquireFileLock(lockDirectory);\n try {\n const current = await this.load(key);\n const currentVersion = current?.version ?? null;\n\n if (options.expectedVersion !== currentVersion) {\n return { ok: false, reason: \"conflict\" };\n }\n\n const version = String((Number(current?.version ?? \"0\") || 0) + 1);\n const payload: StoredSession = structuredClone({\n state: next.state,\n version,\n });\n const tempFile = `${file}.${process.pid}.${randomUUID()}.tmp`;\n\n try {\n await writeFile(\n tempFile,\n `${JSON.stringify(payload, null, 2)}\\n`,\n \"utf8\"\n );\n await rename(tempFile, file);\n } catch (error) {\n await rm(tempFile, { force: true }).catch(() => undefined);\n throw error;\n }\n\n return { ok: true, version };\n } finally {\n await rm(lockDirectory, { force: true, recursive: true });\n }\n }\n\n async delete(key: string): Promise<void> {\n const file = this.#fileForKey(key);\n const lockDirectory = `${file}.lock`;\n await mkdir(dirname(file), { recursive: true });\n await acquireFileLock(lockDirectory);\n try {\n await rm(file, { force: true });\n } finally {\n await rm(lockDirectory, { force: true, recursive: true });\n }\n }\n\n #fileForKey(key: string): string {\n return join(\n this.#directory,\n `${Buffer.from(key).toString(\"base64url\")}.json`\n );\n }\n}\n\nfunction parseStoredFileSession(value: unknown, file: string): StoredSession {\n if (value === null || typeof value !== \"object\") {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: expected an object`\n );\n }\n\n const candidate = value as Partial<StoredSession>;\n if (typeof candidate.version !== \"string\" || !(\"state\" in candidate)) {\n throw new Error(\n `Invalid FileSessionStore file ${JSON.stringify(\n file\n )}: expected state and string version`\n );\n }\n\n return structuredClone({\n state: candidate.state,\n version: candidate.version,\n });\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error;\n}\n\nasync function acquireFileLock(lockDirectory: string): Promise<void> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < LOCK_TIMEOUT_MS) {\n try {\n await mkdir(lockDirectory);\n return;\n } catch (error) {\n if (!(isNodeError(error) && error.code === \"EEXIST\")) {\n throw error;\n }\n await removeStaleLock(lockDirectory);\n }\n\n await setTimeout(LOCK_POLL_INTERVAL_MS);\n }\n\n throw new Error(\n `Timed out waiting for FileSessionStore lock ${JSON.stringify(\n lockDirectory\n )}`\n );\n}\n\nasync function removeStaleLock(lockDirectory: string): Promise<void> {\n try {\n const stats = await stat(lockDirectory);\n if (Date.now() - stats.mtimeMs < LOCK_STALE_AFTER_MS) {\n return;\n }\n await rm(lockDirectory, { force: true, recursive: true });\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n"],"mappings":";;;;;AAWA,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;AAC5B,MAAM,kBAAkB;AAExB,IAAa,mBAAb,MAAsD;CACpD;CAEA,YAAY,WAAmB;EAC7B,KAAKA,aAAa;CACpB;CAEA,MAAM,KAAK,KAA4C;EACrD,MAAM,OAAO,KAAKC,YAAY,GAAG;EAEjC,IAAI;GAEF,OAAO,uBADQ,KAAK,MAAM,MAAM,SAAS,MAAM,MAAM,CAClB,GAAG,IAAI;EAC5C,SAAS,OAAO;GACd,IAAI,YAAY,KAAK,KAAK,MAAM,SAAS,UACvC,OAAO;GAET,IAAI,iBAAiB,aACnB,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,kBAAkB,MAAM,QAAQ,EACpC;GAEF,MAAM;EACR;CACF;CAEA,MAAM,OACJ,KACA,MACA,SACuB;EACvB,MAAM,OAAO,KAAKA,YAAY,GAAG;EACjC,MAAM,gBAAgB,GAAG,KAAK;EAC9B,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,MAAM,gBAAgB,aAAa;EACnC,IAAI;GACF,MAAM,UAAU,MAAM,KAAK,KAAK,GAAG;GACnC,MAAM,iBAAiB,SAAS,WAAW;GAE3C,IAAI,QAAQ,oBAAoB,gBAC9B,OAAO;IAAE,IAAI;IAAO,QAAQ;GAAW;GAGzC,MAAM,UAAU,QAAQ,OAAO,SAAS,WAAW,GAAG,KAAK,KAAK,CAAC;GACjE,MAAM,UAAyB,gBAAgB;IAC7C,OAAO,KAAK;IACZ;GACF,CAAC;GACD,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,IAAI,GAAG,WAAW,EAAE;GAExD,IAAI;IACF,MAAM,UACJ,UACA,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,KACpC,MACF;IACA,MAAM,OAAO,UAAU,IAAI;GAC7B,SAAS,OAAO;IACd,MAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC,EAAE,YAAY,KAAA,CAAS;IACzD,MAAM;GACR;GAEA,OAAO;IAAE,IAAI;IAAM;GAAQ;EAC7B,UAAU;GACR,MAAM,GAAG,eAAe;IAAE,OAAO;IAAM,WAAW;GAAK,CAAC;EAC1D;CACF;CAEA,MAAM,OAAO,KAA4B;EACvC,MAAM,OAAO,KAAKA,YAAY,GAAG;EACjC,MAAM,gBAAgB,GAAG,KAAK;EAC9B,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,MAAM,gBAAgB,aAAa;EACnC,IAAI;GACF,MAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;EAChC,UAAU;GACR,MAAM,GAAG,eAAe;IAAE,OAAO;IAAM,WAAW;GAAK,CAAC;EAC1D;CACF;CAEA,YAAY,KAAqB;EAC/B,OAAO,KACL,KAAKD,YACL,GAAG,OAAO,KAAK,GAAG,EAAE,SAAS,WAAW,EAAE,MAC5C;CACF;AACF;AAEA,SAAS,uBAAuB,OAAgB,MAA6B;CAC3E,IAAI,UAAU,QAAQ,OAAO,UAAU,UACrC,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,qBACJ;CAGF,MAAM,YAAY;CAClB,IAAI,OAAO,UAAU,YAAY,YAAY,EAAE,WAAW,YACxD,MAAM,IAAI,MACR,iCAAiC,KAAK,UACpC,IACF,EAAE,oCACJ;CAGF,OAAO,gBAAgB;EACrB,OAAO,UAAU;EACjB,SAAS,UAAU;CACrB,CAAC;AACH;AAEA,SAAS,YAAY,OAAgD;CACnE,OAAO,iBAAiB,SAAS,UAAU;AAC7C;AAEA,eAAe,gBAAgB,eAAsC;CACnE,MAAM,YAAY,KAAK,IAAI;CAC3B,OAAO,KAAK,IAAI,IAAI,YAAY,iBAAiB;EAC/C,IAAI;GACF,MAAM,MAAM,aAAa;GACzB;EACF,SAAS,OAAO;GACd,IAAI,EAAE,YAAY,KAAK,KAAK,MAAM,SAAS,WACzC,MAAM;GAER,MAAM,gBAAgB,aAAa;EACrC;EAEA,MAAM,WAAW,qBAAqB;CACxC;CAEA,MAAM,IAAI,MACR,+CAA+C,KAAK,UAClD,aACF,GACF;AACF;AAEA,eAAe,gBAAgB,eAAsC;CACnE,IAAI;EACF,MAAM,QAAQ,MAAM,KAAK,aAAa;EACtC,IAAI,KAAK,IAAI,IAAI,MAAM,UAAU,qBAC/B;EAEF,MAAM,GAAG,eAAe;GAAE,OAAO;GAAM,WAAW;EAAK,CAAC;CAC1D,SAAS,OAAO;EACd,IAAI,YAAY,KAAK,KAAK,MAAM,SAAS,UACvC;EAEF,MAAM;CACR;AACF"}
@@ -4,6 +4,7 @@ import { CommitResult, SessionStore, SessionStoreCommit, StoredSession } from ".
4
4
  declare class MemorySessionStore implements SessionStore {
5
5
  #private;
6
6
  load(key: string): Promise<StoredSession | null>;
7
+ delete(key: string): Promise<void>;
7
8
  commit(key: string, next: SessionStoreCommit, options: {
8
9
  expectedVersion: string | null;
9
10
  }): Promise<CommitResult>;
@@ -6,6 +6,11 @@ var MemorySessionStore = class {
6
6
  const stored = this.#sessions.get(key);
7
7
  return Promise.resolve(stored ? structuredClone(stored) : null);
8
8
  }
9
+ delete(key) {
10
+ this.#sessions.delete(key);
11
+ this.#versions.delete(key);
12
+ return Promise.resolve();
13
+ }
9
14
  commit(key, next, options) {
10
15
  const currentVersion = this.#sessions.get(key)?.version ?? null;
11
16
  if (options.expectedVersion !== currentVersion) return Promise.resolve({
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","names":["#sessions","#versions"],"sources":["../../../src/session/store/memory.ts"],"sourcesContent":["import type {\n CommitResult,\n SessionStore,\n SessionStoreCommit,\n StoredSession,\n} from \"./types\";\n\nexport class MemorySessionStore implements SessionStore {\n readonly #sessions = new Map<string, StoredSession>();\n readonly #versions = new Map<string, number>();\n\n load(key: string): Promise<StoredSession | null> {\n const stored = this.#sessions.get(key);\n return Promise.resolve(stored ? structuredClone(stored) : null);\n }\n\n commit(\n key: string,\n next: SessionStoreCommit,\n options: { expectedVersion: string | null }\n ): Promise<CommitResult> {\n const current = this.#sessions.get(key);\n const currentVersion = current?.version ?? null;\n\n if (options.expectedVersion !== currentVersion) {\n return Promise.resolve({ ok: false, reason: \"conflict\" });\n }\n\n const versionNumber = (this.#versions.get(key) ?? 0) + 1;\n const version = String(versionNumber);\n this.#versions.set(key, versionNumber);\n this.#sessions.set(key, structuredClone({ state: next.state, version }));\n return Promise.resolve({ ok: true, version });\n }\n}\n"],"mappings":";AAOA,IAAa,qBAAb,MAAwD;CACtD,4BAAqB,IAAI,IAA2B;CACpD,4BAAqB,IAAI,IAAoB;CAE7C,KAAK,KAA4C;EAC/C,MAAM,SAAS,KAAKA,UAAU,IAAI,GAAG;EACrC,OAAO,QAAQ,QAAQ,SAAS,gBAAgB,MAAM,IAAI,IAAI;CAChE;CAEA,OACE,KACA,MACA,SACuB;EAEvB,MAAM,iBADU,KAAKA,UAAU,IAAI,GACN,GAAG,WAAW;EAE3C,IAAI,QAAQ,oBAAoB,gBAC9B,OAAO,QAAQ,QAAQ;GAAE,IAAI;GAAO,QAAQ;EAAW,CAAC;EAG1D,MAAM,iBAAiB,KAAKC,UAAU,IAAI,GAAG,KAAK,KAAK;EACvD,MAAM,UAAU,OAAO,aAAa;EACpC,KAAKA,UAAU,IAAI,KAAK,aAAa;EACrC,KAAKD,UAAU,IAAI,KAAK,gBAAgB;GAAE,OAAO,KAAK;GAAO;EAAQ,CAAC,CAAC;EACvE,OAAO,QAAQ,QAAQ;GAAE,IAAI;GAAM;EAAQ,CAAC;CAC9C;AACF"}
1
+ {"version":3,"file":"memory.js","names":["#sessions","#versions"],"sources":["../../../src/session/store/memory.ts"],"sourcesContent":["import type {\n CommitResult,\n SessionStore,\n SessionStoreCommit,\n StoredSession,\n} from \"./types\";\n\nexport class MemorySessionStore implements SessionStore {\n readonly #sessions = new Map<string, StoredSession>();\n readonly #versions = new Map<string, number>();\n\n load(key: string): Promise<StoredSession | null> {\n const stored = this.#sessions.get(key);\n return Promise.resolve(stored ? structuredClone(stored) : null);\n }\n\n delete(key: string): Promise<void> {\n this.#sessions.delete(key);\n this.#versions.delete(key);\n return Promise.resolve();\n }\n\n commit(\n key: string,\n next: SessionStoreCommit,\n options: { expectedVersion: string | null }\n ): Promise<CommitResult> {\n const current = this.#sessions.get(key);\n const currentVersion = current?.version ?? null;\n\n if (options.expectedVersion !== currentVersion) {\n return Promise.resolve({ ok: false, reason: \"conflict\" });\n }\n\n const versionNumber = (this.#versions.get(key) ?? 0) + 1;\n const version = String(versionNumber);\n this.#versions.set(key, versionNumber);\n this.#sessions.set(key, structuredClone({ state: next.state, version }));\n return Promise.resolve({ ok: true, version });\n }\n}\n"],"mappings":";AAOA,IAAa,qBAAb,MAAwD;CACtD,4BAAqB,IAAI,IAA2B;CACpD,4BAAqB,IAAI,IAAoB;CAE7C,KAAK,KAA4C;EAC/C,MAAM,SAAS,KAAKA,UAAU,IAAI,GAAG;EACrC,OAAO,QAAQ,QAAQ,SAAS,gBAAgB,MAAM,IAAI,IAAI;CAChE;CAEA,OAAO,KAA4B;EACjC,KAAKA,UAAU,OAAO,GAAG;EACzB,KAAKC,UAAU,OAAO,GAAG;EACzB,OAAO,QAAQ,QAAQ;CACzB;CAEA,OACE,KACA,MACA,SACuB;EAEvB,MAAM,iBADU,KAAKD,UAAU,IAAI,GACN,GAAG,WAAW;EAE3C,IAAI,QAAQ,oBAAoB,gBAC9B,OAAO,QAAQ,QAAQ;GAAE,IAAI;GAAO,QAAQ;EAAW,CAAC;EAG1D,MAAM,iBAAiB,KAAKC,UAAU,IAAI,GAAG,KAAK,KAAK;EACvD,MAAM,UAAU,OAAO,aAAa;EACpC,KAAKA,UAAU,IAAI,KAAK,aAAa;EACrC,KAAKD,UAAU,IAAI,KAAK,gBAAgB;GAAE,OAAO,KAAK;GAAO;EAAQ,CAAC,CAAC;EACvE,OAAO,QAAQ,QAAQ;GAAE,IAAI;GAAM;EAAQ,CAAC;CAC9C;AACF"}
@@ -18,6 +18,7 @@ interface SessionStore {
18
18
  commit(key: string, next: SessionStoreCommit, options: {
19
19
  expectedVersion: ExpectedSessionVersion;
20
20
  }): Promise<CommitResult>;
21
+ delete(key: string): Promise<void>;
21
22
  load(key: string): Promise<StoredSession | null>;
22
23
  }
23
24
  //#endregion
@@ -0,0 +1,28 @@
1
+ import { assertBackgroundTaskId, cancelJob, isActiveJob } from "./subagent-jobs.js";
2
+ import { jsonSchema, tool } from "ai";
3
+ //#region src/subagent-job-cancel.ts
4
+ function createBackgroundCancelTool(jobs) {
5
+ return tool({
6
+ description: "Cancel an active background subagent job.",
7
+ execute: (input) => {
8
+ assertBackgroundTaskId(input.task_id, "background_cancel");
9
+ const job = jobs.get(input.task_id);
10
+ if (!job) throw new Error(`Unknown background subagent task ${input.task_id}.`);
11
+ if (isActiveJob(job.status)) cancelJob(job);
12
+ return {
13
+ status: job.status,
14
+ task_id: job.id
15
+ };
16
+ },
17
+ inputSchema: jsonSchema({
18
+ additionalProperties: false,
19
+ properties: { task_id: { type: "string" } },
20
+ required: ["task_id"],
21
+ type: "object"
22
+ })
23
+ });
24
+ }
25
+ //#endregion
26
+ export { createBackgroundCancelTool };
27
+
28
+ //# sourceMappingURL=subagent-job-cancel.js.map