@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 +5 -5
- package/src/index.test.ts +13 -15
- package/src/index.ts +16 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/kernel-agent-loop",
|
|
3
|
-
"version": "0.3.
|
|
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.
|
|
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.
|
|
20
|
-
"@frockbot/plugin-prompt": "0.3.
|
|
21
|
-
"@frockbot/plugin-tools": "0.3.
|
|
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("
|
|
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 === "
|
|
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
|
|
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 &&
|