@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,14 +1,22 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { delimiter, isAbsolute, join, resolve } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
2
|
+
import { delimiter, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
5
|
import { configuredAgentToDefinition, createConfiguredAgent } from "../agent/agent.js";
|
|
6
|
+
import { selectAgentPermission, selectAgentModelAndEffort } from "../cli/agentConfigurationPicker.js";
|
|
5
7
|
import { runCompletionWizard } from "../completion/completionWizard.js";
|
|
6
8
|
import { usageError } from "../errors/cliError.js";
|
|
9
|
+
import { defaultRoleAgentConfig, resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
10
|
+
import { AgentConfigurationCatalogService } from "../executor/agentConfigurationCatalog.js";
|
|
7
11
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
+
import { resolveTimeZone } from "../output/timePresentation.js";
|
|
13
|
+
import { createGlobalRole, createRoleAgentBinding, updateGlobalRole } from "../role/role.js";
|
|
14
|
+
import { SYSTEM_LEADER_ROLE, SYSTEM_OPERATOR_ROLE, SYSTEM_WORKER_ROLE } from "../role/systemRoles.js";
|
|
15
|
+
import { DEFAULT_REVIEWER_ROLE } from "../review/reviewConfig.js";
|
|
16
|
+
import { builtinAgentProfileInputs, createAgentProfile } from "../profile/agentProfile.js";
|
|
17
|
+
import { assertRoleRuntimeMutationAllowed } from "../commands/roleRuntimeGuard.js";
|
|
18
|
+
import { ensureYuiHome, resolveYuiHome } from "../storage/taskStore.js";
|
|
19
|
+
import { initializeCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
12
20
|
const BUILTIN_AGENTS = Object.freeze([
|
|
13
21
|
Object.freeze({
|
|
14
22
|
id: "codex",
|
|
@@ -36,15 +44,40 @@ export async function runSetupCommand(args, env, executor, io = {}) {
|
|
|
36
44
|
const question = createSetupQuestion(readline, io);
|
|
37
45
|
const home = resolveYuiHome(env);
|
|
38
46
|
ensureYuiHome(home);
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const result = await configureYui(store, home, env, question, io);
|
|
47
|
+
const store = initializeCompatibleFileTaskStore(home);
|
|
48
|
+
const catalogs = new AgentConfigurationCatalogService(home, { environment: env });
|
|
49
|
+
const result = await configureYui(store, home, env, question, setupSelectionIo(question, io), catalogs, io);
|
|
42
50
|
const lines = [
|
|
43
51
|
"Yui home initialized.",
|
|
44
52
|
`Agents configured: ${result.agentIds.join(", ")}.`,
|
|
45
53
|
`Default Agent: ${result.defaultAgentId}.`,
|
|
46
54
|
`Operator Agent: ${result.operatorAgentId}.`,
|
|
47
|
-
`
|
|
55
|
+
`Leader model: ${result.leaderConfig.model ?? "CLI default"}.`,
|
|
56
|
+
`Leader reasoning effort: ${result.leaderConfig.effort ?? "CLI default"}.`,
|
|
57
|
+
`Leader permission: ${result.leaderConfig.permission.strategy}.`,
|
|
58
|
+
`Operator model: ${result.operatorConfig.model ?? "CLI default"}.`,
|
|
59
|
+
`Operator reasoning effort: ${result.operatorConfig.effort ?? "CLI default"}.`,
|
|
60
|
+
`Operator permission: ${result.operatorConfig.permission.strategy}.`,
|
|
61
|
+
`Worker Agent: ${result.workerAgentId}.`,
|
|
62
|
+
`Worker configuration: ${result.workerReusesLeader
|
|
63
|
+
? "Reused Leader configuration"
|
|
64
|
+
: "Configured separately"}.`,
|
|
65
|
+
`Worker model: ${result.workerConfig.model ?? "CLI default"}.`,
|
|
66
|
+
`Worker reasoning effort: ${result.workerConfig.effort ?? "CLI default"}.`,
|
|
67
|
+
`Worker permission: ${result.workerConfig.permission.strategy}.`,
|
|
68
|
+
...(result.reviewerInitialized
|
|
69
|
+
? [
|
|
70
|
+
`Reviewer Agent: ${result.reviewerAgentId}.`,
|
|
71
|
+
`Reviewer model: ${result.reviewerConfig?.model ?? "CLI default"}.`,
|
|
72
|
+
`Reviewer reasoning effort: ${result.reviewerConfig?.effort ?? "CLI default"}.`,
|
|
73
|
+
`Reviewer permission: ${result.reviewerConfig?.permission.strategy}.`,
|
|
74
|
+
...(result.reviewPolicy === undefined
|
|
75
|
+
? ["Review policy: disabled."]
|
|
76
|
+
: [`Review policy: ${result.reviewPolicy.roleName} (${result.reviewPolicy.trigger}).`])
|
|
77
|
+
]
|
|
78
|
+
: []),
|
|
79
|
+
`Project workspace: ${result.workspace}.`,
|
|
80
|
+
`Time zone: ${resolveTimeZone(store.getConfig().timeZone)}.`
|
|
48
81
|
];
|
|
49
82
|
if (dependency === undefined || dependency === "tmux") {
|
|
50
83
|
lines.push(...await setupTmux(env, executor, question));
|
|
@@ -63,7 +96,13 @@ export function validateSetupInvocation(args, io = {}) {
|
|
|
63
96
|
if (!shouldPrompt(io))
|
|
64
97
|
throw setupRequiresInteractiveError();
|
|
65
98
|
}
|
|
66
|
-
async function configureYui(store, home, env, question, io) {
|
|
99
|
+
async function configureYui(store, home, env, question, selectionIo, catalogs, io) {
|
|
100
|
+
const initialConfig = store.getConfig();
|
|
101
|
+
const freshHome = store.listGlobalRoles().length === 0
|
|
102
|
+
&& store.listConfiguredAgents().length === 0
|
|
103
|
+
&& initialConfig.defaultAgent === undefined
|
|
104
|
+
&& initialConfig.defaultWorkspace === undefined
|
|
105
|
+
&& initialConfig.review === undefined;
|
|
67
106
|
const candidates = availableAgentChoices(store, env);
|
|
68
107
|
if (candidates.length === 0) {
|
|
69
108
|
throw usageError("No supported Agent CLI was found. Install Codex or Claude, then run setup again.");
|
|
@@ -83,45 +122,210 @@ async function configureYui(store, home, env, question, io) {
|
|
|
83
122
|
]), tableWidth(io))}\n`);
|
|
84
123
|
const selected = parseAgentSetSelection(await question(`Choose Agents by number or name, comma-separated [all: ${candidates.map(({ id }) => id).join(", ")}]: `), candidates);
|
|
85
124
|
const now = new Date();
|
|
86
|
-
const
|
|
87
|
-
const configuredIds = new Set(
|
|
125
|
+
const prepared = selected.map((choice) => prepareAgent(store, choice, now));
|
|
126
|
+
const configuredIds = new Set(prepared.map(({ id }) => id));
|
|
88
127
|
const config = store.getConfig();
|
|
89
|
-
const defaultFallback =
|
|
128
|
+
const defaultFallback = prepared.some(({ id }) => id === config.defaultAgent)
|
|
90
129
|
? config.defaultAgent
|
|
91
|
-
:
|
|
92
|
-
|
|
93
|
-
throw usageError("At least one Agent must be selected.");
|
|
94
|
-
const defaultAgentId = parseSingleAgentSelection(await question(`Choose default Agent [${defaultFallback}]: `), persisted, defaultFallback);
|
|
130
|
+
: prepared[0].id;
|
|
131
|
+
const defaultAgentId = parseSingleAgentSelection(await question(`Choose default Agent [${defaultFallback}]: `), prepared, defaultFallback);
|
|
95
132
|
const currentOperatorAgent = store.getGlobalRole(SYSTEM_OPERATOR_ROLE)?.activeAgentId;
|
|
96
133
|
const operatorFallback = configuredIds.has(currentOperatorAgent ?? "")
|
|
97
134
|
? currentOperatorAgent
|
|
98
135
|
: defaultAgentId;
|
|
99
|
-
const operatorAgentId = parseSingleAgentSelection(await question(`Choose Operator Agent [${operatorFallback}]: `),
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
const operatorAgentId = parseSingleAgentSelection(await question(`Choose Operator Agent [${operatorFallback}]: `), prepared, operatorFallback);
|
|
137
|
+
const existingReviewer = store.getGlobalRole(DEFAULT_REVIEWER_ROLE);
|
|
138
|
+
const reviewerInitialized = freshHome || existingReviewer !== null;
|
|
139
|
+
let reviewerAgentId;
|
|
140
|
+
let reviewerConfig;
|
|
141
|
+
if (reviewerInitialized) {
|
|
142
|
+
const reviewerFallback = configuredIds.has(existingReviewer?.activeAgentId ?? "")
|
|
143
|
+
? existingReviewer.activeAgentId
|
|
144
|
+
: defaultAgentId;
|
|
145
|
+
reviewerAgentId = parseSingleAgentSelection(await question(`Choose Reviewer Agent [${reviewerFallback}]: `), prepared, reviewerFallback);
|
|
146
|
+
const reviewerAgent = prepared.find(({ id }) => id === reviewerAgentId);
|
|
147
|
+
if (reviewerAgent === undefined) {
|
|
148
|
+
throw usageError("Selected Reviewer Agent is no longer available.");
|
|
149
|
+
}
|
|
150
|
+
reviewerConfig = await promptRoleAgentConfig("Reviewer", reviewerAgent, existingReviewer, home, selectionIo, catalogs, true);
|
|
151
|
+
}
|
|
152
|
+
const defaultAgent = prepared.find(({ id }) => id === defaultAgentId);
|
|
153
|
+
const operatorAgent = prepared.find(({ id }) => id === operatorAgentId);
|
|
105
154
|
if (defaultAgent === undefined || operatorAgent === undefined) {
|
|
106
155
|
throw usageError("Selected setup Agent is no longer available.");
|
|
107
156
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
157
|
+
const leaderConfig = await promptRoleAgentConfig("Leader", defaultAgent, store.getGlobalRole(SYSTEM_LEADER_ROLE), home, selectionIo, catalogs);
|
|
158
|
+
const operatorConfig = await promptRoleAgentConfig("Operator", operatorAgent, store.getGlobalRole(SYSTEM_OPERATOR_ROLE), home, selectionIo, catalogs);
|
|
159
|
+
const existingWorker = store.getGlobalRole(SYSTEM_WORKER_ROLE);
|
|
160
|
+
const workerModeFallback = workerConfigurationModeFallback(existingWorker, defaultAgentId, leaderConfig);
|
|
161
|
+
io.output?.write("\nWorker is the default Agent configuration copied into Task Roles such as "
|
|
162
|
+
+ "investigator and implementer. Each Task Role gets its own Session.\n");
|
|
163
|
+
const workerReusesLeader = parseWorkerConfigurationMode(await question(`Choose Worker configuration (reuse Leader/configure separately) [${workerModeFallback === "reuse-leader" ? "reuse Leader" : "configure separately"}]: `), workerModeFallback) === "reuse-leader";
|
|
164
|
+
let workerAgentId = defaultAgentId;
|
|
165
|
+
let workerConfig = structuredClone(leaderConfig);
|
|
166
|
+
if (!workerReusesLeader) {
|
|
167
|
+
const existingWorkerAgent = existingWorker?.activeAgentId;
|
|
168
|
+
const workerFallback = configuredIds.has(existingWorkerAgent ?? "")
|
|
169
|
+
? existingWorkerAgent
|
|
170
|
+
: defaultAgentId;
|
|
171
|
+
workerAgentId = parseSingleAgentSelection(await question(`Choose Worker Agent [${workerFallback}]: `), prepared, workerFallback);
|
|
172
|
+
const workerAgent = prepared.find(({ id }) => id === workerAgentId);
|
|
173
|
+
if (workerAgent === undefined) {
|
|
174
|
+
throw usageError("Selected Worker Agent is no longer available.");
|
|
175
|
+
}
|
|
176
|
+
workerConfig = await promptRoleAgentConfig("Worker", workerAgent, existingWorker, home, selectionIo, catalogs);
|
|
177
|
+
}
|
|
178
|
+
const suggestedWorkspace = config.defaultWorkspace?.trim()
|
|
179
|
+
|| join(dirname(resolve(home)), "workspace");
|
|
180
|
+
const workspaceAnswer = (await question(`Project workspace for stable checkouts and managed worktrees [${suggestedWorkspace}]: `)).trim();
|
|
181
|
+
const workspace = resolveWorkspace(workspaceAnswer || suggestedWorkspace, home);
|
|
182
|
+
if (config.defaultWorkspace !== undefined
|
|
183
|
+
&& resolve(config.defaultWorkspace) !== workspace) {
|
|
184
|
+
throw usageError(`Project workspace is fixed after setup: ${resolve(config.defaultWorkspace)}.`);
|
|
185
|
+
}
|
|
186
|
+
store.transaction((tx) => {
|
|
187
|
+
for (const agent of prepared)
|
|
188
|
+
tx.saveConfiguredAgent(agent);
|
|
189
|
+
const latestDefaultAgent = requireSetupAgent(tx, defaultAgentId);
|
|
190
|
+
const latestOperatorAgent = requireSetupAgent(tx, operatorAgentId);
|
|
191
|
+
const latestWorkerAgent = requireSetupAgent(tx, workerAgentId);
|
|
192
|
+
const operatorRole = prepareSystemRole(tx, SYSTEM_OPERATOR_ROLE, latestOperatorAgent, workspace, now, operatorConfig);
|
|
193
|
+
const leaderRole = prepareSystemRole(tx, SYSTEM_LEADER_ROLE, latestDefaultAgent, workspace, now, leaderConfig);
|
|
194
|
+
const workerRole = prepareSystemRole(tx, SYSTEM_WORKER_ROLE, latestWorkerAgent, workspace, now, workerConfig);
|
|
195
|
+
const reviewerRole = reviewerInitialized
|
|
196
|
+
&& reviewerAgentId !== undefined
|
|
197
|
+
&& reviewerConfig !== undefined
|
|
198
|
+
? prepareSystemRole(tx, DEFAULT_REVIEWER_ROLE, requireSetupAgent(tx, reviewerAgentId), workspace, now, reviewerConfig, freshHome ? reviewerRoleProfile() : undefined)
|
|
199
|
+
: null;
|
|
200
|
+
const latest = tx.getConfig();
|
|
201
|
+
tx.saveConfig({
|
|
202
|
+
...latest,
|
|
203
|
+
defaultAgent: defaultAgentId,
|
|
204
|
+
defaultWorkspace: workspace,
|
|
205
|
+
timeZone: resolveTimeZone(latest.timeZone),
|
|
206
|
+
...(freshHome
|
|
207
|
+
? { review: { roleName: DEFAULT_REVIEWER_ROLE, trigger: "final" } }
|
|
208
|
+
: {})
|
|
209
|
+
});
|
|
210
|
+
if (operatorRole !== null)
|
|
211
|
+
savePreparedSystemRole(tx, operatorRole, now);
|
|
212
|
+
if (leaderRole !== null)
|
|
213
|
+
savePreparedSystemRole(tx, leaderRole, now);
|
|
214
|
+
if (workerRole !== null)
|
|
215
|
+
savePreparedSystemRole(tx, workerRole, now);
|
|
216
|
+
if (reviewerRole !== null)
|
|
217
|
+
savePreparedSystemRole(tx, reviewerRole, now);
|
|
218
|
+
seedBuiltinProfiles(tx, now);
|
|
115
219
|
});
|
|
116
|
-
ensureSystemRole(store, SYSTEM_OPERATOR_ROLE, operatorAgent, workspace, now);
|
|
117
|
-
ensureSystemRole(store, SYSTEM_LEADER_ROLE, defaultAgent, workspace, now);
|
|
118
220
|
return {
|
|
119
|
-
agentIds:
|
|
221
|
+
agentIds: prepared.map(({ id }) => id),
|
|
120
222
|
defaultAgentId,
|
|
121
223
|
operatorAgentId,
|
|
224
|
+
leaderConfig,
|
|
225
|
+
operatorConfig,
|
|
226
|
+
workerAgentId,
|
|
227
|
+
workerConfig,
|
|
228
|
+
workerReusesLeader,
|
|
229
|
+
reviewerInitialized,
|
|
230
|
+
...(reviewerAgentId === undefined ? {} : { reviewerAgentId }),
|
|
231
|
+
...(reviewerConfig === undefined ? {} : { reviewerConfig }),
|
|
232
|
+
...(freshHome
|
|
233
|
+
? { reviewPolicy: { roleName: DEFAULT_REVIEWER_ROLE, trigger: "final" } }
|
|
234
|
+
: initialConfig.review === undefined ? {} : { reviewPolicy: initialConfig.review }),
|
|
122
235
|
workspace
|
|
123
236
|
};
|
|
124
237
|
}
|
|
238
|
+
function reviewerRoleProfile() {
|
|
239
|
+
const reviewer = builtinAgentProfileInputs().find(({ id }) => id === "reviewer");
|
|
240
|
+
if (reviewer === undefined)
|
|
241
|
+
return {};
|
|
242
|
+
return {
|
|
243
|
+
...(reviewer.description === undefined ? {} : { description: reviewer.description }),
|
|
244
|
+
...(reviewer.instructions === undefined ? {} : { systemPrompt: reviewer.instructions }),
|
|
245
|
+
...(reviewer.skills === undefined ? {} : { skills: [...reviewer.skills] })
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function workerConfigurationModeFallback(existing, leaderAgentId, leaderConfig) {
|
|
249
|
+
if (existing === null)
|
|
250
|
+
return "reuse-leader";
|
|
251
|
+
return existing.activeAgentId === leaderAgentId
|
|
252
|
+
&& isDeepStrictEqual(existing.agentBindings[leaderAgentId]?.config, leaderConfig)
|
|
253
|
+
? "reuse-leader"
|
|
254
|
+
: "configure-separately";
|
|
255
|
+
}
|
|
256
|
+
function parseWorkerConfigurationMode(answer, fallback) {
|
|
257
|
+
const value = answer.trim().toLowerCase();
|
|
258
|
+
if (value.length === 0)
|
|
259
|
+
return fallback;
|
|
260
|
+
if (["1", "reuse", "reuse leader", "leader"].includes(value))
|
|
261
|
+
return "reuse-leader";
|
|
262
|
+
if (["2", "configure", "configure separately", "separate"].includes(value)) {
|
|
263
|
+
return "configure-separately";
|
|
264
|
+
}
|
|
265
|
+
throw usageError("Choose reuse Leader or configure separately for Worker.");
|
|
266
|
+
}
|
|
267
|
+
function savePreparedSystemRole(store, role, now) {
|
|
268
|
+
const sessions = store.getGlobalRoleSessionSet(role.name);
|
|
269
|
+
const activeSession = sessions?.sessions[sessions.activeAgentId];
|
|
270
|
+
if (sessions === null
|
|
271
|
+
|| sessions.activeAgentId === role.activeAgentId
|
|
272
|
+
|| (activeSession !== undefined && activeSession.status !== "stopped")) {
|
|
273
|
+
store.saveGlobalRole(role);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
store.saveGlobalRoleWithSessionSet(role, {
|
|
277
|
+
...sessions,
|
|
278
|
+
activeAgentId: role.activeAgentId,
|
|
279
|
+
updatedAt: now.toISOString()
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function seedBuiltinProfiles(store, now) {
|
|
283
|
+
for (const desired of builtinAgentProfileInputs()) {
|
|
284
|
+
const existing = store.getAgentProfile(desired.id);
|
|
285
|
+
if (existing === null)
|
|
286
|
+
store.saveAgentProfile(createAgentProfile(desired, now));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async function promptRoleAgentConfig(label, agent, existingRole, cwd, io, catalogs, selectPermission = false) {
|
|
290
|
+
const existing = existingRole?.activeAgentId === agent.id
|
|
291
|
+
? existingRole.agentBindings[agent.id]?.config
|
|
292
|
+
: undefined;
|
|
293
|
+
io.write(`\n${label} Agent configuration: ${agent.id}\n`);
|
|
294
|
+
const resolved = await catalogs.resolve({
|
|
295
|
+
agent,
|
|
296
|
+
cwd,
|
|
297
|
+
...(existing === undefined ? {} : { config: existing })
|
|
298
|
+
});
|
|
299
|
+
const selection = await selectAgentModelAndEffort(resolved, io, {
|
|
300
|
+
currentModel: existing?.model,
|
|
301
|
+
currentEffort: existing?.effort
|
|
302
|
+
});
|
|
303
|
+
if (selection.kind === "cancelled") {
|
|
304
|
+
throw usageError(`${label} Agent configuration was cancelled.`);
|
|
305
|
+
}
|
|
306
|
+
const candidate = structuredClone(existing ?? defaultRoleAgentConfig(agent.adapterId));
|
|
307
|
+
if (selection.model === undefined)
|
|
308
|
+
delete candidate.model;
|
|
309
|
+
else
|
|
310
|
+
candidate.model = selection.model;
|
|
311
|
+
if (selection.effort === undefined)
|
|
312
|
+
delete candidate.effort;
|
|
313
|
+
else
|
|
314
|
+
candidate.effort = selection.effort;
|
|
315
|
+
if (selectPermission) {
|
|
316
|
+
const permission = await selectAgentPermission(resolved, io, candidate.permission);
|
|
317
|
+
if (permission.kind === "cancelled") {
|
|
318
|
+
throw usageError(`${label} permission configuration was cancelled.`);
|
|
319
|
+
}
|
|
320
|
+
candidate.permission = permission.permission;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
return resolveAgentAdapter(agent.adapterId).canonicalizeConfig(candidate);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
throw usageError(error instanceof Error ? error.message : String(error));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
125
329
|
function availableAgentChoices(store, env) {
|
|
126
330
|
const existing = new Map(store.listConfiguredAgents().map((agent) => [agent.id, agent]));
|
|
127
331
|
return BUILTIN_AGENTS.flatMap((builtin) => {
|
|
@@ -136,7 +340,16 @@ function availableAgentChoices(store, env) {
|
|
|
136
340
|
}];
|
|
137
341
|
});
|
|
138
342
|
}
|
|
139
|
-
function
|
|
343
|
+
function setupSelectionIo(question, io) {
|
|
344
|
+
return {
|
|
345
|
+
interactive: true,
|
|
346
|
+
json: false,
|
|
347
|
+
width: tableWidth(io),
|
|
348
|
+
write: (value) => { io.output.write(value); },
|
|
349
|
+
question
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function prepareAgent(store, choice, now) {
|
|
140
353
|
const existing = store.getConfiguredAgent(choice.id);
|
|
141
354
|
if (existing !== null
|
|
142
355
|
&& existing.adapterId === choice.adapterId
|
|
@@ -144,24 +357,49 @@ function persistAgent(store, choice, now) {
|
|
|
144
357
|
return existing;
|
|
145
358
|
}
|
|
146
359
|
const agent = createConfiguredAgent(choice.id, choice.adapterId, choice.command, existing?.baseArgs ?? [], existing?.environment ?? [], now);
|
|
147
|
-
store.saveConfiguredAgent(agent);
|
|
148
360
|
return agent;
|
|
149
361
|
}
|
|
150
|
-
function
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
store.saveGlobalRole(createGlobalRole(name, [createRoleAgentBinding(definition)], definition.id, workspace, now));
|
|
362
|
+
function requireSetupAgent(store, agentId) {
|
|
363
|
+
const agent = store.getConfiguredAgent(agentId);
|
|
364
|
+
if (agent === null)
|
|
365
|
+
throw usageError(`Configured Agent not found: ${agentId}.`);
|
|
366
|
+
return agent;
|
|
156
367
|
}
|
|
157
|
-
function
|
|
368
|
+
function prepareSystemRole(store, name, agent, workspace, now, config, profile) {
|
|
158
369
|
const existing = store.getGlobalRole(name);
|
|
159
|
-
if (existing
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
370
|
+
if (existing !== null) {
|
|
371
|
+
const definition = configuredAgentToDefinition(agent);
|
|
372
|
+
const binding = createRoleAgentBinding(definition, config);
|
|
373
|
+
if (name === "operator" && !Object.hasOwn(existing.agentBindings, agent.id)) {
|
|
374
|
+
const sameAdapter = Object.values(existing.agentBindings).find((candidate) => candidate.adapterId === agent.adapterId);
|
|
375
|
+
if (sameAdapter !== undefined) {
|
|
376
|
+
throw usageError(`${name} already has a ${agent.adapterId} Agent: ${sameAdapter.agentId}. `
|
|
377
|
+
+ "Update that Agent's configuration, or activate another adapter and "
|
|
378
|
+
+ "unbind it before selecting this Agent.");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (existing.activeAgentId === agent.id
|
|
382
|
+
&& existing.workspace === workspace
|
|
383
|
+
&& isDeepStrictEqual(existing.agentBindings[agent.id], binding)) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
assertRoleRuntimeMutationAllowed(store, {
|
|
387
|
+
scope: "global",
|
|
388
|
+
roleName: name
|
|
389
|
+
}, "desired launch configuration update");
|
|
390
|
+
return updateGlobalRole(existing, {
|
|
391
|
+
activeAgentId: agent.id,
|
|
392
|
+
workspace,
|
|
393
|
+
agentBindings: { ...existing.agentBindings, [agent.id]: binding },
|
|
394
|
+
...(profile === undefined ? {} : profile)
|
|
395
|
+
}, now);
|
|
396
|
+
}
|
|
397
|
+
assertRoleRuntimeMutationAllowed(store, {
|
|
398
|
+
scope: "global",
|
|
399
|
+
roleName: name
|
|
400
|
+
}, "creation");
|
|
401
|
+
const definition = configuredAgentToDefinition(agent);
|
|
402
|
+
return createGlobalRole(name, [createRoleAgentBinding(definition, config)], definition.id, workspace, now, profile);
|
|
165
403
|
}
|
|
166
404
|
function parseAgentSetSelection(answer, candidates) {
|
|
167
405
|
const value = answer.trim().toLowerCase();
|
|
@@ -197,10 +435,22 @@ function parseSingleAgentSelection(answer, agents, fallback) {
|
|
|
197
435
|
}
|
|
198
436
|
return selected.id;
|
|
199
437
|
}
|
|
200
|
-
function resolveWorkspace(value) {
|
|
438
|
+
function resolveWorkspace(value, home) {
|
|
201
439
|
if (!isAbsolute(value))
|
|
202
|
-
throw usageError("
|
|
203
|
-
|
|
440
|
+
throw usageError("Project workspace must be an absolute path.");
|
|
441
|
+
const requested = resolve(value);
|
|
442
|
+
assertWorkspaceOutsideHome(requested, resolve(home));
|
|
443
|
+
mkdirSync(requested, { recursive: true, mode: 0o700 });
|
|
444
|
+
const workspace = realpathSync(requested);
|
|
445
|
+
const homeRoot = realpathSync(resolve(home));
|
|
446
|
+
assertWorkspaceOutsideHome(workspace, homeRoot);
|
|
447
|
+
return workspace;
|
|
448
|
+
}
|
|
449
|
+
function assertWorkspaceOutsideHome(workspace, homeRoot) {
|
|
450
|
+
const fromHome = relative(homeRoot, workspace);
|
|
451
|
+
if (fromHome === "" || (!fromHome.startsWith("..") && !isAbsolute(fromHome))) {
|
|
452
|
+
throw usageError("Project workspace must be outside YUI_HOME.");
|
|
453
|
+
}
|
|
204
454
|
}
|
|
205
455
|
function commandOnPath(command, env) {
|
|
206
456
|
if (command.includes("/") || command.includes("\\"))
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { StorageCompatibilityError, loadCompatibleSnapshot } from "./migration/index.js";
|
|
4
|
+
import { createProductionStorageRegistry } from "./migration/productionRegistry.js";
|
|
5
|
+
import { FileTaskStore, validateCurrentStorageStateSnapshot } from "./taskStore.js";
|
|
6
|
+
import { ensureStorageSchema, inspectStorageSchema, STORAGE_SCHEMA_FILE } from "./storageSchema.js";
|
|
7
|
+
import { classifyHome } from "./upgrade/homeClassification.js";
|
|
8
|
+
import { inspectSnapshotVersionState } from "./upgrade/homeMigrationTarget.js";
|
|
9
|
+
import { latestStorageVersionState } from "./upgrade/recordVersions.js";
|
|
10
|
+
export { createProductionStorageRegistry } from "./migration/productionRegistry.js";
|
|
11
|
+
/**
|
|
12
|
+
* Initialize a brand-new Home, or open an existing Home through the same
|
|
13
|
+
* compatibility classification as every ordinary command. Setup is the one
|
|
14
|
+
* ordinary flow that is also responsible for creating the initial manifest.
|
|
15
|
+
*/
|
|
16
|
+
export function initializeCompatibleFileTaskStore(home, options = {}) {
|
|
17
|
+
if (inspectStorageSchema(home).status === "uninitialized") {
|
|
18
|
+
ensureStorageSchema(home);
|
|
19
|
+
}
|
|
20
|
+
return openCompatibleFileTaskStore(home, options);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Open current or explicitly compatible-old storage. Compatible records are
|
|
24
|
+
* normalized in memory through strict old-shape validators; FileTaskStore then
|
|
25
|
+
* runs its one current parser and its existing writer emits only current bytes.
|
|
26
|
+
*/
|
|
27
|
+
export function openCompatibleFileTaskStore(home, options = {}) {
|
|
28
|
+
const registry = options.registry ?? createProductionStorageRegistry();
|
|
29
|
+
const latest = options.latest ?? latestStorageVersionState();
|
|
30
|
+
const classification = classifyHome({ home, registry, latest });
|
|
31
|
+
switch (classification.classification.status) {
|
|
32
|
+
case "current":
|
|
33
|
+
return new FileTaskStore(home);
|
|
34
|
+
case "compatible-old":
|
|
35
|
+
return new FileTaskStore(home, {
|
|
36
|
+
normalizeState: (raw) => normalizeState(home, raw, registry, latest)
|
|
37
|
+
});
|
|
38
|
+
case "migration-required":
|
|
39
|
+
throw new StorageCompatibilityError("Storage requires an offline migration. Re-run `yui update` when active Sessions are clear.");
|
|
40
|
+
case "unsupported":
|
|
41
|
+
throw new StorageCompatibilityError(describeUnsupported(classification.classification));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Eagerly prove that a compatible-old Home reaches the strict current parser.
|
|
46
|
+
* Staged update/upgrade preflight uses this before any Controller or binary
|
|
47
|
+
* action; ordinary commands may keep the store's normal lazy-read behavior.
|
|
48
|
+
*/
|
|
49
|
+
export function validateCompatibleFileTaskStore(home, options = {}) {
|
|
50
|
+
openCompatibleFileTaskStore(home, options).getConfig();
|
|
51
|
+
}
|
|
52
|
+
function normalizeState(home, raw, registry, latest) {
|
|
53
|
+
let state;
|
|
54
|
+
try {
|
|
55
|
+
state = JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw new StorageCompatibilityError(`Compatible state is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
+
}
|
|
60
|
+
if (!isRecord(state)) {
|
|
61
|
+
throw new StorageCompatibilityError("Compatible state must be a JSON object.");
|
|
62
|
+
}
|
|
63
|
+
const schemaManifest = JSON.parse(readFileSync(join(home, STORAGE_SCHEMA_FILE), "utf8"));
|
|
64
|
+
if (!isRecord(schemaManifest)) {
|
|
65
|
+
throw new StorageCompatibilityError("Compatible schema manifest must be a JSON object.");
|
|
66
|
+
}
|
|
67
|
+
const snapshot = { schemaManifest, state };
|
|
68
|
+
const source = versionsOf(snapshot, latest);
|
|
69
|
+
const normalized = loadCompatibleSnapshot({
|
|
70
|
+
registry,
|
|
71
|
+
source,
|
|
72
|
+
latest,
|
|
73
|
+
snapshot,
|
|
74
|
+
inspectVersions: (candidate) => versionsOf(candidate, latest),
|
|
75
|
+
validateCurrent: (candidate) => {
|
|
76
|
+
if (candidate.state === null) {
|
|
77
|
+
throw new StorageCompatibilityError("Compatible state unexpectedly disappeared.");
|
|
78
|
+
}
|
|
79
|
+
validateCurrentStorageStateSnapshot(candidate.state);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return `${JSON.stringify(normalized.state)}\n`;
|
|
83
|
+
}
|
|
84
|
+
function versionsOf(snapshot, latest) {
|
|
85
|
+
const inspection = inspectSnapshotVersionState(snapshot, latest);
|
|
86
|
+
if ("corruption" in inspection) {
|
|
87
|
+
throw new StorageCompatibilityError(inspection.corruption.detail);
|
|
88
|
+
}
|
|
89
|
+
return inspection.source;
|
|
90
|
+
}
|
|
91
|
+
function describeUnsupported(classification) {
|
|
92
|
+
if (classification.verdict === "CORRUPTED") {
|
|
93
|
+
return `Invalid state.json: ${classification.detail}`;
|
|
94
|
+
}
|
|
95
|
+
if (classification.verdict === "NEEDS_NEW_VERSION") {
|
|
96
|
+
return classification.blocker.message;
|
|
97
|
+
}
|
|
98
|
+
return "Storage is unsupported by this Yui release.";
|
|
99
|
+
}
|
|
100
|
+
function isRecord(value) {
|
|
101
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
102
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { CURRENT_AGGREGATE_SCHEMA_VERSION, CURRENT_STORAGE_LAYOUT_VERSION } from "../storageVersions.js";
|
|
2
|
+
import { currentRecordVersions } from "../upgrade/recordVersions.js";
|
|
3
|
+
export const BASELINE_STORAGE_LAYOUT_VERSION = 6;
|
|
4
|
+
export const BASELINE_AGGREGATE_SCHEMA_VERSION = 16;
|
|
5
|
+
const BASELINE_RECORD_DESCRIPTORS = Object.freeze({
|
|
6
|
+
config: descriptor(1, "state.json#/config"),
|
|
7
|
+
configuredAgent: descriptor(2, "state.json#/configuredAgents"),
|
|
8
|
+
project: descriptor(2, "state.json#/projects"),
|
|
9
|
+
agentProfile: descriptor(2, "state.json#/agentProfiles"),
|
|
10
|
+
globalRole: descriptor(3, "state.json#/globalRoles"),
|
|
11
|
+
globalRoleSessionSet: descriptor(3, "state.json#/globalRoleSessionSets"),
|
|
12
|
+
storedTask: descriptor(14, "state.json#/tasks/*"),
|
|
13
|
+
task: descriptor(3, "state.json#/tasks/*/task"),
|
|
14
|
+
taskBrief: descriptor(2, "state.json#/tasks/*/brief"),
|
|
15
|
+
taskRole: descriptor(3, "state.json#/tasks/*/roles"),
|
|
16
|
+
managedWorkspace: descriptor(1, "state.json#/tasks/*/managedWorkspaces"),
|
|
17
|
+
taskRoleSessionSet: descriptor(4, "state.json#/tasks/*/roleSessionSets"),
|
|
18
|
+
workItem: descriptor(6, "state.json#/tasks/*/workItems"),
|
|
19
|
+
agentRun: descriptor(5, "state.json#/tasks/*/agentRuns"),
|
|
20
|
+
reviewRound: descriptor(2, "state.json#/tasks/*/reviewRounds"),
|
|
21
|
+
changeSet: descriptor(2, "state.json#/tasks/*/changeSets"),
|
|
22
|
+
integrationAttempt: descriptor(2, "state.json#/tasks/*/integrationAttempts"),
|
|
23
|
+
activeRunPointer: descriptor(1, "state.json#/tasks/*/activeRuns"),
|
|
24
|
+
message: descriptor(2, "state.json#/tasks/*/messages"),
|
|
25
|
+
inputRequest: descriptor(2, "state.json#/tasks/*/inputRequests"),
|
|
26
|
+
decision: descriptor(1, "state.json#/tasks/*/decisions"),
|
|
27
|
+
milestone: descriptor(1, "state.json#/tasks/*/milestones"),
|
|
28
|
+
event: descriptor(2, "state.json#/tasks/*/events"),
|
|
29
|
+
leaderFailure: descriptor(1, "state.json#/tasks/*/leaderFailure"),
|
|
30
|
+
operatorNotification: descriptor(1, "state.json#/tasks/*/operatorNotification"),
|
|
31
|
+
workMailbox: descriptor(1, "state.json#/mailboxes")
|
|
32
|
+
});
|
|
33
|
+
/** Frozen baseline record-family version map retained as a public delivery contract. */
|
|
34
|
+
export const BASELINE_RECORD_VERSIONS = Object.freeze(Object.fromEntries(Object.entries(BASELINE_RECORD_DESCRIPTORS).map(([kind, entry]) => [kind, entry.version])));
|
|
35
|
+
export function baselineStorageVersionState() {
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
layout: BASELINE_STORAGE_LAYOUT_VERSION,
|
|
38
|
+
aggregate: BASELINE_AGGREGATE_SCHEMA_VERSION,
|
|
39
|
+
record: Object.freeze({ ...BASELINE_RECORD_DESCRIPTORS })
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Constant spelling used by delivery-gate callers and historical tests. */
|
|
43
|
+
export const BASELINE_STORAGE_VERSION_STATE = baselineStorageVersionState();
|
|
44
|
+
/**
|
|
45
|
+
* Fail the delivery gate if the immutable baseline is ahead of the live schema,
|
|
46
|
+
* loses a record family, or changes a locator without an explicit scalar
|
|
47
|
+
* storage boundary. The production registry gate separately proves the full
|
|
48
|
+
* executable baseline-to-current transition path.
|
|
49
|
+
*/
|
|
50
|
+
export function assertBaselineConsistency(current = {
|
|
51
|
+
layout: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
52
|
+
aggregate: CURRENT_AGGREGATE_SCHEMA_VERSION,
|
|
53
|
+
record: currentRecordVersions()
|
|
54
|
+
}) {
|
|
55
|
+
if (BASELINE_STORAGE_LAYOUT_VERSION > current.layout) {
|
|
56
|
+
throw new Error(`Baseline layout ${BASELINE_STORAGE_LAYOUT_VERSION} exceeds current layout ${current.layout}.`);
|
|
57
|
+
}
|
|
58
|
+
if (BASELINE_AGGREGATE_SCHEMA_VERSION > current.aggregate) {
|
|
59
|
+
throw new Error(`Baseline aggregate ${BASELINE_AGGREGATE_SCHEMA_VERSION} exceeds current aggregate ${current.aggregate}.`);
|
|
60
|
+
}
|
|
61
|
+
const scalarVersionAdvanced = current.layout > BASELINE_STORAGE_LAYOUT_VERSION
|
|
62
|
+
|| current.aggregate > BASELINE_AGGREGATE_SCHEMA_VERSION;
|
|
63
|
+
for (const [kind, baselineEntry] of Object.entries(BASELINE_RECORD_DESCRIPTORS)) {
|
|
64
|
+
const currentEntry = current.record[kind];
|
|
65
|
+
if (!Number.isSafeInteger(baselineEntry.version) || baselineEntry.version < 1) {
|
|
66
|
+
throw new Error(`Baseline record family '${kind}' has invalid version ${String(baselineEntry.version)}.`);
|
|
67
|
+
}
|
|
68
|
+
if (currentEntry === undefined || baselineEntry.version > currentEntry.version) {
|
|
69
|
+
throw new Error(`Baseline record family '${kind}' is ahead of or missing from current.`);
|
|
70
|
+
}
|
|
71
|
+
if (baselineEntry.path !== currentEntry.path && !scalarVersionAdvanced) {
|
|
72
|
+
throw new Error(`Baseline record family '${kind}' path drift requires a layout or aggregate version change.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function descriptor(version, path) {
|
|
77
|
+
return Object.freeze({ version, path });
|
|
78
|
+
}
|