@frockbot/kernel-agent-loop 0.3.6 → 0.3.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/kernel-agent-loop",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,13 +12,13 @@
12
12
  "typecheck": "tsc --noEmit -p tsconfig.json"
13
13
  },
14
14
  "dependencies": {
15
- "@frockbot/kernel-contracts": "0.3.6",
15
+ "@frockbot/kernel-contracts": "0.3.8",
16
16
  "cordis": "4.0.0-rc.8"
17
17
  },
18
18
  "devDependencies": {
19
- "@frockbot/plugin-models": "0.3.6",
20
- "@frockbot/plugin-prompt": "0.3.6",
21
- "@frockbot/plugin-tools": "0.3.6",
19
+ "@frockbot/plugin-models": "0.3.8",
20
+ "@frockbot/plugin-prompt": "0.3.8",
21
+ "@frockbot/plugin-tools": "0.3.8",
22
22
  "@types/bun": "1.4.0",
23
23
  "@types/node": "26.2.0",
24
24
  "typescript": "^7.0.2"
package/src/index.test.ts CHANGED
@@ -1223,7 +1223,7 @@ describe("AgentLoop", () => {
1223
1223
  ).toBe(false);
1224
1224
  });
1225
1225
 
1226
- test("keeps an unretrievable provider effect open without repeating it", async () => {
1226
+ test("settles a turn whose provider cannot retrieve the effect", async () => {
1227
1227
  let streams = 0;
1228
1228
  const provider: LlmProvider = {
1229
1229
  id: "unretrievable",
@@ -1279,28 +1279,26 @@ describe("AgentLoop", () => {
1279
1279
  model: "model-1",
1280
1280
  });
1281
1281
 
1282
- handle.agent.resume();
1283
- await handle.agent.whenIdle();
1284
1282
  handle.agent.resume();
1285
1283
  await handle.agent.whenIdle();
1286
1284
 
1285
+ // A provider that declares no retrieval will never grow one, so the run
1286
+ // settles as a model error rather than parking on a reconciliation that
1287
+ // can never happen (and throwing out of the Durable Object).
1287
1288
  expect(streams).toBe(0);
1288
- expect(
1289
- handle.agent.session.events.filter(
1290
- (event) => event.type === "model/reconciliation-required",
1291
- ),
1292
- ).toEqual([
1293
- expect.objectContaining({
1294
- requestId: "unretrievable-effect-1",
1295
- reason:
1296
- 'LLM provider "unretrievable" does not support provider-bound retrieval',
1297
- }),
1298
- ]);
1299
1289
  expect(
1300
1290
  handle.agent.session.events.some(
1301
- (event) => event.type === "step/end" || event.type === "turn/end",
1291
+ (event) => event.type === "model/reconciliation-required",
1302
1292
  ),
1303
1293
  ).toBe(false);
1294
+ const turnEnd = handle.agent.session.events.findLast(
1295
+ (event) => event.type === "turn/end",
1296
+ );
1297
+ expect(turnEnd).toMatchObject({
1298
+ outcome: "model-error",
1299
+ reason:
1300
+ 'LLM provider "unretrievable" does not support provider-bound retrieval',
1301
+ });
1304
1302
  });
1305
1303
 
1306
1304
  test("keeps an ambiguous dispatched effect open for provider reconciliation", async () => {
package/src/index.ts CHANGED
@@ -71,7 +71,8 @@ interface ModelResponse {
71
71
 
72
72
  type ModelReconciliation =
73
73
  | { status: "recovered"; response: ModelResponse }
74
- | { status: "unavailable"; reason: string };
74
+ | { status: "unavailable"; reason: string }
75
+ | { status: "not-retrievable"; reason: string };
75
76
 
76
77
  class ModelEffectReconciliationRequiredError extends Error {
77
78
  constructor(
@@ -473,6 +474,19 @@ class LoopAgent implements Agent {
473
474
  latestStep,
474
475
  signal,
475
476
  );
477
+ if (reconciliation.status === "not-retrievable") {
478
+ // No later attempt can retrieve this effect, so the run settles now.
479
+ // The chunks already journaled stay in the session, so whatever the
480
+ // model produced before the interruption is still shown.
481
+ await this.#notifyModelOutcome(
482
+ unresolvedRequest.requestId,
483
+ "not-started",
484
+ );
485
+ turnOutcome = "model-error";
486
+ turnReason = turnEndReason(reconciliation.reason);
487
+ this.#ctx.emit("agent/error", this, new Error(reconciliation.reason));
488
+ return;
489
+ }
476
490
  if (reconciliation.status === "unavailable") {
477
491
  const existing = this.session.events.findLast(
478
492
  (event) =>
@@ -1126,7 +1140,7 @@ class LoopAgent implements Agent {
1126
1140
  signal: AbortSignal,
1127
1141
  ): Promise<ModelReconciliation> {
1128
1142
  const reconciliation = await this.#ctx.llm.reconcile(request, signal);
1129
- if (reconciliation.status === "unavailable") return reconciliation;
1143
+ if (reconciliation.status !== "recovered") return reconciliation;
1130
1144
  const durablePrefix = this.session.events.flatMap((event) =>
1131
1145
  event.type === "assistant/chunk" &&
1132
1146
  event.turn === turn &&