@frockbot/plugin-shell 0.3.11 → 0.3.12
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 +34 -33
- package/src/agent.ts +65 -1
- package/src/backend-configuration.test.ts +6 -6
- package/src/backend-runner.ts +19 -2
- package/src/backend.ts +85 -18
- 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/index.test.ts +25 -5
- package/src/client/index.ts +191 -47
- 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 +118 -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/run-protocol.test.ts +37 -0
- package/src/run-protocol.ts +135 -36
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/plugin-shell",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"./client/FrockBotApp.vue": "./src/client/FrockBotApp.vue",
|
|
28
28
|
"./client/notify": "./src/client/notify.ts",
|
|
29
29
|
"./client/styles.css": "./src/client/styles.css",
|
|
30
|
+
"./focus": "./src/focus.ts",
|
|
30
31
|
"./frockbot.json": "./frockbot.json",
|
|
31
32
|
"./host": "./src/host.ts",
|
|
32
33
|
"./manifest": "./src/manifest.ts",
|
|
@@ -48,46 +49,46 @@
|
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
50
51
|
"@cordisjs/plugin-webui": "0.8.2",
|
|
51
|
-
"@frockbot/agent-runtime": "0.3.
|
|
52
|
-
"@frockbot/application-foundation": "0.3.
|
|
53
|
-
"@frockbot/catalog-core": "0.3.
|
|
54
|
-
"@frockbot/client-core": "0.3.
|
|
55
|
-
"@frockbot/client-ui": "0.3.
|
|
56
|
-
"@frockbot/computer-core": "0.3.
|
|
57
|
-
"@frockbot/computer-host-protocol": "0.3.
|
|
58
|
-
"@frockbot/configuration-core": "0.3.
|
|
59
|
-
"@frockbot/connection-core": "0.3.
|
|
60
|
-
"@frockbot/kernel-agent-loop": "0.3.
|
|
61
|
-
"@frockbot/kernel-composition": "0.3.
|
|
62
|
-
"@frockbot/kernel-contracts": "0.3.
|
|
63
|
-
"@frockbot/kernel-do": "0.3.
|
|
64
|
-
"@frockbot/machine-protocol": "0.3.
|
|
65
|
-
"@frockbot/plugin-applets": "0.3.
|
|
66
|
-
"@frockbot/plugin-authoring": "0.3.
|
|
67
|
-
"@frockbot/plugin-bot-template": "0.3.
|
|
68
|
-
"@frockbot/plugin-computer": "0.3.
|
|
69
|
-
"@frockbot/plugin-flock": "0.3.
|
|
70
|
-
"@frockbot/plugin-image": "0.3.
|
|
71
|
-
"@frockbot/plugin-machine-messages": "0.3.
|
|
72
|
-
"@frockbot/plugin-mcp": "0.3.
|
|
73
|
-
"@frockbot/plugin-memory": "0.3.
|
|
74
|
-
"@frockbot/plugin-package-catalog": "0.3.
|
|
75
|
-
"@frockbot/plugin-routines": "0.3.
|
|
76
|
-
"@frockbot/plugin-skills": "0.3.
|
|
77
|
-
"@frockbot/plugin-subagents": "0.3.
|
|
78
|
-
"@frockbot/plugin-user-machine": "0.3.
|
|
79
|
-
"@frockbot/protocol": "0.3.
|
|
52
|
+
"@frockbot/agent-runtime": "0.3.12",
|
|
53
|
+
"@frockbot/application-foundation": "0.3.12",
|
|
54
|
+
"@frockbot/catalog-core": "0.3.12",
|
|
55
|
+
"@frockbot/client-core": "0.3.12",
|
|
56
|
+
"@frockbot/client-ui": "0.3.12",
|
|
57
|
+
"@frockbot/computer-core": "0.3.12",
|
|
58
|
+
"@frockbot/computer-host-protocol": "0.3.12",
|
|
59
|
+
"@frockbot/configuration-core": "0.3.12",
|
|
60
|
+
"@frockbot/connection-core": "0.3.12",
|
|
61
|
+
"@frockbot/kernel-agent-loop": "0.3.12",
|
|
62
|
+
"@frockbot/kernel-composition": "0.3.12",
|
|
63
|
+
"@frockbot/kernel-contracts": "0.3.12",
|
|
64
|
+
"@frockbot/kernel-do": "0.3.12",
|
|
65
|
+
"@frockbot/machine-protocol": "0.3.12",
|
|
66
|
+
"@frockbot/plugin-applets": "0.3.12",
|
|
67
|
+
"@frockbot/plugin-authoring": "0.3.12",
|
|
68
|
+
"@frockbot/plugin-bot-template": "0.3.12",
|
|
69
|
+
"@frockbot/plugin-computer": "0.3.12",
|
|
70
|
+
"@frockbot/plugin-flock": "0.3.12",
|
|
71
|
+
"@frockbot/plugin-image": "0.3.12",
|
|
72
|
+
"@frockbot/plugin-machine-messages": "0.3.12",
|
|
73
|
+
"@frockbot/plugin-mcp": "0.3.12",
|
|
74
|
+
"@frockbot/plugin-memory": "0.3.12",
|
|
75
|
+
"@frockbot/plugin-package-catalog": "0.3.12",
|
|
76
|
+
"@frockbot/plugin-routines": "0.3.12",
|
|
77
|
+
"@frockbot/plugin-skills": "0.3.12",
|
|
78
|
+
"@frockbot/plugin-subagents": "0.3.12",
|
|
79
|
+
"@frockbot/plugin-user-machine": "0.3.12",
|
|
80
|
+
"@frockbot/protocol": "0.3.12",
|
|
80
81
|
"cordis": "4.0.0-rc.8",
|
|
81
82
|
"vue": "3.5.41"
|
|
82
83
|
},
|
|
83
84
|
"devDependencies": {
|
|
84
85
|
"@cloudflare/workers-types": "latest",
|
|
85
|
-
"@frockbot/plugin-prompt": "0.3.
|
|
86
|
-
"@frockbot/plugin-tools": "0.3.
|
|
86
|
+
"@frockbot/plugin-prompt": "0.3.12",
|
|
87
|
+
"@frockbot/plugin-tools": "0.3.12",
|
|
87
88
|
"@types/bun": "1.3.6",
|
|
88
89
|
"@types/node": "26.2.0",
|
|
89
90
|
"@vitejs/plugin-vue": "6.0.8",
|
|
90
|
-
"typescript": "
|
|
91
|
+
"typescript": "npm:typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2",
|
|
91
92
|
"vite": "8.2.2",
|
|
92
93
|
"vue-tsc": "3.3.10"
|
|
93
94
|
},
|
package/src/agent.ts
CHANGED
|
@@ -39,7 +39,15 @@ import {
|
|
|
39
39
|
} from "@frockbot/kernel-contracts";
|
|
40
40
|
// Merges the Agent loop's event declarations into the cordis Context type.
|
|
41
41
|
import type {} from "@frockbot/kernel-agent-loop/agent";
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
automationParentPointerV1,
|
|
44
|
+
chatWindowV1,
|
|
45
|
+
CHAT_HISTORY_BUDGET_CHARS_V1,
|
|
46
|
+
turnScopedMessagesV1,
|
|
47
|
+
turnTypesByTurnV1,
|
|
48
|
+
} from "./history.js";
|
|
49
|
+
import { runCompactionV1 } from "./compaction.js";
|
|
50
|
+
import { compactionWorkV1 } from "./compaction-scheduler.js";
|
|
43
51
|
import type { Plugin } from "cordis";
|
|
44
52
|
import manifest from "../frockbot.json" with { type: "json" };
|
|
45
53
|
|
|
@@ -85,6 +93,11 @@ function refusal(reason: string): ToolExecutionResult {
|
|
|
85
93
|
return { content: reason, isError: true };
|
|
86
94
|
}
|
|
87
95
|
|
|
96
|
+
/** Adapts a cordis fiber to the plain disposer this Package's list holds. */
|
|
97
|
+
function disposeFiber(fiber: { dispose(): unknown }): () => void {
|
|
98
|
+
return () => void fiber.dispose();
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
/**
|
|
89
102
|
* The open step a Shell event belongs to. The session log is the
|
|
90
103
|
* reconstruction surface, so a send without its turn and step would not
|
|
@@ -348,6 +361,57 @@ export const shellAgentPlugin: Plugin.Function = (ctx) => {
|
|
|
348
361
|
createWakeParentTool(ctx.sessions),
|
|
349
362
|
parentHandoff ? { admissionCeiling: parentHandoff } : undefined,
|
|
350
363
|
),
|
|
364
|
+
// ADR 0030. `ctx.inject` rather than a declared dependency: a host that
|
|
365
|
+
// mounts the Shell without a model still gets its tools and its transcript
|
|
366
|
+
// seam, and simply never compacts. The hook is evaluated after `turn/end`
|
|
367
|
+
// is on the log and flushed — but `agent/turn-stopping` is a hook the loop
|
|
368
|
+
// *awaits* inside its `finally`, so running the summariser here is exactly
|
|
369
|
+
// the latency ADR 0030 says a compaction never costs. It is handed to the
|
|
370
|
+
// detached scheduler instead and this returns at once: the Turn ends, the
|
|
371
|
+
// run settles, the response goes out, and the summariser carries on behind
|
|
372
|
+
// it. Nothing here may throw, and nothing here may wait.
|
|
373
|
+
disposeFiber(
|
|
374
|
+
ctx.inject(["llm"], (scoped) => {
|
|
375
|
+
scoped.on("agent/turn-stopping", async (agent, turn) => {
|
|
376
|
+
const session = agent.session;
|
|
377
|
+
const types = turnTypesByTurnV1(session.events);
|
|
378
|
+
if ((types.get(turn) ?? "chat") !== "chat") return;
|
|
379
|
+
compactionWorkV1(session.id).start(async (signal) => {
|
|
380
|
+
if (signal.aborted) return;
|
|
381
|
+
await runCompactionV1({
|
|
382
|
+
session,
|
|
383
|
+
window: chatWindowV1(session.events, session.deriveMessages()),
|
|
384
|
+
budget: CHAT_HISTORY_BUDGET_CHARS_V1,
|
|
385
|
+
currentTurn: turn,
|
|
386
|
+
newEffectId: () => `compaction-${crypto.randomUUID()}`,
|
|
387
|
+
summarise: async (request) => {
|
|
388
|
+
let text = "";
|
|
389
|
+
// Two deadlines, one call: the compaction's own, and the abort
|
|
390
|
+
// a newly admitted Turn raises when it takes the log back.
|
|
391
|
+
const cancelled = AbortSignal.any([request.signal, signal]);
|
|
392
|
+
for await (const event of scoped.llm.stream(
|
|
393
|
+
{
|
|
394
|
+
requestId: `compaction-${crypto.randomUUID()}`,
|
|
395
|
+
provider: request.provider,
|
|
396
|
+
model: request.model,
|
|
397
|
+
system: request.system,
|
|
398
|
+
messages: request.messages,
|
|
399
|
+
tools: [],
|
|
400
|
+
...(request.modelBinding
|
|
401
|
+
? { modelBinding: request.modelBinding }
|
|
402
|
+
: {}),
|
|
403
|
+
},
|
|
404
|
+
cancelled,
|
|
405
|
+
)) {
|
|
406
|
+
if (event.type === "text-delta") text += event.text;
|
|
407
|
+
}
|
|
408
|
+
return text;
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}),
|
|
414
|
+
),
|
|
351
415
|
// Applied after the rest of the chain, so this Package has the last word on
|
|
352
416
|
// what history a request carries — the one rule the visible transcript
|
|
353
417
|
// rests on.
|
|
@@ -150,7 +150,7 @@ function configuredUser(): UserSettingsViewV1 {
|
|
|
150
150
|
connectionId: "flock-ai-ambient",
|
|
151
151
|
packageId: "provider-flock-ai",
|
|
152
152
|
connectionTypeId: "flock-ai-account",
|
|
153
|
-
displayName: "
|
|
153
|
+
displayName: "Frock AI",
|
|
154
154
|
state: "ready",
|
|
155
155
|
providerType: "flock-ai",
|
|
156
156
|
generation: "foundation-generation-1",
|
|
@@ -160,7 +160,7 @@ function configuredUser(): UserSettingsViewV1 {
|
|
|
160
160
|
state: "fresh",
|
|
161
161
|
models: [
|
|
162
162
|
{
|
|
163
|
-
providerModelId: "@
|
|
163
|
+
providerModelId: "@frock/auto",
|
|
164
164
|
displayName: "Platform",
|
|
165
165
|
capabilities: {
|
|
166
166
|
tools: true,
|
|
@@ -174,7 +174,7 @@ function configuredUser(): UserSettingsViewV1 {
|
|
|
174
174
|
safeMetadata: {},
|
|
175
175
|
},
|
|
176
176
|
],
|
|
177
|
-
platformModel: model("flock-ai-ambient", "@
|
|
177
|
+
platformModel: model("flock-ai-ambient", "@frock/auto"),
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
180
|
|
|
@@ -194,7 +194,7 @@ function host(storage: MemoryStorage, readUser: () => UserSettingsViewV1) {
|
|
|
194
194
|
},
|
|
195
195
|
MEMORY_FILES: {},
|
|
196
196
|
MEMORY_INDEX: {},
|
|
197
|
-
|
|
197
|
+
FROCK_AI: {
|
|
198
198
|
autoRoute: "flock-auto",
|
|
199
199
|
runChatCompletion: () =>
|
|
200
200
|
Promise.resolve(
|
|
@@ -527,7 +527,7 @@ describe("generic per-Turn model resolution", () => {
|
|
|
527
527
|
connectionId: "flock-ai-ambient",
|
|
528
528
|
packageId: "provider-flock-ai",
|
|
529
529
|
provider: "flock-ai",
|
|
530
|
-
providerModelId: "@
|
|
530
|
+
providerModelId: "@frock/auto",
|
|
531
531
|
connectionGeneration: "foundation-generation-1",
|
|
532
532
|
catalogGeneration: "catalog-1",
|
|
533
533
|
});
|
|
@@ -651,7 +651,7 @@ describe("generic per-Turn model resolution", () => {
|
|
|
651
651
|
),
|
|
652
652
|
};
|
|
653
653
|
expect(await contribution.resolveConfiguration(identity)).toMatchObject({
|
|
654
|
-
model: model("flock-ai-ambient", "@
|
|
654
|
+
model: model("flock-ai-ambient", "@frock/auto"),
|
|
655
655
|
});
|
|
656
656
|
expect(await contribution.getSettings(identity)).toMatchObject({
|
|
657
657
|
packageValues: {
|
package/src/backend-runner.ts
CHANGED
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
BotTurnRecoveryRequiredError,
|
|
18
18
|
} from "@frockbot/kernel-do";
|
|
19
19
|
import type { BotTurnCommand, BotTurnCompletion } from "./backend-contracts.js";
|
|
20
|
+
import {
|
|
21
|
+
compactionInFlightV1,
|
|
22
|
+
whenCompactionSettledV1,
|
|
23
|
+
} from "./compaction-scheduler.js";
|
|
20
24
|
|
|
21
25
|
export {
|
|
22
26
|
BotTurnExecutionError,
|
|
@@ -370,11 +374,24 @@ export async function executeBotTurn(
|
|
|
370
374
|
await runtime.agent.agent.whenIdle();
|
|
371
375
|
return settleBotTurn(runtime.agent, command, previousEvents);
|
|
372
376
|
} catch (error) {
|
|
373
|
-
turnExecutionError(error, previousEvents, [
|
|
377
|
+
return turnExecutionError(error, previousEvents, [
|
|
374
378
|
...runtime.agent.agent.session.events,
|
|
375
379
|
]);
|
|
376
380
|
} finally {
|
|
377
|
-
|
|
381
|
+
// ADR 0030: a compaction outlives the Turn that triggered it, and it runs
|
|
382
|
+
// on this Composition's model binding — so the Composition outlives the
|
|
383
|
+
// Turn too, and only by as long as the compaction does. Awaiting the
|
|
384
|
+
// disposal here would put the summariser back in the latency path, which
|
|
385
|
+
// is the whole defect. The next admission aborts anything still running,
|
|
386
|
+
// so this can never stack up.
|
|
387
|
+
if (compactionInFlightV1(command.sessionId)) {
|
|
388
|
+
void whenCompactionSettledV1(command.sessionId).then(
|
|
389
|
+
() => composition.dispose(),
|
|
390
|
+
() => composition.dispose(),
|
|
391
|
+
);
|
|
392
|
+
} else {
|
|
393
|
+
await composition.dispose();
|
|
394
|
+
}
|
|
378
395
|
}
|
|
379
396
|
}
|
|
380
397
|
|
package/src/backend.ts
CHANGED
|
@@ -52,6 +52,8 @@ import {
|
|
|
52
52
|
RUN_ADMISSION_FENCE_PREFIX,
|
|
53
53
|
RUN_INDEX_PREFIX,
|
|
54
54
|
RUN_PREFIX,
|
|
55
|
+
CONVERSATION_BUSY_MESSAGE_V1,
|
|
56
|
+
isConversationBusyV1,
|
|
55
57
|
type BotIdentity,
|
|
56
58
|
type BotDurableAuthorityOptions,
|
|
57
59
|
type BotTurnExecutionInput,
|
|
@@ -389,6 +391,7 @@ import {
|
|
|
389
391
|
projectPackageIframeCompositionV1,
|
|
390
392
|
} from "./composition-views.js";
|
|
391
393
|
import { executeBotTurn, executeDirectToolTurn } from "./backend-runner.js";
|
|
394
|
+
import { yieldCompactionWorkV1 } from "./compaction-scheduler.js";
|
|
392
395
|
import {
|
|
393
396
|
shellTerminalRecordsV1,
|
|
394
397
|
supersededTurnRecordsV1,
|
|
@@ -410,6 +413,7 @@ import {
|
|
|
410
413
|
projectClientTurnV1,
|
|
411
414
|
type ClientRunLookupV1,
|
|
412
415
|
type ClientConversationListV1,
|
|
416
|
+
type ClientConversationOutcomeV1,
|
|
413
417
|
type ClientRunListV1,
|
|
414
418
|
type ClientRunStopReceiptV1,
|
|
415
419
|
type ClientRunV1,
|
|
@@ -475,7 +479,7 @@ import { defineBotBackendContribution } from "@frockbot/kernel-contracts/contrib
|
|
|
475
479
|
* is the direction this deployment wants to be wrong in.
|
|
476
480
|
*
|
|
477
481
|
* Today: the in-process foundation provider, and nothing else. Ollama Cloud
|
|
478
|
-
* exposes no provider-bound retrieval (ADR 0010) and neither does
|
|
482
|
+
* exposes no provider-bound retrieval (ADR 0010) and neither does Frock AI.
|
|
479
483
|
*/
|
|
480
484
|
const RECONCILING_PROVIDER_IDS_V1: ReadonlySet<string> = new Set([
|
|
481
485
|
"foundation",
|
|
@@ -588,8 +592,8 @@ export interface BotStateEnv {
|
|
|
588
592
|
MEMORY_INDEX: VectorizeIndex;
|
|
589
593
|
/** The native AI binding consumed through the image Package adapter. */
|
|
590
594
|
AI?: NativeAiBindingV1;
|
|
591
|
-
/** The
|
|
592
|
-
|
|
595
|
+
/** The Frock AI Gateway adapter constructed by the Cloudflare host. */
|
|
596
|
+
FROCK_AI?: {
|
|
593
597
|
autoRoute: string;
|
|
594
598
|
runChatCompletion(
|
|
595
599
|
gatewayModel: string,
|
|
@@ -1135,14 +1139,43 @@ export class ShellBotBackendContribution {
|
|
|
1135
1139
|
if (expired.length > 0) await transaction.delete(expired);
|
|
1136
1140
|
}
|
|
1137
1141
|
|
|
1138
|
-
/**
|
|
1142
|
+
/**
|
|
1143
|
+
* The announcements the Session shows, oldest first.
|
|
1144
|
+
*
|
|
1145
|
+
* Two sources, and deliberately so. A rename or a settled task has no live
|
|
1146
|
+
* Session to be appended to, so it lives in this object's own bounded
|
|
1147
|
+
* announcement log. A compaction (ADR 0030) is already a durable event on
|
|
1148
|
+
* the conversation's session log — appending a second copy of it here would
|
|
1149
|
+
* be two records of one fact — so it is read back from there instead.
|
|
1150
|
+
*/
|
|
1139
1151
|
async listAnnouncements(): Promise<SessionEvent[]> {
|
|
1140
1152
|
const stored = await this.ctx.storage.list<unknown>({
|
|
1141
1153
|
prefix: BOT_ANNOUNCEMENT_PREFIX,
|
|
1142
1154
|
});
|
|
1143
|
-
|
|
1155
|
+
const announcements = [...stored.entries()]
|
|
1144
1156
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
1145
1157
|
.map(([, value]) => decodeSessionEvent(value));
|
|
1158
|
+
const session =
|
|
1159
|
+
(await this.ctx.storage.get<SessionEvent[]>(LATEST_EVENTS_KEY)) ?? [];
|
|
1160
|
+
for (const event of session) {
|
|
1161
|
+
if (event.type === "conversation/compacted") announcements.push(event);
|
|
1162
|
+
}
|
|
1163
|
+
return announcements
|
|
1164
|
+
.sort((left, right) => left.timestamp.localeCompare(right.timestamp))
|
|
1165
|
+
.slice(-BOT_ANNOUNCEMENT_RETENTION);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* The announcements as the transcript reads them, each already carrying the
|
|
1170
|
+
* timestamp of the place it belongs rather than the moment it was written.
|
|
1171
|
+
*/
|
|
1172
|
+
private async projectAnnouncementPage() {
|
|
1173
|
+
const session =
|
|
1174
|
+
(await this.ctx.storage.get<SessionEvent[]>(LATEST_EVENTS_KEY)) ?? [];
|
|
1175
|
+
return projectClientAnnouncementsV1(
|
|
1176
|
+
await this.listAnnouncements(),
|
|
1177
|
+
session,
|
|
1178
|
+
);
|
|
1146
1179
|
}
|
|
1147
1180
|
|
|
1148
1181
|
private async refreshRecoveryAlarm(
|
|
@@ -1158,6 +1191,9 @@ export class ShellBotBackendContribution {
|
|
|
1158
1191
|
}
|
|
1159
1192
|
|
|
1160
1193
|
async run(command: OwnedBotTurnCommand): Promise<ClientTurnV1> {
|
|
1194
|
+
// Before the authority reads the session log, so a compaction detached
|
|
1195
|
+
// from the previous Turn has already handed the log back (ADR 0030).
|
|
1196
|
+
await yieldCompactionWorkV1(command.sessionId);
|
|
1161
1197
|
// Before admission, so the pin this Turn takes already carries whatever the
|
|
1162
1198
|
// User's Applet directory says now.
|
|
1163
1199
|
await this.resolveAppletComposition(
|
|
@@ -1593,6 +1629,10 @@ export class ShellBotBackendContribution {
|
|
|
1593
1629
|
private async executeTurn(
|
|
1594
1630
|
input: BotTurnExecutionInput<BotSettingsViewV1>,
|
|
1595
1631
|
): Promise<BotTurnCompletion> {
|
|
1632
|
+
// ADR 0030: a compaction detached from the previous Turn yields to this
|
|
1633
|
+
// one rather than holding it. Free when none is running, and an abort when
|
|
1634
|
+
// one is, so this Turn is the only writer of the session log.
|
|
1635
|
+
await yieldCompactionWorkV1(input.command.sessionId);
|
|
1596
1636
|
const settings = input.configurationSnapshot;
|
|
1597
1637
|
const turn = {
|
|
1598
1638
|
runId: input.command.runId,
|
|
@@ -4764,11 +4804,11 @@ export class ShellBotBackendContribution {
|
|
|
4764
4804
|
bindingPackageId,
|
|
4765
4805
|
effectId,
|
|
4766
4806
|
),
|
|
4767
|
-
...(this.env.
|
|
4807
|
+
...(this.env.FROCK_AI
|
|
4768
4808
|
? {
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
this.env.
|
|
4809
|
+
frockAiAutoRoute: this.env.FROCK_AI.autoRoute,
|
|
4810
|
+
runFrockAiChatCompletion: (gatewayModel, body) =>
|
|
4811
|
+
this.env.FROCK_AI!.runChatCompletion(gatewayModel, body),
|
|
4772
4812
|
}
|
|
4773
4813
|
: {}),
|
|
4774
4814
|
fetch: this.outboundFetch,
|
|
@@ -5402,12 +5442,15 @@ export class ShellBotBackendContribution {
|
|
|
5402
5442
|
* so an older run that is somehow still marked running is a reconciliation
|
|
5403
5443
|
* problem and not something a ring should report. A read that fails is no
|
|
5404
5444
|
* ring — liveness is never worth failing a sidebar poll for.
|
|
5445
|
+
*
|
|
5446
|
+
* The record's `status` is not the test and never was. `resolveRunWorking`
|
|
5447
|
+
* holds the rule — running, inside the Turn deadline, and a Turn the log has
|
|
5448
|
+
* not already closed — and settles the record when it finds one that only
|
|
5449
|
+
* claims to be running, which is why this read is also the repair.
|
|
5405
5450
|
*/
|
|
5406
5451
|
private async isWorking(runId: string | undefined): Promise<boolean> {
|
|
5407
|
-
if (runId === undefined) return false;
|
|
5408
5452
|
try {
|
|
5409
|
-
|
|
5410
|
-
return run?.status === "running";
|
|
5453
|
+
return await this.authority.resolveRunWorking(runId);
|
|
5411
5454
|
} catch {
|
|
5412
5455
|
return false;
|
|
5413
5456
|
}
|
|
@@ -5637,6 +5680,16 @@ export class ShellBotBackendContribution {
|
|
|
5637
5680
|
limit: CLIENT_RUN_PAGE_LIMIT + 1,
|
|
5638
5681
|
...(query.before ? { before: query.before } : {}),
|
|
5639
5682
|
});
|
|
5683
|
+
// The open chat draws its own activity ring from whichever run this page
|
|
5684
|
+
// projects as `running`, so it owes the same liveness rule the sidebar row
|
|
5685
|
+
// does — and from the same helper, or the two surfaces disagree about the
|
|
5686
|
+
// same Bot. Only the newest run and the active marker are asked: a Turn
|
|
5687
|
+
// further back cannot be the one anybody is waiting on, and a transcript
|
|
5688
|
+
// read is not the place to walk a Bot's whole history looking for
|
|
5689
|
+
// leftovers.
|
|
5690
|
+
if (!query.before) {
|
|
5691
|
+
await this.isWorking(activeRunId ?? candidates[0]?.runId);
|
|
5692
|
+
}
|
|
5640
5693
|
|
|
5641
5694
|
const selected = new Map<string, { cursor?: string; run: ClientRunV1 }>();
|
|
5642
5695
|
if (activeRunId) {
|
|
@@ -5706,9 +5759,7 @@ export class ShellBotBackendContribution {
|
|
|
5706
5759
|
: { truncated: false },
|
|
5707
5760
|
// Announcements belong to the Session, not to a page of Turns, so only
|
|
5708
5761
|
// the newest page carries them.
|
|
5709
|
-
query.before
|
|
5710
|
-
? []
|
|
5711
|
-
: projectClientAnnouncementsV1(await this.listAnnouncements()),
|
|
5762
|
+
query.before ? [] : await this.projectAnnouncementPage(),
|
|
5712
5763
|
);
|
|
5713
5764
|
if (clientRunListWireBytes(page) > CLIENT_RUN_LIST_MAX_BYTES) {
|
|
5714
5765
|
throw new Error("required run projections exceed the wire byte limit");
|
|
@@ -5739,10 +5790,26 @@ export class ShellBotBackendContribution {
|
|
|
5739
5790
|
*/
|
|
5740
5791
|
async startConversation(
|
|
5741
5792
|
identity: BotIdentity,
|
|
5742
|
-
): Promise<
|
|
5793
|
+
): Promise<ClientConversationOutcomeV1> {
|
|
5743
5794
|
await this.validateIdentity(identity);
|
|
5744
|
-
|
|
5745
|
-
|
|
5795
|
+
try {
|
|
5796
|
+
await this.authority.startConversation(identity);
|
|
5797
|
+
} catch (error) {
|
|
5798
|
+
// The one refusal this can give travels as data. Everything else is a
|
|
5799
|
+
// genuine failure and still throws, so the boundary above answers 500.
|
|
5800
|
+
if (isConversationBusyV1(error)) {
|
|
5801
|
+
return {
|
|
5802
|
+
status: "refused",
|
|
5803
|
+
schemaVersion: 1,
|
|
5804
|
+
reason:
|
|
5805
|
+
error instanceof Error
|
|
5806
|
+
? error.message
|
|
5807
|
+
: CONVERSATION_BUSY_MESSAGE_V1,
|
|
5808
|
+
};
|
|
5809
|
+
}
|
|
5810
|
+
throw error;
|
|
5811
|
+
}
|
|
5812
|
+
return { status: "started", ...(await this.listConversations()) };
|
|
5746
5813
|
}
|
|
5747
5814
|
|
|
5748
5815
|
async lookupRun(input: unknown): Promise<ClientRunLookupV1> {
|