@frockbot/plugin-shell 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 +31 -31
- package/src/agent.test.ts +15 -3
- package/src/backend-authoring.ts +14 -1
- package/src/backend-completion.ts +4 -2
- package/src/backend-recovery.ts +20 -12
- package/src/backend-runner-iframe.test.ts +10 -1
- package/src/backend-runner.ts +9 -2
- package/src/backend-stop.test.ts +6 -6
- package/src/backend-supersede.test.ts +377 -0
- package/src/backend.ts +59 -8
- package/src/client/FrockBotApp.vue +26 -6
- package/src/client/index.test.ts +208 -0
- package/src/client/index.ts +90 -5
- package/src/client/styles.css +11 -0
- package/src/run-protocol.test.ts +92 -0
- package/src/run-protocol.ts +193 -17
- package/src/shared.ts +18 -0
- package/src/terminal-records.test.ts +52 -1
- package/src/terminal-records.ts +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/plugin-shell",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -47,41 +47,41 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@cordisjs/plugin-webui": "0.8.2",
|
|
50
|
-
"@frockbot/agent-runtime": "0.3.
|
|
51
|
-
"@frockbot/application-foundation": "0.3.
|
|
52
|
-
"@frockbot/catalog-core": "0.3.
|
|
53
|
-
"@frockbot/client-core": "0.3.
|
|
54
|
-
"@frockbot/client-ui": "0.3.
|
|
55
|
-
"@frockbot/computer-core": "0.3.
|
|
56
|
-
"@frockbot/computer-host-protocol": "0.3.
|
|
57
|
-
"@frockbot/configuration-core": "0.3.
|
|
58
|
-
"@frockbot/connection-core": "0.3.
|
|
59
|
-
"@frockbot/kernel-agent-loop": "0.3.
|
|
60
|
-
"@frockbot/kernel-composition": "0.3.
|
|
61
|
-
"@frockbot/kernel-contracts": "0.3.
|
|
62
|
-
"@frockbot/kernel-do": "0.3.
|
|
63
|
-
"@frockbot/machine-protocol": "0.3.
|
|
64
|
-
"@frockbot/plugin-applets": "0.3.
|
|
65
|
-
"@frockbot/plugin-authoring": "0.3.
|
|
66
|
-
"@frockbot/plugin-bot-template": "0.3.
|
|
67
|
-
"@frockbot/plugin-computer": "0.3.
|
|
68
|
-
"@frockbot/plugin-flock": "0.3.
|
|
69
|
-
"@frockbot/plugin-image": "0.3.
|
|
70
|
-
"@frockbot/plugin-machine-messages": "0.3.
|
|
71
|
-
"@frockbot/plugin-mcp": "0.3.
|
|
72
|
-
"@frockbot/plugin-memory": "0.3.
|
|
73
|
-
"@frockbot/plugin-package-catalog": "0.3.
|
|
74
|
-
"@frockbot/plugin-routines": "0.3.
|
|
75
|
-
"@frockbot/plugin-skills": "0.3.
|
|
76
|
-
"@frockbot/plugin-subagents": "0.3.
|
|
77
|
-
"@frockbot/plugin-user-machine": "0.3.
|
|
78
|
-
"@frockbot/protocol": "0.3.
|
|
50
|
+
"@frockbot/agent-runtime": "0.3.3",
|
|
51
|
+
"@frockbot/application-foundation": "0.3.3",
|
|
52
|
+
"@frockbot/catalog-core": "0.3.3",
|
|
53
|
+
"@frockbot/client-core": "0.3.3",
|
|
54
|
+
"@frockbot/client-ui": "0.3.3",
|
|
55
|
+
"@frockbot/computer-core": "0.3.3",
|
|
56
|
+
"@frockbot/computer-host-protocol": "0.3.3",
|
|
57
|
+
"@frockbot/configuration-core": "0.3.3",
|
|
58
|
+
"@frockbot/connection-core": "0.3.3",
|
|
59
|
+
"@frockbot/kernel-agent-loop": "0.3.3",
|
|
60
|
+
"@frockbot/kernel-composition": "0.3.3",
|
|
61
|
+
"@frockbot/kernel-contracts": "0.3.3",
|
|
62
|
+
"@frockbot/kernel-do": "0.3.3",
|
|
63
|
+
"@frockbot/machine-protocol": "0.3.3",
|
|
64
|
+
"@frockbot/plugin-applets": "0.3.3",
|
|
65
|
+
"@frockbot/plugin-authoring": "0.3.3",
|
|
66
|
+
"@frockbot/plugin-bot-template": "0.3.3",
|
|
67
|
+
"@frockbot/plugin-computer": "0.3.3",
|
|
68
|
+
"@frockbot/plugin-flock": "0.3.3",
|
|
69
|
+
"@frockbot/plugin-image": "0.3.3",
|
|
70
|
+
"@frockbot/plugin-machine-messages": "0.3.3",
|
|
71
|
+
"@frockbot/plugin-mcp": "0.3.3",
|
|
72
|
+
"@frockbot/plugin-memory": "0.3.3",
|
|
73
|
+
"@frockbot/plugin-package-catalog": "0.3.3",
|
|
74
|
+
"@frockbot/plugin-routines": "0.3.3",
|
|
75
|
+
"@frockbot/plugin-skills": "0.3.3",
|
|
76
|
+
"@frockbot/plugin-subagents": "0.3.3",
|
|
77
|
+
"@frockbot/plugin-user-machine": "0.3.3",
|
|
78
|
+
"@frockbot/protocol": "0.3.3",
|
|
79
79
|
"cordis": "4.0.0-rc.8",
|
|
80
80
|
"vue": "3.5.41"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@cloudflare/workers-types": "latest",
|
|
84
|
-
"@frockbot/plugin-tools": "0.3.
|
|
84
|
+
"@frockbot/plugin-tools": "0.3.3",
|
|
85
85
|
"@types/bun": "1.3.6",
|
|
86
86
|
"@types/node": "26.2.0",
|
|
87
87
|
"@vitejs/plugin-vue": "6.0.8",
|
package/src/agent.test.ts
CHANGED
|
@@ -86,8 +86,16 @@ describe("the Shell's tool admission", () => {
|
|
|
86
86
|
expect(chat).toContain(SEND_TO_USER_TOOL_V1);
|
|
87
87
|
expect(chat).toContain(SEND_MESSAGE_ALIAS_V1);
|
|
88
88
|
expect(chat).not.toContain(WAKE_PARENT_TOOL_V1);
|
|
89
|
-
expect(automation).toEqual([
|
|
90
|
-
|
|
89
|
+
expect(automation).toEqual([
|
|
90
|
+
WAKE_PARENT_TOOL_V1,
|
|
91
|
+
"get_dynamic_tools",
|
|
92
|
+
"call_dynamic_tool",
|
|
93
|
+
]);
|
|
94
|
+
expect(subagent).toEqual([
|
|
95
|
+
WAKE_PARENT_TOOL_V1,
|
|
96
|
+
"get_dynamic_tools",
|
|
97
|
+
"call_dynamic_tool",
|
|
98
|
+
]);
|
|
91
99
|
} finally {
|
|
92
100
|
await mounted.dispose();
|
|
93
101
|
}
|
|
@@ -111,7 +119,11 @@ describe("the Shell's tool admission", () => {
|
|
|
111
119
|
// thing any subagent role is offered here.
|
|
112
120
|
expect(names).not.toContain(SEND_TO_USER_TOOL_V1);
|
|
113
121
|
expect(names).not.toContain(SEND_MESSAGE_ALIAS_V1);
|
|
114
|
-
expect(names).toEqual([
|
|
122
|
+
expect(names).toEqual([
|
|
123
|
+
WAKE_PARENT_TOOL_V1,
|
|
124
|
+
"get_dynamic_tools",
|
|
125
|
+
"call_dynamic_tool",
|
|
126
|
+
]);
|
|
115
127
|
}
|
|
116
128
|
} finally {
|
|
117
129
|
await mounted.dispose();
|
package/src/backend-authoring.ts
CHANGED
|
@@ -278,12 +278,25 @@ export function createPackageAuthoringHost(
|
|
|
278
278
|
* Re-authoring may keep or rename tools owned by that same Package, so its
|
|
279
279
|
* currently stored declarations are subtracted from the mounted catalog;
|
|
280
280
|
* every first-party tool and every other authored Package remains reserved.
|
|
281
|
+
*
|
|
282
|
+
* The comparison is on bare names. Since ADR 0023 the mounted catalog
|
|
283
|
+
* reports a progressively disclosed tool as `namespace/name`, while a Package
|
|
284
|
+
* declares bare names, so comparing the two directly matches nothing and the
|
|
285
|
+
* guard silently stops refusing anything. Stripping the namespace keeps the
|
|
286
|
+
* rule this guard has always enforced — a Bot-authored tool name may not
|
|
287
|
+
* shadow one already registered — rather than quietly relaxing it as a side
|
|
288
|
+
* effect of a disclosure change.
|
|
281
289
|
*/
|
|
282
290
|
async function collidingToolName(
|
|
283
291
|
packageId: string,
|
|
284
292
|
declaredNames: readonly string[],
|
|
285
293
|
): Promise<string | undefined> {
|
|
286
|
-
const registered = new Set(
|
|
294
|
+
const registered = new Set(
|
|
295
|
+
(options.currentToolNames?.() ?? []).map((name) => {
|
|
296
|
+
const separator = name.indexOf("/");
|
|
297
|
+
return separator < 0 ? name : name.slice(separator + 1);
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
287
300
|
const mounted =
|
|
288
301
|
options.mountedGeneration?.() ??
|
|
289
302
|
(await options.composition.lastKnownGood());
|
|
@@ -23,7 +23,7 @@ export function completeStoredRun(
|
|
|
23
23
|
previous: readonly SessionEvent[],
|
|
24
24
|
result: BotTurnCompletion,
|
|
25
25
|
packageRecords?: TerminalPackageRecords<BotSettingsViewV1>,
|
|
26
|
-
): Promise<"completed" | "cancelled"> {
|
|
26
|
+
): Promise<"completed" | "cancelled" | "superseded"> {
|
|
27
27
|
return completeKernelStoredRun(
|
|
28
28
|
storedRunCodecV1,
|
|
29
29
|
storage,
|
|
@@ -42,7 +42,9 @@ export function failStoredRun(
|
|
|
42
42
|
previous: readonly SessionEvent[],
|
|
43
43
|
events: readonly SessionEvent[],
|
|
44
44
|
failure: string,
|
|
45
|
-
): Promise<
|
|
45
|
+
): Promise<
|
|
46
|
+
"failed" | "cancelled" | "superseded" | "preserved-completion" | "missing"
|
|
47
|
+
> {
|
|
46
48
|
return failKernelStoredRun(
|
|
47
49
|
storedRunCodecV1,
|
|
48
50
|
storage,
|
package/src/backend-recovery.ts
CHANGED
|
@@ -22,24 +22,32 @@ export {
|
|
|
22
22
|
type ModelRequestJournalState,
|
|
23
23
|
} from "@frockbot/kernel-do";
|
|
24
24
|
|
|
25
|
-
/** How
|
|
26
|
-
export type
|
|
25
|
+
/** How an interrupted run's unresolved effects settle. */
|
|
26
|
+
export type InterruptedRunRecoveryPlanV1 =
|
|
27
27
|
{ kind: "cancel"; events: SessionEvent[] } | { kind: "reconcile" };
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Classifies the unresolved effects of a
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* Classifies the unresolved effects of a run that has been fenced — by a
|
|
31
|
+
* durable Stop, or by a later user message that superseded it — from their
|
|
32
|
+
* durable admission records. With no compatibility data, a missing admission
|
|
33
|
+
* is the same definitive no-start outcome as `fenced`; only explicit
|
|
34
|
+
* `admitted` remains uncertain.
|
|
35
|
+
*
|
|
36
|
+
* The two intents share this function because they share the whole question:
|
|
37
|
+
* an effect that may already have run is never assumed not to have, whichever
|
|
38
|
+
* intent stopped the Turn.
|
|
34
39
|
*/
|
|
35
|
-
export function
|
|
40
|
+
export function planInterruptedRunRecoveryV1(
|
|
36
41
|
run: StoredRun,
|
|
37
42
|
latest: readonly SessionEvent[],
|
|
38
|
-
):
|
|
43
|
+
): InterruptedRunRecoveryPlanV1 {
|
|
39
44
|
requireStoredRunV1(run);
|
|
40
|
-
if (!run.stopRequestedAt) {
|
|
41
|
-
throw new Error(
|
|
45
|
+
if (!run.stopRequestedAt && !run.supersededAt) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`run "${run.runId}" has no durable stop or supersede intent`,
|
|
48
|
+
);
|
|
42
49
|
}
|
|
50
|
+
const fenceReason = run.stopRequestedAt ? "Durable Stop" : "A supersede";
|
|
43
51
|
const admissionFor = (kind: "model" | "tool", effectId: string) => {
|
|
44
52
|
const admission = run.effectAdmissions.find(
|
|
45
53
|
(candidate) => candidate.effectId === effectId,
|
|
@@ -75,7 +83,7 @@ export function planStoppedRunRecovery(
|
|
|
75
83
|
turn: model.request.turn,
|
|
76
84
|
step: model.request.step,
|
|
77
85
|
requestId: model.request.request.requestId,
|
|
78
|
-
reason:
|
|
86
|
+
reason: `${fenceReason} fenced provider execution before admission`,
|
|
79
87
|
});
|
|
80
88
|
}
|
|
81
89
|
for (const entry of openTools) {
|
|
@@ -86,7 +94,7 @@ export function planStoppedRunRecovery(
|
|
|
86
94
|
step: intent.step,
|
|
87
95
|
occurrenceId: intent.occurrenceId,
|
|
88
96
|
name: intent.name,
|
|
89
|
-
content:
|
|
97
|
+
content: `${fenceReason} fenced tool execution before admission.`,
|
|
90
98
|
isError: true,
|
|
91
99
|
status: "interrupted",
|
|
92
100
|
});
|
|
@@ -12,8 +12,14 @@ describe("Package iframe direct tool Turn", () => {
|
|
|
12
12
|
await root.plugin(ToolRegistry);
|
|
13
13
|
const session = root.sessions.create("user:bot");
|
|
14
14
|
let calls = 0;
|
|
15
|
+
root.tools.registerNamespace({
|
|
16
|
+
name: "weather-page",
|
|
17
|
+
external: true,
|
|
18
|
+
status: "ready",
|
|
19
|
+
});
|
|
15
20
|
root.tools.register({
|
|
16
21
|
name: "weather_lookup",
|
|
22
|
+
namespace: "weather-page",
|
|
17
23
|
description: "Weather",
|
|
18
24
|
inputSchema: {},
|
|
19
25
|
idempotent: true,
|
|
@@ -69,6 +75,9 @@ describe("Package iframe direct tool Turn", () => {
|
|
|
69
75
|
);
|
|
70
76
|
expect(
|
|
71
77
|
result.events.find((event) => event.type === "tool/result"),
|
|
72
|
-
).toMatchObject({
|
|
78
|
+
).toMatchObject({
|
|
79
|
+
name: "call_dynamic_tool",
|
|
80
|
+
content: '{"temperature":21}',
|
|
81
|
+
});
|
|
73
82
|
});
|
|
74
83
|
});
|
package/src/backend-runner.ts
CHANGED
|
@@ -177,8 +177,15 @@ export async function executeDirectToolTurn(
|
|
|
177
177
|
const session = composition.runtime.agent.agent.session;
|
|
178
178
|
const call = {
|
|
179
179
|
id: command.runId,
|
|
180
|
-
name:
|
|
181
|
-
input:
|
|
180
|
+
name: "call_dynamic_tool",
|
|
181
|
+
input: {
|
|
182
|
+
namespace: command.directTool.packageId,
|
|
183
|
+
toolName: command.directTool.name,
|
|
184
|
+
arguments: command.directTool.input,
|
|
185
|
+
mcpDetails: {
|
|
186
|
+
description: `The User invoked ${command.directTool.name} from the Package UI.`,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
182
189
|
};
|
|
183
190
|
try {
|
|
184
191
|
let turnStart = [...session.events].findLast(
|
package/src/backend-stop.test.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
botTurnCommandFingerprintV1,
|
|
13
13
|
type StoredRun,
|
|
14
14
|
} from "./backend-contracts.js";
|
|
15
|
-
import {
|
|
15
|
+
import { planInterruptedRunRecoveryV1 } from "./backend-recovery.js";
|
|
16
16
|
|
|
17
17
|
class MemoryStorage {
|
|
18
18
|
readonly values = new Map<string, unknown>();
|
|
@@ -298,7 +298,7 @@ describe("stopped run recovery", () => {
|
|
|
298
298
|
],
|
|
299
299
|
});
|
|
300
300
|
|
|
301
|
-
const plan =
|
|
301
|
+
const plan = planInterruptedRunRecoveryV1(run, run.events);
|
|
302
302
|
|
|
303
303
|
expect(plan.kind).toBe("cancel");
|
|
304
304
|
if (plan.kind !== "cancel") throw new Error("expected cancellation");
|
|
@@ -323,7 +323,7 @@ describe("stopped run recovery", () => {
|
|
|
323
323
|
],
|
|
324
324
|
});
|
|
325
325
|
|
|
326
|
-
const plan =
|
|
326
|
+
const plan = planInterruptedRunRecoveryV1(run, run.events);
|
|
327
327
|
|
|
328
328
|
expect(plan.kind).toBe("cancel");
|
|
329
329
|
if (plan.kind !== "cancel") throw new Error("expected cancellation");
|
|
@@ -341,7 +341,7 @@ describe("stopped run recovery", () => {
|
|
|
341
341
|
],
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
-
expect(
|
|
344
|
+
expect(planInterruptedRunRecoveryV1(run, run.events)).toEqual({
|
|
345
345
|
kind: "reconcile",
|
|
346
346
|
});
|
|
347
347
|
});
|
|
@@ -349,8 +349,8 @@ describe("stopped run recovery", () => {
|
|
|
349
349
|
test("refuses to plan recovery for a run carrying no durable Stop intent", () => {
|
|
350
350
|
const run = storedRun({ events: modelIntentEvents() });
|
|
351
351
|
|
|
352
|
-
expect(() =>
|
|
353
|
-
`run "${turn.runId}" has no durable stop intent`,
|
|
352
|
+
expect(() => planInterruptedRunRecoveryV1(run, run.events)).toThrow(
|
|
353
|
+
`run "${turn.runId}" has no durable stop or supersede intent`,
|
|
354
354
|
);
|
|
355
355
|
});
|
|
356
356
|
});
|