@frockbot/kernel-do 0.3.38 → 0.3.40

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-do",
3
- "version": "0.3.38",
3
+ "version": "0.3.40",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,8 +12,8 @@
12
12
  "typecheck": "tsc --noEmit -p tsconfig.json"
13
13
  },
14
14
  "dependencies": {
15
- "@frockbot/kernel-composition": "0.3.38",
16
- "@frockbot/kernel-contracts": "0.3.38",
15
+ "@frockbot/kernel-composition": "0.3.40",
16
+ "@frockbot/kernel-contracts": "0.3.40",
17
17
  "cordis": "4.0.0-rc.8"
18
18
  },
19
19
  "devDependencies": {
package/src/authority.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  // detail here; everything above them is Package policy behind narrow hooks.
6
6
  import {
7
7
  decodeSessionEvent,
8
+ validateToolOccurrenceJournal,
8
9
  type NormalizedModelRequest,
9
10
  type SessionEvent,
10
11
  } from "@frockbot/kernel-contracts";
@@ -1982,7 +1983,14 @@ export class BotDurableAuthority<Snapshot> {
1982
1983
  ): Promise<BotTurnCompletion | undefined> {
1983
1984
  const provider = latestModelRequestProviderV1(events);
1984
1985
  const reconciles = this.hooks.providerReconciles ?? (() => true);
1985
- if (provider === undefined || reconciles(provider)) {
1986
+ // A model's retrieval policy says nothing about an unresolved tool effect.
1987
+ // Preserve its intent for the tool reconciliation path (ADR 0028).
1988
+ const unresolvedTool =
1989
+ events.some((event) => event.type === "tool/call") &&
1990
+ [...validateToolOccurrenceJournal(events).values()].some(
1991
+ (entry) => entry.intent && !entry.result,
1992
+ );
1993
+ if (unresolvedTool || provider === undefined || reconciles(provider)) {
1986
1994
  await this.requireRunReconciliation(runId, previous, events, reason);
1987
1995
  return undefined;
1988
1996
  }
@@ -77,7 +77,11 @@ function command(runId: string, text: string): OwnedBotTurnCommand {
77
77
  function createAuthority(
78
78
  storage: MemoryStorage,
79
79
  provider: string,
80
- options: { providerReconciles?: boolean; refuseWith?: string } = {},
80
+ options: {
81
+ providerReconciles?: boolean;
82
+ refuseWith?: string;
83
+ toolEffect?: boolean;
84
+ } = {},
81
85
  ): BotDurableAuthority<undefined> {
82
86
  const hooks: BotDurableAuthorityHooks<undefined> = {
83
87
  resolveAdmissionSnapshot: () => Promise.resolve(undefined),
@@ -164,6 +168,30 @@ function createAuthority(
164
168
  appended,
165
169
  );
166
170
  }
171
+ if (options.toolEffect) {
172
+ await persist(
173
+ {
174
+ type: "assistant/message",
175
+ turn: 1,
176
+ step: 1,
177
+ requestId: "request-1",
178
+ text: "",
179
+ toolCalls: [{ id: "call-one", name: "external_action", input: {} }],
180
+ } as never,
181
+ {
182
+ type: "tool/call",
183
+ turn: 1,
184
+ step: 1,
185
+ occurrenceId: "tool:1:1:0",
186
+ name: "external_action",
187
+ input: {},
188
+ } as never,
189
+ );
190
+ throw new BotTurnReconciliationRequiredError(
191
+ "Tool effect outcome is unknown",
192
+ appended,
193
+ );
194
+ }
167
195
  const reason = `Model response outcome is uncertain: ${MODEL_FIRST_BYTE_DEADLINE_REASON_V1}`;
168
196
  await persist({
169
197
  type: "model/reconciliation-required",
@@ -263,3 +291,26 @@ describe("a model request that ran out of time", () => {
263
291
  expect(storage.values.get("active-run")).toBeUndefined();
264
292
  });
265
293
  });
294
+
295
+ test("a non-retrieving model never terminalizes an unresolved tool effect", async () => {
296
+ const storage = new MemoryStorage();
297
+ const authority = createAuthority(storage, "flock-ai", { toolEffect: true });
298
+ await expect(
299
+ authority.run(command("run-tool", "perform an action")),
300
+ ).rejects.toThrow();
301
+ const run = await storedRun(authority, "run-tool");
302
+ expect(run.status).toBe("reconciliation-required");
303
+ expect(
304
+ run.events.some(
305
+ (event) => event.type === "tool/result" || event.type === "turn/end",
306
+ ),
307
+ ).toBe(false);
308
+ expect(storage.values.get("active-run")).toBe("run-tool");
309
+ const reconstructed = createAuthority(storage, "flock-ai", {
310
+ toolEffect: true,
311
+ });
312
+ await reconstructed.recoverActiveRun();
313
+ expect((await storedRun(reconstructed, "run-tool")).status).toBe(
314
+ "reconciliation-required",
315
+ );
316
+ });