@frockbot/plugin-shell 0.1.4 → 0.2.0
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/frockbot.json +4 -0
- package/package.json +30 -28
- package/src/agent.ts +10 -13
- package/src/backend-authoring.test.ts +356 -2
- package/src/backend-authoring.ts +585 -20
- package/src/backend-composition-input.test.ts +65 -0
- package/src/backend-composition-input.ts +58 -0
- package/src/backend-composition.ts +19 -1
- package/src/backend-configuration.test.ts +166 -0
- package/src/backend-contracts.test.ts +28 -0
- package/src/backend-contracts.ts +3 -1
- package/src/backend-iframe-ui.test.ts +131 -0
- package/src/backend-isolate.test.ts +198 -203
- package/src/backend-isolate.ts +96 -231
- package/src/backend-package-catalog.test.ts +451 -0
- package/src/backend-package-catalog.ts +923 -0
- package/src/backend-runner-iframe.test.ts +74 -0
- package/src/backend-runner.ts +192 -0
- package/src/backend.ts +947 -206
- package/src/client/FrockBotApp.vue +38 -0
- package/src/client/PackageIframeHost.vue +218 -0
- package/src/client/PackageIframeSettings.vue +52 -0
- package/src/client/SendPayloadView.vue +0 -60
- package/src/client/index.test.ts +119 -0
- package/src/client/index.ts +78 -1
- package/src/client/package-iframe-host-message.test.ts +41 -0
- package/src/client/package-iframe-host-message.ts +27 -0
- package/src/composition-views.ts +54 -0
- package/src/shared.ts +10 -0
package/src/backend.ts
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
import type { AgentEffectAdmission } from "@frockbot/kernel-agent-loop/agent";
|
|
2
2
|
import {
|
|
3
|
+
decodeIsolateMemoryReadRequestV1,
|
|
4
|
+
decodeIsolateMemoryWriteRequestV1,
|
|
5
|
+
decodeIsolateNotificationRequestV1,
|
|
6
|
+
decodeIsolateScheduleRequestV1,
|
|
7
|
+
decodeIsolateToolRequestV1,
|
|
8
|
+
decodeIsolateWorkspaceDeleteRequestV1,
|
|
9
|
+
decodeIsolateWorkspaceListRequestV1,
|
|
10
|
+
decodeIsolateWorkspacePathV1,
|
|
11
|
+
decodeIsolateWorkspaceWriteRequestV1,
|
|
12
|
+
decodeWorkspacePathV1,
|
|
13
|
+
decodeWorkspaceRootV1,
|
|
3
14
|
decodeSessionEvent,
|
|
15
|
+
type IsolateConnectionOutcomeV1,
|
|
16
|
+
type IsolateConnectionV1,
|
|
17
|
+
type IsolateMemoryOutcomeV1,
|
|
18
|
+
type IsolateNotificationOutcomeV1,
|
|
19
|
+
type IsolateToolOutcomeV1,
|
|
20
|
+
type IsolateWorkspaceOutcomeV1,
|
|
4
21
|
type PersistSessionEvents,
|
|
5
22
|
type SessionEvent,
|
|
23
|
+
type WorkspacePathV1,
|
|
6
24
|
validateToolOccurrenceJournal,
|
|
7
25
|
type BotCapabilitiesStub,
|
|
8
|
-
type
|
|
9
|
-
type IsolatePendingDecisionV1,
|
|
26
|
+
type IsolateModelInvocationV1,
|
|
10
27
|
type NormalizedModelRequest,
|
|
11
28
|
type PackageBundlerBinding,
|
|
29
|
+
type PackageIframeCompositionV1,
|
|
30
|
+
type PackageIframeToolCommandV1,
|
|
12
31
|
type TurnTypeV1,
|
|
13
32
|
type WorkspaceFilesV1,
|
|
14
33
|
} from "@frockbot/kernel-contracts";
|
|
34
|
+
import {
|
|
35
|
+
decodeFrockBotManifest,
|
|
36
|
+
isClientIframeContribution,
|
|
37
|
+
type FrockBotManifest,
|
|
38
|
+
} from "@frockbot/kernel-composition";
|
|
15
39
|
import type { Plugin } from "cordis";
|
|
16
40
|
import {
|
|
17
41
|
ACTIVE_RUN_KEY,
|
|
@@ -46,7 +70,9 @@ import {
|
|
|
46
70
|
ConfigurationConflictError,
|
|
47
71
|
decodeBotConfigurationExecuteRpcV1,
|
|
48
72
|
decodeBotConfigurationReadRpcV1,
|
|
73
|
+
decodeBotSettingsViewV1,
|
|
49
74
|
decodeCompositionCommandReceiptV1,
|
|
75
|
+
decodeOperationReceiptV1,
|
|
50
76
|
decodeInstalledPackageSettingIdsV1,
|
|
51
77
|
decodeInstalledPackageSettingsPatchV1,
|
|
52
78
|
MAX_COMPOSITION_GENERATION_PAGE_V1,
|
|
@@ -64,6 +90,7 @@ import {
|
|
|
64
90
|
type PackageSettingValueV1,
|
|
65
91
|
type ResolvedModelBindingV1,
|
|
66
92
|
initializeBotSettingsV1,
|
|
93
|
+
migrateStoredBotSettingsV1,
|
|
67
94
|
resolvePackageSettingValuesV1,
|
|
68
95
|
resolveBotExecutionPlanV1,
|
|
69
96
|
resolveEffectiveBotModelV1,
|
|
@@ -95,6 +122,7 @@ import {
|
|
|
95
122
|
type ShellIsolateMountOptions,
|
|
96
123
|
type ShellMountedComposition,
|
|
97
124
|
} from "./backend-composition.js";
|
|
125
|
+
import { compositionFailureTurnTextV1 } from "./backend-composition-input.js";
|
|
98
126
|
import {
|
|
99
127
|
activateCompositionV1,
|
|
100
128
|
type CompositionFailureV1,
|
|
@@ -104,7 +132,13 @@ import {
|
|
|
104
132
|
import {
|
|
105
133
|
createPackageAuthoringHost,
|
|
106
134
|
createR2AuthoringArtifactStore,
|
|
135
|
+
readAuthoredCompositionMemberSourceV1,
|
|
107
136
|
} from "./backend-authoring.js";
|
|
137
|
+
import {
|
|
138
|
+
type CatalogAwarePackageCatalogHost,
|
|
139
|
+
createPackageCatalogHost,
|
|
140
|
+
createR2BotPackageCatalogReader,
|
|
141
|
+
} from "./backend-package-catalog.js";
|
|
108
142
|
import { createBotComputerSyncHost } from "./backend-computer.js";
|
|
109
143
|
import {
|
|
110
144
|
decodeDirectoryViewV1,
|
|
@@ -280,7 +314,9 @@ import type {
|
|
|
280
314
|
RoutineRunListViewV1,
|
|
281
315
|
} from "@frockbot/plugin-routines/shared";
|
|
282
316
|
import {
|
|
317
|
+
authorshipManifestKey,
|
|
283
318
|
decodeAuthoringQuotaReceiptV1,
|
|
319
|
+
type AuthoredManifestRecordV1,
|
|
284
320
|
type AuthoringQuotaBinding,
|
|
285
321
|
type PackageAuthoringHost,
|
|
286
322
|
} from "@frockbot/plugin-authoring";
|
|
@@ -290,14 +326,12 @@ import {
|
|
|
290
326
|
createR2PackageArtifactStore,
|
|
291
327
|
isolateBindingDigestV1,
|
|
292
328
|
type BotCapabilitiesPropsV1,
|
|
293
|
-
type IsolateCapabilityV1,
|
|
294
329
|
type IsolateCapabilityHost,
|
|
295
330
|
type IsolateModelBindingV1,
|
|
296
331
|
type IsolateModelPath,
|
|
297
332
|
type IsolateModelRequestRecordV1,
|
|
298
|
-
type IsolatePendingAuthorityDecisionV1,
|
|
299
|
-
type IsolateUnavailableModelBindingV1,
|
|
300
333
|
} from "./backend-isolate.js";
|
|
334
|
+
import { memoryScopeRootV1 } from "@frockbot/plugin-memory/roots";
|
|
301
335
|
import type { BotIsolateLoader } from "@frockbot/kernel-composition/isolate";
|
|
302
336
|
import type {
|
|
303
337
|
CompositionGenerationV1,
|
|
@@ -310,8 +344,11 @@ import {
|
|
|
310
344
|
type McpMountOutcomeReportV1,
|
|
311
345
|
type McpServerStatusViewV1,
|
|
312
346
|
} from "@frockbot/plugin-mcp/records";
|
|
313
|
-
import {
|
|
314
|
-
|
|
347
|
+
import {
|
|
348
|
+
projectCompositionGenerationV1,
|
|
349
|
+
projectPackageIframeCompositionV1,
|
|
350
|
+
} from "./composition-views.js";
|
|
351
|
+
import { executeBotTurn, executeDirectToolTurn } from "./backend-runner.js";
|
|
315
352
|
import { shellTerminalRecordsV1 } from "./terminal-records.js";
|
|
316
353
|
import {
|
|
317
354
|
CLIENT_RUN_LIST_MAX_BYTES,
|
|
@@ -413,6 +450,17 @@ interface ConfigurationActivity {
|
|
|
413
450
|
promise: Promise<OperationReceiptV1>;
|
|
414
451
|
}
|
|
415
452
|
|
|
453
|
+
interface IsolateCallScopeV1 {
|
|
454
|
+
userId: string;
|
|
455
|
+
botId: string;
|
|
456
|
+
runId: string;
|
|
457
|
+
sessionId: string;
|
|
458
|
+
turnId: string;
|
|
459
|
+
packageId: string;
|
|
460
|
+
generationId: string;
|
|
461
|
+
request: unknown;
|
|
462
|
+
}
|
|
463
|
+
|
|
416
464
|
function requireMatchingConfigurationReceipt(
|
|
417
465
|
stored: StoredConfigurationReceipt,
|
|
418
466
|
commandFingerprint: string,
|
|
@@ -430,6 +478,9 @@ export type { BotIdentity, OwnedBotTurnCommand };
|
|
|
430
478
|
|
|
431
479
|
export interface BotStateEnv {
|
|
432
480
|
MEMORY_FILES: R2Bucket;
|
|
481
|
+
/** Object-storage file surfaces constructed by the Cloudflare adapter. */
|
|
482
|
+
WORKSPACE_FILES?: WorkspaceFilesV1;
|
|
483
|
+
MEMORY_WORKSPACE_FILES?: WorkspaceFilesV1;
|
|
433
484
|
/**
|
|
434
485
|
* The Bot Package Worker Loader (plan Step 4). Optional so a host without
|
|
435
486
|
* Bot-authored Packages — tests, the Electron shell — still compiles; a
|
|
@@ -523,6 +574,29 @@ function optionalStoredRun(input: unknown): StoredRun | undefined {
|
|
|
523
574
|
return input === undefined ? undefined : requireStoredRunV1(input);
|
|
524
575
|
}
|
|
525
576
|
|
|
577
|
+
/** Server-side allowlist for the untrusted page's only effectful message. */
|
|
578
|
+
export function requirePackageUiToolDeclarationV1(
|
|
579
|
+
catalog: PackageIframeCompositionV1,
|
|
580
|
+
command: Pick<
|
|
581
|
+
PackageIframeToolCommandV1,
|
|
582
|
+
"generationId" | "packageId" | "name"
|
|
583
|
+
>,
|
|
584
|
+
): PackageIframeCompositionV1["contributions"][number] {
|
|
585
|
+
const contribution = catalog.contributions.find(
|
|
586
|
+
(candidate) => candidate.packageId === command.packageId,
|
|
587
|
+
);
|
|
588
|
+
if (
|
|
589
|
+
catalog.generationId !== command.generationId ||
|
|
590
|
+
!contribution ||
|
|
591
|
+
!contribution.declaredTools.includes(command.name)
|
|
592
|
+
) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`Package "${command.packageId}" did not declare tool "${command.name}" in generation "${command.generationId}"`,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
return contribution;
|
|
598
|
+
}
|
|
599
|
+
|
|
526
600
|
export class ShellBotBackendContribution {
|
|
527
601
|
readonly ctx: DurableObjectState;
|
|
528
602
|
readonly env: BotStateEnv;
|
|
@@ -539,7 +613,18 @@ export class ShellBotBackendContribution {
|
|
|
539
613
|
>();
|
|
540
614
|
/** The Turn currently executing on this object, for durable Stop. */
|
|
541
615
|
private activeTurn:
|
|
542
|
-
|
|
616
|
+
| {
|
|
617
|
+
runId: string;
|
|
618
|
+
sessionId: string;
|
|
619
|
+
turnId: string;
|
|
620
|
+
generationId: string;
|
|
621
|
+
turnType: TurnTypeV1;
|
|
622
|
+
subagentRole?: string;
|
|
623
|
+
mounted: ShellMountedComposition;
|
|
624
|
+
signal: AbortSignal;
|
|
625
|
+
cancel(): void;
|
|
626
|
+
}
|
|
627
|
+
| undefined;
|
|
543
628
|
/**
|
|
544
629
|
* Admission, the event log, the cursor, idempotency, cancellation, and
|
|
545
630
|
* durable scheduling are kernel authority; this Package supplies only the
|
|
@@ -636,10 +721,10 @@ export class ShellBotBackendContribution {
|
|
|
636
721
|
) {
|
|
637
722
|
throw new Error("Bot authority does not match its durable identity");
|
|
638
723
|
}
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
724
|
+
const stored = await transaction.get<unknown>(BOT_CONFIGURATION_KEY);
|
|
725
|
+
if (stored !== undefined) {
|
|
726
|
+
return decodeBotSettingsViewV1(migrateStoredBotSettingsV1(stored));
|
|
727
|
+
}
|
|
643
728
|
const settings = {
|
|
644
729
|
...this.initialBotSettings(identity.botId),
|
|
645
730
|
profile: {
|
|
@@ -799,9 +884,11 @@ export class ShellBotBackendContribution {
|
|
|
799
884
|
command.commandId,
|
|
800
885
|
);
|
|
801
886
|
}
|
|
887
|
+
const stored = await transaction.get<unknown>(BOT_CONFIGURATION_KEY);
|
|
802
888
|
const current =
|
|
803
|
-
|
|
804
|
-
|
|
889
|
+
stored === undefined
|
|
890
|
+
? this.initialBotSettings(identity.botId)
|
|
891
|
+
: decodeBotSettingsViewV1(migrateStoredBotSettingsV1(stored));
|
|
805
892
|
if (command.expectedRevision !== current.revision) {
|
|
806
893
|
throw new ConfigurationConflictError(current.revision);
|
|
807
894
|
}
|
|
@@ -953,6 +1040,30 @@ export class ShellBotBackendContribution {
|
|
|
953
1040
|
return projectClientTurnV1(await this.authority.run(command));
|
|
954
1041
|
}
|
|
955
1042
|
|
|
1043
|
+
async runPackageUiTool(
|
|
1044
|
+
identity: BotIdentity,
|
|
1045
|
+
command: PackageIframeToolCommandV1,
|
|
1046
|
+
): Promise<ClientTurnV1> {
|
|
1047
|
+
await this.validateIdentity(identity);
|
|
1048
|
+
const catalog = await this.listPackageUi(identity);
|
|
1049
|
+
const contribution = requirePackageUiToolDeclarationV1(catalog, command);
|
|
1050
|
+
return projectClientTurnV1(
|
|
1051
|
+
await this.authority.run({
|
|
1052
|
+
...identity,
|
|
1053
|
+
runId: command.commandId,
|
|
1054
|
+
sessionId: `${identity.userId}:${identity.botId}`,
|
|
1055
|
+
acceptedAt: new Date().toISOString(),
|
|
1056
|
+
text: `${contribution.displayName} · ${command.name}`,
|
|
1057
|
+
directTool: {
|
|
1058
|
+
generationId: command.generationId,
|
|
1059
|
+
packageId: command.packageId,
|
|
1060
|
+
name: command.name,
|
|
1061
|
+
input: command.input,
|
|
1062
|
+
},
|
|
1063
|
+
}),
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
956
1067
|
/**
|
|
957
1068
|
* The Bot's invocable Skills, for the composer's `/` and `@` popover.
|
|
958
1069
|
*
|
|
@@ -1001,6 +1112,46 @@ export class ShellBotBackendContribution {
|
|
|
1001
1112
|
return { schemaVersion: 1, skills: entries };
|
|
1002
1113
|
}
|
|
1003
1114
|
|
|
1115
|
+
/** The one durable manifest lookup used by mounts, commands, and UI views. */
|
|
1116
|
+
private async readCompositionMemberManifest(
|
|
1117
|
+
member: CompositionMemberV1,
|
|
1118
|
+
): Promise<FrockBotManifest | undefined> {
|
|
1119
|
+
const stored = await this.ctx.storage.get<AuthoredManifestRecordV1>(
|
|
1120
|
+
authorshipManifestKey(member.manifestHash),
|
|
1121
|
+
);
|
|
1122
|
+
return stored ? decodeFrockBotManifest(stored.manifest) : undefined;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
private async requireCompositionMemberManifest(
|
|
1126
|
+
member: CompositionMemberV1,
|
|
1127
|
+
): Promise<FrockBotManifest> {
|
|
1128
|
+
const manifest = await this.readCompositionMemberManifest(member);
|
|
1129
|
+
if (!manifest) {
|
|
1130
|
+
throw new Error(
|
|
1131
|
+
`package "${member.packageId}" manifest "${member.manifestHash}" is unavailable`,
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
return manifest;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/** Active fail-closed Composition projected as inert iframe metadata. */
|
|
1138
|
+
async listPackageUi(
|
|
1139
|
+
identity: BotIdentity,
|
|
1140
|
+
): Promise<PackageIframeCompositionV1> {
|
|
1141
|
+
await this.validateIdentity(identity);
|
|
1142
|
+
const current = await this.authority.composition.current();
|
|
1143
|
+
const generation =
|
|
1144
|
+
current.status === "active" || current.status === "superseded"
|
|
1145
|
+
? current
|
|
1146
|
+
: await this.authority.composition.lastKnownGood();
|
|
1147
|
+
return projectPackageIframeCompositionV1({
|
|
1148
|
+
botId: identity.botId,
|
|
1149
|
+
generation,
|
|
1150
|
+
readMemberManifest: (member) =>
|
|
1151
|
+
this.readCompositionMemberManifest(member),
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1004
1155
|
/**
|
|
1005
1156
|
* Write one Skill into this Bot's own instruction root as its **User**.
|
|
1006
1157
|
*
|
|
@@ -1212,11 +1363,17 @@ export class ShellBotBackendContribution {
|
|
|
1212
1363
|
? { subagentTaskId: input.command.origin.taskId }
|
|
1213
1364
|
: {}),
|
|
1214
1365
|
};
|
|
1366
|
+
let mountedRoot: ShellMountedComposition["root"] | undefined;
|
|
1367
|
+
let mountedGeneration: CompositionGenerationV1 | undefined;
|
|
1368
|
+
const currentToolNames = (): readonly string[] =>
|
|
1369
|
+
mountedRoot?.tools.registeredNames?.() ?? [];
|
|
1215
1370
|
const runtime = await this.agentRuntime(
|
|
1216
1371
|
input.identity,
|
|
1217
1372
|
settings,
|
|
1218
1373
|
input.admittedRequest,
|
|
1219
1374
|
turn,
|
|
1375
|
+
currentToolNames,
|
|
1376
|
+
() => mountedGeneration,
|
|
1220
1377
|
);
|
|
1221
1378
|
const promptParts = [
|
|
1222
1379
|
`You are ${settings.profile.name}.`,
|
|
@@ -1233,9 +1390,9 @@ export class ShellBotBackendContribution {
|
|
|
1233
1390
|
runId: input.command.runId,
|
|
1234
1391
|
sessionId: input.command.sessionId,
|
|
1235
1392
|
generationId: mounting.generationId,
|
|
1236
|
-
|
|
1393
|
+
settings,
|
|
1237
1394
|
});
|
|
1238
|
-
|
|
1395
|
+
const mounted = await createShellCompositionHost({
|
|
1239
1396
|
botId: input.identity.botId,
|
|
1240
1397
|
sessionId: input.command.sessionId,
|
|
1241
1398
|
sessionEvents: input.previousEvents,
|
|
@@ -1261,6 +1418,9 @@ export class ShellBotBackendContribution {
|
|
|
1261
1418
|
),
|
|
1262
1419
|
...(isolate ? { isolate } : {}),
|
|
1263
1420
|
}).mount(mounting, signal);
|
|
1421
|
+
mountedRoot = mounted.root;
|
|
1422
|
+
mountedGeneration = mounted.generation;
|
|
1423
|
+
return mounted;
|
|
1264
1424
|
},
|
|
1265
1425
|
};
|
|
1266
1426
|
const controller = new AbortController();
|
|
@@ -1300,14 +1460,78 @@ export class ShellBotBackendContribution {
|
|
|
1300
1460
|
const active = {
|
|
1301
1461
|
runId: input.command.runId,
|
|
1302
1462
|
sessionId: input.command.sessionId,
|
|
1303
|
-
|
|
1463
|
+
turnId: input.command.runId,
|
|
1464
|
+
generationId: activation.mounted.generation.generationId,
|
|
1465
|
+
turnType: input.command.turnType ?? "chat",
|
|
1466
|
+
...(input.command.subagentRole
|
|
1467
|
+
? { subagentRole: input.command.subagentRole }
|
|
1468
|
+
: {}),
|
|
1469
|
+
mounted: activation.mounted,
|
|
1470
|
+
signal: controller.signal,
|
|
1471
|
+
cancel: () => {
|
|
1472
|
+
controller.abort("user");
|
|
1473
|
+
activation.mounted.runtime.agent.agent.cancel("user");
|
|
1474
|
+
},
|
|
1304
1475
|
};
|
|
1305
1476
|
this.activeTurn = active;
|
|
1306
1477
|
try {
|
|
1478
|
+
const directTool = input.command.directTool;
|
|
1479
|
+
if (directTool) {
|
|
1480
|
+
if (
|
|
1481
|
+
directTool.generationId !== activation.mounted.generation.generationId
|
|
1482
|
+
) {
|
|
1483
|
+
throw new Error(
|
|
1484
|
+
"Package UI command does not match the mounted Composition generation",
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
const member = activation.mounted.generation.members.find(
|
|
1488
|
+
(candidate) =>
|
|
1489
|
+
candidate.packageId === directTool.packageId &&
|
|
1490
|
+
candidate.provenance.kind !== "first-party",
|
|
1491
|
+
);
|
|
1492
|
+
if (!member)
|
|
1493
|
+
throw new Error("Package UI command names an unavailable Package");
|
|
1494
|
+
const manifest = await this.requireCompositionMemberManifest(member);
|
|
1495
|
+
const client = manifest.contributions.client;
|
|
1496
|
+
if (
|
|
1497
|
+
!client ||
|
|
1498
|
+
!isClientIframeContribution(client) ||
|
|
1499
|
+
!(manifest.tools ?? []).some((tool) => tool.name === directTool.name)
|
|
1500
|
+
) {
|
|
1501
|
+
throw new Error(
|
|
1502
|
+
`Package "${directTool.packageId}" did not declare tool "${directTool.name}" for its iframe`,
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
return await executeDirectToolTurn({
|
|
1506
|
+
command: { ...input.command, directTool },
|
|
1507
|
+
previousEvents: input.previousEvents,
|
|
1508
|
+
composition: activation.mounted,
|
|
1509
|
+
admitEffect: (effect) =>
|
|
1510
|
+
this.admitRunEffect(
|
|
1511
|
+
input.identity,
|
|
1512
|
+
input.command.runId,
|
|
1513
|
+
input.command.sessionId,
|
|
1514
|
+
effect,
|
|
1515
|
+
),
|
|
1516
|
+
signal: controller.signal,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
const ordinaryInput = await this.turnInputTextV1(input.command);
|
|
1520
|
+
const durableInput =
|
|
1521
|
+
activation.status === "failed-closed"
|
|
1522
|
+
? compositionFailureTurnTextV1(ordinaryInput, {
|
|
1523
|
+
attemptedGenerationId: input.compositionGenerationId,
|
|
1524
|
+
...(activation.generation
|
|
1525
|
+
? { generation: activation.generation }
|
|
1526
|
+
: {}),
|
|
1527
|
+
...(activation.failure ? { failure: activation.failure } : {}),
|
|
1528
|
+
quarantined: activation.quarantined,
|
|
1529
|
+
})
|
|
1530
|
+
: ordinaryInput;
|
|
1307
1531
|
return await executeBotTurn({
|
|
1308
1532
|
command: {
|
|
1309
1533
|
...input.command,
|
|
1310
|
-
text:
|
|
1534
|
+
text: durableInput,
|
|
1311
1535
|
},
|
|
1312
1536
|
previousEvents: input.previousEvents,
|
|
1313
1537
|
composition: activation.mounted,
|
|
@@ -1384,9 +1608,9 @@ export class ShellBotBackendContribution {
|
|
|
1384
1608
|
}
|
|
1385
1609
|
|
|
1386
1610
|
/**
|
|
1387
|
-
* Everything a Bot isolate member needs.
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1611
|
+
* Everything a Bot isolate member needs. Package identity is attribution
|
|
1612
|
+
* only; Connections and model are resolved once for the Bot and every member
|
|
1613
|
+
* receives the same list.
|
|
1390
1614
|
*/
|
|
1391
1615
|
private async isolateMountOptions(
|
|
1392
1616
|
identity: BotIdentity,
|
|
@@ -1394,7 +1618,7 @@ export class ShellBotBackendContribution {
|
|
|
1394
1618
|
runId: string;
|
|
1395
1619
|
sessionId: string;
|
|
1396
1620
|
generationId: string;
|
|
1397
|
-
|
|
1621
|
+
settings: BotSettingsViewV1;
|
|
1398
1622
|
},
|
|
1399
1623
|
): Promise<ShellIsolateMountOptions | undefined> {
|
|
1400
1624
|
const loader = this.env.BOT_PACKAGES;
|
|
@@ -1409,248 +1633,696 @@ export class ShellBotBackendContribution {
|
|
|
1409
1633
|
}
|
|
1410
1634
|
).exports;
|
|
1411
1635
|
if (!loader || !artifacts || !exports?.BotCapabilities) return undefined;
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1414
|
-
|
|
1636
|
+
const authority = await this.isolateAuthoritySnapshot(
|
|
1637
|
+
identity,
|
|
1638
|
+
turn.settings,
|
|
1415
1639
|
);
|
|
1640
|
+
const mintCapabilities = exports.BotCapabilities;
|
|
1416
1641
|
return {
|
|
1417
1642
|
userId: identity.userId,
|
|
1418
1643
|
runId: turn.runId,
|
|
1419
|
-
// One admitted Turn is one run; the Turn ordinal lives in the session log.
|
|
1420
1644
|
turnId: turn.runId,
|
|
1421
1645
|
loader,
|
|
1422
1646
|
artifacts: createR2PackageArtifactStore(artifacts),
|
|
1647
|
+
manifestFor: (member) => this.requireCompositionMemberManifest(member),
|
|
1423
1648
|
capabilitiesFor: (member) =>
|
|
1424
1649
|
mintCapabilities({
|
|
1425
1650
|
props: {
|
|
1426
1651
|
userId: identity.userId,
|
|
1427
1652
|
botId: identity.botId,
|
|
1653
|
+
runId: turn.runId,
|
|
1654
|
+
sessionId: turn.sessionId,
|
|
1655
|
+
turnId: turn.runId,
|
|
1428
1656
|
generationId: turn.generationId,
|
|
1429
1657
|
packageId: member.packageId,
|
|
1430
|
-
|
|
1658
|
+
connections: structuredClone(authority.connections),
|
|
1659
|
+
...(authority.model
|
|
1660
|
+
? { model: structuredClone(authority.model) }
|
|
1661
|
+
: {}),
|
|
1662
|
+
memory: authority.memory,
|
|
1663
|
+
workspace: authority.workspace,
|
|
1431
1664
|
},
|
|
1432
1665
|
}),
|
|
1433
1666
|
bindingDigest: await isolateBindingDigestV1({
|
|
1434
1667
|
userId: identity.userId,
|
|
1435
1668
|
botId: identity.botId,
|
|
1436
|
-
|
|
1437
|
-
|
|
1669
|
+
connections: authority.connections,
|
|
1670
|
+
...(authority.model ? { model: authority.model } : {}),
|
|
1671
|
+
compositionGenerationId: turn.generationId,
|
|
1438
1672
|
}),
|
|
1439
1673
|
compatibilityDate: BOT_ISOLATE_COMPATIBILITY_DATE,
|
|
1440
1674
|
};
|
|
1441
1675
|
}
|
|
1442
1676
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1677
|
+
private async isolateAuthoritySnapshot(
|
|
1678
|
+
identity: BotIdentity,
|
|
1679
|
+
settings: BotSettingsViewV1,
|
|
1680
|
+
): Promise<{
|
|
1681
|
+
connections: IsolateConnectionV1[];
|
|
1682
|
+
model?: IsolateModelBindingV1;
|
|
1683
|
+
memory: boolean;
|
|
1684
|
+
workspace: boolean;
|
|
1685
|
+
}> {
|
|
1686
|
+
const [user, application] = await Promise.all([
|
|
1687
|
+
this.userConfiguration(identity).readConfiguration({
|
|
1688
|
+
schemaVersion: 1,
|
|
1689
|
+
userId: identity.userId,
|
|
1690
|
+
}),
|
|
1691
|
+
this.compileApplication(),
|
|
1692
|
+
]);
|
|
1693
|
+
const connections = user.connections.flatMap((connection) =>
|
|
1694
|
+
connection.state === "ready" && connection.generation
|
|
1695
|
+
? [
|
|
1696
|
+
{
|
|
1697
|
+
connectionId: connection.connectionId,
|
|
1698
|
+
packageId: connection.packageId,
|
|
1699
|
+
connectionTypeId: connection.connectionTypeId,
|
|
1700
|
+
displayName: connection.displayName,
|
|
1701
|
+
generation: connection.generation,
|
|
1702
|
+
safeMetadata: structuredClone(connection.safeMetadata),
|
|
1703
|
+
} satisfies IsolateConnectionV1,
|
|
1704
|
+
]
|
|
1705
|
+
: [],
|
|
1455
1706
|
);
|
|
1707
|
+
const effective = resolveEffectiveBotModelV1({
|
|
1708
|
+
bot: settings,
|
|
1709
|
+
user,
|
|
1710
|
+
packages: application.packages.map((pkg) => ({
|
|
1711
|
+
packageId: pkg.id,
|
|
1712
|
+
version: pkg.version,
|
|
1713
|
+
settings: pkg.manifest.configuration?.settings ?? [],
|
|
1714
|
+
capabilities: pkg.manifest.configuration?.capabilities ?? [],
|
|
1715
|
+
connectionTypes: pkg.manifest.configuration?.connectionTypes ?? [],
|
|
1716
|
+
})),
|
|
1717
|
+
});
|
|
1718
|
+
const binding = effective.binding;
|
|
1719
|
+
const model =
|
|
1720
|
+
effective.model &&
|
|
1721
|
+
binding?.state === "ready" &&
|
|
1722
|
+
binding.connection?.generation &&
|
|
1723
|
+
binding.packageId &&
|
|
1724
|
+
binding.providerType
|
|
1725
|
+
? {
|
|
1726
|
+
connectionId: binding.connection.connectionId,
|
|
1727
|
+
packageId: binding.packageId,
|
|
1728
|
+
provider: binding.providerType,
|
|
1729
|
+
providerModelId: effective.model.providerModelId,
|
|
1730
|
+
connectionGeneration: binding.connection.generation,
|
|
1731
|
+
...(binding.connection.modelCatalog?.generation
|
|
1732
|
+
? {
|
|
1733
|
+
catalogGeneration: binding.connection.modelCatalog.generation,
|
|
1734
|
+
}
|
|
1735
|
+
: {}),
|
|
1736
|
+
}
|
|
1737
|
+
: undefined;
|
|
1738
|
+
return {
|
|
1739
|
+
connections,
|
|
1740
|
+
...(model ? { model } : {}),
|
|
1741
|
+
memory: Boolean(this.env.MEMORY_WORKSPACE_FILES),
|
|
1742
|
+
workspace: Boolean(this.env.WORKSPACE_FILES),
|
|
1743
|
+
};
|
|
1456
1744
|
}
|
|
1457
1745
|
|
|
1458
|
-
/**
|
|
1459
|
-
* The Bot Durable Object side of `CAPABILITIES.invokeModel`. Refuses with a
|
|
1460
|
-
* pending decision unless an enabled model Capability matches; otherwise
|
|
1461
|
-
* records the normalized request and streams through the provider path,
|
|
1462
|
-
* which takes the credential lease on the way.
|
|
1463
|
-
*/
|
|
1464
1746
|
async isolateInvokeModel(
|
|
1465
1747
|
identity: BotIdentity,
|
|
1466
1748
|
input: {
|
|
1749
|
+
runId: string;
|
|
1750
|
+
sessionId: string;
|
|
1751
|
+
turnId: string;
|
|
1467
1752
|
packageId: string;
|
|
1468
1753
|
generationId: string;
|
|
1469
1754
|
request: NormalizedModelRequest;
|
|
1470
1755
|
},
|
|
1471
|
-
): Promise<
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
if (!durableSettings) {
|
|
1478
|
-
return await this.isolateCapabilityHost(
|
|
1479
|
-
{
|
|
1480
|
-
botId: identity.botId,
|
|
1481
|
-
packageId: input.packageId,
|
|
1482
|
-
generationId: input.generationId,
|
|
1483
|
-
},
|
|
1484
|
-
[],
|
|
1485
|
-
).invokeModel(input.request);
|
|
1756
|
+
): Promise<IsolateModelInvocationV1> {
|
|
1757
|
+
if (!this.activeIsolateTurn(input)) {
|
|
1758
|
+
return {
|
|
1759
|
+
status: "unavailable",
|
|
1760
|
+
reason: "the Package is not running in this Bot's active Composition",
|
|
1761
|
+
};
|
|
1486
1762
|
}
|
|
1487
|
-
const
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1763
|
+
const settings = await this.ensureBotSettings(identity);
|
|
1764
|
+
const authority = await this.isolateAuthoritySnapshot(identity, settings);
|
|
1765
|
+
let runtime:
|
|
1766
|
+
| {
|
|
1767
|
+
agentPackages: FoundationAgentPackage[];
|
|
1768
|
+
modelSelection: RuntimeModelSelection;
|
|
1769
|
+
}
|
|
1770
|
+
| undefined;
|
|
1771
|
+
if (authority.model) {
|
|
1772
|
+
try {
|
|
1773
|
+
runtime = await this.agentRuntime(identity, settings);
|
|
1774
|
+
} catch {
|
|
1775
|
+
runtime = undefined;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return this.isolateCapabilities(
|
|
1496
1779
|
{
|
|
1497
1780
|
botId: identity.botId,
|
|
1498
1781
|
packageId: input.packageId,
|
|
1499
1782
|
generationId: input.generationId,
|
|
1500
1783
|
},
|
|
1501
|
-
|
|
1502
|
-
|
|
1784
|
+
authority,
|
|
1785
|
+
runtime && authority.model
|
|
1503
1786
|
? {
|
|
1504
|
-
|
|
1505
|
-
path: this.isolateModelPath(
|
|
1506
|
-
identity,
|
|
1507
|
-
bound.runtime,
|
|
1508
|
-
input.generationId,
|
|
1509
|
-
),
|
|
1787
|
+
path: this.isolateModelPath(identity, runtime, input.generationId),
|
|
1510
1788
|
}
|
|
1511
1789
|
: undefined,
|
|
1512
|
-
|
|
1513
|
-
);
|
|
1514
|
-
return await host.invokeModel(input.request);
|
|
1790
|
+
).invokeModel(input.request);
|
|
1515
1791
|
}
|
|
1516
1792
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1793
|
+
async isolateInvokeTool(input: {
|
|
1794
|
+
userId: string;
|
|
1795
|
+
botId: string;
|
|
1796
|
+
runId: string;
|
|
1797
|
+
sessionId: string;
|
|
1798
|
+
turnId: string;
|
|
1799
|
+
packageId: string;
|
|
1800
|
+
generationId: string;
|
|
1801
|
+
request: unknown;
|
|
1802
|
+
}): Promise<IsolateToolOutcomeV1> {
|
|
1803
|
+
const request = decodeIsolateToolRequestV1(input.request);
|
|
1804
|
+
const active = this.activeIsolateTurn(input);
|
|
1805
|
+
if (!active) {
|
|
1806
|
+
return {
|
|
1807
|
+
status: "unavailable",
|
|
1808
|
+
reason: "the Package is not running in this Bot's active Composition",
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
const session = active.mounted.runtime.root.sessions.get(input.sessionId);
|
|
1812
|
+
if (!session) {
|
|
1813
|
+
return {
|
|
1814
|
+
status: "unavailable",
|
|
1815
|
+
reason: "the active Session is unavailable",
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
const started = session.events.findLast(
|
|
1819
|
+
(event) => event.type === "step/start",
|
|
1820
|
+
);
|
|
1821
|
+
const ended = session.events.findLast((event) => event.type === "step/end");
|
|
1822
|
+
if (
|
|
1823
|
+
started?.type !== "step/start" ||
|
|
1824
|
+
(ended?.type === "step/end" &&
|
|
1825
|
+
ended.turn === started.turn &&
|
|
1826
|
+
ended.step === started.step)
|
|
1827
|
+
) {
|
|
1828
|
+
return {
|
|
1829
|
+
status: "unavailable",
|
|
1830
|
+
reason: "the active step is unavailable",
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
const effectId = await this.isolateToolEffectId(
|
|
1834
|
+
input.packageId,
|
|
1835
|
+
request.callId,
|
|
1836
|
+
);
|
|
1837
|
+
const priorCall = session.events.find(
|
|
1838
|
+
(event) =>
|
|
1839
|
+
event.type === "package/tool-call" && event.effectId === effectId,
|
|
1840
|
+
);
|
|
1841
|
+
const priorResult = session.events.find(
|
|
1842
|
+
(event) =>
|
|
1843
|
+
event.type === "package/tool-result" && event.effectId === effectId,
|
|
1844
|
+
);
|
|
1845
|
+
if (priorResult?.type === "package/tool-result") {
|
|
1846
|
+
return {
|
|
1847
|
+
status: "completed",
|
|
1848
|
+
content: priorResult.content,
|
|
1849
|
+
isError: priorResult.isError,
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
if (
|
|
1853
|
+
priorCall?.type === "package/tool-call" &&
|
|
1854
|
+
(priorCall.packageId !== input.packageId ||
|
|
1855
|
+
priorCall.callId !== request.callId ||
|
|
1856
|
+
priorCall.name !== request.name ||
|
|
1857
|
+
JSON.stringify(priorCall.input) !== JSON.stringify(request.input))
|
|
1858
|
+
) {
|
|
1859
|
+
return {
|
|
1860
|
+
status: "unavailable",
|
|
1861
|
+
reason: "the Package tool idempotency key was reused",
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
if (!priorCall) {
|
|
1865
|
+
session.append({
|
|
1866
|
+
type: "package/tool-call",
|
|
1867
|
+
turn: started.turn,
|
|
1868
|
+
step: started.step,
|
|
1869
|
+
effectId,
|
|
1870
|
+
packageId: input.packageId,
|
|
1871
|
+
callId: request.callId,
|
|
1872
|
+
name: request.name,
|
|
1873
|
+
input: request.input,
|
|
1874
|
+
});
|
|
1875
|
+
await session.flush();
|
|
1876
|
+
}
|
|
1877
|
+
const call = {
|
|
1878
|
+
id: request.callId,
|
|
1879
|
+
name: request.name,
|
|
1880
|
+
input: request.input,
|
|
1881
|
+
};
|
|
1882
|
+
const context = {
|
|
1883
|
+
botId: input.botId,
|
|
1884
|
+
agentId: input.botId,
|
|
1885
|
+
sessionId: input.sessionId,
|
|
1886
|
+
compositionGenerationId: input.generationId,
|
|
1887
|
+
effectId,
|
|
1888
|
+
toolCall: call,
|
|
1889
|
+
turnType: active.turnType,
|
|
1890
|
+
...(active.subagentRole ? { subagentRole: active.subagentRole } : {}),
|
|
1891
|
+
signal: active.signal,
|
|
1892
|
+
};
|
|
1893
|
+
const preparation = await active.mounted.runtime.root.tools.prepare(
|
|
1894
|
+
call,
|
|
1895
|
+
context,
|
|
1896
|
+
);
|
|
1897
|
+
let result: { content: string; isError: boolean };
|
|
1898
|
+
if (preparation.kind === "denied") {
|
|
1899
|
+
result = preparation.result;
|
|
1900
|
+
} else {
|
|
1901
|
+
const admitted = await this.admitRunEffect(
|
|
1902
|
+
{ userId: input.userId, botId: input.botId },
|
|
1903
|
+
input.runId,
|
|
1904
|
+
input.sessionId,
|
|
1905
|
+
{ kind: "tool", effectId },
|
|
1906
|
+
);
|
|
1907
|
+
if (!admitted) {
|
|
1908
|
+
result = {
|
|
1909
|
+
content: "The tool effect was stopped before it started.",
|
|
1910
|
+
isError: true,
|
|
1536
1911
|
};
|
|
1912
|
+
} else if (priorCall) {
|
|
1913
|
+
const recovered =
|
|
1914
|
+
await active.mounted.runtime.root.tools.reconcilePrepared(
|
|
1915
|
+
preparation,
|
|
1916
|
+
context,
|
|
1917
|
+
);
|
|
1918
|
+
result =
|
|
1919
|
+
recovered.status === "recovered"
|
|
1920
|
+
? recovered.result
|
|
1921
|
+
: { content: recovered.reason, isError: true };
|
|
1922
|
+
} else {
|
|
1923
|
+
result = await active.mounted.runtime.root.tools.executePrepared(
|
|
1924
|
+
preparation,
|
|
1925
|
+
context,
|
|
1926
|
+
);
|
|
1537
1927
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
settings: pkg.manifest.configuration?.settings ?? [],
|
|
1550
|
-
capabilities: pkg.manifest.configuration?.capabilities ?? [],
|
|
1551
|
-
connectionTypes: pkg.manifest.configuration?.connectionTypes ?? [],
|
|
1552
|
-
}));
|
|
1553
|
-
const effective = resolveEffectiveBotModelV1({
|
|
1554
|
-
bot: settings,
|
|
1555
|
-
user,
|
|
1556
|
-
packages: packageDefinitions,
|
|
1928
|
+
}
|
|
1929
|
+
session.append({
|
|
1930
|
+
type: "package/tool-result",
|
|
1931
|
+
turn: started.turn,
|
|
1932
|
+
step: started.step,
|
|
1933
|
+
effectId,
|
|
1934
|
+
packageId: input.packageId,
|
|
1935
|
+
callId: request.callId,
|
|
1936
|
+
name: request.name,
|
|
1937
|
+
content: result.content,
|
|
1938
|
+
isError: result.isError,
|
|
1557
1939
|
});
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1940
|
+
await session.flush();
|
|
1941
|
+
return {
|
|
1942
|
+
status: "completed",
|
|
1943
|
+
content: result.content,
|
|
1944
|
+
isError: result.isError,
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
async isolateMemoryRead(input: {
|
|
1949
|
+
userId: string;
|
|
1950
|
+
botId: string;
|
|
1951
|
+
runId: string;
|
|
1952
|
+
sessionId: string;
|
|
1953
|
+
turnId: string;
|
|
1954
|
+
packageId: string;
|
|
1955
|
+
generationId: string;
|
|
1956
|
+
request: unknown;
|
|
1957
|
+
}): Promise<IsolateMemoryOutcomeV1> {
|
|
1958
|
+
const request = decodeIsolateMemoryReadRequestV1(input.request);
|
|
1959
|
+
const memory = await this.isolateMemoryHost(input, request);
|
|
1960
|
+
if (!memory)
|
|
1961
|
+
return { status: "unavailable", reason: "Memory is unavailable" };
|
|
1962
|
+
return {
|
|
1963
|
+
status: "available",
|
|
1964
|
+
value: await memory.store.read(
|
|
1965
|
+
memoryScopeRootV1(request.scope, memory.owner, request.projectId),
|
|
1966
|
+
),
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
async isolateMemoryWrite(input: {
|
|
1971
|
+
userId: string;
|
|
1972
|
+
botId: string;
|
|
1973
|
+
runId: string;
|
|
1974
|
+
sessionId: string;
|
|
1975
|
+
turnId: string;
|
|
1976
|
+
packageId: string;
|
|
1977
|
+
generationId: string;
|
|
1978
|
+
request: unknown;
|
|
1979
|
+
}): Promise<IsolateMemoryOutcomeV1> {
|
|
1980
|
+
const request = decodeIsolateMemoryWriteRequestV1(input.request);
|
|
1981
|
+
const memory = await this.isolateMemoryHost(input, request);
|
|
1982
|
+
if (!memory?.writer) {
|
|
1983
|
+
return { status: "unavailable", reason: "Memory is unavailable" };
|
|
1984
|
+
}
|
|
1985
|
+
return {
|
|
1986
|
+
status: "available",
|
|
1987
|
+
value: await memory.store.write({
|
|
1988
|
+
root: memoryScopeRootV1(request.scope, memory.owner, request.projectId),
|
|
1989
|
+
tier: request.tier ?? "log",
|
|
1990
|
+
fact: request.fact,
|
|
1991
|
+
writer: {
|
|
1992
|
+
kind: "bot",
|
|
1993
|
+
botId: input.botId,
|
|
1994
|
+
...memory.writer,
|
|
1995
|
+
},
|
|
1996
|
+
}),
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
async isolateMemoryForget(input: {
|
|
2001
|
+
userId: string;
|
|
2002
|
+
botId: string;
|
|
2003
|
+
runId: string;
|
|
2004
|
+
sessionId: string;
|
|
2005
|
+
turnId: string;
|
|
2006
|
+
packageId: string;
|
|
2007
|
+
generationId: string;
|
|
2008
|
+
request: unknown;
|
|
2009
|
+
}): Promise<IsolateMemoryOutcomeV1> {
|
|
2010
|
+
const request = decodeIsolateMemoryWriteRequestV1(input.request);
|
|
2011
|
+
const memory = await this.isolateMemoryHost(input, request);
|
|
2012
|
+
if (!memory?.writer) {
|
|
2013
|
+
return { status: "unavailable", reason: "Memory is unavailable" };
|
|
2014
|
+
}
|
|
2015
|
+
return {
|
|
2016
|
+
status: "available",
|
|
2017
|
+
value: await memory.store.forget({
|
|
2018
|
+
root: memoryScopeRootV1(request.scope, memory.owner, request.projectId),
|
|
2019
|
+
fact: request.fact,
|
|
2020
|
+
writer: {
|
|
2021
|
+
kind: "bot",
|
|
2022
|
+
botId: input.botId,
|
|
2023
|
+
...memory.writer,
|
|
2024
|
+
},
|
|
2025
|
+
}),
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
async isolateWorkspaceRead(
|
|
2030
|
+
input: IsolateCallScopeV1,
|
|
2031
|
+
): Promise<IsolateWorkspaceOutcomeV1> {
|
|
2032
|
+
const active = this.activeIsolateTurn(input);
|
|
2033
|
+
const files = this.env.WORKSPACE_FILES;
|
|
2034
|
+
if (!active || !files) {
|
|
2035
|
+
return { status: "unavailable", reason: "Workspace is unavailable" };
|
|
2036
|
+
}
|
|
2037
|
+
const path = this.isolateWorkspacePath(
|
|
2038
|
+
input.userId,
|
|
2039
|
+
decodeIsolateWorkspacePathV1(input.request),
|
|
1562
2040
|
);
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
: {}),
|
|
1574
|
-
providerModelId: effectiveModel.providerModelId,
|
|
1575
|
-
},
|
|
1576
|
-
});
|
|
1577
|
-
if (!effective.binding || effective.binding.state === "unavailable") {
|
|
1578
|
-
return unavailable();
|
|
2041
|
+
return { status: "available", value: await files.read(path) };
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
async isolateWorkspaceList(
|
|
2045
|
+
input: IsolateCallScopeV1,
|
|
2046
|
+
): Promise<IsolateWorkspaceOutcomeV1> {
|
|
2047
|
+
const active = this.activeIsolateTurn(input);
|
|
2048
|
+
const files = this.env.WORKSPACE_FILES;
|
|
2049
|
+
if (!active || !files) {
|
|
2050
|
+
return { status: "unavailable", reason: "Workspace is unavailable" };
|
|
1579
2051
|
}
|
|
1580
|
-
const
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
2052
|
+
const request = decodeIsolateWorkspaceListRequestV1(input.request);
|
|
2053
|
+
const root = this.isolateWorkspaceRoot(input.userId, request.root);
|
|
2054
|
+
return {
|
|
2055
|
+
status: "available",
|
|
2056
|
+
value: await files.list({
|
|
2057
|
+
root,
|
|
2058
|
+
...(request.prefix === undefined ? {} : { prefix: request.prefix }),
|
|
2059
|
+
...(request.cursor === undefined ? {} : { cursor: request.cursor }),
|
|
2060
|
+
...(request.limit === undefined ? {} : { limit: request.limit }),
|
|
2061
|
+
}),
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
async isolateWorkspaceStat(
|
|
2066
|
+
input: IsolateCallScopeV1,
|
|
2067
|
+
): Promise<IsolateWorkspaceOutcomeV1> {
|
|
2068
|
+
const active = this.activeIsolateTurn(input);
|
|
2069
|
+
const files = this.env.WORKSPACE_FILES;
|
|
2070
|
+
if (!active || !files) {
|
|
2071
|
+
return { status: "unavailable", reason: "Workspace is unavailable" };
|
|
2072
|
+
}
|
|
2073
|
+
const path = this.isolateWorkspacePath(
|
|
2074
|
+
input.userId,
|
|
2075
|
+
decodeIsolateWorkspacePathV1(input.request),
|
|
1589
2076
|
);
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
2077
|
+
return { status: "available", value: await files.stat(path) };
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
async isolateWorkspaceWrite(
|
|
2081
|
+
input: IsolateCallScopeV1,
|
|
2082
|
+
): Promise<IsolateWorkspaceOutcomeV1> {
|
|
2083
|
+
const active = this.activeIsolateTurn(input);
|
|
2084
|
+
const files = this.env.WORKSPACE_FILES;
|
|
2085
|
+
if (!active || !files) {
|
|
2086
|
+
return { status: "unavailable", reason: "Workspace is unavailable" };
|
|
2087
|
+
}
|
|
2088
|
+
const request = decodeIsolateWorkspaceWriteRequestV1(input.request);
|
|
2089
|
+
return {
|
|
2090
|
+
status: "available",
|
|
2091
|
+
value: await files.write({
|
|
2092
|
+
path: this.isolateWorkspacePath(input.userId, request.path),
|
|
2093
|
+
bytes: request.bytes,
|
|
2094
|
+
writer: this.isolateWorkspaceWriter(input),
|
|
2095
|
+
expectedGenerationId: request.expectedGenerationId,
|
|
2096
|
+
...(request.mediaType ? { mediaType: request.mediaType } : {}),
|
|
2097
|
+
}),
|
|
1594
2098
|
};
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
async isolateWorkspaceDelete(
|
|
2102
|
+
input: IsolateCallScopeV1,
|
|
2103
|
+
): Promise<IsolateWorkspaceOutcomeV1> {
|
|
2104
|
+
const active = this.activeIsolateTurn(input);
|
|
2105
|
+
const files = this.env.WORKSPACE_FILES;
|
|
2106
|
+
if (!active || !files) {
|
|
2107
|
+
return { status: "unavailable", reason: "Workspace is unavailable" };
|
|
1599
2108
|
}
|
|
1600
|
-
const
|
|
1601
|
-
const connectionId = selection.connectionId;
|
|
1602
|
-
if (!connectionId) return unavailable();
|
|
2109
|
+
const request = decodeIsolateWorkspaceDeleteRequestV1(input.request);
|
|
1603
2110
|
return {
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2111
|
+
status: "available",
|
|
2112
|
+
value: await files.delete({
|
|
2113
|
+
path: this.isolateWorkspacePath(input.userId, request.path),
|
|
2114
|
+
writer: this.isolateWorkspaceWriter(input),
|
|
2115
|
+
expectedGenerationId: request.expectedGenerationId,
|
|
2116
|
+
}),
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
async isolateConnection(
|
|
2121
|
+
input: IsolateCallScopeV1,
|
|
2122
|
+
): Promise<IsolateConnectionOutcomeV1> {
|
|
2123
|
+
if (!this.activeIsolateTurn(input) || typeof input.request !== "string") {
|
|
2124
|
+
return { status: "unavailable", reason: "the Connection is unavailable" };
|
|
2125
|
+
}
|
|
2126
|
+
const identity = { userId: input.userId, botId: input.botId };
|
|
2127
|
+
const user = await this.userConfiguration(identity).readConfiguration({
|
|
2128
|
+
schemaVersion: 1,
|
|
2129
|
+
userId: input.userId,
|
|
2130
|
+
});
|
|
2131
|
+
const connection = user.connections.find(
|
|
2132
|
+
(candidate) =>
|
|
2133
|
+
candidate.connectionId === input.request &&
|
|
2134
|
+
candidate.state === "ready" &&
|
|
2135
|
+
candidate.generation,
|
|
2136
|
+
);
|
|
2137
|
+
if (!connection?.generation) {
|
|
2138
|
+
await this.authority.recordNotification({
|
|
2139
|
+
notificationId: `package-connection-unavailable:${input.runId}:${input.packageId}:${input.request}`,
|
|
2140
|
+
runId: input.runId,
|
|
2141
|
+
createdAt: new Date().toISOString(),
|
|
2142
|
+
title: "Connection unavailable",
|
|
2143
|
+
body: `Package "${input.packageId}" could not use Connection "${input.request}". The User can enable or repair it on Connections.`.slice(
|
|
2144
|
+
0,
|
|
2145
|
+
240,
|
|
2146
|
+
),
|
|
2147
|
+
});
|
|
2148
|
+
return { status: "unavailable", reason: "the Connection is unavailable" };
|
|
2149
|
+
}
|
|
2150
|
+
return {
|
|
2151
|
+
status: "available",
|
|
2152
|
+
leaseId: crypto.randomUUID(),
|
|
2153
|
+
connectionId: connection.connectionId,
|
|
2154
|
+
generation: connection.generation,
|
|
2155
|
+
expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(),
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
async isolateNotify(
|
|
2160
|
+
input: IsolateCallScopeV1,
|
|
2161
|
+
): Promise<IsolateNotificationOutcomeV1> {
|
|
2162
|
+
if (!this.activeIsolateTurn(input)) {
|
|
2163
|
+
return { status: "unavailable", reason: "notifications are unavailable" };
|
|
2164
|
+
}
|
|
2165
|
+
const request = decodeIsolateNotificationRequestV1(input.request);
|
|
2166
|
+
await this.authority.recordNotification({
|
|
2167
|
+
notificationId: `package:${input.packageId}:${request.notificationId}`,
|
|
2168
|
+
runId: input.runId,
|
|
2169
|
+
createdAt: new Date().toISOString(),
|
|
2170
|
+
title: request.title,
|
|
2171
|
+
body: request.body,
|
|
2172
|
+
});
|
|
2173
|
+
return { status: "recorded" };
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
async isolateSchedule(
|
|
2177
|
+
input: IsolateCallScopeV1,
|
|
2178
|
+
): Promise<IsolateToolOutcomeV1> {
|
|
2179
|
+
const request = decodeIsolateScheduleRequestV1(input.request);
|
|
2180
|
+
return this.isolateInvokeTool({
|
|
2181
|
+
...input,
|
|
2182
|
+
request: {
|
|
2183
|
+
callId: request.callId,
|
|
2184
|
+
name: "routine_manage",
|
|
2185
|
+
input: request.input,
|
|
2186
|
+
},
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
private async isolateMemoryHost(
|
|
2191
|
+
input: IsolateCallScopeV1,
|
|
2192
|
+
request: { scope: "bot" | "user" | "project"; projectId?: string },
|
|
2193
|
+
) {
|
|
2194
|
+
if (!this.activeIsolateTurn(input)) return undefined;
|
|
2195
|
+
const host = createBotMemoryHost(
|
|
2196
|
+
{ userId: input.userId, botId: input.botId },
|
|
2197
|
+
{
|
|
2198
|
+
runId: input.runId,
|
|
2199
|
+
sessionId: input.sessionId,
|
|
2200
|
+
turnId: input.turnId,
|
|
1618
2201
|
},
|
|
2202
|
+
this.env,
|
|
2203
|
+
);
|
|
2204
|
+
if (!host) return undefined;
|
|
2205
|
+
if (request.scope === "project") {
|
|
2206
|
+
const projects = await host.projects?.joined();
|
|
2207
|
+
if (
|
|
2208
|
+
!request.projectId ||
|
|
2209
|
+
!projects?.some((project) => project.projectId === request.projectId)
|
|
2210
|
+
) {
|
|
2211
|
+
return undefined;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
return host;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
private activeIsolateTurn(input: {
|
|
2218
|
+
runId: string;
|
|
2219
|
+
sessionId: string;
|
|
2220
|
+
turnId: string;
|
|
2221
|
+
packageId: string;
|
|
2222
|
+
generationId: string;
|
|
2223
|
+
}) {
|
|
2224
|
+
const active = this.activeTurn;
|
|
2225
|
+
if (
|
|
2226
|
+
!active ||
|
|
2227
|
+
active.runId !== input.runId ||
|
|
2228
|
+
active.sessionId !== input.sessionId ||
|
|
2229
|
+
active.turnId !== input.turnId ||
|
|
2230
|
+
active.generationId !== input.generationId ||
|
|
2231
|
+
!active.mounted.generation.members.some(
|
|
2232
|
+
(member) => member.packageId === input.packageId && member.artifact,
|
|
2233
|
+
)
|
|
2234
|
+
) {
|
|
2235
|
+
return undefined;
|
|
2236
|
+
}
|
|
2237
|
+
return active;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
private async isolateToolEffectId(
|
|
2241
|
+
packageId: string,
|
|
2242
|
+
callId: string,
|
|
2243
|
+
): Promise<string> {
|
|
2244
|
+
const digest = await crypto.subtle.digest(
|
|
2245
|
+
"SHA-256",
|
|
2246
|
+
new TextEncoder().encode(`${packageId}\0${callId}`),
|
|
2247
|
+
);
|
|
2248
|
+
const hex = [...new Uint8Array(digest)]
|
|
2249
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
2250
|
+
.join("");
|
|
2251
|
+
return `package-tool:${hex}`;
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
private isolateWorkspaceRoot(
|
|
2255
|
+
userId: string,
|
|
2256
|
+
root: ReturnType<typeof decodeIsolateWorkspacePathV1>["root"],
|
|
2257
|
+
) {
|
|
2258
|
+
return decodeWorkspaceRootV1(
|
|
2259
|
+
root.kind === "user-instructions"
|
|
2260
|
+
? { kind: root.kind, userId }
|
|
2261
|
+
: root.kind === "bot-instructions"
|
|
2262
|
+
? { kind: root.kind, userId, botId: root.botId }
|
|
2263
|
+
: {
|
|
2264
|
+
kind: root.kind,
|
|
2265
|
+
userId,
|
|
2266
|
+
packageId: root.packageId,
|
|
2267
|
+
rootId: root.rootId,
|
|
2268
|
+
},
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
private isolateWorkspacePath(
|
|
2273
|
+
userId: string,
|
|
2274
|
+
path: ReturnType<typeof decodeIsolateWorkspacePathV1>,
|
|
2275
|
+
): WorkspacePathV1 {
|
|
2276
|
+
return decodeWorkspacePathV1({
|
|
2277
|
+
root: this.isolateWorkspaceRoot(userId, path.root),
|
|
2278
|
+
path: path.path,
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
private isolateWorkspaceWriter(input: IsolateCallScopeV1) {
|
|
2283
|
+
return {
|
|
2284
|
+
kind: "bot" as const,
|
|
2285
|
+
botId: input.botId,
|
|
2286
|
+
sessionId: input.sessionId,
|
|
2287
|
+
turnId: input.turnId,
|
|
2288
|
+
runId: input.runId,
|
|
1619
2289
|
};
|
|
1620
2290
|
}
|
|
1621
2291
|
|
|
1622
|
-
private
|
|
2292
|
+
private isolateCapabilities(
|
|
1623
2293
|
scope: {
|
|
1624
2294
|
botId: string;
|
|
1625
2295
|
packageId: string;
|
|
1626
2296
|
generationId: string;
|
|
1627
|
-
request?: unknown;
|
|
1628
2297
|
},
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
2298
|
+
authority: {
|
|
2299
|
+
connections: readonly IsolateConnectionV1[];
|
|
2300
|
+
model?: IsolateModelBindingV1;
|
|
2301
|
+
memory: boolean;
|
|
2302
|
+
workspace: boolean;
|
|
2303
|
+
},
|
|
2304
|
+
model?: { path: IsolateModelPath },
|
|
1632
2305
|
): IsolateCapabilityHost {
|
|
1633
2306
|
return createIsolateCapabilityHost({
|
|
1634
2307
|
storage: {
|
|
1635
2308
|
put: (key, value) => this.ctx.storage.put(key, value),
|
|
1636
|
-
get: (key) => this.ctx.storage.get(key),
|
|
1637
2309
|
list: (options) => this.ctx.storage.list(options),
|
|
1638
2310
|
},
|
|
1639
2311
|
botId: scope.botId,
|
|
1640
2312
|
packageId: scope.packageId,
|
|
1641
2313
|
generationId: scope.generationId,
|
|
1642
|
-
|
|
1643
|
-
...(model ? { modelBinding:
|
|
1644
|
-
...(
|
|
2314
|
+
connections: authority.connections,
|
|
2315
|
+
...(authority.model ? { modelBinding: authority.model } : {}),
|
|
2316
|
+
...(model ? { modelPath: model.path } : {}),
|
|
2317
|
+
memory: authority.memory,
|
|
2318
|
+
workspace: authority.workspace,
|
|
1645
2319
|
});
|
|
1646
2320
|
}
|
|
1647
2321
|
|
|
1648
2322
|
/**
|
|
1649
2323
|
* Streams through the pinned Composition's mounted `ctx.llm` — the same
|
|
1650
2324
|
* provider path a Turn uses, so whichever provider Plugin serves the request
|
|
1651
|
-
* is the one that takes the credential lease.
|
|
1652
|
-
* effective model binding resolved to, so the Package that streams is the
|
|
1653
|
-
* Package the enabled Capability names.
|
|
2325
|
+
* is the one that takes the credential lease.
|
|
1654
2326
|
*/
|
|
1655
2327
|
private isolateModelPath(
|
|
1656
2328
|
identity: BotIdentity,
|
|
@@ -1676,8 +2348,6 @@ export class ShellBotBackendContribution {
|
|
|
1676
2348
|
sessionEvents: [],
|
|
1677
2349
|
agentPackages: runtime.agentPackages,
|
|
1678
2350
|
modelSelection: runtime.modelSelection,
|
|
1679
|
-
// An isolate model invocation is not an admitted Turn, so there is
|
|
1680
|
-
// no run to fence it against; User enablement admitted it.
|
|
1681
2351
|
admitEffect: () => Promise.resolve(true),
|
|
1682
2352
|
}).mount(generation, signal);
|
|
1683
2353
|
try {
|
|
@@ -1707,10 +2377,10 @@ export class ShellBotBackendContribution {
|
|
|
1707
2377
|
transaction: DurableObjectTransaction,
|
|
1708
2378
|
resolved: BotSettingsViewV1,
|
|
1709
2379
|
): Promise<BotSettingsViewV1> {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
resolved
|
|
1713
|
-
|
|
2380
|
+
const stored = await transaction.get<unknown>(BOT_CONFIGURATION_KEY);
|
|
2381
|
+
return stored === undefined
|
|
2382
|
+
? resolved
|
|
2383
|
+
: decodeBotSettingsViewV1(migrateStoredBotSettingsV1(stored));
|
|
1714
2384
|
}
|
|
1715
2385
|
|
|
1716
2386
|
private async scheduledDeadlines(
|
|
@@ -2827,12 +3497,15 @@ export class ShellBotBackendContribution {
|
|
|
2827
3497
|
private authoringHost(
|
|
2828
3498
|
identity: BotIdentity,
|
|
2829
3499
|
turn: { runId: string; turnId: string },
|
|
3500
|
+
currentToolNames: () => readonly string[],
|
|
3501
|
+
mountedGeneration: () => CompositionGenerationV1 | undefined,
|
|
2830
3502
|
): PackageAuthoringHost {
|
|
2831
3503
|
const artifacts = this.env.APPLICATION_ARTIFACTS;
|
|
2832
3504
|
return createPackageAuthoringHost({
|
|
2833
3505
|
storage: {
|
|
2834
3506
|
get: (key) => this.ctx.storage.get(key),
|
|
2835
3507
|
put: (entries) => this.ctx.storage.put(entries),
|
|
3508
|
+
list: (options) => this.ctx.storage.list(options),
|
|
2836
3509
|
},
|
|
2837
3510
|
composition: this.authority.composition,
|
|
2838
3511
|
...(this.env.PACKAGE_BUNDLER
|
|
@@ -2847,6 +3520,43 @@ export class ShellBotBackendContribution {
|
|
|
2847
3520
|
runId: turn.runId,
|
|
2848
3521
|
turnId: turn.turnId,
|
|
2849
3522
|
compatibilityDate: BOT_ISOLATE_COMPATIBILITY_DATE,
|
|
3523
|
+
currentToolNames,
|
|
3524
|
+
mountedGeneration,
|
|
3525
|
+
activationFailures: this.authority.compositionFailures,
|
|
3526
|
+
});
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
/** Catalog reads plus the two-authority mutation seam for one admitted Turn. */
|
|
3530
|
+
private packageCatalogHost(
|
|
3531
|
+
identity: BotIdentity,
|
|
3532
|
+
turn: { runId: string; turnId: string },
|
|
3533
|
+
): CatalogAwarePackageCatalogHost | undefined {
|
|
3534
|
+
if (!this.env.PACKAGE_CATALOG || !this.env.APPLICATION_ARTIFACTS) {
|
|
3535
|
+
return undefined;
|
|
3536
|
+
}
|
|
3537
|
+
const user = this.userConfiguration(identity);
|
|
3538
|
+
return createPackageCatalogHost({
|
|
3539
|
+
storage: {
|
|
3540
|
+
get: (key) => this.ctx.storage.get(key),
|
|
3541
|
+
put: (entries) => this.ctx.storage.put(entries),
|
|
3542
|
+
},
|
|
3543
|
+
composition: this.authority.composition,
|
|
3544
|
+
catalog: createR2BotPackageCatalogReader(
|
|
3545
|
+
this.env.PACKAGE_CATALOG,
|
|
3546
|
+
this.env.APPLICATION_ARTIFACTS,
|
|
3547
|
+
),
|
|
3548
|
+
user: {
|
|
3549
|
+
read: () =>
|
|
3550
|
+
user.readConfiguration({
|
|
3551
|
+
schemaVersion: 1,
|
|
3552
|
+
userId: identity.userId,
|
|
3553
|
+
}),
|
|
3554
|
+
execute: (command) => user.executeConfiguration(command),
|
|
3555
|
+
},
|
|
3556
|
+
userId: identity.userId,
|
|
3557
|
+
botId: identity.botId,
|
|
3558
|
+
runId: turn.runId,
|
|
3559
|
+
turnId: turn.turnId,
|
|
2850
3560
|
});
|
|
2851
3561
|
}
|
|
2852
3562
|
|
|
@@ -2871,6 +3581,9 @@ export class ShellBotBackendContribution {
|
|
|
2871
3581
|
/** The task a child Turn is running, in a Subagent Durable Object. */
|
|
2872
3582
|
subagentTaskId?: string;
|
|
2873
3583
|
},
|
|
3584
|
+
currentToolNames: () => readonly string[] = () => [],
|
|
3585
|
+
mountedGeneration: () => CompositionGenerationV1 | undefined = () =>
|
|
3586
|
+
undefined,
|
|
2874
3587
|
): Promise<{
|
|
2875
3588
|
agentPackages: FoundationAgentPackage[];
|
|
2876
3589
|
capabilities: EnabledCapabilityV1[];
|
|
@@ -2968,13 +3681,29 @@ export class ShellBotBackendContribution {
|
|
|
2968
3681
|
// Filled in once this Turn's model binding is resolved, below. The tool
|
|
2969
3682
|
// and the prompt section both read it lazily, from inside the Turn.
|
|
2970
3683
|
const subagentModels: SubagentModelOptionV1[] = [];
|
|
3684
|
+
const packageCatalog = turn
|
|
3685
|
+
? this.packageCatalogHost(identity, turn)
|
|
3686
|
+
: undefined;
|
|
3687
|
+
const baseAuthoring = turn
|
|
3688
|
+
? this.authoringHost(identity, turn, currentToolNames, mountedGeneration)
|
|
3689
|
+
: undefined;
|
|
3690
|
+
const authoring: PackageAuthoringHost | undefined =
|
|
3691
|
+
baseAuthoring && packageCatalog
|
|
3692
|
+
? {
|
|
3693
|
+
...baseAuthoring,
|
|
3694
|
+
undo: async (request) =>
|
|
3695
|
+
(await packageCatalog.undoCatalogChange(request)) ??
|
|
3696
|
+
baseAuthoring.undo(request),
|
|
3697
|
+
}
|
|
3698
|
+
: baseAuthoring;
|
|
2971
3699
|
const resolvedAgentPackages: FoundationAgentPackage[] = [
|
|
2972
3700
|
...createFoundationHostedRuntimePackages(application, {
|
|
2973
3701
|
userId: identity.userId,
|
|
2974
3702
|
readSecret,
|
|
2975
3703
|
// A Bot authors a Package only inside an admitted Turn, whose run and
|
|
2976
3704
|
// session the artifact provenance names.
|
|
2977
|
-
...(
|
|
3705
|
+
...(authoring ? { authoring } : {}),
|
|
3706
|
+
...(packageCatalog ? { packageCatalog } : {}),
|
|
2978
3707
|
...(turn
|
|
2979
3708
|
? {
|
|
2980
3709
|
skills: createBotSkillsHost(
|
|
@@ -3793,15 +4522,17 @@ export class ShellBotBackendContribution {
|
|
|
3793
4522
|
return quarantine === undefined ? {} : { quarantine };
|
|
3794
4523
|
}
|
|
3795
4524
|
|
|
3796
|
-
/**
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
* member has no recorded source to show and the view carries members only.
|
|
3800
|
-
*/
|
|
3801
|
-
private readCompositionMemberSource(
|
|
3802
|
-
_member: CompositionMemberV1,
|
|
4525
|
+
/** Reads the immutable TypeScript source retained beside an authored artifact. */
|
|
4526
|
+
private async readCompositionMemberSource(
|
|
4527
|
+
member: CompositionMemberV1,
|
|
3803
4528
|
): Promise<string | undefined> {
|
|
3804
|
-
|
|
4529
|
+
const bucket = this.env.APPLICATION_ARTIFACTS;
|
|
4530
|
+
if (!bucket) return undefined;
|
|
4531
|
+
return readAuthoredCompositionMemberSourceV1({
|
|
4532
|
+
storage: { get: (key) => this.ctx.storage.get(key) },
|
|
4533
|
+
artifacts: createR2AuthoringArtifactStore(bucket),
|
|
4534
|
+
member,
|
|
4535
|
+
});
|
|
3805
4536
|
}
|
|
3806
4537
|
|
|
3807
4538
|
/**
|
|
@@ -4345,6 +5076,9 @@ export class ShellBotBackendContribution {
|
|
|
4345
5076
|
schemaVersion: 1;
|
|
4346
5077
|
userId: string;
|
|
4347
5078
|
}): Promise<UserSettingsViewV1>;
|
|
5079
|
+
executeConfiguration(
|
|
5080
|
+
input: Extract<ConfigurationCommandV1, { type: `user/${string}` }>,
|
|
5081
|
+
): Promise<OperationReceiptV1>;
|
|
4348
5082
|
readPackageRevisions(
|
|
4349
5083
|
userId: string,
|
|
4350
5084
|
): ReturnType<PackagePublisherAgentHost["read"]>;
|
|
@@ -4416,6 +5150,7 @@ export class ShellBotBackendContribution {
|
|
|
4416
5150
|
// SAFETY: this namespace is bound to UserConfiguration; generated Worker types do not expose its RPC surface.
|
|
4417
5151
|
const rpc = this.env.USER_CONFIGURATIONS.get(id) as unknown as {
|
|
4418
5152
|
readConfiguration(input: unknown): Promise<UserSettingsViewV1>;
|
|
5153
|
+
executeConfiguration(input: unknown): Promise<unknown>;
|
|
4419
5154
|
readPackageRevisions(
|
|
4420
5155
|
input: unknown,
|
|
4421
5156
|
): ReturnType<PackagePublisherAgentHost["read"]>;
|
|
@@ -4442,6 +5177,14 @@ export class ShellBotBackendContribution {
|
|
|
4442
5177
|
};
|
|
4443
5178
|
return {
|
|
4444
5179
|
readConfiguration: (input) => rpc.readConfiguration(input),
|
|
5180
|
+
executeConfiguration: async (command) =>
|
|
5181
|
+
decodeOperationReceiptV1(
|
|
5182
|
+
await rpc.executeConfiguration({
|
|
5183
|
+
schemaVersion: 1,
|
|
5184
|
+
userId: identity.userId,
|
|
5185
|
+
command,
|
|
5186
|
+
}),
|
|
5187
|
+
),
|
|
4445
5188
|
// A machine is a User asset, so every one of these crosses the seam and
|
|
4446
5189
|
// is decoded on arrival rather than trusted in the shape RPC returned.
|
|
4447
5190
|
listMachines: async (userId) =>
|
|
@@ -4562,12 +5305,10 @@ export class ShellBotBackendContribution {
|
|
|
4562
5305
|
identity: BotIdentity,
|
|
4563
5306
|
): Promise<BotSettingsViewV1> {
|
|
4564
5307
|
await this.validateIdentity(identity);
|
|
4565
|
-
const
|
|
4566
|
-
|
|
4567
|
-
);
|
|
4568
|
-
if (!existing)
|
|
5308
|
+
const stored = await this.ctx.storage.get<unknown>(BOT_CONFIGURATION_KEY);
|
|
5309
|
+
if (stored === undefined)
|
|
4569
5310
|
throw new Error(`Bot "${identity.botId}" is not materialized`);
|
|
4570
|
-
return
|
|
5311
|
+
return decodeBotSettingsViewV1(migrateStoredBotSettingsV1(stored));
|
|
4571
5312
|
}
|
|
4572
5313
|
|
|
4573
5314
|
private async resolveExecutionContext(identity: BotIdentity): Promise<{
|