@zq-silk/yui 0.16.1 → 1.0.0-alpha
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 +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +9 -4
- package/dist/agent/agent.js +4 -9
- package/dist/agent/executionComponents.js +4 -4
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +13 -23
- package/dist/artifacts/managedGit.js +3 -56
- package/dist/brief/taskBrief.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -44
- package/dist/cli/updatePorts.js +76 -86
- package/dist/cli/upgradeCommand.js +4 -8
- package/dist/cli.js +39 -33
- package/dist/commands/controllerCommands.js +2 -2
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +91 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +2 -2
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/agentRuntimeObserver.js +4 -4
- package/dist/controller/clientRuntime.js +19 -44
- package/dist/controller/controller.js +24 -10
- package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
- package/dist/controller/globalInputDelivery.js +31 -8
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/jobSupervisor.js +3 -3
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +136 -119
- package/dist/controller/runtime.js +2 -28
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +28 -1
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/core/controllerIdentity.js +25 -0
- package/dist/core/controllerProcessIdentity.js +2 -2
- package/dist/core/controllerServer.js +4 -2
- package/dist/core/protocol.js +1 -1
- package/dist/doctor/doctor.js +2 -1
- package/dist/domain/validation.js +6 -0
- package/dist/event/taskEvent.js +3 -3
- package/dist/execution/workItemExecution.js +2 -2
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +11 -8
- package/dist/executor/effectiveLaunch.js +3 -3
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/grant/capabilityGrant.js +2 -2
- package/dist/input/inputRequest.js +4 -4
- package/dist/integration/changeSet.js +3 -3
- package/dist/integration/integrationAttempt.js +3 -3
- package/dist/integration/integrationSourceApplication.js +1 -1
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/job/durableJob.js +1 -1
- package/dist/job/jobRunner.js +2 -2
- package/dist/message/message.js +8 -6
- package/dist/message/messageContinuation.js +8 -9
- package/dist/milestone/milestone.js +3 -3
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/profile/agentProfile.js +3 -3
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/release/runtimeRelease.js +9 -7
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/project.js +3 -3
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +5 -7
- package/dist/review/reviewRound.js +5 -5
- package/dist/role/role.js +7 -8
- package/dist/runtime/acpProtocol.js +2 -3
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +25 -65
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +3 -3
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/codexInteractiveHost.js +1 -1
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/launchBroker.js +1 -1
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/providerContinuationReconciliationService.js +86 -38
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeIdentity.js +3 -3
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/runtimeCoherence.js +7 -3
- package/dist/runtime/runtimeObservation.js +3 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/runtime/taskRuntimeIsolation.js +3 -3
- package/dist/runtime/tmuxAdapters.js +3 -3
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/scheduler/taskWake.js +1 -1
- package/dist/storage/baselineSchema.js +606 -0
- package/dist/storage/homeLayout.js +5 -16
- package/dist/storage/recordValidation.js +16 -4
- package/dist/storage/sqliteSchema.js +106 -1741
- package/dist/storage/sqliteStore.js +16 -14
- package/dist/storage/storageSchema.js +8 -7
- package/dist/storage/storageVersions.js +23 -16
- package/dist/storage/taskStore.js +3 -29
- package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +17 -8
- package/dist/task/taskActivation.js +5 -4
- package/dist/telemetry/sqliteTelemetryStore.js +2 -2
- package/dist/verification/gateArtifact.js +5 -3
- package/dist/verification/verificationPlan.js +6 -7
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/dist/workItem/workItem.js +6 -6
- package/dist/workspace/cleanupInspection.js +1 -9
- package/dist/worktree/managedWorkspace.js +3 -3
- package/docs/managed-turn-and-session-runtime.md +50 -7
- package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +70 -214
- package/docs/release-workflow.zh-CN.md +53 -157
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/sqlite-control-plane-design.md +48 -289
- package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
- package/docs/storage-baseline.md +132 -0
- package/docs/storage-baseline.zh-CN.md +106 -0
- package/docs/task-delivery.md +110 -2
- package/docs/task-delivery.zh-CN.md +82 -2
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +42 -164
- package/docs/testing/verification-levels.zh-CN.md +29 -115
- package/i18n/README.zh-CN.md +7 -4
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
- package/dist/storage/migrations/agentFailureContext.js +0 -22
- package/dist/storage/migrations/agentRunContract.js +0 -159
- package/dist/storage/migrations/artifactsToGit.js +0 -338
- package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
- package/dist/storage/migrations/currentInputContract.js +0 -86
- package/dist/storage/migrations/currentRuntimeContract.js +0 -228
- package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
- package/dist/storage/migrations/integrationContinuation.js +0 -105
- package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
- package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
- package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
- package/dist/storage/migrations/submitIntent.js +0 -126
- package/dist/storage/migrations/unifyHomeLayout.js +0 -925
- package/dist/storage/migrations/verificationPlanV1.js +0 -162
- package/dist/storage/migrations/verificationPolicy.js +0 -74
- package/dist/storage/migrations/workItemHistory.js +0 -46
|
@@ -3,6 +3,14 @@ import { acquireHomeLifecycleLock } from "../core/controllerServer.js";
|
|
|
3
3
|
import { cleanControllerResource } from "./resourceCleanupLinux.js";
|
|
4
4
|
import { scanControllerResourceInventory } from "./resourceInventoryLinux.js";
|
|
5
5
|
const MAX_RECONCILIATION_PASSES = 4;
|
|
6
|
+
export class UpdateControllerReconciliationError extends Error {
|
|
7
|
+
result;
|
|
8
|
+
constructor(message, result, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.result = result;
|
|
11
|
+
this.name = "UpdateControllerReconciliationError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
6
14
|
/**
|
|
7
15
|
* Reconcile only Controller-owned resources for the Home being updated.
|
|
8
16
|
*
|
|
@@ -12,21 +20,49 @@ const MAX_RECONCILIATION_PASSES = 4;
|
|
|
12
20
|
* and inode fingerprints. Agent, tmux, app, and foreign-Home resources are
|
|
13
21
|
* deliberately outside this operation.
|
|
14
22
|
*/
|
|
15
|
-
export async function reconcileControllerResourcesForUpdate(home, environment = process.env, tmuxBin
|
|
23
|
+
export async function reconcileControllerResourcesForUpdate(home, environment = process.env, tmuxBin, ports = {
|
|
24
|
+
scan: scanControllerResourceInventory,
|
|
25
|
+
clean: cleanControllerResource,
|
|
26
|
+
acquireLock: acquireHomeLifecycleLock
|
|
27
|
+
}) {
|
|
16
28
|
const resolvedHome = resolve(home);
|
|
17
|
-
const releaseLock = await acquireHomeLifecycleLock(resolvedHome, {
|
|
18
|
-
removeStaleOwner: true
|
|
19
|
-
});
|
|
20
29
|
const cleaned = new Set();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
const attempts = [];
|
|
31
|
+
let remaining = [];
|
|
32
|
+
let observedAt;
|
|
33
|
+
let observationError;
|
|
34
|
+
let lockReleaseError;
|
|
35
|
+
const result = () => ({
|
|
36
|
+
home: resolvedHome, cleaned: [...cleaned], attempts: [...attempts], remaining: [...remaining],
|
|
37
|
+
...(observedAt === undefined ? {} : { observedAt }),
|
|
38
|
+
...(observationError === undefined ? {} : { observationError }),
|
|
39
|
+
...(lockReleaseError === undefined ? {} : { lockReleaseError })
|
|
40
|
+
});
|
|
41
|
+
const scan = async () => {
|
|
42
|
+
try {
|
|
43
|
+
const snapshot = await ports.scan({
|
|
44
|
+
currentHome: resolvedHome, scope: "current", environment,
|
|
27
45
|
...(tmuxBin === undefined ? {} : { tmuxBin })
|
|
28
46
|
});
|
|
47
|
+
if (snapshot.scope === "current" && resolve(snapshot.currentHome) === resolvedHome) {
|
|
48
|
+
remaining = controllerResources(snapshot, resolvedHome);
|
|
49
|
+
observedAt = snapshot.observedAt;
|
|
50
|
+
}
|
|
29
51
|
assertCertainSnapshot(snapshot, resolvedHome);
|
|
52
|
+
observationError = undefined;
|
|
53
|
+
return snapshot;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
observationError = messageOf(error);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
let releaseLock;
|
|
61
|
+
let failure;
|
|
62
|
+
try {
|
|
63
|
+
releaseLock = await ports.acquireLock(resolvedHome, { removeStaleOwner: true });
|
|
64
|
+
for (let pass = 0; pass < MAX_RECONCILIATION_PASSES; pass += 1) {
|
|
65
|
+
const snapshot = await scan();
|
|
30
66
|
const resources = controllerResources(snapshot, resolvedHome);
|
|
31
67
|
const controllers = resources.filter(({ kind }) => kind === "controller");
|
|
32
68
|
const current = controllers.filter(({ state }) => state === "current");
|
|
@@ -50,36 +86,52 @@ export async function reconcileControllerResourcesForUpdate(home, environment =
|
|
|
50
86
|
}
|
|
51
87
|
const candidates = [...historicalCleanup, ...staleArtifactCleanup];
|
|
52
88
|
if (candidates.length === 0) {
|
|
53
|
-
|
|
89
|
+
break;
|
|
54
90
|
}
|
|
55
91
|
for (const candidate of candidates) {
|
|
56
92
|
try {
|
|
57
|
-
await
|
|
93
|
+
await ports.clean(candidate, { environment, ...(tmuxBin === undefined ? {} : { tmuxBin }) });
|
|
58
94
|
cleaned.add(candidate.id);
|
|
95
|
+
attempts.push({ resource: candidate, outcome: "cleaned" });
|
|
96
|
+
remaining = remaining.filter(({ id }) => id !== candidate.id);
|
|
59
97
|
}
|
|
60
98
|
catch (error) {
|
|
61
99
|
// A concurrent exact cleanup that already reached the desired state
|
|
62
100
|
// is harmless. Anything still present or reclassified is a real
|
|
63
101
|
// ownership change and must remain a user-visible blocker.
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
assertCertainSnapshot(afterFailure, resolvedHome);
|
|
102
|
+
const attempt = { resource: candidate, outcome: "unknown", error: messageOf(error) };
|
|
103
|
+
attempts.push(attempt);
|
|
104
|
+
// If this observation also fails, keep BOTH failures and the exact
|
|
105
|
+
// attempted identity. Failure to observe absence is never success.
|
|
106
|
+
const afterFailure = await scan();
|
|
70
107
|
if (!controllerResources(afterFailure, resolvedHome).some(({ id }) => id === candidate.id)) {
|
|
71
108
|
cleaned.add(candidate.id);
|
|
109
|
+
attempts[attempts.length - 1] = { ...attempt, outcome: "absent" };
|
|
72
110
|
continue;
|
|
73
111
|
}
|
|
74
112
|
throw reconciliationBlocked(`resource ${candidate.id} changed or could not be cleaned: ${messageOf(error)}`);
|
|
75
113
|
}
|
|
76
114
|
}
|
|
115
|
+
if (pass === MAX_RECONCILIATION_PASSES - 1) {
|
|
116
|
+
throw reconciliationBlocked("Controller resources did not converge after bounded cleanup");
|
|
117
|
+
}
|
|
77
118
|
}
|
|
78
|
-
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
failure = error;
|
|
79
122
|
}
|
|
80
123
|
finally {
|
|
81
|
-
|
|
124
|
+
try {
|
|
125
|
+
await releaseLock?.();
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
lockReleaseError = messageOf(error);
|
|
129
|
+
failure ??= error;
|
|
130
|
+
}
|
|
82
131
|
}
|
|
132
|
+
if (failure !== undefined)
|
|
133
|
+
throw new UpdateControllerReconciliationError(messageOf(failure), result(), { cause: failure });
|
|
134
|
+
return result();
|
|
83
135
|
}
|
|
84
136
|
function assertCertainSnapshot(snapshot, resolvedHome) {
|
|
85
137
|
if (snapshot.scope !== "current"
|
|
@@ -2,7 +2,7 @@ import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
|
2
2
|
const RECENT_DEDUPE_KEY_LIMIT = 256;
|
|
3
3
|
export function createWorkMailbox(target) {
|
|
4
4
|
return {
|
|
5
|
-
schemaVersion:
|
|
5
|
+
schemaVersion: 1,
|
|
6
6
|
target: copyTarget(target),
|
|
7
7
|
nextSequence: 1,
|
|
8
8
|
processing: null,
|
|
@@ -13,8 +13,8 @@ export function createWorkMailbox(target) {
|
|
|
13
13
|
export function validateWorkMailbox(value) {
|
|
14
14
|
const mailbox = record(value, "WorkMailbox");
|
|
15
15
|
exact(mailbox, ["schemaVersion", "target", "nextSequence", "processing", "pending", "recentDedupeKeys"], "WorkMailbox");
|
|
16
|
-
if (mailbox.schemaVersion !==
|
|
17
|
-
throw new Error("WorkMailbox must use schemaVersion
|
|
16
|
+
if (mailbox.schemaVersion !== 1)
|
|
17
|
+
throw new Error("WorkMailbox must use schemaVersion 1");
|
|
18
18
|
const target = parseTarget(mailbox.target);
|
|
19
19
|
const nextSequence = requireInteger(mailbox.nextSequence, 1, "WorkMailbox nextSequence");
|
|
20
20
|
const processing = mailbox.processing === null ? null : parseProcessing(mailbox.processing);
|
|
@@ -38,7 +38,7 @@ export function validateWorkMailbox(value) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
return {
|
|
41
|
-
schemaVersion:
|
|
41
|
+
schemaVersion: 1,
|
|
42
42
|
target,
|
|
43
43
|
nextSequence,
|
|
44
44
|
processing,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { compareStorageVersions, isStorageVersion } from "../storage/storageVersions.js";
|
|
2
|
+
export function isControllerIdentity(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
4
|
+
return false;
|
|
5
|
+
const record = value;
|
|
6
|
+
return typeof record.executablePath === "string" && record.executablePath.length > 0
|
|
7
|
+
&& Array.isArray(record.args) && record.args.every(arg => typeof arg === "string")
|
|
8
|
+
&& typeof record.version === "string" && record.version.length > 0
|
|
9
|
+
&& Number.isSafeInteger(record.controllerProtocolVersion) && record.controllerProtocolVersion > 0
|
|
10
|
+
&& isStorageVersion(record.storageVersion) && isStorageVersion(record.minimumStorageVersion)
|
|
11
|
+
&& compareStorageVersions(record.minimumStorageVersion, record.storageVersion) <= 0;
|
|
12
|
+
}
|
|
13
|
+
export function parseControllerIdentity(value) {
|
|
14
|
+
if (!isControllerIdentity(value)) {
|
|
15
|
+
throw new Error("Controller identity must include the exact executable, argv, package, protocol and storage versions.");
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
executablePath: value.executablePath,
|
|
19
|
+
args: [...value.args],
|
|
20
|
+
version: value.version,
|
|
21
|
+
controllerProtocolVersion: value.controllerProtocolVersion,
|
|
22
|
+
storageVersion: value.storageVersion,
|
|
23
|
+
minimumStorageVersion: value.minimumStorageVersion
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
|
|
3
3
|
import { readHomeFilesystemId, validateHomeFilesystemId } from "./homeFilesystemIdentity.js";
|
|
4
|
-
/** Exact live-process fence used
|
|
4
|
+
/** Exact live-process fence used at Controller startup and handover. */
|
|
5
5
|
export function inspectLiveControllerProcess(pid, homeFilesystemId, expectedProcessStartIdentity) {
|
|
6
6
|
if (!Number.isSafeInteger(pid) || pid < 1 || pid === process.pid)
|
|
7
7
|
return undefined;
|
|
@@ -39,7 +39,7 @@ export function inspectLiveControllerProcess(pid, homeFilesystemId, expectedProc
|
|
|
39
39
|
return undefined;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
/** Finds
|
|
42
|
+
/** Finds a Controller whose discovery record was lost. */
|
|
43
43
|
export function findLiveControllerProcessForHome(homeFilesystemId) {
|
|
44
44
|
validateHomeFilesystemId(homeFilesystemId);
|
|
45
45
|
if (process.platform !== "linux")
|
|
@@ -171,7 +171,7 @@ async function startControllerServerLocked(home, dispatcher, beforeDiscoveryRemo
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
|
-
*
|
|
174
|
+
* Prevent a second Controller from claiming a Home with existing discovery.
|
|
175
175
|
* A dead or PID-reused owner is stale and may be replaced; an unverifiable
|
|
176
176
|
* live owner fails closed so an endpoint migration can never create dual
|
|
177
177
|
* writers for one durable Home.
|
|
@@ -362,6 +362,8 @@ async function routeRequest(socket, line, token, dispatcher, stop, status, telem
|
|
|
362
362
|
cliRealpath: controllerCliRealpath(),
|
|
363
363
|
controllerRealpath: realpathSync(fileURLToPath(import.meta.url)),
|
|
364
364
|
controllerProtocolVersion: FILE_TASK_CONTROLLER_PROTOCOL_VERSION,
|
|
365
|
+
storageVersion: yuiVersionIdentity().storageVersion,
|
|
366
|
+
minimumStorageVersion: yuiVersionIdentity().minimumStorageVersion,
|
|
365
367
|
homeId,
|
|
366
368
|
homeFilesystemId,
|
|
367
369
|
controllerInstanceId,
|
|
@@ -676,7 +678,7 @@ export function buildRuntimeIdentityReceipt(input) {
|
|
|
676
678
|
}
|
|
677
679
|
})();
|
|
678
680
|
return Object.freeze({
|
|
679
|
-
schemaVersion:
|
|
681
|
+
schemaVersion: 1,
|
|
680
682
|
version: YUI_VERSION,
|
|
681
683
|
executablePath: process.execPath,
|
|
682
684
|
args: process.argv.slice(1),
|
package/dist/core/protocol.js
CHANGED
|
@@ -3,7 +3,7 @@ import { validateHomeFilesystemId } from "./homeFilesystemIdentity.js";
|
|
|
3
3
|
export const MAX_CONTROLLER_MESSAGE_BYTES = 1_048_576;
|
|
4
4
|
export const CONTROLLER_DISCOVERY_PATH = "runtime/controller.json";
|
|
5
5
|
/** Bump when a running Controller cannot safely share one YUI_HOME with this CLI. */
|
|
6
|
-
export const FILE_TASK_CONTROLLER_PROTOCOL_VERSION =
|
|
6
|
+
export const FILE_TASK_CONTROLLER_PROTOCOL_VERSION = 1;
|
|
7
7
|
export class ControllerProtocolError extends Error {
|
|
8
8
|
code;
|
|
9
9
|
id;
|
package/dist/doctor/doctor.js
CHANGED
|
@@ -694,7 +694,8 @@ function checkAgent(agent, executor, environment) {
|
|
|
694
694
|
detail: [
|
|
695
695
|
`start resume interrupt nativeSession=${snapshot.lifecycle.nativeSessionDiscovery}`,
|
|
696
696
|
`preInputReady=${snapshot.lifecycle.preInputReadiness.status}`,
|
|
697
|
-
`fields=${available}/${degraded}/${unavailable}`,
|
|
697
|
+
`fields(help-observed/static-or-unverified/unavailable)=${available}/${degraded}/${unavailable}`,
|
|
698
|
+
"installation/help inspection only; no model or account capability query",
|
|
698
699
|
...snapshot.warnings.map((warning) => `warning=${warning}`)
|
|
699
700
|
].join(" ")
|
|
700
701
|
}
|
|
@@ -51,6 +51,12 @@ export function normalizedUniqueIdentities(values, label) {
|
|
|
51
51
|
export function cloneJson(value) {
|
|
52
52
|
return JSON.parse(JSON.stringify(value));
|
|
53
53
|
}
|
|
54
|
+
/** Current envelopes accept only declared fields; no historical-field denylist. */
|
|
55
|
+
export function requireKnownFields(value, fields, label) {
|
|
56
|
+
const unknown = Object.keys(value).find(key => !fields.includes(key));
|
|
57
|
+
if (unknown !== undefined)
|
|
58
|
+
throw new Error(`${label} has unsupported field: ${unknown}.`);
|
|
59
|
+
}
|
|
54
60
|
/**
|
|
55
61
|
* True for a concrete, pinnable package version — a semver-shaped `X.Y.Z` with
|
|
56
62
|
* an optional pre-release/build suffix. Rejects dist-tag sentinels (`latest`,
|
package/dist/event/taskEvent.js
CHANGED
|
@@ -2,7 +2,7 @@ import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
|
2
2
|
import { requireTimestamp } from "../domain/validation.js";
|
|
3
3
|
export function createTaskEvent(id, taskId, type, payload, now) {
|
|
4
4
|
const event = {
|
|
5
|
-
schemaVersion:
|
|
5
|
+
schemaVersion: 1,
|
|
6
6
|
id: requireText(id, "Task event id"),
|
|
7
7
|
taskId: requireText(taskId, "Task event Task id"),
|
|
8
8
|
type: requireText(type, "Task event type"),
|
|
@@ -12,8 +12,8 @@ export function createTaskEvent(id, taskId, type, payload, now) {
|
|
|
12
12
|
return validateTaskEvent(event);
|
|
13
13
|
}
|
|
14
14
|
export function validateTaskEvent(event) {
|
|
15
|
-
if (event.schemaVersion !==
|
|
16
|
-
throw new Error("Task Event must use schemaVersion
|
|
15
|
+
if (event.schemaVersion !== 1)
|
|
16
|
+
throw new Error("Task Event must use schemaVersion 1.");
|
|
17
17
|
validateTaskRecordReference({ taskId: event.taskId, localId: event.id }, "event");
|
|
18
18
|
requireText(event.type, "Task event type");
|
|
19
19
|
if (event.payload === null || typeof event.payload !== "object" || Array.isArray(event.payload)) {
|
|
@@ -3,8 +3,8 @@ import { normalizedUniqueIdentities, normalizedUniqueText, requireIdentity, requ
|
|
|
3
3
|
import { validateContextSnapshotRef } from "../context/contextSnapshot.js";
|
|
4
4
|
import { validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
5
5
|
import { MAX_SYNTHESIS_SOURCE_RUNS } from "../context/sourceRunContext.js";
|
|
6
|
-
export const WORK_ITEM_EXECUTION_GROUP_SCHEMA_VERSION =
|
|
7
|
-
export const WORK_ITEM_EXECUTION_LANE_SCHEMA_VERSION =
|
|
6
|
+
export const WORK_ITEM_EXECUTION_GROUP_SCHEMA_VERSION = 1;
|
|
7
|
+
export const WORK_ITEM_EXECUTION_LANE_SCHEMA_VERSION = 1;
|
|
8
8
|
export const WORK_ITEM_EXECUTION_ASSIGNMENT_SCHEMA_VERSION = 1;
|
|
9
9
|
export const MINIMUM_EXECUTION_GROUP_LANES = 2;
|
|
10
10
|
export const EXECUTION_GROUP_SCHEMA_VERSION = WORK_ITEM_EXECUTION_GROUP_SCHEMA_VERSION;
|
|
@@ -6,11 +6,12 @@ import { ownedArgumentsForAdapter, validateAgentAdvancedArguments, validateAgent
|
|
|
6
6
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
7
7
|
import { discoverAcpConfiguration, discoverClaudeConfiguration, discoverCodexConfiguration } from "./agentConfigurationProbe.js";
|
|
8
8
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
9
|
-
|
|
10
|
-
const APPROVALS = ["untrusted", "on-request", "never"];
|
|
9
|
+
import { CODEX_SANDBOXES as SANDBOXES, CODEX_APPROVALS as APPROVALS, configurationFieldsFromHelp, configurationFieldWarnings, staticAgentConfigurationFields, STATIC_CONFIGURATION_NOTICE } from "./agentConfigurationFields.js";
|
|
11
10
|
const PROBE_TIMEOUT_MS = 2_000;
|
|
12
11
|
const PROBE_MAX_BYTES = 1024 * 1024;
|
|
13
|
-
|
|
12
|
+
// Offline producer/type evidence, not a claim of live account/model testing.
|
|
13
|
+
// Keep the existing minimum support floors separate: see provider-protocol-contracts.md.
|
|
14
|
+
const AUDITED_PRODUCER_VERSIONS = { codex: "0.153.4", claude: "2.1.270" };
|
|
14
15
|
class BaseAdapter {
|
|
15
16
|
launchContextArgs(_input) {
|
|
16
17
|
return [];
|
|
@@ -556,7 +557,7 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
|
|
|
556
557
|
const at = now.toISOString();
|
|
557
558
|
const adapter = resolveAgentAdapter(agent.adapterId);
|
|
558
559
|
validateAgentBaseArguments(agent.adapterId, agent.baseArgs);
|
|
559
|
-
const versionRun = run(agent.command, ["--version"]);
|
|
560
|
+
const versionRun = run(agent.command, [...agent.baseArgs, "--version"]);
|
|
560
561
|
const failure = failed(versionRun);
|
|
561
562
|
if (failure !== undefined) {
|
|
562
563
|
const missing = versionRun.error?.code === "ENOENT";
|
|
@@ -583,7 +584,7 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
|
|
|
583
584
|
probedAt: at
|
|
584
585
|
}, fields, at, [`Installed version ${version} is not supported by adapter ${adapter.id}.`]);
|
|
585
586
|
}
|
|
586
|
-
const help = run(agent.command, ["--help"]);
|
|
587
|
+
const help = run(agent.command, [...agent.baseArgs, "--help"]);
|
|
587
588
|
const helpFailure = failed(help);
|
|
588
589
|
if (helpFailure !== undefined) {
|
|
589
590
|
return snapshot(agent, adapter, {
|
|
@@ -593,114 +594,45 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
|
|
|
593
594
|
}, fields, at);
|
|
594
595
|
}
|
|
595
596
|
const helpOutput = output(help.stdout, help.stderr);
|
|
596
|
-
|
|
597
|
+
const helpFields = configurationFieldsFromHelp(agent.adapterId, helpOutput);
|
|
598
|
+
fields = capabilityFields(helpFields);
|
|
599
|
+
warnings.push(...configurationFieldWarnings(helpFields));
|
|
597
600
|
const missing = missingRequiredCapabilities(adapter.id, helpOutput);
|
|
598
601
|
if (missing.length > 0) {
|
|
599
602
|
return snapshot(agent, adapter, {
|
|
600
603
|
status: "unsupported-version", command: agent.command, version,
|
|
601
604
|
reason: `${adapter.label} CLI is missing required capabilities: ${missing.join(", ")}.`,
|
|
602
605
|
probedAt: at
|
|
603
|
-
}, fields, at);
|
|
606
|
+
}, fields, at, warnings);
|
|
604
607
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
warnings.push(`Installed
|
|
608
|
-
+ `${
|
|
608
|
+
const audited = adapter.id === "acp" ? undefined : AUDITED_PRODUCER_VERSIONS[adapter.id];
|
|
609
|
+
if (audited !== undefined && compareVersions(version, audited) > 0) {
|
|
610
|
+
warnings.push(`Installed ${adapter.label} version ${version} is newer than the latest audited producer `
|
|
611
|
+
+ `${audited}; required CLI flags were detected, not live protocol compatibility.`);
|
|
609
612
|
}
|
|
610
613
|
return snapshot(agent, adapter, {
|
|
611
614
|
status: "installed", command: agent.command, version, probedAt: at
|
|
612
615
|
}, fields, at, warnings);
|
|
613
616
|
}
|
|
614
617
|
function baseline(id) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
field("permission.mode", "enum", "degraded", true),
|
|
628
|
-
// Configurable, but only reaches an Agent that advertises
|
|
629
|
-
// `sessionCapabilities.additionalDirectories` at `initialize`. A static
|
|
630
|
-
// baseline cannot see that handshake, so it reports the field as degraded
|
|
631
|
-
// rather than promising delivery it cannot guarantee.
|
|
632
|
-
field("additionalDirectories", "path-list", "degraded", true)
|
|
633
|
-
];
|
|
634
|
-
if (id === "codex")
|
|
635
|
-
return [
|
|
636
|
-
field("model", "enum", "degraded", true), field("effort", "enum", "unavailable", true),
|
|
637
|
-
field("permission.strategy", "enum", "available", false, ["default", "bypass", "configured"]),
|
|
638
|
-
field("permission.sandbox", "enum", "available", false, SANDBOXES),
|
|
639
|
-
field("permission.approval", "enum", "available", false, APPROVALS),
|
|
640
|
-
field("profile", "string", "available", true), field("search", "boolean", "available", false, ["true"]),
|
|
641
|
-
field("additionalDirectories", "path-list", "available", true)
|
|
642
|
-
];
|
|
643
|
-
return [
|
|
644
|
-
field("model", "enum", "degraded", true, ["fable", "opus", "sonnet"]),
|
|
645
|
-
field("effort", "enum", "unavailable", true),
|
|
646
|
-
field("permission.strategy", "enum", "available", false, ["default", "bypass", "configured"]),
|
|
647
|
-
field("permission.mode", "enum", "unavailable", true),
|
|
648
|
-
field("permission.allowedTools", "string-list", "available", true),
|
|
649
|
-
field("permission.disallowedTools", "string-list", "available", true),
|
|
650
|
-
field("additionalDirectories", "path-list", "available", true), field("settingsFile", "path", "available", true),
|
|
651
|
-
field("settingsSources", "string-list", "degraded", false, ["user", "project", "local"])
|
|
652
|
-
];
|
|
653
|
-
}
|
|
654
|
-
function fromHelp(id, help) {
|
|
655
|
-
const fields = baseline(id);
|
|
656
|
-
// Nothing in an ACP Agent's `--help` describes the protocol it speaks, so
|
|
657
|
-
// there is nothing here to refine: the baseline is already the whole truth.
|
|
658
|
-
if (id === "acp")
|
|
659
|
-
return fields;
|
|
660
|
-
const replacements = id === "codex"
|
|
661
|
-
? [permissionStrategyField(help, "--dangerously-bypass-approvals-and-sandbox"),
|
|
662
|
-
choiceField("permission.sandbox", help, "--sandbox", SANDBOXES),
|
|
663
|
-
choiceField("permission.approval", help, "--ask-for-approval", APPROVALS)]
|
|
664
|
-
: [choiceField("model", help, "--model", ["fable", "opus", "sonnet"], true),
|
|
665
|
-
choiceField("effort", help, "--effort", [], true),
|
|
666
|
-
permissionStrategyField(help, "--dangerously-skip-permissions"),
|
|
667
|
-
choiceField("permission.mode", help, "--permission-mode", [], true),
|
|
668
|
-
choiceField("settingsSources", help, "--setting-sources", ["user", "project", "local"])];
|
|
669
|
-
const byKey = new Map(replacements.map((value) => [value.key, value]));
|
|
670
|
-
return fields.map((value) => byKey.get(value.key) ?? value);
|
|
671
|
-
}
|
|
672
|
-
function permissionStrategyField(help, bypassFlag) {
|
|
673
|
-
const choices = [
|
|
674
|
-
"default",
|
|
675
|
-
...(help.includes(bypassFlag) ? ["bypass"] : []),
|
|
676
|
-
"configured"
|
|
677
|
-
];
|
|
678
|
-
return field("permission.strategy", "enum", choices.includes("bypass") ? "available" : "degraded", false, choices);
|
|
679
|
-
}
|
|
680
|
-
function choiceField(key, help, flag, fallback, custom = false) {
|
|
681
|
-
const choices = helpChoices(help, flag);
|
|
682
|
-
return field(key, key === "settingsSources" ? "string-list" : "enum", choices.length > 0 ? "available" : fallback.length > 0 ? "degraded" : "unavailable", custom, choices.length > 0 ? choices : fallback);
|
|
683
|
-
}
|
|
684
|
-
function field(key, kind, status, allowCustom, choices) {
|
|
685
|
-
return { key, kind, status, allowCustom, ...(choices === undefined ? {} : { choices: [...choices] }) };
|
|
686
|
-
}
|
|
687
|
-
function helpChoices(help, flag) {
|
|
688
|
-
const lines = help.replace(/\r\n/g, "\n").split("\n");
|
|
689
|
-
const start = lines.findIndex((line) => line.includes(flag));
|
|
690
|
-
if (start < 0)
|
|
691
|
-
return [];
|
|
692
|
-
let end = start + 1;
|
|
693
|
-
while (end < lines.length && !/^\s{2,}(?:-[A-Za-z](?:,\s*)?|--)[\w-]/.test(lines[end]))
|
|
694
|
-
end += 1;
|
|
695
|
-
const body = /(?:\[possible values:\s*([^\]]+)\]|\(choices:\s*([^)]*)\)|\(([^)]*)\))/i
|
|
696
|
-
.exec(lines.slice(start, end).join("\n"));
|
|
697
|
-
return [...new Set((body?.[1] ?? body?.[2] ?? body?.[3] ?? "").replace(/["']/g, "").split(",")
|
|
698
|
-
.map((value) => value.trim().replace(/\.$/, "")).filter((value) => /^[\w.+-]+$/.test(value)))];
|
|
618
|
+
return capabilityFields(staticAgentConfigurationFields(id));
|
|
619
|
+
}
|
|
620
|
+
function capabilityFields(fields) {
|
|
621
|
+
const kinds = {
|
|
622
|
+
profile: "string", search: "boolean", additionalDirectories: "path-list", settingsFile: "path",
|
|
623
|
+
settingsSources: "string-list", "permission.allowedTools": "string-list", "permission.disallowedTools": "string-list"
|
|
624
|
+
};
|
|
625
|
+
return fields.map(field => ({
|
|
626
|
+
key: field.key, kind: kinds[field.key] ?? "enum", allowCustom: field.allowCustom,
|
|
627
|
+
status: field.available === true ? "available" : field.available === false ? "unavailable" : "degraded",
|
|
628
|
+
choices: field.choices.map(choice => choice.value)
|
|
629
|
+
}));
|
|
699
630
|
}
|
|
700
631
|
function snapshot(agent, adapter, installation, fields, at, warnings = []) {
|
|
701
632
|
return { schemaVersion: 1, agentId: agent.id, adapterId: agent.adapterId, installation,
|
|
702
633
|
lifecycle: { start: true, resume: true, nativeSessionDiscovery: adapter.capabilities.nativeSessionDiscovery,
|
|
703
|
-
interrupt: true, preInputReadiness: adapter.capabilities.preInputReadiness }, fields,
|
|
634
|
+
interrupt: true, preInputReadiness: adapter.capabilities.preInputReadiness }, fields,
|
|
635
|
+
warnings: [...new Set([STATIC_CONFIGURATION_NOTICE, ...warnings])], refreshedAt: at };
|
|
704
636
|
}
|
|
705
637
|
function runProbe(command, args) {
|
|
706
638
|
const result = spawnSync(command, [...args], { encoding: "utf8", shell: false, timeout: PROBE_TIMEOUT_MS,
|
|
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
6
6
|
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
7
7
|
import { redactAgentErrorText } from "../runtime/agentError.js";
|
|
8
|
+
import { staticAgentConfigurationFields, STATIC_CONFIGURATION_NOTICE } from "./agentConfigurationFields.js";
|
|
8
9
|
const DEFAULT_TIMEOUT_MS = 8_000;
|
|
9
10
|
export class AgentConfigurationCatalogService {
|
|
10
11
|
yuiHome;
|
|
@@ -119,71 +120,14 @@ export class AgentConfigurationCatalogService {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
export function fallbackAgentConfigurationCatalog(agent) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (agent.adapterId === "acp") {
|
|
131
|
-
return {
|
|
132
|
-
schemaVersion: 1,
|
|
133
|
-
agentId: agent.id,
|
|
134
|
-
adapterId: "acp",
|
|
135
|
-
models: [],
|
|
136
|
-
fields: [
|
|
137
|
-
...common,
|
|
138
|
-
field("permission.strategy", [choice("default"), choice("bypass"), choice("configured")], false),
|
|
139
|
-
// The mode ids come from a live Session, so an offline catalog can only
|
|
140
|
-
// say the axis exists. Listing candidates here would invent an Agent's
|
|
141
|
-
// vocabulary from a build-time guess.
|
|
142
|
-
field("permission.mode", [], true)
|
|
143
|
-
],
|
|
144
|
-
warnings: ["Runtime configuration catalog is unavailable."]
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
return agent.adapterId === "codex"
|
|
148
|
-
? {
|
|
149
|
-
schemaVersion: 1,
|
|
150
|
-
agentId: agent.id,
|
|
151
|
-
adapterId: "codex",
|
|
152
|
-
models: [],
|
|
153
|
-
fields: [
|
|
154
|
-
...common,
|
|
155
|
-
field("permission.strategy", ["default", "bypass", "configured"].map(choice), false),
|
|
156
|
-
field("permission.sandbox", [
|
|
157
|
-
"read-only", "workspace-write", "danger-full-access"
|
|
158
|
-
].map(choice), false),
|
|
159
|
-
field("permission.approval", [
|
|
160
|
-
"untrusted", "on-request", "never"
|
|
161
|
-
].map(choice), false),
|
|
162
|
-
field("search", [choice("true")], false),
|
|
163
|
-
field("profile", [], true),
|
|
164
|
-
field("additionalDirectories", [], true)
|
|
165
|
-
],
|
|
166
|
-
warnings: ["Runtime configuration catalog is unavailable."]
|
|
167
|
-
}
|
|
168
|
-
: {
|
|
169
|
-
schemaVersion: 1,
|
|
170
|
-
agentId: agent.id,
|
|
171
|
-
adapterId: "claude",
|
|
172
|
-
models: [],
|
|
173
|
-
fields: [
|
|
174
|
-
...common,
|
|
175
|
-
field("permission.strategy", ["default", "bypass", "configured"].map(choice), false),
|
|
176
|
-
field("permission.mode", [
|
|
177
|
-
"acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan"
|
|
178
|
-
].map(choice), true),
|
|
179
|
-
field("permission.allowedTools", [], true),
|
|
180
|
-
field("permission.disallowedTools", [], true),
|
|
181
|
-
field("settingsSources", ["user", "project", "local"].map(choice), false),
|
|
182
|
-
field("settingsFile", [], true),
|
|
183
|
-
field("additionalDirectories", [], true)
|
|
184
|
-
],
|
|
185
|
-
warnings: ["Runtime configuration catalog is unavailable."]
|
|
186
|
-
};
|
|
123
|
+
return {
|
|
124
|
+
schemaVersion: 1,
|
|
125
|
+
agentId: agent.id,
|
|
126
|
+
adapterId: agent.adapterId,
|
|
127
|
+
models: [],
|
|
128
|
+
fields: staticAgentConfigurationFields(agent.adapterId),
|
|
129
|
+
warnings: ["Runtime configuration catalog is unavailable.", STATIC_CONFIGURATION_NOTICE]
|
|
130
|
+
};
|
|
187
131
|
}
|
|
188
132
|
export function configurationField(catalog, key) {
|
|
189
133
|
return catalog.fields.find((candidate) => candidate.key === key);
|
|
@@ -232,9 +176,6 @@ export function validateAgentLaunchConfiguration(catalog, config) {
|
|
|
232
176
|
+ `Inspect current options: yui config agent capabilities ${catalog.agentId}.`);
|
|
233
177
|
}
|
|
234
178
|
}
|
|
235
|
-
function field(key, choices, allowCustom) {
|
|
236
|
-
return { key, choices, allowCustom };
|
|
237
|
-
}
|
|
238
179
|
function catalogFingerprint(input, environment) {
|
|
239
180
|
const bindings = input.agent.environment.map((binding) => ({
|
|
240
181
|
target: binding.target,
|
|
@@ -259,6 +200,10 @@ function catalogFingerprint(input, environment) {
|
|
|
259
200
|
}
|
|
260
201
|
: null;
|
|
261
202
|
return createHash("sha256").update(JSON.stringify({
|
|
203
|
+
// Old derived caches may contain guessed help values. They cannot establish
|
|
204
|
+
// the current discovery contract. No durable record or cache shape changes.
|
|
205
|
+
discoveryContract: "explicit-native-enumeration",
|
|
206
|
+
agentId: input.agent.id,
|
|
262
207
|
// The component, not just the plan: two ACP products answer the same
|
|
263
208
|
// handshake differently, so a cache keyed on the plan alone would serve one
|
|
264
209
|
// product's capabilities for the other.
|