@dxos/agent-runtime 0.11.1 → 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264

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 (53) hide show
  1. package/dist/lib/chunk-AgentService.mjs +165 -102
  2. package/dist/lib/chunk-AgentService.mjs.map +1 -1
  3. package/dist/lib/index.mjs +2 -2
  4. package/dist/lib/testing.mjs +33 -26
  5. package/dist/lib/testing.mjs.map +1 -1
  6. package/dist/types/src/agent-service/AgentService.d.ts +8 -1
  7. package/dist/types/src/agent-service/AgentService.d.ts.map +1 -1
  8. package/dist/types/src/agent-service/agent-process.d.ts +281 -314
  9. package/dist/types/src/agent-service/agent-process.d.ts.map +1 -1
  10. package/dist/types/src/agent-service/delegation-strategy.d.ts +1 -1
  11. package/dist/types/src/agent-service/delegation-strategy.d.ts.map +1 -1
  12. package/dist/types/src/agent-service/index.d.ts +1 -0
  13. package/dist/types/src/agent-service/index.d.ts.map +1 -1
  14. package/dist/types/src/agent-service/redelivery.test.d.ts +2 -0
  15. package/dist/types/src/agent-service/redelivery.test.d.ts.map +1 -0
  16. package/dist/types/src/agent-service/turn-producer.d.ts +52 -0
  17. package/dist/types/src/agent-service/turn-producer.d.ts.map +1 -0
  18. package/dist/types/src/testing/ResearchService.d.ts +1 -1
  19. package/dist/types/src/testing/ResearchService.d.ts.map +1 -1
  20. package/dist/types/src/testing/assistant-test-layer.d.ts +13 -6
  21. package/dist/types/src/testing/assistant-test-layer.d.ts.map +1 -1
  22. package/dist/types/src/testing/index.d.ts +0 -1
  23. package/dist/types/src/testing/index.d.ts.map +1 -1
  24. package/dist/types/src/testing/test-agent.d.ts +4 -3
  25. package/dist/types/src/testing/test-agent.d.ts.map +1 -1
  26. package/dist/types/src/testing/trace-pretty-print.d.ts +1 -1
  27. package/dist/types/src/testing/trace-pretty-print.d.ts.map +1 -1
  28. package/dist/types/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +18 -21
  30. package/src/agent-service/AgentService.test.ts +121 -42
  31. package/src/agent-service/AgentService.ts +15 -5
  32. package/src/agent-service/agent-process.test.ts +2 -2
  33. package/src/agent-service/agent-process.ts +121 -74
  34. package/src/agent-service/delegation-scripted.test.ts +4 -3
  35. package/src/agent-service/delegation-strategy.ts +1 -1
  36. package/src/agent-service/index.ts +1 -0
  37. package/src/agent-service/redelivery.test.ts +90 -0
  38. package/src/agent-service/turn-producer.ts +69 -0
  39. package/src/assistant-session-tests/format.test.ts +5 -2
  40. package/src/assistant-session-tests/request.test.ts +7 -7
  41. package/src/assistant-session-tests/scripted-loop.test.ts +5 -5
  42. package/src/assistant-session-tests/session.test.ts +2 -2
  43. package/src/assistant-session-tests/xml-response.test.ts +2 -2
  44. package/src/functions.test.ts +66 -6
  45. package/src/testing/ResearchService.ts +4 -6
  46. package/src/testing/assistant-test-layer.ts +22 -23
  47. package/src/testing/index.ts +0 -2
  48. package/src/testing/test-agent.ts +4 -3
  49. package/src/testing/trace-pretty-print.ts +2 -1
  50. package/src/agent-service/AgentService.conversations.json +0 -1
  51. package/src/assistant-session-tests/request.conversations.json +0 -1
  52. package/src/assistant-session-tests/xml-response.conversations.json +0 -1
  53. package/src/functions.conversations.json +0 -1
@@ -3,22 +3,29 @@ import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
4
  import * as Option from "effect/Option";
5
5
  import { AgentRequestBegin, AgentRequestEnd, AiContext, AiSession, HarnessControl, SkillHooks, getOperationFromTool, makeToolExecutionService, makeToolResolverFromOperations } from "@dxos/assistant";
6
- import { Credential, Instructions, Operation, Process, Skill, Trace } from "@dxos/compute";
7
6
  import { ProcessManager } from "@dxos/compute-runtime";
8
- import { AgentService as AgentService$1, getSession } from "@dxos/compute/AgentService";
7
+ import { AgentService, getSession } from "@dxos/compute/AgentService";
8
+ import * as Process from "@dxos/compute/Process";
9
+ import * as Skill from "@dxos/compute/Skill";
9
10
  import { Annotation, Database, Feed, Obj, Ref, Registry } from "@dxos/echo";
10
11
  import { EffectEx } from "@dxos/effect";
11
12
  import { DXN, EID } from "@dxos/keys";
12
13
  import { log } from "@dxos/log";
13
- import * as Tool from "@effect/ai/Tool";
14
- import * as Toolkit from "@effect/ai/Toolkit";
14
+ import * as Clock from "effect/Clock";
15
15
  import * as DateTime from "effect/DateTime";
