@zq-silk/yui 0.2.0 → 0.4.2
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/ARCHITECTURE.md +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- package/dist/worktree/roleWorkspace.js +0 -62
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { callController as defaultCallController } from "../core/controllerClient.js";
|
|
5
|
+
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
6
|
+
import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
7
|
+
import { yuiVersionIdentity } from "../version.js";
|
|
8
|
+
import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "./lifecycleReservation.js";
|
|
9
|
+
import { nativeSessionIdForLaunch } from "./preallocatedNativeSession.js";
|
|
10
|
+
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
11
|
+
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
12
|
+
export const EXACT_CONTROL_ARGUMENT = "--yui-control";
|
|
13
|
+
export const YUI_CONTROL_PLANE_DESCRIPTOR = "YUI_CONTROL_PLANE_DESCRIPTOR";
|
|
14
|
+
export const YUI_TASK_RUNTIME_DESCRIPTOR = "YUI_TASK_RUNTIME_DESCRIPTOR";
|
|
15
|
+
export function createExactControlPlaneDescriptor(input) {
|
|
16
|
+
const identity = validateVersionIdentity(input.identity ?? yuiVersionIdentity());
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
schemaVersion: 1,
|
|
19
|
+
kind: "yui-control-plane",
|
|
20
|
+
executable: canonicalPath(input.executable),
|
|
21
|
+
cliEntry: canonicalPath(input.cliEntry),
|
|
22
|
+
yuiHome: canonicalPath(input.yuiHome),
|
|
23
|
+
identity: Object.freeze({ ...identity })
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function createExactTaskRuntimeDescriptor(input) {
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
schemaVersion: 1,
|
|
29
|
+
kind: "yui-task-runtime",
|
|
30
|
+
controlPlaneDigest: requireDigest(input.controlPlaneDigest),
|
|
31
|
+
taskId: requireIdentity(input.taskId, "Task id"),
|
|
32
|
+
roleName: requireIdentity(input.roleName, "Role name"),
|
|
33
|
+
agentId: requireIdentity(input.agentId, "Agent id"),
|
|
34
|
+
adapterId: requireAdapter(input.adapterId),
|
|
35
|
+
workspace: canonicalPath(input.workspace),
|
|
36
|
+
...(input.runId === undefined ? {} : { runId: requireIdentity(input.runId, "Run id") }),
|
|
37
|
+
...(input.launchId === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { launchId: requireIdentity(input.launchId, "Launch id") }),
|
|
40
|
+
...(input.nativeSessionId === undefined
|
|
41
|
+
? {}
|
|
42
|
+
: { nativeSessionId: requireIdentity(input.nativeSessionId, "Native session id") })
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function serializeExactDescriptor(descriptor) {
|
|
46
|
+
return JSON.stringify(descriptor);
|
|
47
|
+
}
|
|
48
|
+
export function parseExactControlPlaneDescriptor(value) {
|
|
49
|
+
const record = parseDescriptorRecord(value);
|
|
50
|
+
assertDescriptorKind(record, "yui-control-plane");
|
|
51
|
+
if (record.schemaVersion !== 1) {
|
|
52
|
+
throw new Error("Exact control-plane descriptor schema version is invalid.");
|
|
53
|
+
}
|
|
54
|
+
return createExactControlPlaneDescriptor({
|
|
55
|
+
executable: requireText(record.executable, "Control-plane executable"),
|
|
56
|
+
cliEntry: requireText(record.cliEntry, "Control-plane CLI entry"),
|
|
57
|
+
yuiHome: requireText(record.yuiHome, "Control-plane YUI_HOME"),
|
|
58
|
+
identity: validateVersionIdentity(record.identity)
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function parseExactTaskRuntimeDescriptor(value) {
|
|
62
|
+
const record = parseDescriptorRecord(value);
|
|
63
|
+
assertDescriptorKind(record, "yui-task-runtime");
|
|
64
|
+
if (record.schemaVersion !== 1) {
|
|
65
|
+
throw new Error("Exact Task runtime descriptor schema version is invalid.");
|
|
66
|
+
}
|
|
67
|
+
return createExactTaskRuntimeDescriptor({
|
|
68
|
+
controlPlaneDigest: requireText(record.controlPlaneDigest, "Control-plane digest"),
|
|
69
|
+
taskId: requireText(record.taskId, "Task id"),
|
|
70
|
+
roleName: requireText(record.roleName, "Role name"),
|
|
71
|
+
agentId: requireText(record.agentId, "Agent id"),
|
|
72
|
+
adapterId: requireAdapter(record.adapterId),
|
|
73
|
+
workspace: requireText(record.workspace, "Task runtime workspace"),
|
|
74
|
+
...(record.runId === undefined ? {} : { runId: requireText(record.runId, "Run id") }),
|
|
75
|
+
...(record.launchId === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: { launchId: requireText(record.launchId, "Launch id") }),
|
|
78
|
+
...(record.nativeSessionId === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: { nativeSessionId: requireText(record.nativeSessionId, "Native session id") })
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function exactControlPlaneDigest(descriptor) {
|
|
84
|
+
return createHash("sha256").update(serializeExactDescriptor(descriptor)).digest("hex");
|
|
85
|
+
}
|
|
86
|
+
/** Stable per Task/Role/Agent location; volatile Run/launch/native fields are content. */
|
|
87
|
+
export function exactTaskRuntimeDescriptorPath(home, descriptor) {
|
|
88
|
+
const validated = createExactTaskRuntimeDescriptor(descriptor);
|
|
89
|
+
const digest = createHash("sha256").update(JSON.stringify([
|
|
90
|
+
validated.controlPlaneDigest,
|
|
91
|
+
validated.taskId,
|
|
92
|
+
validated.roleName,
|
|
93
|
+
validated.agentId,
|
|
94
|
+
validated.adapterId,
|
|
95
|
+
validated.workspace
|
|
96
|
+
])).digest("hex");
|
|
97
|
+
return join(canonicalPath(home), "runtime", "exact-task-runtime", `${digest}.json`);
|
|
98
|
+
}
|
|
99
|
+
export function readExactTaskRuntimeDescriptorSource(source, home) {
|
|
100
|
+
return readTaskRuntimeSource(source, home).descriptor;
|
|
101
|
+
}
|
|
102
|
+
export function exactControlPlaneCommandPrefix(descriptor) {
|
|
103
|
+
return [
|
|
104
|
+
descriptor.executable,
|
|
105
|
+
descriptor.cliEntry,
|
|
106
|
+
EXACT_CONTROL_ARGUMENT,
|
|
107
|
+
exactControlPlaneDigest(descriptor)
|
|
108
|
+
].map(shellQuote).join(" ");
|
|
109
|
+
}
|
|
110
|
+
export function extractExactControlArgument(args) {
|
|
111
|
+
const later = args.indexOf(EXACT_CONTROL_ARGUMENT);
|
|
112
|
+
if (later < 0)
|
|
113
|
+
return { args: [...args] };
|
|
114
|
+
if (later !== 0) {
|
|
115
|
+
return {
|
|
116
|
+
args: [...args],
|
|
117
|
+
error: `${EXACT_CONTROL_ARGUMENT} must be the first CLI argument.`
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const digest = args[1];
|
|
121
|
+
if (digest === undefined || !/^[a-f0-9]{64}$/u.test(digest)) {
|
|
122
|
+
return {
|
|
123
|
+
args: args.slice(2),
|
|
124
|
+
error: "Exact control-plane digest is invalid."
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return { digest, args: args.slice(2) };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* One read-only gate shared by every managed Task control command. It verifies
|
|
131
|
+
* the frozen executable/CLI/Home/digest, protocol and scalar-storage identity,
|
|
132
|
+
* on-disk schema, and any live Controller before command routing may construct
|
|
133
|
+
* a writable store. Package version alone may advance at the same managed path
|
|
134
|
+
* so an existing Session can cross an explicitly compatible in-place update.
|
|
135
|
+
*/
|
|
136
|
+
export async function assertExactControlPlanePreflight(input, options = {}) {
|
|
137
|
+
const descriptor = parseExactControlPlaneDescriptor(input.serializedDescriptor);
|
|
138
|
+
if (exactControlPlaneDigest(descriptor) !== requireDigest(input.digest)) {
|
|
139
|
+
throw new Error("Exact control-plane digest does not match its frozen descriptor.");
|
|
140
|
+
}
|
|
141
|
+
assertSamePath(descriptor.executable, input.actualExecutable, "Control-plane executable");
|
|
142
|
+
assertSamePath(descriptor.cliEntry, input.actualCliEntry, "Control-plane CLI entry");
|
|
143
|
+
assertSamePath(descriptor.yuiHome, input.actualHome, "Control-plane YUI_HOME");
|
|
144
|
+
const localIdentity = validateVersionIdentity(options.identity ?? yuiVersionIdentity());
|
|
145
|
+
assertContinuityIdentity("Local CLI", descriptor.identity, localIdentity);
|
|
146
|
+
const storage = (options.inspectStorage ?? inspectStorageSchema)(descriptor.yuiHome);
|
|
147
|
+
if (storage.status !== "current") {
|
|
148
|
+
const exactRecordOnlyOlder = storage.status === "unsupported"
|
|
149
|
+
&& storage.incompatibleComponent === "record"
|
|
150
|
+
&& storage.direction === "older"
|
|
151
|
+
&& storage.currentLayoutVersion === descriptor.identity.storageLayoutVersion
|
|
152
|
+
&& storage.currentAggregateSchemaVersion === descriptor.identity.aggregateSchemaVersion;
|
|
153
|
+
if (!exactRecordOnlyOlder) {
|
|
154
|
+
throw new Error(`Exact control-plane storage is not current: ${storage.status}.`);
|
|
155
|
+
}
|
|
156
|
+
(options.openCompatibleStore ?? openCompatibleFileTaskStore)(descriptor.yuiHome).getConfig();
|
|
157
|
+
}
|
|
158
|
+
if (storage.currentLayoutVersion !== descriptor.identity.storageLayoutVersion) {
|
|
159
|
+
throw new Error("Exact control-plane storage layout does not match its frozen descriptor "
|
|
160
|
+
+ `(expected ${descriptor.identity.storageLayoutVersion}, found `
|
|
161
|
+
+ `${storage.currentLayoutVersion ?? "unknown"}).`);
|
|
162
|
+
}
|
|
163
|
+
if (storage.currentAggregateSchemaVersion !== descriptor.identity.aggregateSchemaVersion) {
|
|
164
|
+
throw new Error("Exact control-plane aggregate schema does not match its frozen descriptor "
|
|
165
|
+
+ `(expected ${descriptor.identity.aggregateSchemaVersion}, found `
|
|
166
|
+
+ `${storage.currentAggregateSchemaVersion ?? "unknown"}).`);
|
|
167
|
+
}
|
|
168
|
+
if (options.checkController !== false) {
|
|
169
|
+
const call = options.callController ?? defaultCallController;
|
|
170
|
+
try {
|
|
171
|
+
const status = await call(descriptor.yuiHome, "controller.status", {});
|
|
172
|
+
assertControllerContinuityIdentity(status, descriptor.identity);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (!isDefinitelyNotRunning(error))
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return descriptor;
|
|
180
|
+
}
|
|
181
|
+
export function assertControllerStatusIdentity(status, expected = yuiVersionIdentity()) {
|
|
182
|
+
if (!isRecord(status) || status.running !== true) {
|
|
183
|
+
throw new Error("Controller status does not describe a running Controller.");
|
|
184
|
+
}
|
|
185
|
+
assertControllerField(status.protocolVersion, expected.controllerProtocolVersion, "protocol");
|
|
186
|
+
assertControllerField(status.version, expected.version, "version");
|
|
187
|
+
assertControllerField(status.storageLayoutVersion, expected.storageLayoutVersion, "storage layout");
|
|
188
|
+
assertControllerField(status.aggregateSchemaVersion, expected.aggregateSchemaVersion, "aggregate schema");
|
|
189
|
+
}
|
|
190
|
+
export function assertExactTaskRuntimeEnvironment(runtimeSource, environment, expectedControlDigest, expectedHome = requireText(environment.YUI_HOME, "YUI_HOME")) {
|
|
191
|
+
const resolved = readTaskRuntimeSource(runtimeSource, expectedHome);
|
|
192
|
+
const runtime = resolved.descriptor;
|
|
193
|
+
if (runtime.controlPlaneDigest !== requireDigest(expectedControlDigest)) {
|
|
194
|
+
throw new Error("Task runtime descriptor belongs to another exact control plane.");
|
|
195
|
+
}
|
|
196
|
+
assertEnvironment(runtime.taskId, environment.YUI_TASK_ID, "Task id");
|
|
197
|
+
assertEnvironment(runtime.roleName, environment.YUI_ROLE, "Role name");
|
|
198
|
+
assertEnvironment(runtime.agentId, environment.YUI_AGENT_ID, "Agent id");
|
|
199
|
+
assertEnvironment(runtime.adapterId, environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
200
|
+
assertSamePath(runtime.workspace, requireText(environment.YUI_WORKSPACE, "YUI_WORKSPACE"), "Task runtime workspace");
|
|
201
|
+
// A reused native pane retains its original process environment. Volatile
|
|
202
|
+
// identity therefore comes only from the atomically published descriptor
|
|
203
|
+
// plus durable state, never from stale ambient Run/launch/native variables.
|
|
204
|
+
if (!resolved.fromFile) {
|
|
205
|
+
assertOptionalEnvironment(runtime.runId, environment.YUI_RUN_ID, "Run id");
|
|
206
|
+
assertOptionalEnvironment(runtime.launchId, environment.YUI_LAUNCH_ID, "Launch id");
|
|
207
|
+
assertOptionalEnvironment(runtime.nativeSessionId, environment.YUI_NATIVE_SESSION_ID, "Native session id");
|
|
208
|
+
}
|
|
209
|
+
return runtime;
|
|
210
|
+
}
|
|
211
|
+
/** Fences a descriptor to the one currently active durable Task runtime. */
|
|
212
|
+
export function assertExactTaskRuntimeState(runtime, store, options = {}) {
|
|
213
|
+
const task = store.getTask(runtime.taskId);
|
|
214
|
+
if (task === null || task.status !== "active") {
|
|
215
|
+
throw new Error("Exact Task runtime Task is not current and active.");
|
|
216
|
+
}
|
|
217
|
+
const role = store.getRole(runtime.taskId, runtime.roleName);
|
|
218
|
+
if (role === null || role.activeAgentId !== runtime.agentId) {
|
|
219
|
+
throw new Error("Exact Task runtime Role or Agent is not current.");
|
|
220
|
+
}
|
|
221
|
+
const run = store.getActiveAgentRun(runtime.taskId, runtime.roleName);
|
|
222
|
+
if (runtime.runId === undefined) {
|
|
223
|
+
if (run !== null)
|
|
224
|
+
throw new Error("Exact Task runtime Run is not current.");
|
|
225
|
+
}
|
|
226
|
+
else if (run === null
|
|
227
|
+
|| run.id !== runtime.runId
|
|
228
|
+
|| run.status !== "active"
|
|
229
|
+
|| run.effective.agentId !== runtime.agentId
|
|
230
|
+
|| run.effective.adapterId !== runtime.adapterId
|
|
231
|
+
|| canonicalPath(run.effective.workspace.root) !== runtime.workspace) {
|
|
232
|
+
throw new Error("Exact Task runtime Run is not current.");
|
|
233
|
+
}
|
|
234
|
+
const sessions = store.getTaskRoleSessionSet(runtime.taskId, runtime.roleName);
|
|
235
|
+
const session = sessions?.sessions[runtime.agentId];
|
|
236
|
+
if (sessions !== null && sessions.activeAgentId !== runtime.agentId) {
|
|
237
|
+
throw new Error("Exact Task runtime Session Agent is not current.");
|
|
238
|
+
}
|
|
239
|
+
const lifecycleMailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
240
|
+
scope: "task",
|
|
241
|
+
taskId: runtime.taskId,
|
|
242
|
+
roleName: runtime.roleName
|
|
243
|
+
}));
|
|
244
|
+
const reservation = runtime.launchId !== undefined
|
|
245
|
+
&& isRuntimeLaunchReservation(lifecycleMailbox?.processing, runtime.launchId);
|
|
246
|
+
const sessionLaunch = runtime.launchId !== undefined
|
|
247
|
+
&& session?.launchId === runtime.launchId;
|
|
248
|
+
const executionRef = lifecycleMailbox?.processing?.executionRef;
|
|
249
|
+
const preallocated = options.preallocatedNativeSessionReservation;
|
|
250
|
+
const exactPreallocatedReservation = preallocated !== undefined
|
|
251
|
+
&& runtime.adapterId === "claude"
|
|
252
|
+
&& runtime.runId !== undefined
|
|
253
|
+
&& runtime.launchId !== undefined
|
|
254
|
+
&& runtime.nativeSessionId !== undefined
|
|
255
|
+
&& session === undefined
|
|
256
|
+
&& reservation
|
|
257
|
+
&& !hasRuntimeCleanupObligation(lifecycleMailbox)
|
|
258
|
+
&& executionRef?.type === "run"
|
|
259
|
+
&& executionRef.taskId === runtime.taskId
|
|
260
|
+
&& executionRef.id === runtime.runId
|
|
261
|
+
&& runtime.nativeSessionId === nativeSessionIdForLaunch(preallocated.yuiHome, runtime.launchId, runtime.agentId, runtime.adapterId);
|
|
262
|
+
const preallocatedBeforeInFlightProjection = exactPreallocatedReservation
|
|
263
|
+
&& (sessions?.inFlight === null || sessions?.inFlight === undefined);
|
|
264
|
+
if (runtime.runId !== undefined && (sessions?.inFlight?.agentId !== runtime.agentId
|
|
265
|
+
|| sessions.inFlight.runId !== runtime.runId
|
|
266
|
+
|| sessions.inFlight.receiptId !== formatAgentRunReceiptId(runtime.taskId, runtime.runId)) && !preallocatedBeforeInFlightProjection) {
|
|
267
|
+
throw new Error("Exact Task runtime in-flight Run fence is not current.");
|
|
268
|
+
}
|
|
269
|
+
if (runtime.launchId === undefined || (!reservation && !sessionLaunch)) {
|
|
270
|
+
throw new Error("Exact Task runtime launch fence is not current.");
|
|
271
|
+
}
|
|
272
|
+
if (runtime.nativeSessionId === undefined) {
|
|
273
|
+
if (session !== undefined) {
|
|
274
|
+
throw new Error("Exact Task runtime native Session fence is missing.");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else if (session === undefined) {
|
|
278
|
+
if (!exactPreallocatedReservation) {
|
|
279
|
+
throw new Error("Exact Task runtime native Session fence is not current.");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (session.agentId !== runtime.agentId
|
|
283
|
+
|| session.adapterId !== runtime.adapterId
|
|
284
|
+
|| session.nativeSessionId !== runtime.nativeSessionId
|
|
285
|
+
|| session.launchId !== runtime.launchId
|
|
286
|
+
|| session.status === "stopped"
|
|
287
|
+
|| session.status === "broken"
|
|
288
|
+
|| session.effective.agentId !== runtime.agentId
|
|
289
|
+
|| session.effective.adapterId !== runtime.adapterId
|
|
290
|
+
|| canonicalPath(session.effective.workspace.root) !== runtime.workspace) {
|
|
291
|
+
throw new Error("Exact Task runtime native Session fence is not current.");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/** Publishes provider-discovered native identity without changing the stable source path. */
|
|
295
|
+
export function refreshExactTaskRuntimeDescriptorSource(source, home, store) {
|
|
296
|
+
const resolved = readTaskRuntimeSource(source, home);
|
|
297
|
+
if (!resolved.fromFile) {
|
|
298
|
+
throw new Error("A managed Task runtime descriptor must use its stable file source.");
|
|
299
|
+
}
|
|
300
|
+
const current = resolved.descriptor;
|
|
301
|
+
const session = store.getTaskRoleSessionSet(current.taskId, current.roleName)
|
|
302
|
+
?.sessions[current.agentId];
|
|
303
|
+
if (session === undefined) {
|
|
304
|
+
throw new Error("Exact Task runtime native Session is not available for refresh.");
|
|
305
|
+
}
|
|
306
|
+
const refreshed = createExactTaskRuntimeDescriptor({
|
|
307
|
+
...current,
|
|
308
|
+
nativeSessionId: session.nativeSessionId
|
|
309
|
+
});
|
|
310
|
+
if (exactTaskRuntimeDescriptorPath(home, refreshed) !== resolve(source)) {
|
|
311
|
+
throw new Error("Refreshed Task runtime descriptor changed its stable identity.");
|
|
312
|
+
}
|
|
313
|
+
assertExactTaskRuntimeState(refreshed, store);
|
|
314
|
+
writeTextFileAtomically(resolve(source), `${serializeExactDescriptor(refreshed)}\n`);
|
|
315
|
+
return refreshed;
|
|
316
|
+
}
|
|
317
|
+
function readTaskRuntimeSource(source, home) {
|
|
318
|
+
const value = requireText(source, "Exact Task runtime descriptor source");
|
|
319
|
+
if (value.trimStart().startsWith("{")) {
|
|
320
|
+
return { descriptor: parseExactTaskRuntimeDescriptor(value), fromFile: false };
|
|
321
|
+
}
|
|
322
|
+
const requested = resolve(value);
|
|
323
|
+
const root = resolve(canonicalPath(home), "runtime", "exact-task-runtime");
|
|
324
|
+
if (dirname(requested) !== root) {
|
|
325
|
+
throw new Error("Exact Task runtime descriptor path is outside its control plane.");
|
|
326
|
+
}
|
|
327
|
+
let actual;
|
|
328
|
+
try {
|
|
329
|
+
actual = realpathSync(requested);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
throw new Error("Exact Task runtime descriptor file is unavailable.", { cause: error });
|
|
333
|
+
}
|
|
334
|
+
if (actual !== requested) {
|
|
335
|
+
throw new Error("Exact Task runtime descriptor file must not be a symbolic link.");
|
|
336
|
+
}
|
|
337
|
+
const descriptor = parseExactTaskRuntimeDescriptor(readFileSync(actual, "utf8"));
|
|
338
|
+
if (exactTaskRuntimeDescriptorPath(home, descriptor) !== requested) {
|
|
339
|
+
throw new Error("Exact Task runtime descriptor path does not match its stable identity.");
|
|
340
|
+
}
|
|
341
|
+
return { descriptor, fromFile: true };
|
|
342
|
+
}
|
|
343
|
+
function validateVersionIdentity(value) {
|
|
344
|
+
if (!isRecord(value))
|
|
345
|
+
throw new Error("Yui version identity is invalid.");
|
|
346
|
+
const version = requireText(value.version, "Yui version");
|
|
347
|
+
const controllerProtocolVersion = requireVersion(value.controllerProtocolVersion, "Controller protocol version");
|
|
348
|
+
const storageLayoutVersion = requireVersion(value.storageLayoutVersion, "Storage layout version");
|
|
349
|
+
const aggregateSchemaVersion = requireVersion(value.aggregateSchemaVersion, "Aggregate schema version");
|
|
350
|
+
return {
|
|
351
|
+
version,
|
|
352
|
+
controllerProtocolVersion,
|
|
353
|
+
storageLayoutVersion,
|
|
354
|
+
aggregateSchemaVersion
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/** Managed continuity is a protocol/storage contract, not a package pin. */
|
|
358
|
+
function assertContinuityIdentity(label, expected, actual) {
|
|
359
|
+
for (const field of [
|
|
360
|
+
"controllerProtocolVersion",
|
|
361
|
+
"storageLayoutVersion",
|
|
362
|
+
"aggregateSchemaVersion"
|
|
363
|
+
]) {
|
|
364
|
+
if (expected[field] !== actual[field]) {
|
|
365
|
+
throw new Error(`${label} ${field} does not match the frozen control plane `
|
|
366
|
+
+ `(expected ${expected[field]}, found ${actual[field]}).`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function assertControllerContinuityIdentity(status, expected) {
|
|
371
|
+
if (!isRecord(status) || status.running !== true) {
|
|
372
|
+
throw new Error("Controller status does not describe a running Controller.");
|
|
373
|
+
}
|
|
374
|
+
if (typeof status.version !== "string" || status.version.trim().length === 0) {
|
|
375
|
+
throw new Error("Controller version is invalid at the managed continuity gate.");
|
|
376
|
+
}
|
|
377
|
+
assertControllerField(status.protocolVersion, expected.controllerProtocolVersion, "protocol");
|
|
378
|
+
assertControllerField(status.storageLayoutVersion, expected.storageLayoutVersion, "storage layout");
|
|
379
|
+
assertControllerField(status.aggregateSchemaVersion, expected.aggregateSchemaVersion, "aggregate schema");
|
|
380
|
+
}
|
|
381
|
+
function assertControllerField(actual, expected, label) {
|
|
382
|
+
if (actual !== expected) {
|
|
383
|
+
throw new Error(`Controller ${label} is incompatible with the exact control plane `
|
|
384
|
+
+ `(expected ${expected}, found ${typeof actual === "string" || typeof actual === "number" ? actual : "unknown"}). `
|
|
385
|
+
+ "Run controller restart through the matching exact control-plane invocation "
|
|
386
|
+
+ "before writing new Task records.");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function assertSamePath(expected, actual, label) {
|
|
390
|
+
const normalized = canonicalPath(actual);
|
|
391
|
+
if (expected !== normalized) {
|
|
392
|
+
throw new Error(`${label} does not match the frozen control plane (expected ${expected}, found ${normalized}).`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function assertEnvironment(expected, actual, label) {
|
|
396
|
+
if (actual !== expected) {
|
|
397
|
+
throw new Error(`${label} does not match the exact Task runtime descriptor.`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function assertOptionalEnvironment(expected, actual, label) {
|
|
401
|
+
if (actual !== expected) {
|
|
402
|
+
throw new Error(`${label} does not match the exact Task runtime descriptor.`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function parseDescriptorRecord(value) {
|
|
406
|
+
let parsed;
|
|
407
|
+
try {
|
|
408
|
+
parsed = JSON.parse(requireText(value, "Exact descriptor"));
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
throw new Error("Exact descriptor is not valid JSON.", { cause: error });
|
|
412
|
+
}
|
|
413
|
+
if (!isRecord(parsed))
|
|
414
|
+
throw new Error("Exact descriptor must be an object.");
|
|
415
|
+
return parsed;
|
|
416
|
+
}
|
|
417
|
+
function assertDescriptorKind(value, expected) {
|
|
418
|
+
if (value.kind !== expected) {
|
|
419
|
+
throw new Error(`Exact descriptor kind is invalid: expected ${expected}, found `
|
|
420
|
+
+ `${typeof value.kind === "string" ? value.kind : "unknown"}.`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function requireAdapter(value) {
|
|
424
|
+
if (value !== "codex" && value !== "claude") {
|
|
425
|
+
throw new Error("Agent adapter id is invalid.");
|
|
426
|
+
}
|
|
427
|
+
return value;
|
|
428
|
+
}
|
|
429
|
+
function requireIdentity(value, label) {
|
|
430
|
+
const text = requireText(value, label).trim();
|
|
431
|
+
if (text.length === 0 || text.length > 1_024 || text.includes("\0")) {
|
|
432
|
+
throw new Error(`${label} is invalid.`);
|
|
433
|
+
}
|
|
434
|
+
return text;
|
|
435
|
+
}
|
|
436
|
+
function requireText(value, label) {
|
|
437
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
|
|
438
|
+
throw new Error(`${label} is invalid.`);
|
|
439
|
+
}
|
|
440
|
+
return value;
|
|
441
|
+
}
|
|
442
|
+
function requireDigest(value) {
|
|
443
|
+
const digest = requireText(value, "Control-plane digest");
|
|
444
|
+
if (!/^[a-f0-9]{64}$/u.test(digest)) {
|
|
445
|
+
throw new Error("Control-plane digest is invalid.");
|
|
446
|
+
}
|
|
447
|
+
return digest;
|
|
448
|
+
}
|
|
449
|
+
function requireVersion(value, label) {
|
|
450
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
451
|
+
throw new Error(`${label} is invalid.`);
|
|
452
|
+
}
|
|
453
|
+
return value;
|
|
454
|
+
}
|
|
455
|
+
function canonicalPath(value) {
|
|
456
|
+
const absolute = resolve(requireText(value, "Path"));
|
|
457
|
+
try {
|
|
458
|
+
return realpathSync(absolute);
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return absolute;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function shellQuote(value) {
|
|
465
|
+
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
466
|
+
}
|
|
467
|
+
function isRecord(value) {
|
|
468
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
469
|
+
}
|
|
470
|
+
function isDefinitelyNotRunning(error) {
|
|
471
|
+
return isRecord(error) && error.code === "CONTROLLER_NOT_RUNNING";
|
|
472
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createPromptEnvelope } from "./promptEnvelope.js";
|
|
2
|
+
export { createRuntimeBinding } from "./runtimeBinding.js";
|
|
3
|
+
export { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
4
|
+
export { createSessionLaunchRequest } from "./sessionLaunchRequest.js";
|
|
5
|
+
export { createPendingTurnCompletion, DEFAULT_RECENT_TURN_ID_LIMIT, hasRecentTurnId, rememberRecentTurnId, validatePendingTurnCompletion, validateRecentTurnIds } from "./turnCompletion.js";
|
|
6
|
+
export { RuntimeLaunchError } from "./ports.js";
|
|
7
|
+
export { TmuxPromptPushAdapter, TmuxSessionHost } from "./tmuxAdapters.js";
|
|
8
|
+
export { FileTaskRuntimeIsolation, YUI_TASK_RUNTIME_ISOLATION_DESCRIPTOR, YUI_TASK_RUNTIME_SERVICE_NAMESPACE, assertTaskRuntimeIsolationPreflight, createTaskRuntimeIsolationDescriptor, parseTaskRuntimeIsolationDescriptor, planTaskRuntimeCleanup, taskRuntimeIsolationEnvironment, taskRuntimeIsolationFingerprint } from "./taskRuntimeIsolation.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const RUNTIME_LIFECYCLE_OWNER = "runtime-lifecycle";
|
|
2
|
+
export const RUNTIME_LAUNCH_RESERVED_REASON = "runtime-launch-reserved";
|
|
3
|
+
export const RUNTIME_CLEANUP_REQUIRED_REASON = "runtime-cleanup-required";
|
|
4
|
+
export function runtimeLifecycleTarget(owner) {
|
|
5
|
+
return owner.scope === "task"
|
|
6
|
+
? {
|
|
7
|
+
kind: "role-runtime",
|
|
8
|
+
taskId: owner.taskId,
|
|
9
|
+
roleName: owner.roleName
|
|
10
|
+
}
|
|
11
|
+
: {
|
|
12
|
+
kind: "global-role-runtime",
|
|
13
|
+
roleName: owner.roleName
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function runtimeLifecycleSignalKey(owner) {
|
|
17
|
+
return owner.scope === "task"
|
|
18
|
+
? `role:${encodeURIComponent(owner.taskId)}/${encodeURIComponent(owner.roleName)}`
|
|
19
|
+
: `global-role:${encodeURIComponent(owner.roleName)}`;
|
|
20
|
+
}
|
|
21
|
+
export function isRuntimeLaunchReservation(processing, launchId) {
|
|
22
|
+
return processing?.owner === RUNTIME_LIFECYCLE_OWNER
|
|
23
|
+
&& processing.batch.reasons.length === 1
|
|
24
|
+
&& processing.batch.reasons[0] === RUNTIME_LAUNCH_RESERVED_REASON
|
|
25
|
+
&& (launchId === undefined || processing.batchId === launchId);
|
|
26
|
+
}
|
|
27
|
+
export function hasRuntimeLaunchReservation(mailbox) {
|
|
28
|
+
return isRuntimeLaunchReservation(mailbox?.processing);
|
|
29
|
+
}
|
|
30
|
+
export function hasRuntimeCleanupObligation(mailbox) {
|
|
31
|
+
return mailbox?.pending?.reasons.includes(RUNTIME_CLEANUP_REQUIRED_REASON) === true
|
|
32
|
+
|| (!isRuntimeLaunchReservation(mailbox?.processing)
|
|
33
|
+
&& mailbox?.processing?.batch.reasons.includes(RUNTIME_CLEANUP_REQUIRED_REASON) === true);
|
|
34
|
+
}
|
|
35
|
+
export function hasRuntimeLifecycleWork(mailbox) {
|
|
36
|
+
return mailbox !== null
|
|
37
|
+
&& (mailbox.processing !== null || mailbox.pending !== null);
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A persisted launch is either temporarily unavailable or permanently lost. */
|
|
2
|
+
export class RuntimeLaunchError extends Error {
|
|
3
|
+
retryable;
|
|
4
|
+
launchId;
|
|
5
|
+
name = "RuntimeLaunchError";
|
|
6
|
+
constructor(retryable, launchId, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.retryable = retryable;
|
|
9
|
+
this.launchId = launchId;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { requireText } from "./validation.js";
|
|
4
|
+
/** Deterministic native identity preallocated for one exact provider launch. */
|
|
5
|
+
export function nativeSessionIdForLaunch(home, launchId, agentId, adapterId) {
|
|
6
|
+
const hex = createHash("sha256").update(JSON.stringify([
|
|
7
|
+
resolve(requireText(home, "YUI_HOME")),
|
|
8
|
+
requireText(launchId, "Launch id"),
|
|
9
|
+
requireText(agentId, "Agent id"),
|
|
10
|
+
requireText(adapterId, "Agent adapter id")
|
|
11
|
+
])).digest("hex");
|
|
12
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { requireText, requireTimestamp } from "./validation.js";
|
|
2
|
+
import { formatAgentRunReceiptId, formatInputRequestReceiptId, validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
3
|
+
export function createPromptEnvelope(input) {
|
|
4
|
+
if (input.source.kind !== "agent-run" && input.source.kind !== "input-request") {
|
|
5
|
+
throw new Error("Prompt source kind is invalid.");
|
|
6
|
+
}
|
|
7
|
+
const source = validateTaskRecordReference({
|
|
8
|
+
taskId: input.source.taskId,
|
|
9
|
+
localId: input.source.localId
|
|
10
|
+
}, input.source.kind === "agent-run" ? "agentRun" : "inputRequest");
|
|
11
|
+
const id = requireQualifiedReceiptId(input.id, input.source.kind, source.taskId, source.localId);
|
|
12
|
+
return {
|
|
13
|
+
id,
|
|
14
|
+
source: {
|
|
15
|
+
kind: input.source.kind,
|
|
16
|
+
taskId: source.taskId,
|
|
17
|
+
localId: source.localId
|
|
18
|
+
},
|
|
19
|
+
text: requireText(input.text, "Prompt text"),
|
|
20
|
+
createdAt: requireTimestamp(input.createdAt, "Prompt createdAt")
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function requireQualifiedReceiptId(value, kind, taskId, localId) {
|
|
24
|
+
const expected = kind === "agent-run"
|
|
25
|
+
? formatAgentRunReceiptId(taskId, localId)
|
|
26
|
+
: formatInputRequestReceiptId(taskId, localId);
|
|
27
|
+
if (value !== expected)
|
|
28
|
+
throw new Error("Prompt envelope id does not match its source.");
|
|
29
|
+
return expected;
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
2
|
+
import { requireSafeIdentity, requireText } from "./validation.js";
|
|
3
|
+
export function createRuntimeBinding(input) {
|
|
4
|
+
const hostCreated = input.hostCreated === undefined
|
|
5
|
+
? undefined
|
|
6
|
+
: requireBoolean(input.hostCreated, "Runtime host-created flag");
|
|
7
|
+
const initialPromptRunId = input.initialPromptRunId === undefined
|
|
8
|
+
? undefined
|
|
9
|
+
: requireSafeIdentity(input.initialPromptRunId, "Initial prompt Run id");
|
|
10
|
+
if (initialPromptRunId !== undefined && hostCreated !== true) {
|
|
11
|
+
throw new TypeError("An initial prompt Run id requires a newly-created runtime host.");
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
id: requireSafeIdentity(input.id, "Runtime binding id"),
|
|
15
|
+
launchId: requireSafeIdentity(input.launchId, "Launch id"),
|
|
16
|
+
owner: normalizeRuntimeOwner(input.owner),
|
|
17
|
+
agentId: requireSafeIdentity(input.agentId, "Agent id"),
|
|
18
|
+
adapterId: requireSafeIdentity(input.adapterId, "Agent adapter id"),
|
|
19
|
+
hostRef: requireText(input.hostRef, "Session host reference"),
|
|
20
|
+
...(hostCreated === undefined ? {} : { hostCreated }),
|
|
21
|
+
...(initialPromptRunId === undefined ? {} : { initialPromptRunId }),
|
|
22
|
+
...(input.nativeSessionId === undefined
|
|
23
|
+
? {}
|
|
24
|
+
: { nativeSessionId: requireText(input.nativeSessionId, "Native session id") })
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function requireBoolean(value, label) {
|
|
28
|
+
if (typeof value !== "boolean")
|
|
29
|
+
throw new TypeError(`${label} must be boolean.`);
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { requireSafeIdentity } from "./validation.js";
|
|
2
|
+
export function normalizeRuntimeOwner(owner) {
|
|
3
|
+
const roleName = requireSafeIdentity(owner.roleName, "Role name");
|
|
4
|
+
if (owner.scope === "global")
|
|
5
|
+
return { scope: "global", roleName };
|
|
6
|
+
if (owner.scope === "task") {
|
|
7
|
+
return {
|
|
8
|
+
scope: "task",
|
|
9
|
+
taskId: requireSafeIdentity(owner.taskId, "Task id"),
|
|
10
|
+
roleName
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
throw new Error("Runtime owner scope is invalid.");
|
|
14
|
+
}
|