@frockbot/plugin-routines 0.3.2 → 0.3.4
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 +7 -7
- package/src/inbox.test.ts +35 -0
- package/src/inbox.ts +53 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/plugin-routines",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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.
|
|
36
|
-
"@frockbot/client-ui": "0.3.
|
|
37
|
-
"@frockbot/configuration-core": "0.3.
|
|
38
|
-
"@frockbot/kernel-agent-loop": "0.3.
|
|
39
|
-
"@frockbot/kernel-contracts": "0.3.
|
|
40
|
-
"@frockbot/plugin-shell": "0.3.
|
|
35
|
+
"@frockbot/client-core": "0.3.4",
|
|
36
|
+
"@frockbot/client-ui": "0.3.4",
|
|
37
|
+
"@frockbot/configuration-core": "0.3.4",
|
|
38
|
+
"@frockbot/kernel-agent-loop": "0.3.4",
|
|
39
|
+
"@frockbot/kernel-contracts": "0.3.4",
|
|
40
|
+
"@frockbot/plugin-shell": "0.3.4",
|
|
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.`,
|