16
16
  import * as Duration from "effect/Duration";
17
17
  import * as Exit from "effect/Exit";
18
18
  import * as Match from "effect/Match";
19
19
  import * as Schema from "effect/Schema";
20
+ import * as Struct from "effect/Struct";
21
+ import * as Tool from "effect/unstable/ai/Tool";
22
+ import * as Toolkit from "effect/unstable/ai/Toolkit";
20
23
  import { AiService, OpaqueToolkit } from "@dxos/ai";
24
+ import * as Credential from "@dxos/compute/Credential";
25
+ import * as Instructions from "@dxos/compute/Instructions";
26
+ import * as Operation from "@dxos/compute/Operation";
21
27
  import * as StorageService from "@dxos/compute/StorageService";
28
+ import * as Trace from "@dxos/compute/Trace";
22
29
  import { ContentBlock } from "@dxos/types";
23
30
  import { trim } from "@dxos/util";
24
31
  //#region \0rolldown/runtime.js
@@ -33,6 +40,20 @@ var __exportAll = (all, no_symbols) => {
33
40
  return target;
34
41
  };
35
42
  //#endregion
43
+ //#region src/agent-service/turn-producer.ts
44
+ /**
45
+ * The default producer: `AiSession.Session`, which resolves the model, binds skills and context
46
+ * objects as tools, and appends each message to the feed via its `onOutput` hook.
47
+ */
48
+ var makeAiSessionTurnProducer = ({ feed, runtime, instructions }) => EffectEx.acquireReleaseResource(() => new AiSession.Session({
49
+ feed,
50
+ runtime,
51
+ instructions
52
+ })).pipe(Effect.map((session) => ({
53
+ runTurn: (params) => session.createRequest(params),
54
+ getSkills: () => session.context.getSkills()
55
+ })));
56
+ //#endregion
36
57
  //#region src/agent-service/agent-process.ts
37
58
  var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/compute/agent-runtime/src/agent-service/agent-process.ts";
38
59
  var AGENT_PROCESS_KEY = "org.dxos.testing.process.agent";
@@ -42,7 +63,7 @@ var AGENT_PROCESS_KEY = "org.dxos.testing.process.agent";
42
63
  */
