@frockbot/plugin-routines 0.3.2 → 0.3.3

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/plugin-routines",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -32,12 +32,12 @@
32
32
  "typecheck": "vue-tsc --noEmit -p tsconfig.json"
33
33
  },
34
34
  "dependencies": {
35
- "@frockbot/client-core": "0.3.2",
36
- "@frockbot/client-ui": "0.3.2",
37
- "@frockbot/configuration-core": "0.3.2",
38
- "@frockbot/kernel-agent-loop": "0.3.2",
39
- "@frockbot/kernel-contracts": "0.3.2",
40
- "@frockbot/plugin-shell": "0.3.2",
35
+ "@frockbot/client-core": "0.3.3",
36
+ "@frockbot/client-ui": "0.3.3",
37
+ "@frockbot/configuration-core": "0.3.3",
38
+ "@frockbot/kernel-agent-loop": "0.3.3",
39
+ "@frockbot/kernel-contracts": "0.3.3",
40
+ "@frockbot/plugin-shell": "0.3.3",
41
41
  "cordis": "4.0.0-rc.8",
42
42
  "croner": "10.0.1",
43
43
  "vue": "3.5.41"
package/src/inbox.test.ts CHANGED
@@ -400,3 +400,38 @@ describe("the machine-result variant", () => {
400
400
  ).toBe("exit 0 — nothing to commit");
401
401
  });
402
402
  });
403
+
404
+ describe("a Turn the User's next message replaced", () => {
405
+ const superseded = {
406
+ schemaVersion: 1 as const,
407
+ kind: "superseded-turn" as const,
408
+ runId: "run-1",
409
+ unfinishedWork: true,
410
+ createdAt: NOW,
411
+ };
412
+
413
+ test("round-trips, and is keyed by the Turn it replaced", () => {
414
+ expect(decodePendingBotInputV1(superseded, "input")).toEqual(superseded);
415
+ expect(() =>
416
+ decodePendingBotInputV1(
417
+ { ...superseded, unfinishedWork: "yes" },
418
+ "input",
419
+ ),
420
+ ).toThrow(/unfinishedWork is invalid/);
421
+ expect(() =>
422
+ decodePendingBotInputV1({ ...superseded, extra: 1 }, "input"),
423
+ ).toThrow();
424
+ });
425
+
426
+ test("tells the next Turn what happened and what is still running", () => {
427
+ const preamble = pendingBotInputPreambleV1([superseded]);
428
+
429
+ expect(preamble).toContain("[Superseded]");
430
+ expect(preamble).toContain("must not be assumed to have happened");
431
+ expect(preamble).toContain("Subagents that Turn dispatched are still");
432
+ // With nothing left running the reminder says nothing about subagents.
433
+ expect(
434
+ pendingBotInputPreambleV1([{ ...superseded, unfinishedWork: false }]),
435
+ ).not.toContain("Subagents");
436
+ });
437
+ });
package/src/inbox.ts CHANGED
@@ -144,16 +144,38 @@ export interface RoutinePendingMachineResultV1 {
144
144
  /** The longest preview a machine-result input carries. */
145
145
  export const MACHINE_RESULT_PREVIEW_MAX_V1 = 400;
146
146
 
147
+ /**
148
+ * A Turn the User's next message took the place of, waiting to be told to the
149
+ * Bot.
150
+ *
151
+ * The fourth variant, and the only one a Turn writes about itself. A
152
+ * superseded Turn's sends and completed tool results are already in the
153
+ * session log, so the model sees what it did; what it cannot see is *why* it
154
+ * stopped mid-sentence, or that a subagent it dispatched is still working. It
155
+ * reaches the next Turn as durable input rather than as a silent gap.
156
+ */
157
+ export interface RoutinePendingSupersededTurnV1 {
158
+ schemaVersion: 1;
159
+ kind: "superseded-turn";
160
+ /** The Turn that was superseded. */
161
+ runId: string;
162
+ /** Whether that Turn had dispatched subagents that outlive it. */
163
+ unfinishedWork: boolean;
164
+ createdAt: string;
165
+ }
166
+
147
167
  /** One durable input the Bot's next conversational Turn is owed. */
148
168
  export type PendingBotInputV1 =
149
169
  | RoutinePendingWakeV1
150
170
  | RoutinePendingApprovalV1
151
- | RoutinePendingMachineResultV1;
171
+ | RoutinePendingMachineResultV1
172
+ | RoutinePendingSupersededTurnV1;
152
173
 
153
174
  /** The id one pending input is keyed and de-duplicated by. */
154
175
  export function pendingBotInputIdV1(input: PendingBotInputV1): string {
155
176
  if (input.kind === "wake") return input.wakeId;
156
177
  if (input.kind === "approval") return input.approvalId;
178
+ if (input.kind === "superseded-turn") return `superseded-turn:${input.runId}`;
157
179
  return `machine-result:${input.commandId}`;
158
180
  }
159
181
 
@@ -297,6 +319,24 @@ export function decodePendingBotInputV1(
297
319
  createdAt: routineTimestamp(candidate.createdAt, `${label} createdAt`),
298
320
  };
299
321
  }
322
+ if (candidate.kind === "superseded-turn") {
323
+ routineExactKeys(
324
+ candidate,
325
+ ["schemaVersion", "kind", "runId", "unfinishedWork", "createdAt"],
326
+ [],
327
+ label,
328
+ );
329
+ if (typeof candidate.unfinishedWork !== "boolean") {
330
+ throw new RoutineDecodeError(`${label} unfinishedWork is invalid`);
331
+ }
332
+ return {
333
+ schemaVersion: 1,
334
+ kind: "superseded-turn",
335
+ runId: routineText(candidate.runId, 256, `${label} runId`),
336
+ unfinishedWork: candidate.unfinishedWork,
337
+ createdAt: routineTimestamp(candidate.createdAt, `${label} createdAt`),
338
+ };
339
+ }
300
340
  if (candidate.kind !== "wake") {
301
341
  throw new RoutineDecodeError(`${label} kind is invalid`);
302
342
  }
@@ -395,6 +435,18 @@ export function pendingBotInputPreambleV1(
395
435
  );
396
436
  continue;
397
437
  }
438
+ if (input.kind === "superseded-turn") {
439
+ lines.push(
440
+ "[Superseded] Your previous Turn was interrupted because the User sent this message before it finished. What you had already sent and the tool results you had already received are above; anything still in flight was abandoned and must not be assumed to have happened.",
441
+ ...(input.unfinishedWork
442
+ ? [
443
+ "Subagents that Turn dispatched are still running and will report when they finish.",
444
+ ]
445
+ : []),
446
+ "",
447
+ );
448
+ continue;
449
+ }
398
450
  lines.push(
399
451
  `[Machine] Command "${input.commandId}" on machine ${input.machineId} finished ${input.outcome}: ${input.preview}`,
400
452
  `Call machine_command_check with commandId "${input.commandId}" to read the whole result.`,