@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
|
@@ -1,31 +1,650 @@
|
|
|
1
1
|
import { reconciliationIntervalMilliseconds } from "../config/yuiConfig.js";
|
|
2
|
-
import {
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
|
+
import { controllerSocketPath } from "../core/controllerEndpoint.js";
|
|
6
|
+
import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
|
|
7
|
+
import { hasRuntimeCleanupObligation, runtimeLifecycleSignalKey, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
8
|
+
import { agentProcessReadinessProbe, ExecutorRegistry } from "../executor/executorRegistry.js";
|
|
9
|
+
import { activeLiveRoleAgentSession, roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
|
|
10
|
+
import { effectiveLaunchSnapshotsCompatible, resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
11
|
+
import { isTaskOwnedWorkspace } from "../worktree/managedWorkspace.js";
|
|
3
12
|
import { FileRoleLaunchPlanner } from "../executor/fileRoleLaunchPlanner.js";
|
|
4
|
-
import {
|
|
13
|
+
import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
5
14
|
import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
|
|
6
15
|
import { NodeCommandExecutor } from "../tmux/commandExecutor.js";
|
|
7
|
-
import { TmuxManager } from "../tmux/tmuxManager.js";
|
|
16
|
+
import { TmuxManager, yuiTmuxServerName } from "../tmux/tmuxManager.js";
|
|
17
|
+
import { FileTaskRuntimeIsolation, TmuxPromptPushAdapter, TmuxSessionHost } from "../runtime/index.js";
|
|
8
18
|
import { startFileTaskController } from "./controller.js";
|
|
9
19
|
import { FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
|
|
10
|
-
import {
|
|
20
|
+
import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
|
|
21
|
+
import { FileRuntimeEventProcessor } from "./runtimeEventProcessor.js";
|
|
22
|
+
import { RuntimeLaunchCoordinator } from "./runtimeLaunchCoordinator.js";
|
|
23
|
+
import { ephemeralDomainFromEnvironment, recordEphemeralTmuxTarget } from "./domainIdentity.js";
|
|
24
|
+
import { createEphemeralResourceReaper } from "./ephemeralResourceReaper.js";
|
|
25
|
+
import { scanControllerResourceInventory } from "./resourceInventoryLinux.js";
|
|
26
|
+
import { createRuntimeResourceActivityTracker } from "./resourceInventory.js";
|
|
11
27
|
/** Production composition root for the lean FileTaskStore + tmux Controller. */
|
|
12
28
|
export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
13
|
-
const store = options.store ??
|
|
29
|
+
const store = options.store ?? openCompatibleFileTaskStore(home);
|
|
14
30
|
const schedulerStore = options.schedulerStore ?? new FileSchedulerStoreAdapter(store);
|
|
31
|
+
const domainIdentity = options.domainIdentity
|
|
32
|
+
?? ephemeralDomainFromEnvironment(options.environment ?? process.env);
|
|
15
33
|
const planner = options.planner ?? new FileRoleLaunchPlanner(home, store, {
|
|
16
34
|
environment: options.environment
|
|
17
35
|
});
|
|
18
|
-
const tmux = options.tmux ?? new TmuxManager(options.environment?.YUI_TMUX_BIN ?? process.env.YUI_TMUX_BIN ?? "tmux", new NodeCommandExecutor(), {
|
|
19
|
-
|
|
36
|
+
const tmux = options.tmux ?? new TmuxManager(options.environment?.YUI_TMUX_BIN ?? process.env.YUI_TMUX_BIN ?? "tmux", new NodeCommandExecutor(), {
|
|
37
|
+
yuiHome: home,
|
|
38
|
+
...(domainIdentity === undefined
|
|
39
|
+
? {}
|
|
40
|
+
: {
|
|
41
|
+
onRoleTargetRecorded: (target) => {
|
|
42
|
+
if (!recordEphemeralTmuxTarget(home, domainIdentity.token, target)) {
|
|
43
|
+
throw new Error(`Ephemeral tmux target fence could not be recorded: ${target}.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
const sessionHost = options.sessionHost ?? new TmuxSessionHost(planner, tmux);
|
|
49
|
+
const promptPush = options.promptPush
|
|
50
|
+
?? new TmuxPromptPushAdapter(tmux, agentProcessReadinessProbe);
|
|
51
|
+
const runtimeIsolation = options.runtimeIsolation
|
|
52
|
+
?? new FileTaskRuntimeIsolation({
|
|
53
|
+
// A sibling of the exact control Home keeps provider data/cache/tmp out
|
|
54
|
+
// of both the shared control plane and every managed Git workspace.
|
|
55
|
+
runtimeRoot: `${resolve(home)}.task-runtimes`,
|
|
56
|
+
controlPlane: {
|
|
57
|
+
yuiHome: home,
|
|
58
|
+
controllerSocketPath: controllerSocketPath(home),
|
|
59
|
+
tmuxNamespace: yuiTmuxServerName(home),
|
|
60
|
+
globalInstallPaths: [process.execPath]
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const lifecycleHost = {
|
|
64
|
+
inspectOwner: (owner) => (sessionHost.inspectOwner(owner)),
|
|
65
|
+
...(sessionHost.inspectOwners === undefined
|
|
66
|
+
? {}
|
|
67
|
+
: {
|
|
68
|
+
inspectOwners: (owners) => sessionHost.inspectOwners(owners)
|
|
69
|
+
}),
|
|
70
|
+
stopOwner: (owner) => (sessionHost.stopOwner(owner)),
|
|
71
|
+
...(runtimeIsolation.cleanupTaskLaunch === undefined
|
|
72
|
+
? {}
|
|
73
|
+
: {
|
|
74
|
+
cleanupTaskLaunch: (input) => runtimeIsolation.cleanupTaskLaunch(input)
|
|
75
|
+
})
|
|
76
|
+
};
|
|
77
|
+
const resourceActivity = createRuntimeResourceActivityTracker();
|
|
78
|
+
let runningRuntime;
|
|
79
|
+
let runningController;
|
|
80
|
+
const signalRuntimeCleanup = (target) => {
|
|
81
|
+
runningRuntime?.signal(runtimeLifecycleSignalKey(target.kind === "role-runtime"
|
|
82
|
+
? {
|
|
83
|
+
scope: "task",
|
|
84
|
+
taskId: target.taskId,
|
|
85
|
+
roleName: target.roleName
|
|
86
|
+
}
|
|
87
|
+
: { scope: "global", roleName: target.roleName }));
|
|
88
|
+
};
|
|
89
|
+
const launchCoordinator = new RuntimeLaunchCoordinator(schedulerStore, sessionHost, {
|
|
90
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
91
|
+
assertCurrent: (request) => {
|
|
92
|
+
assertRuntimeLaunchRequestCurrent(store, request);
|
|
93
|
+
},
|
|
94
|
+
launchFingerprint: (request) => (runtimeLaunchFingerprint(store, request)),
|
|
95
|
+
onCleanupRequired: signalRuntimeCleanup,
|
|
96
|
+
runtimeIsolation
|
|
97
|
+
});
|
|
98
|
+
const delivery = options.delivery ?? new ExecutorRegistry(planner, tmux, agentProcessReadinessProbe, {
|
|
99
|
+
sessionHost,
|
|
100
|
+
promptPush,
|
|
101
|
+
launchCoordinator,
|
|
102
|
+
roleResourceInventory: async (panes, inputs) => {
|
|
103
|
+
const inventory = await scanControllerResourceInventory({
|
|
104
|
+
currentHome: home,
|
|
105
|
+
scope: "current",
|
|
106
|
+
panes,
|
|
107
|
+
...(options.environment === undefined
|
|
108
|
+
? {}
|
|
109
|
+
: { environment: options.environment })
|
|
110
|
+
});
|
|
111
|
+
return inventory.resources.flatMap((resource) => {
|
|
112
|
+
if (resource.kind !== "agent-session")
|
|
113
|
+
return [];
|
|
114
|
+
const owner = resource.owner;
|
|
115
|
+
if (owner.kind !== "task-role")
|
|
116
|
+
return [];
|
|
117
|
+
const active = resource.state === "running" || resource.state === "current";
|
|
118
|
+
const input = inputs.find((candidate) => (candidate.taskId === owner.taskId
|
|
119
|
+
&& candidate.roleName === owner.roleName));
|
|
120
|
+
if (input === undefined)
|
|
121
|
+
return [];
|
|
122
|
+
const identity = owner.runId === undefined
|
|
123
|
+
|| owner.adapterId === undefined
|
|
124
|
+
|| (owner.nativeSessionId === undefined && owner.launchId === undefined)
|
|
125
|
+
|| (owner.nativeSessionId !== undefined && owner.nativeSessionId.trim().length === 0)
|
|
126
|
+
|| (owner.launchId !== undefined && owner.launchId.trim().length === 0)
|
|
127
|
+
? undefined
|
|
128
|
+
: {
|
|
129
|
+
taskId: owner.taskId,
|
|
130
|
+
roleName: owner.roleName,
|
|
131
|
+
runId: owner.runId,
|
|
132
|
+
agentId: owner.agentId,
|
|
133
|
+
adapterId: owner.adapterId,
|
|
134
|
+
...(owner.nativeSessionId === undefined
|
|
135
|
+
? {}
|
|
136
|
+
: { nativeSessionId: owner.nativeSessionId }),
|
|
137
|
+
...(owner.launchId === undefined ? {} : { launchId: owner.launchId })
|
|
138
|
+
};
|
|
139
|
+
const changed = !active || input.runId === undefined
|
|
140
|
+
? false
|
|
141
|
+
: resourceActivity({
|
|
142
|
+
taskId: input.taskId,
|
|
143
|
+
roleName: input.roleName,
|
|
144
|
+
runId: input.runId,
|
|
145
|
+
agentId: input.agentId,
|
|
146
|
+
adapterId: input.adapterId,
|
|
147
|
+
...(input.nativeSessionId === undefined
|
|
148
|
+
? {}
|
|
149
|
+
: { nativeSessionId: input.nativeSessionId }),
|
|
150
|
+
...(input.launchId === undefined ? {} : { launchId: input.launchId })
|
|
151
|
+
}, resource);
|
|
152
|
+
return [{
|
|
153
|
+
taskId: owner.taskId,
|
|
154
|
+
roleName: owner.roleName,
|
|
155
|
+
resource: {
|
|
156
|
+
observedAt: inventory.observedAt,
|
|
157
|
+
active,
|
|
158
|
+
changed: active && changed,
|
|
159
|
+
...(identity === undefined ? {} : { identity }),
|
|
160
|
+
...(input.progressAt === undefined ? {} : { progressAt: input.progressAt }),
|
|
161
|
+
cpuTimeMs: resource.cpuTimeMs,
|
|
162
|
+
...(resource.ioReadBytes === undefined
|
|
163
|
+
? {}
|
|
164
|
+
: { ioReadBytes: resource.ioReadBytes }),
|
|
165
|
+
...(resource.ioWriteBytes === undefined
|
|
166
|
+
? {}
|
|
167
|
+
: { ioWriteBytes: resource.ioWriteBytes }),
|
|
168
|
+
rssBytes: resource.rssBytes
|
|
169
|
+
}
|
|
170
|
+
}];
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
20
174
|
const workspacePreparer = options.workspacePreparer
|
|
21
175
|
?? new FileTaskWorkspacePreparer(home, store);
|
|
22
|
-
const
|
|
23
|
-
|
|
176
|
+
const resourceReaper = options.resourceReaper
|
|
177
|
+
?? (domainIdentity === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: createEphemeralResourceReaper({
|
|
180
|
+
currentHome: home,
|
|
181
|
+
// The detached Controller owns one YUI_HOME. Keep automatic
|
|
182
|
+
// recovery bounded to that domain; cross-home cleanup remains an
|
|
183
|
+
// explicit `controller cleanup --all` inventory operation.
|
|
184
|
+
scope: "current",
|
|
185
|
+
environment: options.environment
|
|
186
|
+
}));
|
|
187
|
+
const lifecycleDispatcher = createRuntimeLifecycleDispatcher(store, schedulerStore, sessionHost, options.dispatcher, signalRuntimeCleanup, launchCoordinator, planner);
|
|
188
|
+
const running = await startFileTaskController(home, schedulerStore, delivery, lifecycleDispatcher, {
|
|
24
189
|
intervalMs: options.intervalMs
|
|
25
190
|
?? reconciliationIntervalMilliseconds(store.getConfig().reconciliationIntervalSeconds),
|
|
191
|
+
signalWindowMs: options.signalWindowMs,
|
|
192
|
+
deliveryRetryMs: options.deliveryRetryMs,
|
|
193
|
+
deliveryRetryLimit: options.deliveryRetryLimit,
|
|
26
194
|
now: options.now,
|
|
27
195
|
onError: options.onError,
|
|
196
|
+
lifecycleHost,
|
|
197
|
+
...(resourceReaper === undefined ? {} : { resourceReaper }),
|
|
198
|
+
onExpiredEphemeralDomain: (domain) => {
|
|
199
|
+
if (domain.yuiHome !== home)
|
|
200
|
+
return;
|
|
201
|
+
void runningController?.close().catch(options.onError ?? (() => undefined));
|
|
202
|
+
},
|
|
203
|
+
workspacePreparer,
|
|
204
|
+
runtimeEventProcessor: options.runtimeEventProcessor
|
|
205
|
+
?? new FileRuntimeEventProcessor(new FileRuntimeEventInbox(home), schedulerStore, {
|
|
206
|
+
onTaskRuntimeApplied: (input) => {
|
|
207
|
+
planner.refreshTaskRuntimeDescriptor(input);
|
|
208
|
+
}
|
|
209
|
+
}),
|
|
210
|
+
domainIdentity,
|
|
211
|
+
...(options.configuration !== undefined
|
|
212
|
+
? { configuration: options.configuration }
|
|
213
|
+
: options.intervalMs === undefined
|
|
214
|
+
? {
|
|
215
|
+
configuration: {
|
|
216
|
+
reconciliationIntervalMs: () => reconciliationIntervalMilliseconds(store.getConfig().reconciliationIntervalSeconds)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
: {})
|
|
220
|
+
});
|
|
221
|
+
runningController = running;
|
|
222
|
+
runningRuntime = running.runtime;
|
|
223
|
+
return {
|
|
224
|
+
...running,
|
|
225
|
+
store,
|
|
226
|
+
schedulerStore,
|
|
227
|
+
planner,
|
|
228
|
+
tmux,
|
|
229
|
+
delivery,
|
|
230
|
+
sessionHost,
|
|
231
|
+
promptPush,
|
|
232
|
+
runtimeIsolation,
|
|
28
233
|
workspacePreparer
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionHost, fallback, onCleanupRequired, sharedLaunchCoordinator, environmentRefresher) {
|
|
237
|
+
const launchCoordinator = sharedLaunchCoordinator
|
|
238
|
+
?? new RuntimeLaunchCoordinator(schedulerStore, sessionHost, {
|
|
239
|
+
assertCurrent: (request) => {
|
|
240
|
+
assertRuntimeLaunchRequestCurrent(store, request);
|
|
241
|
+
},
|
|
242
|
+
launchFingerprint: (request) => (runtimeLaunchFingerprint(store, request)),
|
|
243
|
+
onCleanupRequired
|
|
244
|
+
});
|
|
245
|
+
const lifecycleTails = new Map();
|
|
246
|
+
return async (method, params) => {
|
|
247
|
+
if (method === "runtime.replace-agent-environment") {
|
|
248
|
+
if (environmentRefresher === undefined) {
|
|
249
|
+
throw applicationError("METHOD_NOT_FOUND", "Controller method was not found.");
|
|
250
|
+
}
|
|
251
|
+
const refresh = parseEnvironmentRefresh(params);
|
|
252
|
+
validateEnvironmentRefreshSources(store, refresh);
|
|
253
|
+
environmentRefresher.refreshAgentEnvironment(refresh);
|
|
254
|
+
return {
|
|
255
|
+
replaced: true,
|
|
256
|
+
count: Object.keys(refresh.sources).length
|
|
257
|
+
+ Object.keys(refresh.nativeSources).length
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (method !== "runtime.ensure-role-session") {
|
|
261
|
+
if (fallback !== undefined)
|
|
262
|
+
return fallback(method, params);
|
|
263
|
+
throw applicationError("METHOD_NOT_FOUND", "Controller method was not found.");
|
|
264
|
+
}
|
|
265
|
+
const request = parseEnsureRoleSession(params);
|
|
266
|
+
const lifecycleKey = request.scope === "task"
|
|
267
|
+
? `task\0${request.taskId}\0${request.roleName}`
|
|
268
|
+
: `global\0${request.roleName}`;
|
|
269
|
+
const previous = lifecycleTails.get(lifecycleKey) ?? Promise.resolve();
|
|
270
|
+
let release;
|
|
271
|
+
const current = new Promise((resolve) => { release = resolve; });
|
|
272
|
+
const tail = previous.then(() => current);
|
|
273
|
+
lifecycleTails.set(lifecycleKey, tail);
|
|
274
|
+
await previous;
|
|
275
|
+
try {
|
|
276
|
+
const task = request.scope === "task"
|
|
277
|
+
? store.getTask(request.taskId)
|
|
278
|
+
: null;
|
|
279
|
+
if (request.scope === "task" && task?.status !== "active") {
|
|
280
|
+
throw applicationError("INVALID_PARAMS", task === null
|
|
281
|
+
? `Task not found: ${request.taskId}.`
|
|
282
|
+
: `Task is not active: ${request.taskId}.`);
|
|
283
|
+
}
|
|
284
|
+
if (request.scope === "task" && task !== null) {
|
|
285
|
+
const taskWorkspace = store.getTaskWorkspace(task.id);
|
|
286
|
+
if (!isTaskOwnedWorkspace(taskWorkspace, task.id, task.cwd, task.projectBindings.map(({ projectId, directory }) => ({ projectId, directory })))) {
|
|
287
|
+
throw new Error(`Task workspace is not ready: ${task.id}.`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (request.scope === "task"
|
|
291
|
+
&& hasPendingRuntimeCleanup(store, request)) {
|
|
292
|
+
throw new Error(`Runtime cleanup is still pending: ${request.taskId}/${request.roleName}.`);
|
|
293
|
+
}
|
|
294
|
+
if (request.scope === "global"
|
|
295
|
+
&& hasPendingRuntimeCleanup(store, request)) {
|
|
296
|
+
throw new Error(`Runtime cleanup is still pending: ${request.roleName}.`);
|
|
297
|
+
}
|
|
298
|
+
const role = request.scope === "task"
|
|
299
|
+
? store.getRole(request.taskId, request.roleName)
|
|
300
|
+
: store.getGlobalRole(request.roleName);
|
|
301
|
+
if (role === null) {
|
|
302
|
+
throw applicationError("INVALID_PARAMS", request.scope === "task"
|
|
303
|
+
? `Role not found: ${request.taskId}/${request.roleName}.`
|
|
304
|
+
: `Global Role not found: ${request.roleName}.`);
|
|
305
|
+
}
|
|
306
|
+
const activeRun = request.scope === "task"
|
|
307
|
+
? store.getActiveAgentRun(request.taskId, request.roleName)
|
|
308
|
+
: null;
|
|
309
|
+
const managedWorkspace = request.scope === "task"
|
|
310
|
+
? activeRun?.workspace
|
|
311
|
+
?? currentDesiredManagedWorkspace(store, request.taskId, request.roleName)
|
|
312
|
+
: undefined;
|
|
313
|
+
const sessions = request.scope === "task"
|
|
314
|
+
? store.getTaskRoleSessionSet(request.taskId, request.roleName)
|
|
315
|
+
: store.getGlobalRoleSessionSet(request.roleName);
|
|
316
|
+
const effective = activeRun?.effective
|
|
317
|
+
?? activeLiveRoleAgentSession(sessions)?.effective
|
|
318
|
+
?? resolveRuntimeDesiredEffective(store, request, role);
|
|
319
|
+
const binding = role.agentBindings[effective.agentId];
|
|
320
|
+
const agent = store.getConfiguredAgent(effective.agentId);
|
|
321
|
+
if (binding === undefined || binding.adapterId !== effective.adapterId) {
|
|
322
|
+
throw applicationError("INVALID_PARAMS", `Role binding does not match effective launch: ${effective.agentId}.`);
|
|
323
|
+
}
|
|
324
|
+
if (agent === null || agent.adapterId !== binding.adapterId) {
|
|
325
|
+
throw applicationError("INVALID_PARAMS", `Configured Agent does not match Role: ${effective.agentId}.`);
|
|
326
|
+
}
|
|
327
|
+
validateLifecycleEnvironment(request.environment, agent);
|
|
328
|
+
const mode = roleAgentSessionResumeMode(sessions, effective.agentId, effective);
|
|
329
|
+
const session = sessions?.sessions[effective.agentId];
|
|
330
|
+
const owner = request.scope === "task"
|
|
331
|
+
? { scope: "task", taskId: request.taskId, roleName: request.roleName }
|
|
332
|
+
: { scope: "global", roleName: request.roleName };
|
|
333
|
+
const common = {
|
|
334
|
+
owner,
|
|
335
|
+
agentId: effective.agentId,
|
|
336
|
+
adapterId: binding.adapterId,
|
|
337
|
+
effective,
|
|
338
|
+
workspace: effective.workspace.root,
|
|
339
|
+
...(managedWorkspace === undefined ? {} : { managedWorkspace }),
|
|
340
|
+
...(activeRun === null ? {} : { runId: activeRun.id }),
|
|
341
|
+
...(request.environment === undefined
|
|
342
|
+
? {}
|
|
343
|
+
: { environment: request.environment })
|
|
344
|
+
};
|
|
345
|
+
const launchRequest = mode === "new"
|
|
346
|
+
? { ...common, mode: "new" }
|
|
347
|
+
: {
|
|
348
|
+
...common,
|
|
349
|
+
mode: "resume",
|
|
350
|
+
nativeSessionId: session.nativeSessionId
|
|
351
|
+
};
|
|
352
|
+
const assertCurrent = () => assertRuntimeLaunchRequestCurrent(store, launchRequest);
|
|
353
|
+
const runtimeBinding = await launchCoordinator.prepare(launchRequest, "immediate", assertCurrent);
|
|
354
|
+
return {
|
|
355
|
+
ensured: true,
|
|
356
|
+
sessionStarted: runtimeBinding.hostCreated === true,
|
|
357
|
+
scope: request.scope,
|
|
358
|
+
roleName: request.roleName,
|
|
359
|
+
...(request.scope === "task" ? { taskId: request.taskId } : {})
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
release();
|
|
364
|
+
if (lifecycleTails.get(lifecycleKey) === tail) {
|
|
365
|
+
lifecycleTails.delete(lifecycleKey);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function resolveRuntimeDesiredEffective(store, request, role) {
|
|
371
|
+
if (request.scope === "global") {
|
|
372
|
+
return resolveEffectiveLaunch({
|
|
373
|
+
role: role,
|
|
374
|
+
purpose: "execution"
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const taskRole = role;
|
|
378
|
+
const item = store.listWorkItems(request.taskId).find((candidate) => (candidate.assignee === request.roleName
|
|
379
|
+
&& !["completed", "failed", "retired"].includes(candidate.status))) ?? null;
|
|
380
|
+
const workspace = (item === null
|
|
381
|
+
? store.getTaskWorkspace(request.taskId)
|
|
382
|
+
: store.getWorkItemWorkspace(request.taskId, item.id))
|
|
383
|
+
?? store.getTaskWorkspace(request.taskId)
|
|
384
|
+
?? undefined;
|
|
385
|
+
return resolveEffectiveLaunch({
|
|
386
|
+
role: taskRole,
|
|
387
|
+
purpose: "execution",
|
|
388
|
+
...(workspace === undefined ? {} : { workspace }),
|
|
389
|
+
...(item === null ? {} : { workItemWriteProjectIds: item.writeProjectIds })
|
|
29
390
|
});
|
|
30
|
-
|
|
391
|
+
}
|
|
392
|
+
function assertRuntimeLaunchRequestCurrent(store, request) {
|
|
393
|
+
let activeRun = null;
|
|
394
|
+
if (request.owner.scope === "task") {
|
|
395
|
+
const task = store.getTask(request.owner.taskId);
|
|
396
|
+
if (task === null || task.status !== "active") {
|
|
397
|
+
throw new Error(`Task is no longer active: ${request.owner.taskId}.`);
|
|
398
|
+
}
|
|
399
|
+
activeRun = store.getActiveAgentRun(request.owner.taskId, request.owner.roleName);
|
|
400
|
+
if (request.runId !== undefined
|
|
401
|
+
&& activeRun?.id !== request.runId) {
|
|
402
|
+
throw new Error(`Role Run is no longer current: ${request.runId}.`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const role = request.owner.scope === "task"
|
|
406
|
+
? store.getRole(request.owner.taskId, request.owner.roleName)
|
|
407
|
+
: store.getGlobalRole(request.owner.roleName);
|
|
408
|
+
if (role === null) {
|
|
409
|
+
throw new Error(`Role no longer exists: ${request.owner.roleName}.`);
|
|
410
|
+
}
|
|
411
|
+
const sessions = request.owner.scope === "task"
|
|
412
|
+
? store.getTaskRoleSessionSet(request.owner.taskId, request.owner.roleName)
|
|
413
|
+
: store.getGlobalRoleSessionSet(request.owner.roleName);
|
|
414
|
+
const expectedEffective = request.owner.scope === "task" && request.runId !== undefined
|
|
415
|
+
? activeRun?.effective
|
|
416
|
+
: activeLiveRoleAgentSession(sessions)?.effective
|
|
417
|
+
?? currentDesiredEffective(store, request, role);
|
|
418
|
+
if (expectedEffective === undefined
|
|
419
|
+
|| !isDeepStrictEqual(expectedEffective, request.effective)
|
|
420
|
+
|| request.effective.agentId !== request.agentId
|
|
421
|
+
|| request.effective.adapterId !== request.adapterId
|
|
422
|
+
|| request.effective.workspace.root !== request.workspace) {
|
|
423
|
+
throw new Error(`Role launch state changed: ${request.owner.roleName}.`);
|
|
424
|
+
}
|
|
425
|
+
if (request.owner.scope === "task" && request.managedWorkspace !== undefined) {
|
|
426
|
+
const expectedWorkspace = activeRun?.workspace
|
|
427
|
+
?? currentDesiredManagedWorkspace(store, request.owner.taskId, request.owner.roleName);
|
|
428
|
+
if (!isDeepStrictEqual(expectedWorkspace, request.managedWorkspace)) {
|
|
429
|
+
throw new Error(`Managed workspace launch state changed: ${request.owner.roleName}.`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const binding = role.agentBindings[request.agentId];
|
|
433
|
+
if (binding === undefined || binding.adapterId !== request.adapterId) {
|
|
434
|
+
throw new Error(`Role effective binding changed: ${request.owner.roleName}.`);
|
|
435
|
+
}
|
|
436
|
+
const agent = store.getConfiguredAgent(request.agentId);
|
|
437
|
+
if (agent === null || agent.adapterId !== request.adapterId) {
|
|
438
|
+
throw new Error(`Agent launch state changed: ${request.agentId}.`);
|
|
439
|
+
}
|
|
440
|
+
if (request.mode === "resume") {
|
|
441
|
+
const session = request.owner.scope === "task"
|
|
442
|
+
? store.getTaskRoleSessionSet(request.owner.taskId, request.owner.roleName)
|
|
443
|
+
?.sessions[request.agentId]
|
|
444
|
+
: store.getGlobalRoleSessionSet(request.owner.roleName)
|
|
445
|
+
?.sessions[request.agentId];
|
|
446
|
+
if (session === null || session === undefined
|
|
447
|
+
|| session.nativeSessionId !== request.nativeSessionId) {
|
|
448
|
+
throw new Error(`Native session changed: ${request.owner.roleName}.`);
|
|
449
|
+
}
|
|
450
|
+
if (!effectiveLaunchSnapshotsCompatible(session.effective, request.effective)) {
|
|
451
|
+
throw new Error(`Native session effective launch changed: ${request.owner.roleName}.`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function currentDesiredEffective(store, request, role) {
|
|
456
|
+
if (request.owner.scope === "global") {
|
|
457
|
+
return resolveEffectiveLaunch({ role: role, purpose: "execution" });
|
|
458
|
+
}
|
|
459
|
+
const item = store.listWorkItems(request.owner.taskId).find((candidate) => (candidate.assignee === request.owner.roleName
|
|
460
|
+
&& !["completed", "failed", "retired"].includes(candidate.status))) ?? null;
|
|
461
|
+
const workspace = (item === null
|
|
462
|
+
? store.getTaskWorkspace(request.owner.taskId)
|
|
463
|
+
: store.getWorkItemWorkspace(request.owner.taskId, item.id))
|
|
464
|
+
?? store.getTaskWorkspace(request.owner.taskId)
|
|
465
|
+
?? undefined;
|
|
466
|
+
return resolveEffectiveLaunch({
|
|
467
|
+
role: role,
|
|
468
|
+
purpose: "execution",
|
|
469
|
+
...(workspace === undefined ? {} : { workspace }),
|
|
470
|
+
...(item === null ? {} : { workItemWriteProjectIds: item.writeProjectIds })
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function runtimeLaunchFingerprint(store, request) {
|
|
474
|
+
const agent = store.getConfiguredAgent(request.effective.agentId);
|
|
475
|
+
if (agent === null) {
|
|
476
|
+
throw new Error(`Agent no longer exists: ${request.effective.agentId}.`);
|
|
477
|
+
}
|
|
478
|
+
return createHash("sha256").update(JSON.stringify([
|
|
479
|
+
request.owner,
|
|
480
|
+
request.effective,
|
|
481
|
+
request.managedWorkspace,
|
|
482
|
+
request.runtimePolicy,
|
|
483
|
+
agent
|
|
484
|
+
])).digest("hex");
|
|
485
|
+
}
|
|
486
|
+
function currentDesiredManagedWorkspace(store, taskId, roleName) {
|
|
487
|
+
const item = store.listWorkItems(taskId).find((candidate) => (candidate.assignee === roleName
|
|
488
|
+
&& !["completed", "failed", "retired"].includes(candidate.status))) ?? null;
|
|
489
|
+
return (item === null
|
|
490
|
+
? store.getTaskWorkspace(taskId)
|
|
491
|
+
: store.getWorkItemWorkspace(taskId, item.id))
|
|
492
|
+
?? store.getTaskWorkspace(taskId)
|
|
493
|
+
?? undefined;
|
|
494
|
+
}
|
|
495
|
+
function hasPendingRuntimeCleanup(store, request) {
|
|
496
|
+
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(request));
|
|
497
|
+
return hasRuntimeCleanupObligation(mailbox);
|
|
498
|
+
}
|
|
499
|
+
function parseEnsureRoleSession(params) {
|
|
500
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
501
|
+
throw applicationError("INVALID_PARAMS", "Runtime session params are invalid.");
|
|
502
|
+
}
|
|
503
|
+
const value = params;
|
|
504
|
+
const roleName = requiredParam(value.roleName);
|
|
505
|
+
const environment = parseLifecycleEnvironment(value.environment);
|
|
506
|
+
const expectedFields = environment === undefined ? 0 : 1;
|
|
507
|
+
if (value.scope === "global" && Object.keys(value).length === 2 + expectedFields) {
|
|
508
|
+
return {
|
|
509
|
+
scope: "global",
|
|
510
|
+
roleName,
|
|
511
|
+
...(environment === undefined ? {} : { environment })
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
if (value.scope === "task" && Object.keys(value).length === 3 + expectedFields) {
|
|
515
|
+
return {
|
|
516
|
+
scope: "task",
|
|
517
|
+
taskId: requiredParam(value.taskId),
|
|
518
|
+
roleName,
|
|
519
|
+
...(environment === undefined ? {} : { environment })
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
throw applicationError("INVALID_PARAMS", "Runtime session params are invalid.");
|
|
523
|
+
}
|
|
524
|
+
function parseLifecycleEnvironment(value) {
|
|
525
|
+
if (value === undefined)
|
|
526
|
+
return undefined;
|
|
527
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
528
|
+
throw applicationError("INVALID_PARAMS", "Runtime session environment is invalid.");
|
|
529
|
+
}
|
|
530
|
+
const entries = Object.entries(value);
|
|
531
|
+
if (entries.length > 256) {
|
|
532
|
+
throw applicationError("INVALID_PARAMS", "Runtime session environment is invalid.");
|
|
533
|
+
}
|
|
534
|
+
const environment = {};
|
|
535
|
+
for (const [name, item] of entries) {
|
|
536
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
|
|
537
|
+
|| typeof item !== "string"
|
|
538
|
+
|| item.includes("\0")
|
|
539
|
+
|| MANAGED_RUNTIME_ENVIRONMENT.has(name)) {
|
|
540
|
+
throw applicationError("INVALID_PARAMS", "Runtime session environment is invalid.");
|
|
541
|
+
}
|
|
542
|
+
environment[name] = item;
|
|
543
|
+
}
|
|
544
|
+
return environment;
|
|
545
|
+
}
|
|
546
|
+
function validateLifecycleEnvironment(environment, agent) {
|
|
547
|
+
if (environment === undefined)
|
|
548
|
+
return;
|
|
549
|
+
const declared = new Map(agent.environment.map((binding) => [
|
|
550
|
+
binding.sourceName,
|
|
551
|
+
binding
|
|
552
|
+
]));
|
|
553
|
+
const allowed = new Set([
|
|
554
|
+
...declared.keys(),
|
|
555
|
+
...AGENT_OPERATIONAL_ENVIRONMENT_NAMES,
|
|
556
|
+
...nativeAgentEnvironmentNames(agent.adapterId)
|
|
557
|
+
]);
|
|
558
|
+
for (const name of Object.keys(environment)) {
|
|
559
|
+
if (!allowed.has(name)) {
|
|
560
|
+
throw applicationError("INVALID_PARAMS", `Runtime session environment source is not declared: ${name}.`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const missing = agent.environment.find((binding) => (binding.required
|
|
564
|
+
&& !MANAGED_RUNTIME_ENVIRONMENT.has(binding.sourceName)
|
|
565
|
+
&& environment[binding.sourceName] === undefined));
|
|
566
|
+
if (missing !== undefined) {
|
|
567
|
+
throw applicationError("INVALID_PARAMS", `Required Agent environment is missing: ${missing.sourceName}.`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const MANAGED_RUNTIME_ENVIRONMENT = new Set(YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES);
|
|
571
|
+
function parseEnvironmentRefresh(params) {
|
|
572
|
+
if (typeof params !== "object"
|
|
573
|
+
|| params === null
|
|
574
|
+
|| Array.isArray(params)
|
|
575
|
+
|| Object.keys(params).length !== 4) {
|
|
576
|
+
throw applicationError("INVALID_PARAMS", "Runtime Agent environment refresh params are invalid.");
|
|
577
|
+
}
|
|
578
|
+
const values = params;
|
|
579
|
+
return {
|
|
580
|
+
sources: parseEnvironmentValues(values.sources, "sources"),
|
|
581
|
+
sourceNames: parseEnvironmentNames(values.sourceNames, "source names"),
|
|
582
|
+
nativeSources: parseEnvironmentValues(values.nativeSources, "native sources"),
|
|
583
|
+
nativeNames: parseEnvironmentNames(values.nativeNames, "native names")
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
function parseEnvironmentValues(value, label) {
|
|
587
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
588
|
+
throw applicationError("INVALID_PARAMS", `Runtime Agent environment ${label} are invalid.`);
|
|
589
|
+
}
|
|
590
|
+
const entries = Object.entries(value);
|
|
591
|
+
if (entries.length > 256) {
|
|
592
|
+
throw applicationError("INVALID_PARAMS", "Runtime Agent environment refresh has too many sources.");
|
|
593
|
+
}
|
|
594
|
+
const sources = {};
|
|
595
|
+
for (const [name, item] of entries) {
|
|
596
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
|
|
597
|
+
|| typeof item !== "string"
|
|
598
|
+
|| item.includes("\0")
|
|
599
|
+
|| MANAGED_RUNTIME_ENVIRONMENT.has(name)) {
|
|
600
|
+
throw applicationError("INVALID_PARAMS", `Runtime Agent environment source is invalid: ${name}.`);
|
|
601
|
+
}
|
|
602
|
+
sources[name] = item;
|
|
603
|
+
}
|
|
604
|
+
return sources;
|
|
605
|
+
}
|
|
606
|
+
function parseEnvironmentNames(value, label) {
|
|
607
|
+
if (!Array.isArray(value)
|
|
608
|
+
|| value.length > 256
|
|
609
|
+
|| value.some((name) => (typeof name !== "string"
|
|
610
|
+
|| !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
|
|
611
|
+
|| MANAGED_RUNTIME_ENVIRONMENT.has(name)))) {
|
|
612
|
+
throw applicationError("INVALID_PARAMS", `Runtime Agent environment ${label} are invalid.`);
|
|
613
|
+
}
|
|
614
|
+
return [...new Set(value)];
|
|
615
|
+
}
|
|
616
|
+
function validateEnvironmentRefreshSources(store, refresh) {
|
|
617
|
+
const declared = new Set(store.listConfiguredAgents().flatMap((agent) => (agent.environment.map((binding) => binding.sourceName))));
|
|
618
|
+
for (const name of Object.keys(refresh.sources)) {
|
|
619
|
+
if (!declared.has(name)) {
|
|
620
|
+
throw applicationError("INVALID_PARAMS", `Runtime Agent environment source is not declared: ${name}.`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (Object.keys(refresh.sources).some((name) => !refresh.sourceNames.includes(name))) {
|
|
624
|
+
throw applicationError("INVALID_PARAMS", "Runtime Agent environment source scope is invalid.");
|
|
625
|
+
}
|
|
626
|
+
const native = new Set(store.listConfiguredAgents().flatMap((agent) => (nativeAgentEnvironmentNames(agent.adapterId))));
|
|
627
|
+
for (const name of Object.keys(refresh.nativeSources)) {
|
|
628
|
+
if (!native.has(name) || !refresh.nativeNames.includes(name)) {
|
|
629
|
+
throw applicationError("INVALID_PARAMS", `Runtime native Agent environment source is not declared: ${name}.`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
const allowedNative = new Set(["CODEX_HOME", "CLAUDE_CONFIG_DIR"]);
|
|
633
|
+
if (refresh.nativeNames.some((name) => !allowedNative.has(name))) {
|
|
634
|
+
throw applicationError("INVALID_PARAMS", "Runtime native Agent environment scope is invalid.");
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function requiredParam(value) {
|
|
638
|
+
if (typeof value !== "string"
|
|
639
|
+
|| value.length === 0
|
|
640
|
+
|| value.trim() !== value
|
|
641
|
+
|| value.includes("\0")) {
|
|
642
|
+
throw applicationError("INVALID_PARAMS", "Runtime session params are invalid.");
|
|
643
|
+
}
|
|
644
|
+
return value;
|
|
645
|
+
}
|
|
646
|
+
function applicationError(code, message) {
|
|
647
|
+
const error = Object.assign(new Error(message), { code });
|
|
648
|
+
error.name = "CoreApplicationError";
|
|
649
|
+
return error;
|
|
31
650
|
}
|