@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
package/dist/doctor/doctor.js
CHANGED
|
@@ -1,35 +1,131 @@
|
|
|
1
|
-
import { accessSync, constants, existsSync, lstatSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { configuredAgentToDefinition } from "../agent/agent.js";
|
|
4
|
-
import {
|
|
1
|
+
import { accessSync, constants, existsSync, lstatSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
|
|
4
|
+
import { operationalAgentEnvironment } from "../agent/launchEnvironment.js";
|
|
5
|
+
import { inspectAgentCapabilities, resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
6
|
+
import { inspectCodexLaunchConfig } from "../executor/codexConfigConflict.js";
|
|
7
|
+
import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
8
|
+
import { compileRoleSessionContext } from "../context/roleSessionContext.js";
|
|
5
9
|
import { usageError } from "../errors/cliError.js";
|
|
6
10
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
7
|
-
import {
|
|
11
|
+
import { resolveYuiHome, STORAGE_STATE_FILE } from "../storage/taskStore.js";
|
|
12
|
+
import { createProductionStorageRegistry, openCompatibleFileTaskStore, validateCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
8
13
|
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
14
|
+
import { classifyHome } from "../storage/upgrade/homeClassification.js";
|
|
15
|
+
import { latestStorageVersionState } from "../storage/upgrade/recordVersions.js";
|
|
9
16
|
import { CommandExecutionError } from "../tmux/commandExecutor.js";
|
|
17
|
+
import { EPHEMERAL_DOMAIN_GRACE_MS, readEphemeralDomainIdentity, readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
|
|
18
|
+
/**
|
|
19
|
+
* The storage-relevant doctor checks, in the order they are produced. Post-update
|
|
20
|
+
* verification (P1-3) keys off exactly these: a green `yui doctor` for the update
|
|
21
|
+
* flow means every one of these is `ok`. Tool/agent checks are diagnostic only
|
|
22
|
+
* and never block an update.
|
|
23
|
+
*/
|
|
24
|
+
export const STORAGE_DOCTOR_CHECK_NAMES = Object.freeze([
|
|
25
|
+
"storage schema",
|
|
26
|
+
"storage compatibility",
|
|
27
|
+
"storage state"
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* Classify the storage checks into a machine-readable health verdict. This is the
|
|
31
|
+
* canonical, parseable signal the update post-verify consumes (P1-3): storage is
|
|
32
|
+
* healthy only when schema, compatibility, and state are all `ok`. Any
|
|
33
|
+
* `unsupported` (version mismatch / needs-new-version), `invalid` (corrupted /
|
|
34
|
+
* unreadable), or `missing` (uninitialized) storage check is blocking — even
|
|
35
|
+
* though `yui doctor` itself exits 0.
|
|
36
|
+
*/
|
|
37
|
+
export function summarizeStorageHealth(checks) {
|
|
38
|
+
const names = new Set(STORAGE_DOCTOR_CHECK_NAMES);
|
|
39
|
+
const blocking = checks.filter((check) => names.has(check.name) && check.status !== "ok");
|
|
40
|
+
return { healthy: blocking.length === 0, blocking };
|
|
41
|
+
}
|
|
42
|
+
/** Build the full machine-readable doctor report (checks + storage health). */
|
|
43
|
+
export function buildDoctorReport(env, executor, storageOptions = {}) {
|
|
44
|
+
const inspection = inspectDoctor(env, executor, storageOptions);
|
|
45
|
+
return {
|
|
46
|
+
checks: inspection.checks,
|
|
47
|
+
storage: summarizeStorageHealth(inspection.checks),
|
|
48
|
+
review: inspection.review
|
|
49
|
+
};
|
|
50
|
+
}
|
|
10
51
|
/** Runs the read-only FileTaskStore diagnostics used by `yui doctor`. */
|
|
11
|
-
export function runDoctorCommand(args, env, executor) {
|
|
52
|
+
export function runDoctorCommand(args, env, executor, storageOptions = {}) {
|
|
12
53
|
if (args.length !== 0)
|
|
13
54
|
throw usageError("Doctor usage: yui doctor");
|
|
14
|
-
|
|
55
|
+
const inspection = inspectDoctor(env, executor, storageOptions);
|
|
56
|
+
return renderDoctor(inspection.checks, inspection.review);
|
|
57
|
+
}
|
|
58
|
+
export function getDoctorChecks(env, executor, storageOptions = {}) {
|
|
59
|
+
return inspectDoctor(env, executor, storageOptions).checks;
|
|
15
60
|
}
|
|
16
|
-
|
|
61
|
+
function inspectDoctor(env, executor, storageOptions = {}) {
|
|
17
62
|
const home = resolveYuiHome(env);
|
|
18
63
|
const homeCheck = checkHome(home);
|
|
19
64
|
const schema = readSchema(home);
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
storage.check,
|
|
65
|
+
const compatibility = inspectCompatibility(home, homeCheck, schema, storageOptions);
|
|
66
|
+
const schemaCheck = checkSchema(schema, compatibility);
|
|
67
|
+
const storage = inspectState(home, homeCheck, schema, compatibility);
|
|
68
|
+
const domain = checkEphemeralDomain(home);
|
|
69
|
+
const toolChecks = [
|
|
26
70
|
checkExecutable("git", env.YUI_GIT_BIN ?? "git", ["--version"], executor),
|
|
27
|
-
checkExecutable("tmux", env.YUI_TMUX_BIN ?? "tmux", ["-V"], executor)
|
|
28
|
-
...storage.agents.flatMap((agent) => checkAgent(agent, executor))
|
|
71
|
+
checkExecutable("tmux", env.YUI_TMUX_BIN ?? "tmux", ["-V"], executor)
|
|
29
72
|
];
|
|
73
|
+
const agentChecks = storage.agents.flatMap((agent) => checkAgent(agent, executor, env));
|
|
74
|
+
const review = inspectReview({ ...storage.review, home }, agentChecks, env);
|
|
75
|
+
return {
|
|
76
|
+
checks: [
|
|
77
|
+
homeCheck,
|
|
78
|
+
schemaCheck,
|
|
79
|
+
compatibility.check,
|
|
80
|
+
storage.check,
|
|
81
|
+
...(domain === undefined ? [] : [domain]),
|
|
82
|
+
...toolChecks,
|
|
83
|
+
...agentChecks,
|
|
84
|
+
...review.checks
|
|
85
|
+
],
|
|
86
|
+
review: review.report
|
|
87
|
+
};
|
|
30
88
|
}
|
|
31
|
-
|
|
32
|
-
|
|
89
|
+
function checkEphemeralDomain(home) {
|
|
90
|
+
const identity = readEphemeralDomainIdentity(home);
|
|
91
|
+
if (identity.status === "absent")
|
|
92
|
+
return undefined;
|
|
93
|
+
if (identity.status === "invalid" || identity.identity === undefined) {
|
|
94
|
+
return {
|
|
95
|
+
name: "ephemeral domain",
|
|
96
|
+
status: "invalid",
|
|
97
|
+
detail: "runtime/domain.json is invalid; recreate the isolated test domain."
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const host = readLinuxProcessStartIdentity(identity.identity.hostPid);
|
|
101
|
+
if (host === identity.identity.hostProcessStartIdentity) {
|
|
102
|
+
return {
|
|
103
|
+
name: "ephemeral domain",
|
|
104
|
+
status: "ok",
|
|
105
|
+
detail: `active host=${identity.identity.hostPid}:${host} token=${identity.identity.token.slice(0, 12)}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (host === undefined && existsSync(`/proc/${identity.identity.hostPid}`)) {
|
|
109
|
+
return {
|
|
110
|
+
name: "ephemeral domain",
|
|
111
|
+
status: "invalid",
|
|
112
|
+
detail: "host process is present but its start identity is unreadable; keep the domain protected and retry."
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const ageMs = Math.max(0, Date.now() - Date.parse(identity.identity.createdAt));
|
|
116
|
+
return {
|
|
117
|
+
name: "ephemeral domain",
|
|
118
|
+
status: ageMs >= EPHEMERAL_DOMAIN_GRACE_MS ? "missing" : "invalid",
|
|
119
|
+
detail: ageMs >= EPHEMERAL_DOMAIN_GRACE_MS
|
|
120
|
+
? "host is expired; run yui controller cleanup --all or wait for Controller recovery."
|
|
121
|
+
: "host identity is unavailable; bounded grace is still in progress."
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export function renderDoctor(checks, review) {
|
|
125
|
+
const reviewLine = review === undefined
|
|
126
|
+
? ""
|
|
127
|
+
: `Reviewer: ${review.status} (${review.detail})\n`;
|
|
128
|
+
return `Yui doctor\n${reviewLine}${renderTable("Checks", [
|
|
33
129
|
{ header: "Check", minWidth: 8, maxWidth: 28 },
|
|
34
130
|
{ header: "Status", minWidth: 7, maxWidth: 11 },
|
|
35
131
|
{ header: "Detail", minWidth: 12, maxWidth: 88 }
|
|
@@ -63,7 +159,7 @@ function readSchema(home) {
|
|
|
63
159
|
return { status: "read-error", detail: errorMessage(error) };
|
|
64
160
|
}
|
|
65
161
|
}
|
|
66
|
-
function checkSchema(state) {
|
|
162
|
+
function checkSchema(state, compatibility) {
|
|
67
163
|
switch (state.status) {
|
|
68
164
|
case "uninitialized":
|
|
69
165
|
return { name: "storage schema", status: "missing", detail: "run yui setup" };
|
|
@@ -74,6 +170,15 @@ function checkSchema(state) {
|
|
|
74
170
|
detail: `current=${state.currentVersion} latest=${state.latestVersion}`
|
|
75
171
|
};
|
|
76
172
|
case "unsupported":
|
|
173
|
+
if (compatibility.storageStatus === "compatible-old"
|
|
174
|
+
&& compatibility.check.status === "ok") {
|
|
175
|
+
return {
|
|
176
|
+
name: "storage schema",
|
|
177
|
+
status: "ok",
|
|
178
|
+
detail: `current=${state.currentVersion} latest=${state.latestVersion} `
|
|
179
|
+
+ `direction=${state.direction}; compatible-old validated`
|
|
180
|
+
};
|
|
181
|
+
}
|
|
77
182
|
return {
|
|
78
183
|
name: "storage schema",
|
|
79
184
|
status: "unsupported",
|
|
@@ -85,20 +190,134 @@ function checkSchema(state) {
|
|
|
85
190
|
return { name: "storage schema", status: "invalid", detail: state.detail };
|
|
86
191
|
}
|
|
87
192
|
}
|
|
88
|
-
|
|
193
|
+
/**
|
|
194
|
+
* The four-state storage compatibility check: USABLE / MIGRATABLE /
|
|
195
|
+
* NEEDS_NEW_VERSION / CORRUPTED. This complements the raw "storage schema" check
|
|
196
|
+
* with the migration framework's verdict, so a user sees whether a Home can be
|
|
197
|
+
* used as-is, upgraded with `yui upgrade`, needs a newer release, or is damaged.
|
|
198
|
+
* Doctor uses the same explicit production registry as `yui upgrade`; tests
|
|
199
|
+
* may inject a synthetic registry so the same classification and validation
|
|
200
|
+
* path proves compatible-old behavior. Missing paths fail closed, and
|
|
201
|
+
* CORRUPTED is reserved for structural or reference failure.
|
|
202
|
+
*/
|
|
203
|
+
function inspectCompatibility(home, homeCheck, schema, storageOptions) {
|
|
204
|
+
const name = "storage compatibility";
|
|
205
|
+
if (homeCheck.status !== "ok") {
|
|
206
|
+
return { check: { name, status: homeCheck.status, detail: homeCheck.detail } };
|
|
207
|
+
}
|
|
208
|
+
if (schema.status === "uninitialized") {
|
|
209
|
+
return { check: { name, status: "missing", detail: "run yui setup" } };
|
|
210
|
+
}
|
|
211
|
+
if (schema.status === "read-error") {
|
|
212
|
+
return { check: { name, status: "invalid", detail: schema.detail } };
|
|
213
|
+
}
|
|
214
|
+
let classification;
|
|
215
|
+
let resolvedStorageOptions;
|
|
216
|
+
try {
|
|
217
|
+
resolvedStorageOptions = {
|
|
218
|
+
registry: storageOptions.registry ?? createProductionStorageRegistry(),
|
|
219
|
+
latest: storageOptions.latest ?? latestStorageVersionState()
|
|
220
|
+
};
|
|
221
|
+
classification = classifyHome({
|
|
222
|
+
home,
|
|
223
|
+
registry: resolvedStorageOptions.registry,
|
|
224
|
+
latest: resolvedStorageOptions.latest
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return { check: { name, status: "invalid", detail: errorMessage(error) } };
|
|
229
|
+
}
|
|
230
|
+
const storageStatus = classification.classification.status;
|
|
231
|
+
if (storageStatus === "compatible-old") {
|
|
232
|
+
try {
|
|
233
|
+
validateCompatibleFileTaskStore(home, resolvedStorageOptions);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
return {
|
|
237
|
+
storageStatus,
|
|
238
|
+
storageOptions: resolvedStorageOptions,
|
|
239
|
+
check: {
|
|
240
|
+
name,
|
|
241
|
+
status: "invalid",
|
|
242
|
+
detail: `unsupported compatible-old shape: ${errorMessage(error)}`
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const versions = `layout=${classification.layoutVersion ?? "?"}/${classification.latestLayoutVersion}`
|
|
248
|
+
+ ` aggregate=${classification.aggregateVersion ?? "?"}/${classification.latestAggregateVersion}`;
|
|
249
|
+
const incompatible = classification.incompatibleComponent === undefined
|
|
250
|
+
? ""
|
|
251
|
+
: ` incompatibleComponent=${classification.incompatibleComponent}`;
|
|
252
|
+
switch (storageStatus) {
|
|
253
|
+
case "current":
|
|
254
|
+
return {
|
|
255
|
+
storageStatus,
|
|
256
|
+
storageOptions: resolvedStorageOptions,
|
|
257
|
+
check: { name, status: "ok", detail: `current (USABLE) ${versions}` }
|
|
258
|
+
};
|
|
259
|
+
case "compatible-old":
|
|
260
|
+
return {
|
|
261
|
+
storageStatus,
|
|
262
|
+
storageOptions: resolvedStorageOptions,
|
|
263
|
+
check: {
|
|
264
|
+
name,
|
|
265
|
+
status: "ok",
|
|
266
|
+
detail: `compatible-old (COMPATIBLE) ${versions}; ordinary commands are supported`
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
case "migration-required":
|
|
270
|
+
return {
|
|
271
|
+
storageStatus,
|
|
272
|
+
storageOptions: resolvedStorageOptions,
|
|
273
|
+
check: {
|
|
274
|
+
name,
|
|
275
|
+
status: "ok",
|
|
276
|
+
detail: `migration-required (MIGRATABLE) ${versions}; run yui update when Sessions are clear`
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
case "unsupported": {
|
|
280
|
+
const unsupported = classification.classification;
|
|
281
|
+
return {
|
|
282
|
+
storageStatus,
|
|
283
|
+
storageOptions: resolvedStorageOptions,
|
|
284
|
+
check: unsupported.verdict === "CORRUPTED"
|
|
285
|
+
? {
|
|
286
|
+
name,
|
|
287
|
+
status: "invalid",
|
|
288
|
+
detail: `unsupported (CORRUPTED): ${unsupported.detail}`
|
|
289
|
+
}
|
|
290
|
+
: {
|
|
291
|
+
name,
|
|
292
|
+
status: "unsupported",
|
|
293
|
+
detail: `unsupported (NEEDS_NEW_VERSION) ${versions}${incompatible}; ` +
|
|
294
|
+
`${unsupported.blocker.reason}`
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function inspectState(home, homeCheck, schema, compatibility) {
|
|
89
301
|
if (homeCheck.status !== "ok") {
|
|
90
302
|
return blockedStorage(homeCheck.status, homeCheck.detail);
|
|
91
303
|
}
|
|
92
304
|
if (schema.status === "uninitialized") {
|
|
93
305
|
return blockedStorage("missing", "run yui setup");
|
|
94
306
|
}
|
|
95
|
-
if (schema.status === "unsupported") {
|
|
96
|
-
return blockedStorage("unsupported", `current=${schema.currentVersion} latest=${schema.latestVersion}`);
|
|
97
|
-
}
|
|
98
307
|
if (schema.status === "invalid")
|
|
99
308
|
return blockedStorage("invalid", schema.detail);
|
|
100
309
|
if (schema.status === "read-error")
|
|
101
310
|
return blockedStorage("invalid", schema.detail);
|
|
311
|
+
if (compatibility.check.status !== "ok") {
|
|
312
|
+
return blockedStorage(compatibility.check.status, storageBlockerDetail(compatibility.check));
|
|
313
|
+
}
|
|
314
|
+
if (compatibility.storageStatus !== "current"
|
|
315
|
+
&& compatibility.storageStatus !== "compatible-old") {
|
|
316
|
+
return blockedStorage("unsupported", compatibility.check.detail);
|
|
317
|
+
}
|
|
318
|
+
if (compatibility.storageOptions === undefined) {
|
|
319
|
+
return blockedStorage("invalid", "Storage compatibility options were not resolved.");
|
|
320
|
+
}
|
|
102
321
|
const statePath = join(home, STORAGE_STATE_FILE);
|
|
103
322
|
if (!existsSync(statePath))
|
|
104
323
|
return blockedStorage("missing", "run yui setup");
|
|
@@ -108,7 +327,7 @@ function inspectState(home, homeCheck, schema) {
|
|
|
108
327
|
return blockedStorage("invalid", `${STORAGE_STATE_FILE} must be a regular file.`);
|
|
109
328
|
}
|
|
110
329
|
accessSync(statePath, constants.R_OK);
|
|
111
|
-
const store =
|
|
330
|
+
const store = openCompatibleFileTaskStore(home, compatibility.storageOptions);
|
|
112
331
|
const config = store.getConfig();
|
|
113
332
|
const agents = store.listConfiguredAgents();
|
|
114
333
|
const tasks = store.listTasks();
|
|
@@ -120,17 +339,424 @@ function inspectState(home, homeCheck, schema) {
|
|
|
120
339
|
status: "ok",
|
|
121
340
|
detail: `readable agents=${agents.length} tasks=${tasks.length} roles=${roleCount} globalRoles=${globalRoles.length} defaultAgent=${config.defaultAgent ?? "none"}`
|
|
122
341
|
},
|
|
123
|
-
agents
|
|
342
|
+
agents,
|
|
343
|
+
review: {
|
|
344
|
+
storageReady: true,
|
|
345
|
+
storageDetail: "state.json is readable",
|
|
346
|
+
...(config.review === undefined ? {} : { policy: config.review }),
|
|
347
|
+
...(config.review === undefined
|
|
348
|
+
? {}
|
|
349
|
+
: {
|
|
350
|
+
role: store.getGlobalRole(config.review.roleName),
|
|
351
|
+
agent: (() => {
|
|
352
|
+
const role = store.getGlobalRole(config.review.roleName);
|
|
353
|
+
return role === null
|
|
354
|
+
? null
|
|
355
|
+
: store.getConfiguredAgent(role.activeAgentId);
|
|
356
|
+
})()
|
|
357
|
+
})
|
|
358
|
+
}
|
|
124
359
|
};
|
|
125
360
|
}
|
|
126
361
|
catch (error) {
|
|
127
362
|
return blockedStorage("invalid", errorMessage(error));
|
|
128
363
|
}
|
|
129
364
|
}
|
|
365
|
+
function storageBlockerDetail(check) {
|
|
366
|
+
const corruptionPrefix = "unsupported (CORRUPTED): ";
|
|
367
|
+
return check.status === "invalid" && check.detail.startsWith(corruptionPrefix)
|
|
368
|
+
? `Invalid state.json: ${check.detail.slice(corruptionPrefix.length)}`
|
|
369
|
+
: check.detail;
|
|
370
|
+
}
|
|
130
371
|
function blockedStorage(status, detail) {
|
|
131
372
|
return {
|
|
132
373
|
check: { name: "storage state", status, detail },
|
|
133
|
-
agents: []
|
|
374
|
+
agents: [],
|
|
375
|
+
review: {
|
|
376
|
+
storageReady: false,
|
|
377
|
+
storageDetail: detail
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function inspectReview(source, agentChecks, environment) {
|
|
382
|
+
if (!source.storageReady) {
|
|
383
|
+
const raw = source.home === undefined
|
|
384
|
+
? null
|
|
385
|
+
: inspectRawReview(source.home, source.storageDetail);
|
|
386
|
+
if (raw !== null)
|
|
387
|
+
return raw;
|
|
388
|
+
return {
|
|
389
|
+
report: {
|
|
390
|
+
status: "misconfigured",
|
|
391
|
+
providerNative: "unverified",
|
|
392
|
+
detail: `Review configuration unavailable: ${source.storageDetail}`
|
|
393
|
+
},
|
|
394
|
+
checks: [{
|
|
395
|
+
name: "review policy",
|
|
396
|
+
status: "invalid",
|
|
397
|
+
detail: `unavailable: ${source.storageDetail}`
|
|
398
|
+
}]
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const policy = source.policy;
|
|
402
|
+
if (policy === undefined) {
|
|
403
|
+
return {
|
|
404
|
+
report: {
|
|
405
|
+
status: "disabled",
|
|
406
|
+
providerNative: "unverified",
|
|
407
|
+
detail: "Review policy is disabled; no Reviewer dispatch is scheduled."
|
|
408
|
+
},
|
|
409
|
+
checks: [{
|
|
410
|
+
name: "review policy",
|
|
411
|
+
status: "ok",
|
|
412
|
+
detail: "disabled"
|
|
413
|
+
}]
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const checks = [{
|
|
417
|
+
name: "review policy",
|
|
418
|
+
status: "ok",
|
|
419
|
+
detail: `role=${policy.roleName} trigger=${policy.trigger}`
|
|
420
|
+
}];
|
|
421
|
+
const role = source.role ?? null;
|
|
422
|
+
if (role === null) {
|
|
423
|
+
checks.push({
|
|
424
|
+
name: "reviewer role",
|
|
425
|
+
status: "missing",
|
|
426
|
+
detail: `Global Role not found: ${policy.roleName}.`
|
|
427
|
+
});
|
|
428
|
+
return reviewMisconfigured(policy, checks, `Reviewer Role is missing: ${policy.roleName}.`);
|
|
429
|
+
}
|
|
430
|
+
checks.push({
|
|
431
|
+
name: "reviewer role",
|
|
432
|
+
status: "ok",
|
|
433
|
+
detail: `name=${role.name} activeAgent=${role.activeAgentId}`
|
|
434
|
+
});
|
|
435
|
+
const binding = role.agentBindings[role.activeAgentId];
|
|
436
|
+
if (binding === undefined) {
|
|
437
|
+
checks.push({
|
|
438
|
+
name: "reviewer binding",
|
|
439
|
+
status: "missing",
|
|
440
|
+
detail: `active Agent binding is missing: ${role.activeAgentId}.`
|
|
441
|
+
});
|
|
442
|
+
return reviewMisconfigured(policy, checks, "Reviewer active Agent binding is missing.", role);
|
|
443
|
+
}
|
|
444
|
+
checks.push({
|
|
445
|
+
name: "reviewer binding",
|
|
446
|
+
status: "ok",
|
|
447
|
+
detail: `agent=${binding.agentId} adapter=${binding.adapterId}`
|
|
448
|
+
});
|
|
449
|
+
const agent = source.agent ?? null;
|
|
450
|
+
if (agent === null) {
|
|
451
|
+
checks.push({
|
|
452
|
+
name: "reviewer agent",
|
|
453
|
+
status: "missing",
|
|
454
|
+
detail: `Configured Agent not found: ${binding.agentId}.`
|
|
455
|
+
});
|
|
456
|
+
return reviewMisconfigured(policy, checks, `Reviewer Agent is missing: ${binding.agentId}.`, role, binding.agentId, binding.adapterId);
|
|
457
|
+
}
|
|
458
|
+
checks.push({
|
|
459
|
+
name: "reviewer agent",
|
|
460
|
+
status: "ok",
|
|
461
|
+
detail: `id=${agent.id} adapter=${agent.adapterId} command=${agent.command}`
|
|
462
|
+
});
|
|
463
|
+
if (agent.adapterId !== binding.adapterId) {
|
|
464
|
+
checks.push({
|
|
465
|
+
name: "reviewer adapter",
|
|
466
|
+
status: "invalid",
|
|
467
|
+
detail: `binding adapter=${binding.adapterId} does not match Agent adapter=${agent.adapterId}.`
|
|
468
|
+
});
|
|
469
|
+
return reviewMisconfigured(policy, checks, "Reviewer adapter identity does not match its Agent binding.", role, agent.id, agent.adapterId, agent.command);
|
|
470
|
+
}
|
|
471
|
+
let adapter;
|
|
472
|
+
try {
|
|
473
|
+
adapter = resolveAgentAdapter(agent.adapterId);
|
|
474
|
+
checks.push({
|
|
475
|
+
name: "reviewer adapter",
|
|
476
|
+
status: "ok",
|
|
477
|
+
detail: `adapter=${adapter.id} nativeSession=${adapter.capabilities.nativeSessionDiscovery}`
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
checks.push({
|
|
482
|
+
name: "reviewer adapter",
|
|
483
|
+
status: "unsupported",
|
|
484
|
+
detail: errorMessage(error)
|
|
485
|
+
});
|
|
486
|
+
return reviewMisconfigured(policy, checks, `Reviewer adapter is unavailable: ${errorMessage(error)}`, role, agent.id, agent.adapterId, agent.command);
|
|
487
|
+
}
|
|
488
|
+
const commandCheck = agentChecks.find((check) => check.name === `agent:${agent.id}:command`);
|
|
489
|
+
const capabilityCheck = agentChecks.find((check) => check.name === `agent:${agent.id}:capability`);
|
|
490
|
+
checks.push({
|
|
491
|
+
name: "reviewer command",
|
|
492
|
+
status: commandCheck?.status ?? "invalid",
|
|
493
|
+
detail: commandCheck === undefined
|
|
494
|
+
? `No command inspection was recorded for ${agent.id}.`
|
|
495
|
+
: commandCheck.detail
|
|
496
|
+
});
|
|
497
|
+
checks.push({
|
|
498
|
+
name: "reviewer capability",
|
|
499
|
+
status: capabilityCheck?.status ?? "invalid",
|
|
500
|
+
detail: capabilityCheck === undefined
|
|
501
|
+
? `No capability inspection was recorded for ${agent.id}.`
|
|
502
|
+
: capabilityCheck.detail
|
|
503
|
+
});
|
|
504
|
+
const launch = checkReviewerLaunch(agent, role, binding, adapter, environment, source.home);
|
|
505
|
+
checks.push(launch);
|
|
506
|
+
const dispatch = checkReviewerDispatch(policy, role, binding, agent, launch, commandCheck, capabilityCheck);
|
|
507
|
+
checks.push(dispatch);
|
|
508
|
+
const failedCheck = checks.find((check) => check.status !== "ok");
|
|
509
|
+
if (failedCheck !== undefined) {
|
|
510
|
+
return reviewMisconfigured(policy, checks, `${failedCheck.name}: ${failedCheck.detail}`, role, agent.id, agent.adapterId, agent.command);
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
report: {
|
|
514
|
+
status: "ready",
|
|
515
|
+
policy,
|
|
516
|
+
roleName: role.name,
|
|
517
|
+
activeAgentId: agent.id,
|
|
518
|
+
adapterId: agent.adapterId,
|
|
519
|
+
command: agent.command,
|
|
520
|
+
providerNative: "unverified",
|
|
521
|
+
detail: "Static Reviewer configuration and exact dispatch prerequisites are ready; provider-native acceptance is unverified."
|
|
522
|
+
},
|
|
523
|
+
checks
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Preserve a bounded Reviewer diagnosis when another malformed record prevents
|
|
528
|
+
* FileTaskStore from opening. This is read-only evidence, never a compatibility
|
|
529
|
+
* path: the storage check remains invalid and the review projection cannot be
|
|
530
|
+
* reported as ready from unvalidated bytes.
|
|
531
|
+
*/
|
|
532
|
+
function inspectRawReview(home, storageDetail) {
|
|
533
|
+
try {
|
|
534
|
+
const parsed = JSON.parse(readFileSync(join(home, STORAGE_STATE_FILE), "utf8"));
|
|
535
|
+
if (!isRecord(parsed) || !isRecord(parsed.config))
|
|
536
|
+
return null;
|
|
537
|
+
const rawPolicy = parsed.config.review;
|
|
538
|
+
if (!isRecord(rawPolicy)
|
|
539
|
+
|| typeof rawPolicy.roleName !== "string"
|
|
540
|
+
|| typeof rawPolicy.trigger !== "string") {
|
|
541
|
+
return {
|
|
542
|
+
report: {
|
|
543
|
+
status: "misconfigured",
|
|
544
|
+
providerNative: "unverified",
|
|
545
|
+
detail: `Storage state is invalid; Review policy is unavailable: ${storageDetail}`
|
|
546
|
+
},
|
|
547
|
+
checks: [{
|
|
548
|
+
name: "review policy",
|
|
549
|
+
status: "invalid",
|
|
550
|
+
detail: `storage state invalid: ${storageDetail}`
|
|
551
|
+
}]
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const policy = {
|
|
555
|
+
roleName: rawPolicy.roleName,
|
|
556
|
+
trigger: rawPolicy.trigger
|
|
557
|
+
};
|
|
558
|
+
const roles = isRecord(parsed.globalRoles) ? parsed.globalRoles : {};
|
|
559
|
+
const roleValue = roles[policy.roleName];
|
|
560
|
+
if (!isRecord(roleValue)) {
|
|
561
|
+
return rawReviewMisconfigured(policy, storageDetail, [{
|
|
562
|
+
name: "review policy",
|
|
563
|
+
status: "ok",
|
|
564
|
+
detail: `role=${policy.roleName} trigger=${policy.trigger}`
|
|
565
|
+
}, {
|
|
566
|
+
name: "reviewer role",
|
|
567
|
+
status: "missing",
|
|
568
|
+
detail: `Global Role not found: ${policy.roleName}.`
|
|
569
|
+
}]);
|
|
570
|
+
}
|
|
571
|
+
const roleName = typeof roleValue.name === "string" ? roleValue.name : policy.roleName;
|
|
572
|
+
const activeAgentId = typeof roleValue.activeAgentId === "string"
|
|
573
|
+
? roleValue.activeAgentId
|
|
574
|
+
: "";
|
|
575
|
+
const checks = [{
|
|
576
|
+
name: "review policy",
|
|
577
|
+
status: "ok",
|
|
578
|
+
detail: `role=${policy.roleName} trigger=${policy.trigger}`
|
|
579
|
+
}, {
|
|
580
|
+
name: "reviewer role",
|
|
581
|
+
status: "ok",
|
|
582
|
+
detail: `name=${roleName} activeAgent=${activeAgentId || "missing"}`
|
|
583
|
+
}];
|
|
584
|
+
const bindings = isRecord(roleValue.agentBindings) ? roleValue.agentBindings : {};
|
|
585
|
+
const bindingValue = activeAgentId.length === 0 ? undefined : bindings[activeAgentId];
|
|
586
|
+
if (!isRecord(bindingValue)) {
|
|
587
|
+
checks.push({
|
|
588
|
+
name: "reviewer binding",
|
|
589
|
+
status: "missing",
|
|
590
|
+
detail: `active Agent binding is missing: ${activeAgentId || "unknown"}.`
|
|
591
|
+
});
|
|
592
|
+
return rawReviewMisconfigured(policy, storageDetail, checks, roleName, activeAgentId || undefined);
|
|
593
|
+
}
|
|
594
|
+
const bindingAgentId = typeof bindingValue.agentId === "string"
|
|
595
|
+
? bindingValue.agentId
|
|
596
|
+
: activeAgentId;
|
|
597
|
+
const adapterId = typeof bindingValue.adapterId === "string"
|
|
598
|
+
? bindingValue.adapterId
|
|
599
|
+
: "unknown";
|
|
600
|
+
checks.push({
|
|
601
|
+
name: "reviewer binding",
|
|
602
|
+
status: "ok",
|
|
603
|
+
detail: `agent=${bindingAgentId} adapter=${adapterId}`
|
|
604
|
+
});
|
|
605
|
+
const configuredAgents = isRecord(parsed.configuredAgents)
|
|
606
|
+
? parsed.configuredAgents
|
|
607
|
+
: {};
|
|
608
|
+
const agentValue = configuredAgents[bindingAgentId];
|
|
609
|
+
if (!isRecord(agentValue)) {
|
|
610
|
+
checks.push({
|
|
611
|
+
name: "reviewer agent",
|
|
612
|
+
status: "missing",
|
|
613
|
+
detail: `Configured Agent not found: ${bindingAgentId}.`
|
|
614
|
+
});
|
|
615
|
+
return rawReviewMisconfigured(policy, storageDetail, checks, roleName, bindingAgentId, adapterId);
|
|
616
|
+
}
|
|
617
|
+
checks.push({
|
|
618
|
+
name: "reviewer agent",
|
|
619
|
+
status: "invalid",
|
|
620
|
+
detail: `Storage state is invalid; configured Agent ${bindingAgentId} cannot be validated.`
|
|
621
|
+
});
|
|
622
|
+
return rawReviewMisconfigured(policy, storageDetail, checks, roleName, bindingAgentId, adapterId, typeof agentValue.command === "string" ? agentValue.command : undefined);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function rawReviewMisconfigured(policy, storageDetail, checks, roleName, activeAgentId, adapterId, command) {
|
|
629
|
+
return {
|
|
630
|
+
report: {
|
|
631
|
+
status: "misconfigured",
|
|
632
|
+
policy,
|
|
633
|
+
...(roleName === undefined ? {} : { roleName }),
|
|
634
|
+
...(activeAgentId === undefined ? {} : { activeAgentId }),
|
|
635
|
+
...(adapterId === undefined ? {} : { adapterId }),
|
|
636
|
+
...(command === undefined ? {} : { command }),
|
|
637
|
+
providerNative: "unverified",
|
|
638
|
+
detail: `Storage state is invalid; ${checks.at(-1)?.detail ?? storageDetail}`
|
|
639
|
+
},
|
|
640
|
+
checks
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function isRecord(value) {
|
|
644
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
645
|
+
}
|
|
646
|
+
function reviewMisconfigured(policy, checks, detail, role, activeAgentId, adapterId, command) {
|
|
647
|
+
return {
|
|
648
|
+
report: {
|
|
649
|
+
status: "misconfigured",
|
|
650
|
+
policy,
|
|
651
|
+
roleName: role?.name ?? policy.roleName,
|
|
652
|
+
...(activeAgentId === undefined ? {} : { activeAgentId }),
|
|
653
|
+
...(adapterId === undefined ? {} : { adapterId }),
|
|
654
|
+
...(command === undefined ? {} : { command }),
|
|
655
|
+
providerNative: "unverified",
|
|
656
|
+
detail
|
|
657
|
+
},
|
|
658
|
+
checks
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function checkReviewerLaunch(agent, role, binding, adapter, environment, home) {
|
|
662
|
+
try {
|
|
663
|
+
const workspace = role.workspace;
|
|
664
|
+
if (!isAbsolute(workspace)) {
|
|
665
|
+
return {
|
|
666
|
+
name: "reviewer launch",
|
|
667
|
+
status: "invalid",
|
|
668
|
+
detail: "Reviewer Role workspace must be absolute."
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const metadata = lstatSync(workspace);
|
|
672
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
673
|
+
return {
|
|
674
|
+
name: "reviewer launch",
|
|
675
|
+
status: "invalid",
|
|
676
|
+
detail: "Reviewer Role workspace must be an existing real directory."
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
const definition = configuredAgentToDefinition(agent);
|
|
680
|
+
const launchEnvironment = resolveDoctorAgentEnvironment(definition, environment);
|
|
681
|
+
const effective = resolveEffectiveLaunch({ role, purpose: "execution" });
|
|
682
|
+
const agentWorkspace = effective.workspace.entries.length === 1
|
|
683
|
+
? effective.workspace.entries[0].path
|
|
684
|
+
: effective.workspace.root;
|
|
685
|
+
const codexConfig = adapter.id === "codex"
|
|
686
|
+
? inspectCodexLaunchConfig({
|
|
687
|
+
environment: launchEnvironment,
|
|
688
|
+
workspace: agentWorkspace,
|
|
689
|
+
profile: binding.config.adapterId === "codex" ? binding.config.profile : undefined,
|
|
690
|
+
trustWorkspace: true
|
|
691
|
+
})
|
|
692
|
+
: undefined;
|
|
693
|
+
if (codexConfig?.notify.status === "configured") {
|
|
694
|
+
return {
|
|
695
|
+
name: "reviewer launch",
|
|
696
|
+
status: "invalid",
|
|
697
|
+
detail: "Codex notify is already configured by "
|
|
698
|
+
+ `${codexConfig.notify.source}; Yui requires exclusive ownership of the structured `
|
|
699
|
+
+ "notify callback and refuses to replace or be replaced by native configuration."
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const reviewerContext = adapter.id === "codex"
|
|
703
|
+
? compileRoleSessionContext(home, role, { scope: "global" }, { purpose: "review" })
|
|
704
|
+
: undefined;
|
|
705
|
+
const compiled = adapter.compileNew({
|
|
706
|
+
agent: definition,
|
|
707
|
+
config: binding.config,
|
|
708
|
+
workspace: agentWorkspace,
|
|
709
|
+
sessionTitle: "reviewer",
|
|
710
|
+
...(reviewerContext === undefined
|
|
711
|
+
? {}
|
|
712
|
+
: {
|
|
713
|
+
developerInstructions: reviewerContext.developerInstructions,
|
|
714
|
+
skills: reviewerContext.skills,
|
|
715
|
+
codexDeveloperInstructions: codexConfig?.developerInstructions
|
|
716
|
+
})
|
|
717
|
+
});
|
|
718
|
+
if (compiled.argv.length === 0)
|
|
719
|
+
throw new Error("compiled launch argv is empty");
|
|
720
|
+
return {
|
|
721
|
+
name: "reviewer launch",
|
|
722
|
+
status: "ok",
|
|
723
|
+
detail: `adapter=${adapter.id} strategy=${compiled.sessionStrategy} command=${agent.command}`
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
catch (error) {
|
|
727
|
+
return {
|
|
728
|
+
name: "reviewer launch",
|
|
729
|
+
status: "invalid",
|
|
730
|
+
detail: errorMessage(error)
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function checkReviewerDispatch(_policy, role, binding, agent, launch, command, capability) {
|
|
735
|
+
if (role.defaultAccess !== "write") {
|
|
736
|
+
return {
|
|
737
|
+
name: "reviewer dispatch",
|
|
738
|
+
status: "invalid",
|
|
739
|
+
detail: "Reviewer Role must retain write access for an isolated ReviewRound workspace."
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
if (binding.agentId !== agent.id) {
|
|
743
|
+
return {
|
|
744
|
+
name: "reviewer dispatch",
|
|
745
|
+
status: "invalid",
|
|
746
|
+
detail: `active binding Agent id does not match configured Agent: ${binding.agentId}/${agent.id}.`
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
if (launch.status !== "ok" || command?.status !== "ok" || capability?.status !== "ok") {
|
|
750
|
+
return {
|
|
751
|
+
name: "reviewer dispatch",
|
|
752
|
+
status: "invalid",
|
|
753
|
+
detail: "Reviewer exact dispatch is blocked until command, capability, and launch checks are ok."
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
name: "reviewer dispatch",
|
|
758
|
+
status: "ok",
|
|
759
|
+
detail: "static exact dispatch prerequisites confirmed; no Session or model was launched."
|
|
134
760
|
};
|
|
135
761
|
}
|
|
136
762
|
function checkExecutable(name, command, args, executor) {
|
|
@@ -150,11 +776,13 @@ function checkExecutable(name, command, args, executor) {
|
|
|
150
776
|
};
|
|
151
777
|
}
|
|
152
778
|
}
|
|
153
|
-
function checkAgent(agent, executor) {
|
|
779
|
+
function checkAgent(agent, executor, environment) {
|
|
154
780
|
let snapshot;
|
|
155
781
|
try {
|
|
156
|
-
|
|
157
|
-
|
|
782
|
+
const definition = configuredAgentToDefinition(agent);
|
|
783
|
+
const launchEnvironment = resolveDoctorAgentEnvironment(definition, environment);
|
|
784
|
+
snapshot = inspectAgentCapabilities(definition, {
|
|
785
|
+
run: (command, args) => runAgentProbe(executor, command, args, launchEnvironment)
|
|
158
786
|
});
|
|
159
787
|
}
|
|
160
788
|
catch (error) {
|
|
@@ -184,13 +812,22 @@ function checkAgent(agent, executor) {
|
|
|
184
812
|
{
|
|
185
813
|
name: `agent:${agent.id}:capability`,
|
|
186
814
|
status,
|
|
187
|
-
detail:
|
|
815
|
+
detail: [
|
|
816
|
+
`start resume interrupt nativeSession=${snapshot.lifecycle.nativeSessionDiscovery}`,
|
|
817
|
+
`preInputReady=${snapshot.lifecycle.preInputReadiness.status}`,
|
|
818
|
+
`fields=${available}/${degraded}/${unavailable}`,
|
|
819
|
+
...snapshot.warnings.map((warning) => `warning=${warning}`)
|
|
820
|
+
].join(" ")
|
|
188
821
|
}
|
|
189
822
|
];
|
|
190
823
|
}
|
|
191
|
-
function runAgentProbe(executor, command, args) {
|
|
824
|
+
function runAgentProbe(executor, command, args, environment) {
|
|
192
825
|
try {
|
|
193
|
-
return {
|
|
826
|
+
return {
|
|
827
|
+
status: 0,
|
|
828
|
+
stdout: executor.run(command, [...args], { environment }),
|
|
829
|
+
stderr: ""
|
|
830
|
+
};
|
|
194
831
|
}
|
|
195
832
|
catch (error) {
|
|
196
833
|
const missing = isMissingCommand(error);
|
|
@@ -205,6 +842,18 @@ function runAgentProbe(executor, command, args) {
|
|
|
205
842
|
};
|
|
206
843
|
}
|
|
207
844
|
}
|
|
845
|
+
/**
|
|
846
|
+
* Resolve the complete environment used by FileRoleLaunchPlanner for an Agent.
|
|
847
|
+
* Doctor probes must inspect the same command resolution context as a native
|
|
848
|
+
* launch; inheriting the Doctor process PATH would allow a shell-installed
|
|
849
|
+
* command to mask a missing configured binding.
|
|
850
|
+
*/
|
|
851
|
+
function resolveDoctorAgentEnvironment(agent, environment) {
|
|
852
|
+
return {
|
|
853
|
+
...operationalAgentEnvironment(agent.adapterId, environment),
|
|
854
|
+
...resolveAgentEnvironment(agent, environment)
|
|
855
|
+
};
|
|
856
|
+
}
|
|
208
857
|
function isMissingCommand(error) {
|
|
209
858
|
return error instanceof CommandExecutionError
|
|
210
859
|
? error.code === "COMMAND_NOT_FOUND"
|