@frockbot/plugin-shell 0.3.11 → 0.3.13
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 +35 -33
- package/src/agent.test.ts +78 -0
- package/src/agent.ts +130 -2
- package/src/backend-configuration.test.ts +26 -26
- package/src/backend-recovery-integration.test.ts +10 -10
- package/src/backend-runner.ts +19 -2
- package/src/backend.ts +85 -18
- package/src/client/AppletCanvas.vue +19 -6
- package/src/client/FrockBotApp.vue +405 -75
- package/src/client/activity-trail.test.ts +205 -0
- package/src/client/activity-trail.ts +227 -0
- package/src/client/applets-client.test.ts +62 -0
- package/src/client/applets-client.ts +19 -0
- package/src/client/index.test.ts +128 -21
- package/src/client/index.ts +359 -114
- package/src/client/model-presentation.test.ts +3 -3
- package/src/client/no-bot-model-label.test.ts +7 -7
- package/src/client/skill-invocation.test.ts +34 -0
- package/src/client/skill-invocation.ts +22 -0
- package/src/client/styles.css +69 -19
- package/src/client/transcript-cache.test.ts +125 -0
- package/src/client/transcript-cache.ts +190 -0
- package/src/compaction-scheduler.test.ts +96 -0
- package/src/compaction-scheduler.ts +108 -0
- package/src/compaction-transcript.test.ts +174 -0
- package/src/compaction.test.ts +596 -0
- package/src/compaction.ts +539 -0
- package/src/focus.test.ts +222 -0
- package/src/focus.ts +93 -0
- package/src/history.ts +86 -8
- package/src/legacy-frock-model-id.test.ts +148 -0
- package/src/notification-id.ts +0 -0
- package/src/run-failure-copy.test.ts +150 -0
- package/src/run-failure-copy.ts +110 -0
- package/src/run-protocol.test.ts +50 -7
- package/src/run-protocol.ts +152 -43
- package/src/settings-links.test.ts +8 -2
- package/src/settings-links.ts +11 -2
- package/src/shared.ts +36 -0
- package/tsconfig.json +1 -2
- package/src/client/activity-ring.test.ts +0 -89
- package/src/client/activity-ring.ts +0 -94
package/src/settings-links.ts
CHANGED
|
@@ -275,9 +275,18 @@ export function decodeSettingsLinkV1(
|
|
|
275
275
|
const fragment = decodeURIComponent(url.hash.replace(/^#/u, ""));
|
|
276
276
|
const entry = fragment ? settingsAnchorV1(fragment) : undefined;
|
|
277
277
|
const botId = url.searchParams.get("bot") ?? undefined;
|
|
278
|
+
/*
|
|
279
|
+
* A fragment naming a row that lives somewhere else is still a request for
|
|
280
|
+
* that row, so the anchor decides which surface opens. Dropping it instead
|
|
281
|
+
* is what made `?settings=bot-panel#bot-routines` open the panel and then
|
|
282
|
+
* sit there: a link that resolves to nothing visible reads as a broken app,
|
|
283
|
+
* and a row moving between surfaces is exactly what these links have to
|
|
284
|
+
* survive. A fragment nobody registered still opens the named surface with
|
|
285
|
+
* no row.
|
|
286
|
+
*/
|
|
278
287
|
return {
|
|
279
|
-
surface,
|
|
280
|
-
...(entry
|
|
288
|
+
surface: entry ? entry.surface : surface,
|
|
289
|
+
...(entry ? { anchor: entry.anchor } : {}),
|
|
281
290
|
...(botId ? { botId } : {}),
|
|
282
291
|
};
|
|
283
292
|
}
|
package/src/shared.ts
CHANGED
|
@@ -222,6 +222,29 @@ export function withoutConnectionReturnV1(search: string): string {
|
|
|
222
222
|
return rest ? `?${rest}` : "";
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* What the shell holds of the conversations the User is not looking at, as
|
|
227
|
+
* everything outside the shell may address it.
|
|
228
|
+
*
|
|
229
|
+
* Two callers: the thread, which records and reads back where the reader had
|
|
230
|
+
* each Bot scrolled, and any surface that changes a Bot enough that its held
|
|
231
|
+
* transcript would be a lie rather than merely old — archive, delete, and a
|
|
232
|
+
* change of signed-in User.
|
|
233
|
+
*/
|
|
234
|
+
export interface TranscriptMemoryV1 {
|
|
235
|
+
/** Records where the reader had this Bot's thread. */
|
|
236
|
+
rememberViewport(
|
|
237
|
+
botId: string,
|
|
238
|
+
viewport: { scrollTop: number; pinnedToLatest: boolean },
|
|
239
|
+
): void;
|
|
240
|
+
/** Where the reader had this Bot's thread, if it is still held. */
|
|
241
|
+
viewportFor(
|
|
242
|
+
botId: string,
|
|
243
|
+
): { scrollTop: number; pinnedToLatest: boolean } | undefined;
|
|
244
|
+
/** Drops what is held for one Bot, or — with no argument — for every Bot. */
|
|
245
|
+
forget(botId?: string): void;
|
|
246
|
+
}
|
|
247
|
+
|
|
225
248
|
export interface FrockBotWebData {
|
|
226
249
|
connection: WebConnection;
|
|
227
250
|
modelLabel: string;
|
|
@@ -249,6 +272,14 @@ export interface FrockBotWebData {
|
|
|
249
272
|
runningRunId?: string;
|
|
250
273
|
activeRun?: WebActiveRun;
|
|
251
274
|
error?: string;
|
|
275
|
+
/**
|
|
276
|
+
* The Bot list could not be read, so "no Bot is open" means "we do not know
|
|
277
|
+
* yet" rather than "this account has none". Set by whichever Package owns
|
|
278
|
+
* the list. Without it a transport failure reads as data loss: the
|
|
279
|
+
* first-run empty state offering to add a first Bot to a User who has
|
|
280
|
+
* several is the worst thing this surface can say.
|
|
281
|
+
*/
|
|
282
|
+
botsUnavailable?: boolean;
|
|
252
283
|
botSettings?: BotSettingsViewV1;
|
|
253
284
|
userSettings?: UserSettingsViewV1;
|
|
254
285
|
pluginCatalog: PluginCatalogItem[];
|
|
@@ -328,6 +359,11 @@ export interface FrockBotWebData {
|
|
|
328
359
|
* cancelled or failed grant is reported rather than silently discarded.
|
|
329
360
|
*/
|
|
330
361
|
connectionReturn?: ConnectionReturnV1;
|
|
362
|
+
/**
|
|
363
|
+
* The conversations this client is still holding, so switching back to a
|
|
364
|
+
* Bot is a paint rather than a reload.
|
|
365
|
+
*/
|
|
366
|
+
transcripts: TranscriptMemoryV1;
|
|
331
367
|
selectBot(botId: string): Promise<void>;
|
|
332
368
|
loadBotSettings(): Promise<void>;
|
|
333
369
|
saveBotProfile(profile: BotProfile): Promise<void>;
|
package/tsconfig.json
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
"module": "ESNext",
|
|
5
5
|
"moduleResolution": "Bundler",
|
|
6
6
|
"allowImportingTsExtensions": true,
|
|
7
|
-
"baseUrl": ".",
|
|
8
7
|
"paths": {
|
|
9
|
-
"@cordisjs/client": ["src/client/cordis-client-shim.d.ts"]
|
|
8
|
+
"@cordisjs/client": ["./src/client/cordis-client-shim.d.ts"]
|
|
10
9
|
},
|
|
11
10
|
"strict": true,
|
|
12
11
|
"noEmit": true,
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
ACTIVITY_RING_MAX_LAPS_V1,
|
|
4
|
-
ACTIVITY_RING_SEGMENTS_V1,
|
|
5
|
-
activityRingV1,
|
|
6
|
-
type ActivityStepStatusV1,
|
|
7
|
-
} from "./activity-ring.js";
|
|
8
|
-
|
|
9
|
-
/** `count` settled steps, plus whatever is still in flight. */
|
|
10
|
-
function steps(settled: number, inFlight = 0): ActivityStepStatusV1[] {
|
|
11
|
-
return [
|
|
12
|
-
...Array.from<ActivityStepStatusV1>({ length: settled }).fill("completed"),
|
|
13
|
-
...Array.from<ActivityStepStatusV1>({ length: inFlight }).fill("running"),
|
|
14
|
-
];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe("the avatar's activity ring", () => {
|
|
18
|
-
test("a Turn with nothing done yet still shows a ring", () => {
|
|
19
|
-
// Liveness before progress: the ring is the answer to "is it paused?", so
|
|
20
|
-
// it is drawn from the first moment of the Turn, empty and pulsing.
|
|
21
|
-
const ring = activityRingV1({ toolStatuses: [], status: "streaming" });
|
|
22
|
-
expect(ring.active).toBe(true);
|
|
23
|
-
expect(ring.running).toBe(true);
|
|
24
|
-
expect(ring.filled).toBe(0);
|
|
25
|
-
expect(ring.progress).toBe(0);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("a step that is still running has not ticked the ring", () => {
|
|
29
|
-
const ring = activityRingV1({
|
|
30
|
-
toolStatuses: steps(2, 1),
|
|
31
|
-
status: "streaming",
|
|
32
|
-
});
|
|
33
|
-
expect(ring.steps).toBe(2);
|
|
34
|
-
expect(ring.filled).toBe(2);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("a failed step ticks like any other — it is a step that happened", () => {
|
|
38
|
-
const ring = activityRingV1({
|
|
39
|
-
toolStatuses: ["completed", "failed"],
|
|
40
|
-
status: "streaming",
|
|
41
|
-
});
|
|
42
|
-
expect(ring.filled).toBe(2);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("each settled step fills one more segment of the lap", () => {
|
|
46
|
-
for (let settled = 0; settled < ACTIVITY_RING_SEGMENTS_V1; settled += 1) {
|
|
47
|
-
const ring = activityRingV1({
|
|
48
|
-
toolStatuses: steps(settled),
|
|
49
|
-
status: "streaming",
|
|
50
|
-
});
|
|
51
|
-
expect(ring.filled).toBe(settled);
|
|
52
|
-
expect(ring.progress).toBeCloseTo(settled / ACTIVITY_RING_SEGMENTS_V1);
|
|
53
|
-
expect(ring.laps).toBe(0);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("a full lap leaves a faint ring behind and starts again", () => {
|
|
58
|
-
const ring = activityRingV1({
|
|
59
|
-
toolStatuses: steps(ACTIVITY_RING_SEGMENTS_V1 + 1),
|
|
60
|
-
status: "streaming",
|
|
61
|
-
});
|
|
62
|
-
expect(ring.laps).toBe(1);
|
|
63
|
-
expect(ring.filled).toBe(1);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("a Turn of forty tool calls never grows a fortieth ring", () => {
|
|
67
|
-
// The bound is the point: the ring reports that work is happening, not how
|
|
68
|
-
// much, so past the last lap it loops inside the strokes it already has.
|
|
69
|
-
const ring = activityRingV1({
|
|
70
|
-
toolStatuses: steps(40),
|
|
71
|
-
status: "streaming",
|
|
72
|
-
});
|
|
73
|
-
expect(ring.laps).toBe(ACTIVITY_RING_MAX_LAPS_V1);
|
|
74
|
-
expect(ring.filled).toBeLessThan(ACTIVITY_RING_SEGMENTS_V1);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("a settled Turn keeps no ring, however it ended", () => {
|
|
78
|
-
for (const status of [
|
|
79
|
-
"completed",
|
|
80
|
-
"failed",
|
|
81
|
-
"interrupted",
|
|
82
|
-
"reconciliation-required",
|
|
83
|
-
] as const) {
|
|
84
|
-
const ring = activityRingV1({ toolStatuses: steps(3), status });
|
|
85
|
-
expect(ring.active).toBe(false);
|
|
86
|
-
expect(ring.running).toBe(false);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The activity ring: how much a running Turn has done, without saying so.
|
|
3
|
-
*
|
|
4
|
-
* A Turn can spend a minute making tool calls, and the thread used to answer
|
|
5
|
-
* that with either nothing or a list of tool names. Neither is right — the
|
|
6
|
-
* first reads as a paused app, the second puts the model's plumbing into a
|
|
7
|
-
* conversation. The ring is the third answer: a stroke around the Bot's avatar
|
|
8
|
-
* that pulses while the Turn runs and advances one segment for every step that
|
|
9
|
-
* settles, so a person sees liveness and rough progress and reads no words at
|
|
10
|
-
* all.
|
|
11
|
-
*
|
|
12
|
-
* This module is the whole rule, kept out of the component so it is testable
|
|
13
|
-
* without mounting Vue.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/** Segments in one lap of the ring. A step fills one. */
|
|
17
|
-
export const ACTIVITY_RING_SEGMENTS_V1 = 8;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* How many laps the ring will draw at once. A Turn making forty tool calls
|
|
21
|
-
* must not grow a fortieth ring, so the lap beyond the first is the last one:
|
|
22
|
-
* past it the ring keeps looping inside those two strokes.
|
|
23
|
-
*/
|
|
24
|
-
export const ACTIVITY_RING_MAX_LAPS_V1 = 1;
|
|
25
|
-
|
|
26
|
-
/** A step's state, as the Turn's tool activity reports it. */
|
|
27
|
-
export type ActivityStepStatusV1 = "running" | "completed" | "failed";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A Turn's state, as the thread's message carries it.
|
|
31
|
-
*
|
|
32
|
-
* Deliberately the whole string rather than the union: only `streaming` means
|
|
33
|
-
* the Turn is still going, and every other value — including one a newer Bot
|
|
34
|
-
* invents — is an ending. The ring is drawn off that one positive test, so it
|
|
35
|
-
* cannot be left spinning by a status this file has never heard of.
|
|
36
|
-
*/
|
|
37
|
-
export type ActivityTurnStatusV1 = string;
|
|
38
|
-
|
|
39
|
-
export interface ActivityRingViewV1 {
|
|
40
|
-
/** Whether the ring is drawn at all. A settled Turn keeps none. */
|
|
41
|
-
active: boolean;
|
|
42
|
-
/** Whether the ring pulses. False once the Turn has settled. */
|
|
43
|
-
running: boolean;
|
|
44
|
-
/** Steps that have settled in this Turn. Unbounded, and never rendered. */
|
|
45
|
-
steps: number;
|
|
46
|
-
/** Filled segments of the current lap, `0 … ACTIVITY_RING_SEGMENTS_V1`. */
|
|
47
|
-
filled: number;
|
|
48
|
-
/** Faint laps completed behind the live stroke, capped. */
|
|
49
|
-
laps: number;
|
|
50
|
-
/** The fraction of the circumference the live stroke draws, `0 … 1`. */
|
|
51
|
-
progress: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The ring for one assistant line.
|
|
56
|
-
*
|
|
57
|
-
* A running Turn ticks: every settled tool call advances the stroke one
|
|
58
|
-
* segment, and the lap that fills leaves a faint ring behind it — up to
|
|
59
|
-
* {@link ACTIVITY_RING_MAX_LAPS_V1}, after which the ring simply loops. A Turn
|
|
60
|
-
* that has settled draws no ring; the reply is the answer by then, and the
|
|
61
|
-
* component fades the ring out on its way off screen.
|
|
62
|
-
*/
|
|
63
|
-
export function activityRingV1(input: {
|
|
64
|
-
toolStatuses: readonly ActivityStepStatusV1[];
|
|
65
|
-
status: ActivityTurnStatusV1;
|
|
66
|
-
}): ActivityRingViewV1 {
|
|
67
|
-
const running = input.status === "streaming";
|
|
68
|
-
const steps = input.toolStatuses.filter(
|
|
69
|
-
(status) => status !== "running",
|
|
70
|
-
).length;
|
|
71
|
-
if (!running) {
|
|
72
|
-
return {
|
|
73
|
-
active: false,
|
|
74
|
-
running: false,
|
|
75
|
-
steps,
|
|
76
|
-
filled: 0,
|
|
77
|
-
laps: 0,
|
|
78
|
-
progress: 0,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
const filled = steps % ACTIVITY_RING_SEGMENTS_V1;
|
|
82
|
-
const laps = Math.min(
|
|
83
|
-
Math.floor(steps / ACTIVITY_RING_SEGMENTS_V1),
|
|
84
|
-
ACTIVITY_RING_MAX_LAPS_V1,
|
|
85
|
-
);
|
|
86
|
-
return {
|
|
87
|
-
active: true,
|
|
88
|
-
running: true,
|
|
89
|
-
steps,
|
|
90
|
-
filled,
|
|
91
|
-
laps,
|
|
92
|
-
progress: filled / ACTIVITY_RING_SEGMENTS_V1,
|
|
93
|
-
};
|
|
94
|
-
}
|