@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/plugin-shell",
3
- "version": "0.3.2",
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.2",
51
- "@frockbot/application-foundation": "0.3.2",
52
- "@frockbot/catalog-core": "0.3.2",
53
- "@frockbot/client-core": "0.3.2",
54
- "@frockbot/client-ui": "0.3.2",
55
- "@frockbot/computer-core": "0.3.2",
56
- "@frockbot/computer-host-protocol": "0.3.2",
57
- "@frockbot/configuration-core": "0.3.2",
58
- "@frockbot/connection-core": "0.3.2",
59
- "@frockbot/kernel-agent-loop": "0.3.2",
60
- "@frockbot/kernel-composition": "0.3.2",
61
- "@frockbot/kernel-contracts": "0.3.2",
62
- "@frockbot/kernel-do": "0.3.2",
63
- "@frockbot/machine-protocol": "0.3.2",
64
- "@frockbot/plugin-applets": "0.3.2",
65
- "@frockbot/plugin-authoring": "0.3.2",
66
- "@frockbot/plugin-bot-template": "0.3.2",
67
- "@frockbot/plugin-computer": "0.3.2",
68
- "@frockbot/plugin-flock": "0.3.2",
69
- "@frockbot/plugin-image": "0.3.2",
70
- "@frockbot/plugin-machine-messages": "0.3.2",
71
- "@frockbot/plugin-mcp": "0.3.2",
72
- "@frockbot/plugin-memory": "0.3.2",
73
- "@frockbot/plugin-package-catalog": "0.3.2",
74
- "@frockbot/plugin-routines": "0.3.2",
75
- "@frockbot/plugin-skills": "0.3.2",
76
- "@frockbot/plugin-subagents": "0.3.2",
77
- "@frockbot/plugin-user-machine": "0.3.2",
78
- "@frockbot/protocol": "0.3.2",
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.2",
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([WAKE_PARENT_TOOL_V1]);
90
- expect(subagent).toEqual([WAKE_PARENT_TOOL_V1]);
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([WAKE_PARENT_TOOL_V1]);
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();
@@ -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(options.currentToolNames?.() ?? []);
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<"failed" | "cancelled" | "preserved-completion" | "missing"> {
45
+ ): Promise<
46
+ "failed" | "cancelled" | "superseded" | "preserved-completion" | "missing"
47
+ > {
46
48
  return failKernelStoredRun(
47
49
  storedRunCodecV1,
48
50
  storage,
@@ -22,24 +22,32 @@ export {
22
22
  type ModelRequestJournalState,
23
23
  } from "@frockbot/kernel-do";
24
24
 
25
- /** How a stopped run's unresolved effects settle. */
26
- export type StoppedRunRecoveryPlan =
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 stopped run from their durable
31
- * admission records. With no compatibility data, a missing admission is the
32
- * same definitive no-start outcome as `fenced`; only explicit `admitted`
33
- * remains uncertain.
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 planStoppedRunRecovery(
40
+ export function planInterruptedRunRecoveryV1(
36
41
  run: StoredRun,
37
42
  latest: readonly SessionEvent[],
38
- ): StoppedRunRecoveryPlan {
43
+ ): InterruptedRunRecoveryPlanV1 {
39
44
  requireStoredRunV1(run);
40
- if (!run.stopRequestedAt) {
41
- throw new Error(`run "${run.runId}" has no durable stop intent`);
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: "Durable Stop fenced provider execution before admission",
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: "Durable Stop fenced tool execution before admission.",
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({ name: "weather_lookup", content: '{"temperature":21}' });
78
+ ).toMatchObject({
79
+ name: "call_dynamic_tool",
80
+ content: '{"temperature":21}',
81
+ });
73
82
  });
74
83
  });
@@ -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: command.directTool.name,
181
- input: command.directTool.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(
@@ -12,7 +12,7 @@ import {
12
12
  botTurnCommandFingerprintV1,
13
13
  type StoredRun,
14
14
  } from "./backend-contracts.js";
15
- import { planStoppedRunRecovery } from "./backend-recovery.js";
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 = planStoppedRunRecovery(run, run.events);
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 = planStoppedRunRecovery(run, run.events);
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(planStoppedRunRecovery(run, run.events)).toEqual({
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(() => planStoppedRunRecovery(run, run.events)).toThrow(
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
  });