@frockbot/plugin-shell 0.3.15 → 0.3.16

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.15",
3
+ "version": "0.3.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -51,42 +51,42 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@cordisjs/plugin-webui": "0.8.2",
54
- "@frockbot/agent-runtime": "0.3.15",
55
- "@frockbot/application-foundation": "0.3.15",
56
- "@frockbot/catalog-core": "0.3.15",
57
- "@frockbot/client-core": "0.3.15",
58
- "@frockbot/client-ui": "0.3.15",
59
- "@frockbot/computer-core": "0.3.15",
60
- "@frockbot/computer-host-protocol": "0.3.15",
61
- "@frockbot/configuration-core": "0.3.15",
62
- "@frockbot/connection-core": "0.3.15",
63
- "@frockbot/kernel-agent-loop": "0.3.15",
64
- "@frockbot/kernel-composition": "0.3.15",
65
- "@frockbot/kernel-contracts": "0.3.15",
66
- "@frockbot/kernel-do": "0.3.15",
67
- "@frockbot/machine-protocol": "0.3.15",
68
- "@frockbot/plugin-applets": "0.3.15",
69
- "@frockbot/plugin-authoring": "0.3.15",
70
- "@frockbot/plugin-bot-template": "0.3.15",
71
- "@frockbot/plugin-computer": "0.3.15",
72
- "@frockbot/plugin-flock": "0.3.15",
73
- "@frockbot/plugin-image": "0.3.15",
74
- "@frockbot/plugin-machine-messages": "0.3.15",
75
- "@frockbot/plugin-mcp": "0.3.15",
76
- "@frockbot/plugin-memory": "0.3.15",
77
- "@frockbot/plugin-package-catalog": "0.3.15",
78
- "@frockbot/plugin-routines": "0.3.15",
79
- "@frockbot/plugin-skills": "0.3.15",
80
- "@frockbot/plugin-subagents": "0.3.15",
81
- "@frockbot/plugin-user-machine": "0.3.15",
82
- "@frockbot/protocol": "0.3.15",
54
+ "@frockbot/agent-runtime": "0.3.16",
55
+ "@frockbot/application-foundation": "0.3.16",
56
+ "@frockbot/catalog-core": "0.3.16",
57
+ "@frockbot/client-core": "0.3.16",
58
+ "@frockbot/client-ui": "0.3.16",
59
+ "@frockbot/computer-core": "0.3.16",
60
+ "@frockbot/computer-host-protocol": "0.3.16",
61
+ "@frockbot/configuration-core": "0.3.16",
62
+ "@frockbot/connection-core": "0.3.16",
63
+ "@frockbot/kernel-agent-loop": "0.3.16",
64
+ "@frockbot/kernel-composition": "0.3.16",
65
+ "@frockbot/kernel-contracts": "0.3.16",
66
+ "@frockbot/kernel-do": "0.3.16",
67
+ "@frockbot/machine-protocol": "0.3.16",
68
+ "@frockbot/plugin-applets": "0.3.16",
69
+ "@frockbot/plugin-authoring": "0.3.16",
70
+ "@frockbot/plugin-bot-template": "0.3.16",
71
+ "@frockbot/plugin-computer": "0.3.16",
72
+ "@frockbot/plugin-flock": "0.3.16",
73
+ "@frockbot/plugin-image": "0.3.16",
74
+ "@frockbot/plugin-machine-messages": "0.3.16",
75
+ "@frockbot/plugin-mcp": "0.3.16",
76
+ "@frockbot/plugin-memory": "0.3.16",
77
+ "@frockbot/plugin-package-catalog": "0.3.16",
78
+ "@frockbot/plugin-routines": "0.3.16",
79
+ "@frockbot/plugin-skills": "0.3.16",
80
+ "@frockbot/plugin-subagents": "0.3.16",
81
+ "@frockbot/plugin-user-machine": "0.3.16",
82
+ "@frockbot/protocol": "0.3.16",
83
83
  "cordis": "4.0.0-rc.8",
84
84
  "vue": "3.5.41"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@cloudflare/workers-types": "latest",
88
- "@frockbot/plugin-prompt": "0.3.15",
89
- "@frockbot/plugin-tools": "0.3.15",
88
+ "@frockbot/plugin-prompt": "0.3.16",
89
+ "@frockbot/plugin-tools": "0.3.16",
90
90
  "@types/bun": "1.3.6",
91
91
  "@types/node": "26.2.0",
92
92
  "@vitejs/plugin-vue": "6.0.8",
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { type SessionEvent } from "@frockbot/kernel-contracts";
3
3
  import { initializeBotSettingsV1 } from "@frockbot/configuration-core";