43
64
  var AgentProcess = (options) => Process.make({
44
65
  key: AGENT_PROCESS_KEY,
45
- input: Schema.Union(Schema.String, Schema.Array(ContentBlock.Any)),
66
+ input: Schema.Union([Schema.String, Schema.Array(ContentBlock.Any)]),
46
67
  output: Schema.Void,
47
68
  services: [
48
69
  Database.Service,
@@ -61,38 +82,38 @@ var AgentProcess = (options) => Process.make({
61
82
  const feed = yield* Database.resolve(feedDxn, Feed.Feed).pipe(Effect.orDie);
62
83
  const instructionsDxn = Annotation.getDictionary(ctx.params.annotations, Process.InstructionsAnnotation).pipe(Option.getOrUndefined);
63
84
  const instructions = instructionsDxn ? yield* Database.resolve(instructionsDxn, Instructions.Instructions).pipe(Effect.orElseSucceed(() => void 0)) : void 0;
64
- const runtime = yield* Effect.runtime();
65
- const session = yield* EffectEx.acquireReleaseResource(() => new AiSession.Session({
85
+ const runtime = yield* Effect.context();
86
+ const session = yield* (options.makeTurnProducer ?? makeAiSessionTurnProducer)({
66
87
  feed,
67
88
  runtime,
68
89
  instructions: instructions ? [instructions] : []
69
- }));
90
+ });
70
91
  let inputQueue = [...yield* AgentEventsCell.get];
71
92
  const storageService = yield* StorageService.StorageService;
72
93
  const toolCallManager = new ToolCallManager(storageService);
73
94
  yield* toolCallManager.load();
74
- const clock = yield* Effect.clock;
95
+ const clock = yield* Clock.Clock;
75
96
  const alarmManager = new AlarmManager({
76
97
  storageService,
77
98
  setAlarm: (timeout) => ctx.setAlarm(timeout),
78
- now: () => clock.unsafeCurrentTimeMillis()
99
+ now: () => clock.currentTimeMillisUnsafe()
79
100
  });
80
101
  yield* alarmManager.load();
81
102
  yield* toolCallManager.reconcileWithInputQueue(inputQueue);
82
- const strategy = Option.fromNullable(options.delegationStrategy);
103
+ const strategy = Option.fromNullishOr(options.delegationStrategy);
83
104
  let delegations = [...yield* DelegationsCell.get];
84
105
  const requestModelLayer = AiService.model(options.model ? DXN.getName(options.model) : "com.anthropic.model.claude-opus-4-8.default", { provider: options.provider });
85
106
  const operationInvoker = yield* ProcessManager.ProcessOperationInvoker.Service;
86
107
  const runEndRequestHooks = Effect.gen(function* () {
87
108
  yield* SkillHooks.runHooks({
88
- skills: session.context.getSkills(),
109
+ skills: session.getSkills(),
89
110
  phase: "end-request",
90
111
  invoke: (operation, input) => Effect.gen(function* () {
91
112
  const exit = yield* (yield* operationInvoker.invokeFiber(operation, input, {
92
113
  environment: { conversation: Obj.getURI(feed) },
93
114
  traceMeta: { conversation: Ref.make(feed) }
94
115
  })).await;
95
- if (Exit.isFailure(exit)) yield* Effect.failCause(exit.cause);
116
+ if (Exit.isFailure(exit)) return yield* Effect.failCause(exit.cause);
96
117
  }).pipe(Effect.asVoid, Effect.orDie)
97
118
  });
98
119
  });
@@ -113,7 +134,7 @@ var AgentProcess = (options) => Process.make({
113
134
  log("agent work enqueued by end-request hook, continuing", void 0, {
114
135
  "~LogMeta": "~LogMeta",
115
136
  F: __dxlog_file$1,
116
- L: 193,
137
+ L: 205,
117
138
  S: this
118
139
  });
119
140
  alarmManager.reconcile(true);
@@ -122,13 +143,13 @@ var AgentProcess = (options) => Process.make({
122
143
  log("agent work complete, succeeding", void 0, {
123
144
  "~LogMeta": "~LogMeta",
124
145
  F: __dxlog_file$1,
125
- L: 198,
146
+ L: 210,
126
147
  S: this
127
148
  });
128
149
  ctx.succeed();
129
150
  });
130
151
  return {
131
- rpcHandlers: yield* HarnessControl.toHandlersContext({
152
+ rpcHandlers: yield* HarnessControl.toHandlers({
132
153
  setAlarm: Effect.fn(function* ({ at, message }) {
133
154
  yield* alarmManager.setWakeAt(DateTime.toEpochMillis(at), message);
134
155
  alarmManager.reconcile(inputQueue.length > 0);
@@ -146,7 +167,7 @@ var AgentProcess = (options) => Process.make({
146
167
  log("agent onInput received", { backlog: inputQueue.length }, {
147
168
  "~LogMeta": "~LogMeta",
148
169
  F: __dxlog_file$1,
149
- L: 217,
170
+ L: 229,
150
171
  S: this
151
172
  });
152
173
  const content = typeof prompt === "string" ? [ContentBlock.Text.make({ text: prompt })] : [...prompt];
@@ -157,21 +178,21 @@ var AgentProcess = (options) => Process.make({
157
178
  log("agent onInput persisting queue", { depth: inputQueue.length }, {
158
179
  "~LogMeta": "~LogMeta",
159
180
  F: __dxlog_file$1,
160
- L: 220,
181
+ L: 232,
161
182
  S: this
162
183
  });
163
184
  yield* AgentEventsCell.set(inputQueue);
164
185
  log("agent onInput persisted", { depth: inputQueue.length }, {
165
186
  "~LogMeta": "~LogMeta",
166
187
  F: __dxlog_file$1,
167
- L: 222,
188
+ L: 234,
168
189
  S: this
169
190
  });
170
191
  alarmManager.reconcile(true);
171
192
  log("agent onInput alarm scheduled", void 0, {
172
193
  "~LogMeta": "~LogMeta",
173
194
  F: __dxlog_file$1,
174
- L: 224,
195
+ L: 236,
175
196
  S: this
176
197
  });
177
198
  }),
@@ -179,7 +200,7 @@ var AgentProcess = (options) => Process.make({
179
200
  log("agent onAlarm fired", { pending: inputQueue.length }, {
180
201
  "~LogMeta": "~LogMeta",
181
202
  F: __dxlog_file$1,
182
- L: 228,
203
+ L: 240,
183
204
  S: this
184
205
  });
185
206
  const fired = yield* alarmManager.takeFiredAlarm();
@@ -187,7 +208,7 @@ var AgentProcess = (options) => Process.make({
187
208
  log("agent onAlarm self-wake", { firedAt: fired.firedAt }, {
188
209
  "~LogMeta": "~LogMeta",
189
210
  F: __dxlog_file$1,
190
- L: 233,
211
+ L: 245,
191
212
  S: this
192
213
  });
193
214
  inputQueue.push({
@@ -197,26 +218,18 @@ var AgentProcess = (options) => Process.make({
197
218
  });
198
219
  yield* AgentEventsCell.set(inputQueue);
199
220
  }
200
- while (inputQueue.length > 0) {
201
- const head = inputQueue[0];
202
- if (head._tag === "tool_result" && toolCallManager.isReported(head.pid)) {
203
- inputQueue.shift();
204
- log.info("skip tool result that was reported synchronously", { pid: head.pid }, {
205
- "~LogMeta": "~LogMeta",
206
- F: __dxlog_file$1,
207
- L: 243,
208
- S: this
209
- });
210
- continue;
211
- }
212
- break;
213
- }
221
+ for (const pid of dropReportedToolResults(inputQueue, (pid) => toolCallManager.isReported(pid))) log.info("skip tool result that was reported synchronously", { pid }, {
222
+ "~LogMeta": "~LogMeta",
223
+ F: __dxlog_file$1,
224
+ L: 251,
225
+ S: this
226
+ });
214
227
  const item = inputQueue.shift();
215
228
  if (!item) {
216
229
  log("agent onAlarm empty queue", {}, {
217
230
  "~LogMeta": "~LogMeta",
218
231
  F: __dxlog_file$1,
219
- L: 251,
232
+ L: 256,
220
233
  S: this
221
234
  });
222
235
  alarmManager.reconcile(false);
@@ -227,44 +240,35 @@ var AgentProcess = (options) => Process.make({
227
240
  log("agent onAlarm handling", { tag: item._tag }, {
228
241
  "~LogMeta": "~LogMeta",
229
242
  F: __dxlog_file$1,
230
- L: 258,
243
+ L: 263,
231
244
  S: this
232
245
  });
233
- const prompt = Match.value(item).pipe(Match.tag("prompt", (item) => [...item.content]), Match.tag("tool_result", (item) => item.isError ? [ContentBlock.Text.make({
234
- text: toolErrorResponse(item.pid, item.result),
235
- disposition: "synthetic"
236
- })] : [ContentBlock.Text.make({
237
- text: toolResultResponse(item.pid, item.result),
238
- disposition: "synthetic"
239
- })]), Match.tag("alarm", (item) => [ContentBlock.Text.make({
240
- text: wakeUpPrompt(item.firedAt, item.message),
241
- disposition: "synthetic"
242
- })]), Match.exhaustive);
246
+ const prompt = agentEventToPrompt(item);
243
247
  log("begin request", { prompt }, {
244
248
  "~LogMeta": "~LogMeta",
245
249
  F: __dxlog_file$1,
246
- L: 283,
250
+ L: 267,
247
251
  S: this
248
252
  });
249
253
  log("trace agent request begin", void 0, {
250
254
  "~LogMeta": "~LogMeta",
251
255
  F: __dxlog_file$1,
252
- L: 284,
256
+ L: 268,
253
257
  S: this
254
258
  });
255
259
  yield* Trace.write(AgentRequestBegin, {});
256
- yield* session.createRequest({
260
+ yield* session.runTurn({
257
261
  prompt,
258
262
  system: options.systemPrompt,
259
263
  mcpServers: options.getMcpServers?.()
260
264
  }).pipe(Effect.onExit((exit) => Trace.write(AgentRequestEnd, {
261
- status: Exit.isSuccess(exit) ? "success" : Exit.isInterrupted(exit) ? "interrupted" : "error",
265
+ status: Exit.isSuccess(exit) ? "success" : Exit.hasInterrupts(exit) ? "interrupted" : "error",
262
266
  error: Exit.isFailure(exit) ? Cause.pretty(exit.cause) : void 0
263
267
  })));
264
268
  log("end request", void 0, {
265
269
  "~LogMeta": "~LogMeta",
266
270
  F: __dxlog_file$1,
267
- L: 304,
271
+ L: 288,
268
272
  S: this
269
273
  });
270
274
  yield* AgentEventsCell.set(inputQueue);
@@ -283,7 +287,7 @@ var AgentProcess = (options) => Process.make({
283
287
  }, {
284
288
  "~LogMeta": "~LogMeta",
285
289
  F: __dxlog_file$1,
286
- L: 315,
290
+ L: 299,
287
291
  S: this
288
292
  });
289
293
  }
@@ -300,7 +304,7 @@ var AgentProcess = (options) => Process.make({
300
304
  log("childEvent", { event }, {
301
305
  "~LogMeta": "~LogMeta",
302
306
  F: __dxlog_file$1,
303
- L: 341,
307
+ L: 325,
304
308
  S: this
305
309
  });
306
310
  if (event._tag === "exited") {
@@ -309,7 +313,28 @@ var AgentProcess = (options) => Process.make({
309
313
  delegations = delegations.filter((other) => other.pid !== event.pid);
310
314
  yield* DelegationsCell.set(delegations);
311
315
  const exit = yield* (yield* (yield* ProcessManager.ProcessOperationInvoker.Service).attachFiber(event.pid).pipe(Effect.orDie)).await;
312
- if (Option.isSome(strategy)) yield* strategy.value.onComplete(feed, delegation.id, exit);
316
+ if (Option.isSome(strategy)) {
317
+ yield* strategy.value.onComplete(feed, delegation.id, exit);
318
+ const activeIds = new Set(delegations.map((delegation) => delegation.id));
319
+ const pending = yield* strategy.value.reconcile(feed, activeIds);
320
+ for (const next of pending) {
321
+ const pid = yield* next.spawn;
322
+ delegations.push({
323
+ pid,
324
+ id: next.id
325
+ });
326
+ log("delegated work", {
327
+ pid,
328
+ id: next.id
329
+ }, {
330
+ "~LogMeta": "~LogMeta",
331
+ F: __dxlog_file$1,
332
+ L: 347,
333
+ S: this
334
+ });
335
+ }
336
+ if (pending.length > 0) yield* DelegationsCell.set(delegations);
337
+ }
313
338
  log("delegated work completed", {
314
339
  pid: event.pid,
315
340
  id: delegation.id,
@@ -317,7 +342,7 @@ var AgentProcess = (options) => Process.make({
317
342
  }, {
318
343
  "~LogMeta": "~LogMeta",
319
344
  F: __dxlog_file$1,
320
- L: 356,
345
+ L: 353,
321
346
  S: this
322
347
  });
323
348
  yield* maybeComplete;
@@ -328,7 +353,7 @@ var AgentProcess = (options) => Process.make({
328
353
  log.verbose("childEvent skipped (process gone, result already handled)", { pid: event.pid }, {
329
354
  "~LogMeta": "~LogMeta",
330
355
  F: __dxlog_file$1,
331
- L: 369,
356
+ L: 366,
332
357
  S: this
333
358
  });
334
359
  return;
@@ -356,7 +381,7 @@ var AgentProcess = (options) => Process.make({
356
381
  }, {
357
382
  "~LogMeta": "~LogMeta",
358
383
  F: __dxlog_file$1,
359
- L: 394,
384
+ L: 391,
360
385
  S: this
361
386
  });
362
387
  yield* AgentEventsCell.set(inputQueue);
@@ -364,28 +389,32 @@ var AgentProcess = (options) => Process.make({
364
389
  log("agent onChildEvent alarm scheduled", { depth: inputQueue.length }, {
365
390
  "~LogMeta": "~LogMeta",
366
391
  F: __dxlog_file$1,
367
- L: 397,
392
+ L: 394,
368
393
  S: this
369
394
  });
370
395
  } else log.verbose("childEvent ignored non-tool call and not a delegation", { pid: event.pid }, {
371
396
  "~LogMeta": "~LogMeta",
372
397
  F: __dxlog_file$1,
373
- L: 399,
398
+ L: 396,
374
399
  S: this
375
400
  });
376
401
  }
377
402
  })
378
403
  };
379
404
  }));
380
- var AgentEvent = Schema.Union(Schema.TaggedStruct("prompt", { content: Schema.Array(ContentBlock.Any) }), Schema.TaggedStruct("tool_result", {
381
- pid: Process.ID,
382
- result: Schema.Unknown,
383
- isError: Schema.Boolean
384
- }), Schema.TaggedStruct("alarm", {
385
- firedAt: Schema.Number,
386
- message: Schema.NullOr(Schema.String)
387
- }));
388
- var AgentEventsCell = StorageService.cell(Schema.parseJson(Schema.Array(AgentEvent).pipe(Schema.mutable)), "inputQueue").pipe(StorageService.withDefault(() => []));
405
+ var AgentEvent = Schema.Union([
406
+ Schema.TaggedStruct("prompt", { content: Schema.Array(ContentBlock.Any) }),
407
+ Schema.TaggedStruct("tool_result", {
408
+ pid: Process.ID,
409
+ result: Schema.Unknown,
410
+ isError: Schema.Boolean
411
+ }),
412
+ Schema.TaggedStruct("alarm", {
413
+ firedAt: Schema.Number,
414
+ message: Schema.NullOr(Schema.String)
415
+ })
416
+ ]);
417
+ var AgentEventsCell = StorageService.cell(Schema.fromJsonString(Schema.Array(AgentEvent).pipe(Schema.mutable)), "inputQueue").pipe(StorageService.withDefault(() => []));
389
418
  /**
390
419
  * Tracks delegated sub-agent child processes (pid -> correlation id) so that, after a hibernation,
391
420
  * a delegated child's exit can be matched back to the work it was fulfilling.
@@ -393,13 +422,13 @@ var AgentEventsCell = StorageService.cell(Schema.parseJson(Schema.Array(AgentEve
393
422
  var Delegation = Schema.Struct({
394
423
  pid: Process.ID,
395
424
  id: Schema.String
396
- }).pipe(Schema.mutable);
397
- var DelegationsCell = StorageService.cell(Schema.parseJson(Schema.Array(Delegation).pipe(Schema.mutable)), "delegations").pipe(StorageService.withDefault(() => []));
425
+ }).mapFields(Struct.map(Schema.mutableKey));
426
+ var DelegationsCell = StorageService.cell(Schema.fromJsonString(Schema.Array(Delegation).pipe(Schema.mutable)), "delegations").pipe(StorageService.withDefault(() => []));
398
427
  var ToolCallState = Schema.Struct({ activeCalls: Schema.Array(Schema.Struct({
399
428
  pid: Process.ID,
400
429
  reported: Schema.Boolean
401
- }).pipe(Schema.mutable)).pipe(Schema.mutable) });
402
- var ToolCallStateCell = StorageService.cell(Schema.parseJson(ToolCallState.pipe(Schema.mutable)), "toolCallState").pipe(StorageService.withDefault(() => ({ activeCalls: [] })));
430
+ }).mapFields(Struct.map(Schema.mutableKey))).pipe(Schema.mutable) });
431
+ var ToolCallStateCell = StorageService.cell(Schema.fromJsonString(ToolCallState.mapFields(Struct.map(Schema.mutableKey))), "toolCallState").pipe(StorageService.withDefault(() => ({ activeCalls: [] })));
403
432
  var ToolCallManager = class {
404
433
  #storageService;
405
434
  #state = { activeCalls: [] };
@@ -407,12 +436,12 @@ var ToolCallManager = class {
407
436
  this.#storageService = storageService;
408
437
  }
409
438
  load() {
410
- return Effect.gen(this, function* () {
439
+ return Effect.gen({ self: this }, function* () {
411
440
  this.#state = yield* ToolCallStateCell.get;
412
441
  }).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
413
442
  }
414
443
  beginCall(pid) {
415
- return Effect.gen(this, function* () {
444
+ return Effect.gen({ self: this }, function* () {
416
445
  this.#state.activeCalls.push({
417
446
  pid,
418
447
  reported: false
@@ -421,7 +450,7 @@ var ToolCallManager = class {
421
450
  }).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
422
451
  }
423
452
  markAsReported(pid) {
424
- return Effect.gen(this, function* () {
453
+ return Effect.gen({ self: this }, function* () {
425
454
  const call = this.#state.activeCalls.find((call) => call.pid === pid);
426
455
  if (!call) return;
427
456
  call.reported = true;
@@ -443,7 +472,7 @@ var ToolCallManager = class {
443
472
  * After reload the in-flight createRequest is gone, so those results must be redelivered via onAlarm.
444
473
  */
445
474
  reconcileWithInputQueue(queue) {
446
- return Effect.gen(this, function* () {
475
+ return Effect.gen({ self: this }, function* () {
447
476
  let changed = false;
448
477
  for (const item of queue) {
449
478
  if (item._tag !== "tool_result") continue;
@@ -454,7 +483,7 @@ var ToolCallManager = class {
454
483
  log("reconcile queued tool result", { pid: item.pid }, {
455
484
  "~LogMeta": "~LogMeta",
456
485
  F: __dxlog_file$1,
457
- L: 537,
486
+ L: 535,
458
487
  S: this
459
488
  });
460
489
  }
@@ -466,10 +495,43 @@ var ToolCallManager = class {
466
495
  /** True while the agent still has queued work, a self-wake, subprocesses, or undelivered tool results. */
467
496
  var isAgentWorkPending = ({ inputQueue, alarmManager, delegations, toolCallManager }) => inputQueue.length > 0 || alarmManager.wakeAt != null || delegations.length > 0 || toolCallManager.hasPendingToolResults();
468
497
  /**
498
+ * Discards tool results at the head of the queue whose values already reached the agent.
499
+ *
500
+ * A tool that returned inside its turn is reported synchronously AND left queued; after a reload the
501
+ * queue is replayed, so without this the model would be handed a result it has already seen. Only the
502
+ * head is examined: a result further back belongs to a turn that has not run yet.
503
+ *
504
+ * Mutates `queue` and returns the pids dropped, so the caller owns the logging.
505
+ */
506
+ var dropReportedToolResults = (queue, isReported) => {
507
+ const dropped = [];
508
+ while (queue.length > 0) {
509
+ const head = queue[0];
510
+ if (head._tag !== "tool_result" || !isReported(head.pid)) break;
511
+ queue.shift();
512
+ dropped.push(head.pid);
513
+ }
514
+ return dropped;
515
+ };
516
+ /**
517
+ * Renders a queued event as the next turn's prompt.
518
+ *
519
+ * A tool result recovered across a reload is redelivered as a synthetic `<result pid=N>` TEXT block
520
+ * rather than a tool-result part, because the request it belonged to is gone and its tool-call id
521
+ * cannot be answered. `disposition: 'synthetic'` keeps it out of the user-visible transcript.
522
+ */
523
+ var agentEventToPrompt = (event) => Match.value(event).pipe(Match.tag("prompt", (event) => [...event.content]), Match.tag("tool_result", (event) => [ContentBlock.Text.make({
524
+ text: event.isError ? toolErrorResponse(event.pid, event.result) : toolResultResponse(event.pid, event.result),
525
+ disposition: "synthetic"
526
+ })]), Match.tag("alarm", (event) => [ContentBlock.Text.make({
527
+ text: wakeUpPrompt(event.firedAt, event.message),
528
+ disposition: "synthetic"
529
+ })]), Match.exhaustive);
530
+ /**
469
531
  * Persisted next agent-scheduled self-wake: the UNIX timestamp (ms) and an optional reminder
470
532
  * message delivered when it fires, or `null` when no self-wake is set.
471
533
  */
472
- var AgentAlarmCell = StorageService.cell(Schema.parseJson(Schema.NullOr(Schema.Struct({
534
+ var AgentAlarmCell = StorageService.cell(Schema.fromJsonString(Schema.NullOr(Schema.Struct({
473
535
  wakeAt: Schema.Number,
474
536
  message: Schema.NullOr(Schema.String)
475
537
  }))), "agentAlarm").pipe(StorageService.withDefault(() => null));
@@ -512,7 +574,7 @@ var AlarmManager = class {
512
574
  }
513
575
  /** Restores the persisted alarm state. */
514
576
  load() {
515
- return Effect.gen(this, function* () {
577
+ return Effect.gen({ self: this }, function* () {
516
578
  const persisted = yield* AgentAlarmCell.get;
517
579
  this.#wakeAt = persisted?.wakeAt ?? null;
518
580
  this.#message = persisted?.message ?? null;
@@ -520,7 +582,7 @@ var AlarmManager = class {
520
582
  }
521
583
  /** Records a new self-wake target (with an optional reminder message) and persists it. */
522
584
  setWakeAt(wakeAt, message = null) {
523
- return Effect.gen(this, function* () {
585
+ return Effect.gen({ self: this }, function* () {
524
586
  this.#wakeAt = wakeAt;
525
587
  this.#message = message;
526
588
  yield* AgentAlarmCell.set({
@@ -534,7 +596,7 @@ var AlarmManager = class {
534
596
  * reminder message it carried (or `null` if no alarm was due).
535
597
  */
536
598
  takeFiredAlarm() {
537
- return Effect.gen(this, function* () {
599
+ return Effect.gen({ self: this }, function* () {
538
600
  if (this.#wakeAt == null || this.#now() < this.#wakeAt) return null;
539
601
  const firedAt = this.#wakeAt;
540
602
  const message = this.#message;
@@ -571,7 +633,7 @@ var wakeUpPrompt = (firedAt, message) => message != null ? trim`
571
633
  Your scheduled alarm fired (it was set for ${new Date(firedAt).toISOString()}).
572
634
  Continue with whatever you intended to do when you scheduled this wake-up.
573
635
  `;
574
- var ToolExecutionService = ({ enableBackgrounding, backgroundThreshold = Duration.seconds(1), toolCallManager, feed }) => Layer.unwrapEffect(Effect.gen(function* () {
636
+ var ToolExecutionService = ({ enableBackgrounding, backgroundThreshold = Duration.seconds(1), toolCallManager, feed }) => Layer.unwrap(Effect.gen(function* () {
575
637
  const operationInvoker = yield* ProcessManager.ProcessOperationInvoker.Service;
576
638
  return makeToolExecutionService({ invoke: (tool, input) => Effect.gen(function* () {
577
639
  const operationDef = getOperationFromTool(tool).pipe(Option.getOrThrow);
@@ -581,7 +643,7 @@ var ToolExecutionService = ({ enableBackgrounding, backgroundThreshold = Duratio
581
643
  }, {
582
644
  "~LogMeta": "~LogMeta",
583
645
  F: __dxlog_file$1,
584
- L: 723,
646
+ L: 770,
585
647
  S: this
586
648
  });
587
649
  const fiber = yield* operationInvoker.invokeFiber(operationDef, input, {
@@ -596,15 +658,15 @@ var ToolExecutionService = ({ enableBackgrounding, backgroundThreshold = Duratio
596
658
  }, {
597
659
  "~LogMeta": "~LogMeta",
598
660
  F: __dxlog_file$1,
599
- L: 733,
661
+ L: 780,
600
662
  S: this
601
663
  });
602
664
  const awaitWithReport = fiber.await.pipe(Effect.tap(() => toolCallManager.markAsReported(fiber.pid)));
603
- const result = enableBackgrounding ? yield* awaitWithReport.pipe(Effect.timeout(backgroundThreshold), Effect.catchTag("TimeoutException", () => Effect.succeed(Exit.succeed(toolIsRunningInBackgroundResponse(fiber.pid))))) : yield* awaitWithReport;
665
+ const result = enableBackgrounding ? yield* awaitWithReport.pipe(Effect.timeout(backgroundThreshold), Effect.catchTag("TimeoutError", () => Effect.succeed(Exit.succeed(toolIsRunningInBackgroundResponse(fiber.pid))))) : yield* awaitWithReport;
604
666
  log("result", { result }, {
605
667
  "~LogMeta": "~LogMeta",
606
668
  F: __dxlog_file$1,
607
- L: 744,
669
+ L: 791,
608
670
  S: this
609
671
  });
610
672
  return yield* result;
@@ -618,17 +680,17 @@ var AsynchronousExectionToolkit = class extends Toolkit.make(Tool.make("poll-too
618
680
  Set an appropriate timeout to avoid waiting forever.
619
681
  You will also be notified about the job completion separatelly, so you do not always need to inspect the job if you dont need the result right now.
620
682
  `,
621
- parameters: {
622
- ids: Schema.Array(Schema.String).annotations({ description: "The IDs of the jobs to inspect." }),
623
- wait: Schema.optional(Schema.Boolean).annotations({
683
+ parameters: Schema.Struct({
684
+ ids: Schema.Array(Schema.String).annotate({ description: "The IDs of the jobs to inspect." }),
685
+ wait: Schema.optional(Schema.Boolean).annotate({
624
686
  description: "Whether to wait for the tool call to complete before returning.",
625
687
  default: false
626
688
  }),
627
- timeout: Schema.optional(Schema.Number).annotations({
689
+ timeout: Schema.optional(Schema.Number).annotate({
628
690
  description: "Maximum time to wait for the job to complete. If the job does not complete within the timeout, the current state is returned.",
629
691
  default: 1e4
630
692
  })
631
- }
693
+ })
632
694
  })) {};
633
695
  var AsynchronousExectionToolkitLayer = AsynchronousExectionToolkit.toLayer(Effect.gen(function* () {
634
696
  const invoker = yield* ProcessManager.ProcessOperationInvoker.Service;
@@ -636,7 +698,7 @@ var AsynchronousExectionToolkitLayer = AsynchronousExectionToolkit.toLayer(Effec
636
698
  return yield* Effect.forEach(ids, (pid) => invoker.attachFiber(Process.ID.make(pid)).pipe(Effect.flatMap((_) => _.await), Effect.timeout(Duration.millis(timeout)), Effect.flatMap(Exit.match({
637
699
  onSuccess: (value) => Effect.succeed(toolResultResponse(pid, value)),
638
700
  onFailure: (cause) => Effect.succeed(toolErrorResponse(pid, Cause.pretty(cause)))
639
- })), Effect.catchTag("ProcessNotFoundError", () => Effect.succeed(`Process not found: ${pid}`)), Effect.catchTag("TimeoutException", () => Effect.succeed(`Process still running: ${pid}`))));
701
+ })), Effect.catchTag("ProcessNotFoundError", () => Effect.succeed(`Process not found: ${pid}`)), Effect.catchTag("TimeoutError", () => Effect.succeed(`Process still running: ${pid}`))));
640
702
  }) };
641
703
  }));
642
704
  /**
@@ -656,7 +718,7 @@ var isTerminalProcess = (state) => state === Process.State.SUCCEEDED || state ==
656
718
  var createSession = Effect.fn("createSession")(function* (opts) {
657
719
  const skills = yield* Effect.forEach(opts?.skills ?? [], (skill) => Skill.upsert(Skill.getKey(skill)).pipe(Effect.map(Ref.make)));
658
720
  const feed = yield* Database.add(Feed.make());
659
- const runtime = yield* Effect.runtime();
721
+ const runtime = yield* Effect.context();
660
722
  const binder = yield* EffectEx.acquireReleaseResource(() => new AiContext.Binder({
661
723
  feed,
662
724
  runtime
@@ -670,11 +732,12 @@ var createSession = Effect.fn("createSession")(function* (opts) {
670
732
  provider: opts?.provider
671
733
  });
672
734
  }, Effect.scoped);
673
- var layer = (opts) => Layer.effect(AgentService$1, Effect.gen(function* () {
735
+ var layer = (opts) => Layer.effect(AgentService, Effect.gen(function* () {
674
736
  const processManager = yield* ProcessManager.Service;
675
737
  const sessionCache = /* @__PURE__ */ new Map();
676
738
  const makeExecutable = (model, provider) => AgentProcess({
677
739
  systemPrompt: opts?.systemPrompt,
740
+ makeTurnProducer: opts?.makeTurnProducer,
678
741
  model: model ?? opts?.model,
679
742
  provider: provider ?? opts?.provider,
680
743
  getMcpServers: opts?.getMcpServers,
@@ -743,16 +806,16 @@ var layer = (opts) => Layer.effect(AgentService$1, Effect.gen(function* () {
743
806
  log("agent hydrate", { count: agents.length }, {
744
807
  "~LogMeta": "~LogMeta",
745
808
  F: __dxlog_file,
746
- L: 140,
809
+ L: 150,
747
810
  S: this
748
811
  });
749
- for (const agent of agents) yield* agent.hydrate(executable).pipe(Effect.catchAllCause((cause) => Effect.sync(() => log.warn("agent hydrate skipped", {
812
+ for (const agent of agents) yield* agent.hydrate(executable).pipe(Effect.catchCause((cause) => Effect.sync(() => log.warn("agent hydrate skipped", {
750
813
  pid: agent.pid,
751
814
  cause: Cause.pretty(cause)
752
815
  }, {
753
816
  "~LogMeta": "~LogMeta",
754
817
  F: __dxlog_file,
755
- L: 146,
818
+ L: 156,
756
819
  S: this
757
820
  }))));
758
821
  })
@@ -761,14 +824,14 @@ var layer = (opts) => Layer.effect(AgentService$1, Effect.gen(function* () {
761
824
  var makeSession = (process, feed, releaseSession) => ({
762
825
  feed,
763
826
  getContext: () => Effect.gen(function* () {
764
- const runtime = yield* Effect.runtime();
827
+ const runtime = yield* Effect.context();
765
828
  return (yield* EffectEx.acquireReleaseResource(() => new AiContext.Binder({
766
829
  feed,
767
830
  runtime
768
831
  }))).getObjects().map((object) => Ref.make(object));
769
832
  }).pipe(Effect.scoped),
770
833
  addContext: (context) => Effect.gen(function* () {
771
- const runtime = yield* Effect.runtime();
834
+ const runtime = yield* Effect.context();
772
835
  const binder = yield* EffectEx.acquireReleaseResource(() => new AiContext.Binder({
773
836
  feed,
774
837
  runtime
@@ -784,6 +847,6 @@ var makeSession = (process, feed, releaseSession) => ({
784
847
  subscribeEphemeral: () => process.subscribeEphemeral()
785
848
  });
786
849
  //#endregion
787
- export { __exportAll as i, layer as n, AGENT_PROCESS_KEY as r, AgentService_exports as t };
850
+ export { __exportAll as a, makeAiSessionTurnProducer as i, layer as n, AGENT_PROCESS_KEY as r, AgentService_exports as t };
788
851
 
789
852
  //# sourceMappingURL=chunk-AgentService.mjs.map