4
+ import { SessionEventLog } from "@frockbot/kernel-do";
4
5
  import {
5
6
  botTurnCommandFingerprintV1,
6
7
  type BotTurnCompletion,
@@ -81,11 +82,15 @@ class MemoryRunStorage implements RunTerminalStorage {
81
82
  return Promise.resolve(this.values.get(key) as T | undefined);
82
83
  }
83
84
 
84
- put(entries: Record<string, unknown>): Promise<void> {
85
- this.putBatches.push(structuredClone(entries));
86
- if (this.putFailure) return Promise.reject(this.putFailure);
87
- for (const [key, value] of Object.entries(entries)) {
85
+ put(key: string | Record<string, unknown>, value?: unknown): Promise<void> {
86
+ if (typeof key === "string") {
88
87
  this.values.set(key, structuredClone(value));
88
+ return Promise.resolve();
89
+ }
90
+ this.putBatches.push(structuredClone(key));
91
+ if (this.putFailure) return Promise.reject(this.putFailure);
92
+ for (const [entry, item] of Object.entries(key)) {
93
+ this.values.set(entry, structuredClone(item));
89
94
  }
90
95
  return Promise.resolve();
91
96
  }
@@ -93,6 +98,16 @@ class MemoryRunStorage implements RunTerminalStorage {
93
98
  delete(key: string): Promise<boolean> {
94
99
  return Promise.resolve(this.values.delete(key));
95
100
  }
101
+
102
+ list<T>(options: { prefix: string }): Promise<Map<string, T>> {
103
+ return Promise.resolve(
104
+ new Map(
105
+ [...this.values.entries()].filter(([key]) =>
106
+ key.startsWith(options.prefix),
107
+ ) as Array<[string, T]>,
108
+ ),
109
+ );
110
+ }
96
111
  }
97
112
 
98
113
  describe("Bot run terminal persistence", () => {
@@ -284,6 +299,8 @@ describe("Bot run terminal persistence", () => {
284
299
  failure: "provider-bound retrieval unavailable",
285
300
  });
286
301
  expect(storage.values.get(keys.activeRun)).toBe("run-1");
287
- expect(storage.values.get(keys.latestEvents)).toEqual([request]);
302
+ expect(await new SessionEventLog(storage).read("user:primary")).toEqual([
303
+ request,
304
+ ]);
288
305
  });
289
306
  });
@@ -6,6 +6,7 @@ import type {
6
6
  UserSettingsViewV1,
7
7
  } from "@frockbot/configuration-core";
8
8
  import type { PackageSettingDefinition } from "@frockbot/kernel-composition";
9
+ import { SessionEventLog } from "@frockbot/kernel-do";
9
10
  import { createShellBotBackendContribution } from "./backend.js";
10
11
  import { createIsolateCapabilityHost } from "./backend-isolate.js";
11
12
  import { notificationIdV1 } from "./notification-id.js";
@@ -549,11 +550,12 @@ describe("generic per-Turn model resolution", () => {
549
550
  });
550
551
 
551
552
  expect(result.text).toBe("Cordis runtime: hello");
552
- const durableEvents = storage.values.get("latest-events") as
553
- Array<{ seq: number }> | undefined;
554
- expect(durableEvents?.length).toBeGreaterThan(0);
555
- expect(durableEvents?.map((event) => event.seq)).toEqual(
556
- durableEvents?.map((_, index) => index),
553
+ const durableEvents = await new SessionEventLog(storage).read(
554
+ "user-1:primary",
555
+ );
556
+ expect(durableEvents.length).toBeGreaterThan(0);
557
+ expect(durableEvents.map((event) => event.seq)).toEqual(
558
+ durableEvents.map((_, index) => index),
557
559
  );
558
560
  const settings = await contribution.getSettings(identity);
559
561
  expect(settings).toMatchObject({ revision: 0, packageValues: {} });
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { initializeBotSettingsV1 } from "@frockbot/configuration-core";
3
+ import { SessionEventLog, storedRunRecordV2 } from "@frockbot/kernel-do";
3
4
  import { createShellBotBackendContribution } from "./backend.js";
4
5
  import type { StoredRun } from "./backend-contracts.js";
5
6
 
@@ -154,6 +155,54 @@ describe("Bot debug snapshot", () => {
154
155
  expect(snapshot.activeRunId).toBeUndefined();
155
156
  });
156
157
 
158
+ test("reads bounded projections instead of hydrating full model prompts", async () => {
159
+ const storage = new MemoryStorage();
160
+ const event = {
161
+ type: "model/request",
162
+ turn: 1,
163
+ step: 1,
164
+ request: {
165
+ requestId: "request-large",
166
+ provider: "fake",
167
+ model: "large-context",
168
+ system: "s".repeat(80_000),
169
+ messages: [{ role: "user", content: "last question" }],
170
+ tools: [],
171
+ },
172
+ seq: 0,
173
+ timestamp: "2026-08-28T00:00:01.000Z",
174
+ } as StoredRun["events"][number];
175
+ const run = storedRun({ events: [event] });
176
+ await new SessionEventLog(storage).rewrite(run.sessionId, [event]);
177
+ await storage.put({
178
+ identity: IDENTITY,
179
+ [`run:${run.runId}`]: storedRunRecordV2(run),
180
+ [`run-index:${run.acceptedAt}:${run.runId}`]: run.runId,
181
+ });
182
+
183
+ const snapshot = await contributionOver(storage).debugSnapshot(IDENTITY, {
184
+ schemaVersion: 1,
185
+ runId: run.runId,
186
+ });
187
+
188
+ expect(snapshot.runs[0]?.eventCount).toBe(1);
189
+ expect(snapshot.runs[0]?.events?.[0]).toMatchObject({
190
+ type: "model/request",
191
+ cut: { marker: "content-cut", originalBytes: expect.any(Number) },
192
+ request: {
193
+ requestId: "request-large",
194
+ messageCount: 1,
195
+ toolCount: 0,
196
+ sha256: expect.stringMatching(/^[0-9a-f]{64}$/),
197
+ truncated: true,
198
+ },
199
+ });
200
+ expect(JSON.stringify(snapshot)).not.toContain("s".repeat(10_000));
201
+ expect(
202
+ new TextEncoder().encode(JSON.stringify(snapshot)).byteLength,
203
+ ).toBeLessThan(20_000);
204
+ });
205
+
157
206
  test("includes an active run older than the page it would otherwise fall off", async () => {
158
207
  const storage = new MemoryStorage();
159
208
  const stale = storedRun({
@@ -9,6 +9,7 @@ import {
9
9
  type UserSettingsViewV1,
10
10
  } from "@frockbot/configuration-core";
11
11
  import { compileFoundationApplication } from "@frockbot/application-foundation/runtime";
12
+ import { SessionEventLog } from "@frockbot/kernel-do";
12
13
  import { createShellBotBackendContribution } from "./backend.js";
13
14
  import {
14
15
  botTurnCommandFingerprintV1,
@@ -291,10 +292,23 @@ describe("Bot recovery", () => {
291
292
  },
292
293
  ]);
293
294
  expect(settledEffects).toHaveLength(2);
295
+ const eventLog = new SessionEventLog(storage);
294
296
  for (const runId of ["ollama-run-1", "ollama-run-2"]) {
295
- const run = await storage.get<StoredRun>(`run:${runId}`);
297
+ const run = await storage.get<
298
+ Omit<StoredRun, "events"> & {
299
+ eventRange: { startSeq: number; endSeq: number };
300
+ }
301
+ >(`run:${runId}`);
302
+ expect(run).not.toHaveProperty("events");
303
+ const events = run
304
+ ? await eventLog.readRange(
305
+ "user-1:primary",
306
+ run.eventRange.startSeq,
307
+ run.eventRange.endSeq,
308
+ )
309
+ : [];
296
310
  expect(
297
- run?.events.find((event) => event.type === "model/request"),
311
+ events.find((event) => event.type === "model/request"),
298
312
  ).toMatchObject({
299
313
  request: {
300
314
  provider: "ollama-cloud",
package/src/backend.ts CHANGED
@@ -46,12 +46,13 @@ import {
46
46
  ACTIVE_RUN_KEY,
47
47
  BotDurableAuthority,
48
48
  IDENTITY_KEY,
49
- LATEST_EVENTS_KEY,
50
49
  NOTIFICATION_PREFIX,
51
50
  RECOVERY_ALARM_DELAY_MS,
52
51
  RUN_ADMISSION_FENCE_PREFIX,
53
52
  RUN_INDEX_PREFIX,
54
53
  RUN_PREFIX,
54
+ SessionEventLog,
55
+ storedRunRecordV2,
55
56
  CONVERSATION_BUSY_MESSAGE_V1,
56
57
  isConversationBusyV1,
57
58
  type BotIdentity,
@@ -1156,8 +1157,10 @@ export class ShellBotBackendContribution {
1156
1157
  const announcements = [...stored.entries()]
1157
1158
  .sort(([left], [right]) => left.localeCompare(right))
1158
1159
  .map(([, value]) => decodeSessionEvent(value));
1159
- const session =
1160
- (await this.ctx.storage.get<SessionEvent[]>(LATEST_EVENTS_KEY)) ?? [];
1160
+ const sessionId = await this.authority.readConversationSessionId();
1161
+ const session = sessionId
1162
+ ? await this.authority.readSessionEvents(sessionId)
1163
+ : [];
1161
1164
  for (const event of session) {
1162
1165
  if (event.type === "conversation/compacted") announcements.push(event);
1163
1166
  }
@@ -1171,8 +1174,10 @@ export class ShellBotBackendContribution {
1171
1174
  * timestamp of the place it belongs rather than the moment it was written.
1172
1175
  */
1173
1176
  private async projectAnnouncementPage() {
1174
- const session =
1175
- (await this.ctx.storage.get<SessionEvent[]>(LATEST_EVENTS_KEY)) ?? [];
1177
+ const sessionId = await this.authority.readConversationSessionId();
1178
+ const session = sessionId
1179
+ ? await this.authority.readSessionEvents(sessionId)
1180
+ : [];
1176
1181
  return projectClientAnnouncementsV1(
1177
1182
  await this.listAnnouncements(),
1178
1183
  session,
@@ -5951,10 +5956,13 @@ export class ShellBotBackendContribution {
5951
5956
  let budget = BOT_DEBUG_EVENT_BYTES_V1;
5952
5957
  const runs: BotDebugRunV1[] = [];
5953
5958
  for (const candidate of candidates) {
5954
- const stored = await this.authority.readStoredRun(candidate.runId);
5955
- if (!stored) continue;
5959
+ const projected = await this.authority.readRunEventProjections(
5960
+ candidate.runId,
5961
+ );
5962
+ if (!projected) continue;
5963
+ const { run: stored } = projected;
5956
5964
  const bounded = includeEvents
5957
- ? boundDebugEventsV1(stored.events, budget)
5965
+ ? boundDebugEventsV1(projected.events, budget)
5958
5966
  : undefined;
5959
5967
  if (bounded) budget = Math.max(0, budget - bounded.spent);
5960
5968
  runs.push({
@@ -5967,7 +5975,7 @@ export class ShellBotBackendContribution {
5967
5975
  commandFingerprint: stored.commandFingerprint,
5968
5976
  compositionGenerationId: stored.compositionGenerationId,
5969
5977
  previousEventCount: stored.previousEventCount,
5970
- eventCount: stored.events.length,
5978
+ eventCount: projected.eventCount,
5971
5979
  ...(stored.responseText === undefined
5972
5980
  ? {}
5973
5981
  : { responseText: stored.responseText }),
@@ -6328,7 +6336,24 @@ export class ShellBotBackendContribution {
6328
6336
  transaction.get<BotIdentity>(IDENTITY_KEY),
6329
6337
  transaction.get<unknown>(`${RUN_PREFIX}${runId}`),
6330
6338
  ]);
6331
- const run = optionalStoredRun(candidate);
6339
+ const storedRun = optionalStoredRun(candidate);
6340
+ let run = storedRun;
6341
+ if (storedRun?.eventRange) {
6342
+ const events = await new SessionEventLog(transaction).readRange(
6343
+ storedRun.sessionId,
6344
+ storedRun.eventRange.startSeq,
6345
+ storedRun.eventRange.endSeq,
6346
+ );
6347
+ if (
6348
+ events.length !==
6349
+ storedRun.eventRange.endSeq - storedRun.eventRange.startSeq
6350
+ ) {
6351
+ throw new Error(
6352
+ `run "${storedRun.runId}" has an incomplete event range`,
6353
+ );
6354
+ }
6355
+ run = requireStoredRunV1({ ...storedRun, events });
6356
+ }
6332
6357
  if (
6333
6358
  activeRunId !== runId ||
6334
6359
  !run ||
@@ -6388,7 +6413,10 @@ export class ShellBotBackendContribution {
6388
6413
  { kind: effect.kind, effectId: effect.effectId, outcome },
6389
6414
  ],
6390
6415
  } satisfies StoredRun);
6391
- await transaction.put(`${RUN_PREFIX}${runId}`, structuredClone(next));
6416
+ await transaction.put(
6417
+ `${RUN_PREFIX}${runId}`,
6418
+ structuredClone(storedRunRecordV2(next)),
6419
+ );
6392
6420
  return outcome === "admitted";
6393
6421
  });
6394
6422
  }
@@ -20,9 +20,10 @@ export const BOT_DEBUG_RUN_LIMIT_V1 = 20;
20
20
  export const BOT_DEBUG_DEFAULT_RUN_LIMIT_V1 = 5;
21
21
  export const BOT_DEBUG_GENERATION_LIMIT_V1 = 5;
22
22
  /**
23
- * The event budget one snapshot spends. Session events carry whole prompts, so
24
- * a handful of runs can be megabytes; past this the *oldest* events of a run
25
- * are dropped, because a failure is described by the tail of its log.
23
+ * The event budget one snapshot spends. Large durable events are already
24
+ * bounded diagnostic projections, but a handful of runs can still accumulate
25
+ * many of them; past this the *oldest* events of a run are dropped, because a
26
+ * failure is described by the tail of its log.
26
27
  */
27
28
  export const BOT_DEBUG_EVENT_BYTES_V1 = 512_000;
28
29