@zq-silk/yui 0.15.7 → 0.15.9
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 +194 -399
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +306 -1131
- package/dist/agent/adapterCatalog.js +15 -2
- package/dist/agent/agent.js +23 -3
- package/dist/agent/argumentPolicy.js +7 -1
- package/dist/agent/connectionPlan.js +62 -0
- package/dist/agent/executionComponents.js +158 -0
- package/dist/agent/launchEnvironment.js +31 -3
- package/dist/agent/managedRuntimeEnvironment.js +3 -5
- package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
- package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
- package/dist/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +165 -74
- package/dist/cli/interactionCandidates.js +5 -5
- package/dist/cli/interactionPolicy.js +38 -8
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/managedDiagnostics.js +28 -0
- package/dist/cli/operatorWizard.js +1 -7
- package/dist/cli/roleOptionOrder.js +27 -0
- package/dist/cli/roleWizard.js +50 -14
- package/dist/cli/updateOrchestrator.js +1 -1
- package/dist/cli/updatePorts.js +3 -4
- package/dist/cli.js +245 -95
- package/dist/commands/agentCommands.js +72 -14
- package/dist/commands/capabilityCommands.js +9 -6
- package/dist/commands/configCommands.js +20 -20
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +24 -24
- package/dist/commands/globalRoleCommands.js +1 -1
- package/dist/commands/grantCommands.js +4 -4
- package/dist/commands/operatorCommands.js +34 -9
- package/dist/commands/projectCommands.js +4 -4
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/roleConfiguration.js +25 -5
- package/dist/commands/roleRuntimeGuard.js +4 -5
- package/dist/commands/sessionCommands.js +3 -7
- package/dist/commands/taskActivationCommands.js +281 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1461 -720
- package/dist/commands/taskContextCommand.js +39 -583
- package/dist/commands/taskExecutionCommands.js +32 -32
- package/dist/commands/taskInputCommands.js +40 -104
- package/dist/commands/taskIntegrationCommands.js +3 -2
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -8
- package/dist/commands/taskOverviewCommand.js +33 -45
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
- package/dist/commands/taskRoleRuntimeStatus.js +133 -102
- package/dist/commands/telemetryCommands.js +36 -38
- package/dist/config/configCatalog.js +4 -4
- package/dist/config/yuiConfig.js +8 -8
- package/dist/context/contextSnapshot.js +10 -10
- package/dist/context/dispatchContext.js +11 -11
- package/dist/context/roleSessionContext.js +6 -3
- package/dist/context/{turnContextPack.js → runContextPack.js} +158 -81
- package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
- package/dist/context/sessionBootstrapManifest.js +21 -2
- package/dist/context/sourceRunContext.js +30 -0
- package/dist/context/taskContext.js +481 -0
- package/dist/context/wakeNotification.js +27 -27
- package/dist/controller/agentRuntimeObserver.js +21 -24
- package/dist/controller/capabilityBridge.js +17 -6
- package/dist/controller/clientRuntime.js +65 -92
- package/dist/controller/controller.js +120 -188
- package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
- package/dist/controller/jobControl.js +54 -85
- package/dist/controller/resourceInventory.js +8 -27
- package/dist/controller/resourceInventoryLinux.js +12 -13
- package/dist/controller/runtime.js +529 -479
- package/dist/controller/runtimeEventInbox.js +55 -25
- package/dist/controller/runtimeEventProcessor.js +22 -31
- package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
- package/dist/controller/runtimeLaunchCoordinator.js +80 -426
- package/dist/controller/runtimeObservationHook.js +14 -18
- package/dist/controller/sessionNotify.js +16 -24
- package/dist/controller/sessionOwnerReconciliation.js +168 -50
- package/dist/controller/structuredProviderObservation.js +138 -99
- package/dist/coordination/workMailbox.js +3 -3
- package/dist/coordination/workMailboxQueue.js +36 -33
- package/dist/core/boundedRpc.js +8 -1
- package/dist/core/controllerClient.js +20 -1
- package/dist/core/controllerServer.js +4 -4
- package/dist/doctor/doctor.js +13 -2
- package/dist/domain/agentResultTransport.js +9 -9
- package/dist/execution/codexThreadNaming.js +2 -8
- package/dist/execution/executionHealth.js +51 -63
- package/dist/execution/reviewMainRun.js +137 -0
- package/dist/execution/workItemExecution.js +28 -29
- package/dist/execution/workItemExecutionProjection.js +99 -107
- package/dist/execution/workItemMainRun.js +141 -0
- package/dist/executor/agentAdapter.js +227 -20
- package/dist/executor/agentConfigurationCatalog.js +126 -4
- package/dist/executor/agentConfigurationProbe.js +162 -4
- package/dist/executor/agentExecutor.js +79 -78
- package/dist/executor/effectiveLaunch.js +105 -18
- package/dist/executor/executorRegistry.js +29 -44
- package/dist/executor/fileRoleLaunchPlanner.js +229 -154
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +6 -3
- package/dist/input/inputRequest.js +12 -10
- package/dist/integration/gitIntegrationService.js +4 -11
- package/dist/integration/integrationQueueService.js +4 -4
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/kernel/builtinCapabilities.js +255 -12
- package/dist/kernel/capabilityRegistry.js +64 -18
- package/dist/kernel/instanceHost.js +12 -1
- package/dist/kernel/kernelPorts.js +2 -2
- package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
- package/dist/lifecycle/exactRunTerminalization.js +449 -0
- package/dist/message/message.js +118 -6
- package/dist/message/messageContinuation.js +204 -0
- package/dist/observability/executionAudit.js +70 -72
- package/dist/observability/faultClassification.js +2 -2
- package/dist/observability/orchestrationMetrics.js +8 -8
- package/dist/operator/operatorSessionHistory.js +1 -7
- package/dist/output/agentConfigurationPresentation.js +8 -3
- package/dist/output/agentRunConfigurationPresentation.js +128 -0
- package/dist/output/rolePresentation.js +54 -3
- package/dist/plugins/pluginChild.js +104 -0
- package/dist/plugins/pluginIntent.js +26 -0
- package/dist/plugins/pluginInterpreter.js +43 -0
- package/dist/plugins/pluginPackage.js +101 -0
- package/dist/plugins/pluginProcess.js +112 -0
- package/dist/plugins/pluginService.js +388 -0
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/gitWorkspace.js +26 -4
- package/dist/repository/project.js +19 -4
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +20 -27
- package/dist/repository/taskWorkspacePreparer.js +344 -83
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +3 -3
- package/dist/resources/projectResource.js +75 -0
- package/dist/resources/projectResourceService.js +343 -0
- package/dist/resources/resourceDiscovery.js +6 -6
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/resourceRegistrar.js +1 -1
- package/dist/resources/resourceTypes.js +1 -1
- package/dist/review/deltaRecheck.js +3 -3
- package/dist/review/reviewAcceptance.js +16 -16
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewRound.js +21 -20
- package/dist/review/reviewerAvailability.js +2 -2
- package/dist/role/role.js +51 -7
- package/dist/role/taskRoleUpdate.js +30 -0
- package/dist/runtime/acpProtocol.js +425 -0
- package/dist/runtime/acpSession.js +731 -0
- package/dist/runtime/acpSessionConfiguration.js +260 -0
- package/dist/runtime/agentDriver.js +30 -11
- package/dist/runtime/agentEndpoint.js +278 -0
- package/dist/runtime/agentEndpointIdentity.js +86 -0
- package/dist/runtime/agentEndpointOwnership.js +239 -0
- package/dist/runtime/agentError.js +2 -10
- package/dist/runtime/agentHost.js +565 -314
- package/dist/runtime/agentRunConfiguration.js +258 -0
- package/dist/runtime/builtinAgentDrivers.js +134 -18
- package/dist/runtime/builtinAgentErrorMappers.js +55 -3
- package/dist/runtime/builtinTranscriptUsage.js +1 -1
- package/dist/runtime/claude-process-owner +0 -0
- package/dist/runtime/codexAppServerRuntime.js +38 -30
- package/dist/runtime/codexInteractiveHost.js +41 -6
- package/dist/runtime/continuationManager.js +2 -6
- package/dist/runtime/executionEnvironment.js +30 -0
- package/dist/runtime/firstProgressAdvisory.js +11 -11
- package/dist/runtime/index.js +4 -3
- package/dist/runtime/jsonLineChannel.js +109 -0
- package/dist/runtime/launchBroker.js +91 -16
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +10 -18
- package/dist/runtime/managedCaller.js +61 -17
- package/dist/runtime/nativeSessionControl.js +102 -0
- package/dist/runtime/ports.js +6 -21
- package/dist/runtime/processExitObservation.js +8 -7
- package/dist/runtime/promptEnvelope.js +17 -6
- package/dist/runtime/providerContinuation.js +3 -9
- package/dist/runtime/providerContinuationReconciliationService.js +4 -13
- package/dist/runtime/providerControl.js +2 -7
- package/dist/runtime/providerRuntimeIdentity.js +110 -222
- package/dist/runtime/providerRuntimeReconciler.js +5 -9
- package/dist/runtime/runtimeBinding.js +0 -1
- package/dist/runtime/runtimeContinuationProjection.js +4 -7
- package/dist/runtime/runtimeDeadlines.js +9 -0
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +29 -65
- package/dist/runtime/runtimeProjection.js +43 -51
- package/dist/runtime/runtimeSessionCandidate.js +1 -3
- package/dist/runtime/sessionLaunchRequest.js +3 -7
- package/dist/runtime/sessionOwnerIdentity.js +7 -54
- package/dist/runtime/sessionOwnerRegistry.js +22 -17
- package/dist/runtime/sessionReconciliation.js +4 -8
- package/dist/runtime/sessionTerminationGuard.js +70 -259
- package/dist/runtime/sessionTokenMetrics.js +5 -16
- package/dist/runtime/structuredProviderHost.js +237 -117
- package/dist/runtime/taskRuntimeIsolation.js +39 -122
- package/dist/runtime/tmuxAdapters.js +39 -86
- package/dist/scheduler/activeRoleRunDelivery.js +354 -0
- package/dist/scheduler/leaderWakeupProcessor.js +75 -266
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +80 -9
- package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
- package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
- package/dist/scheduler/taskExecutionProjection.js +120 -124
- package/dist/scheduler/taskObservabilityProjection.js +29 -29
- package/dist/scheduler/taskWake.js +11 -4
- package/dist/scheduler/wakeReason.js +9 -1
- package/dist/setup/setupCommand.js +3 -7
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/sqliteSchema.js +467 -7
- package/dist/storage/sqliteStore.js +355 -220
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +10 -5
- package/dist/storage/taskStore.js +13 -11
- package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
- package/dist/surface/surfaceContributions.js +102 -0
- package/dist/task/completionReadiness.js +32 -6
- package/dist/task/deliveryGuard.js +16 -16
- package/dist/task/draftPlan.js +72 -12
- package/dist/task/nextAction.js +144 -128
- package/dist/task/remoteDelivery.js +6 -6
- package/dist/task/task.js +184 -18
- package/dist/task/taskActivation.js +327 -0
- package/dist/task/taskActivationService.js +408 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/task/taskSubmission.js +236 -0
- package/dist/telemetry/sqliteTelemetryStore.js +55 -68
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +2 -2
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +121 -20
- package/dist/web/assets/client/components.js +87 -54
- package/dist/web/assets/client/i18n.js +83 -41
- package/dist/web/assets/client/markdown.js +1 -1
- package/dist/web/assets/client/taskSurface.js +442 -0
- package/dist/web/assets/client/view.js +49 -44
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +22 -4
- package/dist/web/controllerWeb.js +60 -0
- package/dist/web/webMutation.js +28 -0
- package/dist/web/webServer.js +133 -8
- package/dist/web/webSnapshot.js +81 -74
- package/dist/web/webTaskSurface.js +64 -0
- package/dist/workItem/dependencyGate.js +1 -1
- package/dist/workItem/workItem.js +84 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +96 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +93 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +50 -0
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -0
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +224 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +83 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +393 -0
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +165 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +305 -0
- package/docs/release-workflow.zh-CN.md +237 -0
- package/docs/roles-and-configuration.md +115 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +78 -0
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -0
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +105 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +8 -6
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +88 -0
- package/docs/testing/verification-levels.zh-CN.md +69 -0
- package/i18n/README.zh-CN.md +270 -722
- package/package.json +3 -2
- package/skills/yui-leader/SKILL.md +88 -304
- package/skills/yui-leader/references/execution.md +303 -0
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +37 -0
- package/skills/yui-operator/SKILL.md +46 -62
- package/skills/yui-reviewer/SKILL.md +35 -36
- package/skills/yui-runtime/SKILL.md +88 -24
- package/skills/yui-runtime/references/publication.md +22 -0
- package/skills/yui-runtime/references/recovery.md +64 -0
- package/skills/yui-worker/SKILL.md +37 -39
- package/dist/cli/roleOptionCatalog.js +0 -68
- package/dist/context/sourceTurnContext.js +0 -30
- package/dist/execution/reviewMainTurn.js +0 -161
- package/dist/execution/workItemMainTurn.js +0 -164
- package/dist/lifecycle/exactTurnTerminalization.js +0 -407
- package/dist/runtime/preallocatedNativeSession.js +0 -13
- package/dist/runtime/runtimeStopReceipt.js +0 -42
- package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { enqueueRoleRunDispatch } from "../coordination/workMailboxQueue.js";
|
|
3
|
+
import { createRunInput } from "../context/runInputContract.js";
|
|
4
|
+
import { contextSnapshotDeltaRefIds, freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
5
|
+
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
6
|
+
import { roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
|
|
7
|
+
import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
8
|
+
import { createTaskEvent } from "../event/taskEvent.js";
|
|
9
|
+
import { createRun } from "../agentRun/agentRun.js";
|
|
10
|
+
import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
11
|
+
export function selectedWorkItemSynthesisProducers(store, item, group, sourceRunIds) {
|
|
12
|
+
if (sourceRunIds.length === 0 || new Set(sourceRunIds).size !== sourceRunIds.length) {
|
|
13
|
+
throw new Error("Synthesis requires explicit, distinct source AgentRun references.");
|
|
14
|
+
}
|
|
15
|
+
return sourceRunIds.map((runId) => {
|
|
16
|
+
const run = store.getRun(item.taskId, runId);
|
|
17
|
+
const lane = group.lanes.find(({ id }) => id === run?.executionLaneId);
|
|
18
|
+
if (run === null
|
|
19
|
+
|| lane === undefined
|
|
20
|
+
|| !["completed", "failed"].includes(run.status)
|
|
21
|
+
|| run.purpose !== "execution"
|
|
22
|
+
|| run.workItemId !== item.id
|
|
23
|
+
|| run.executionGroupId !== group.id
|
|
24
|
+
|| run.executionLaneId !== lane.id
|
|
25
|
+
|| run.roleName !== lane.roleName
|
|
26
|
+
|| run.result === undefined) {
|
|
27
|
+
throw new Error(`Synthesis source is not an exact terminal Producer result: ${group.id}/${runId}.`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
laneId: lane.id,
|
|
31
|
+
roleName: lane.roleName,
|
|
32
|
+
runId: run.id
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Called within the caller's transaction after Task authority is checked. */
|
|
37
|
+
export function dispatchWorkItemSynthesis(store, taskId, workItemId, sourceRunIds, now) {
|
|
38
|
+
const task = store.getTask(taskId);
|
|
39
|
+
if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
|
|
40
|
+
throw new Error(`Task execution is not enabled: ${taskId}.`);
|
|
41
|
+
}
|
|
42
|
+
const item = store.getWorkItem(taskId, workItemId);
|
|
43
|
+
if (item === null || item.status !== "open") {
|
|
44
|
+
throw new Error(`WorkItem is not open: ${taskId}/${workItemId}.`);
|
|
45
|
+
}
|
|
46
|
+
const group = currentWorkItemExecutionGroup(item);
|
|
47
|
+
if (group === undefined)
|
|
48
|
+
throw new Error(`WorkItem has no ExecutionGroup: ${item.id}.`);
|
|
49
|
+
const producers = selectedWorkItemSynthesisProducers(store, item, group, sourceRunIds);
|
|
50
|
+
const existing = store.listRuns(taskId).some((run) => (run.purpose === "execution"
|
|
51
|
+
&& run.workItemId === item.id
|
|
52
|
+
&& run.sourceExecutionGroupId === group.id));
|
|
53
|
+
if (existing)
|
|
54
|
+
throw new Error(`Synthesis already exists for ${group.id}; retry its AgentRun explicitly.`);
|
|
55
|
+
if (item.assignee === undefined) {
|
|
56
|
+
throw new Error(`Replicated WorkItem has no main assignee: ${item.id}.`);
|
|
57
|
+
}
|
|
58
|
+
const role = store.getRole(taskId, item.assignee);
|
|
59
|
+
if (role === null)
|
|
60
|
+
throw new Error(`WorkItem main Role is missing: ${taskId}/${item.assignee}.`);
|
|
61
|
+
if (store.getActiveRun(taskId, role.name) !== null) {
|
|
62
|
+
throw new Error(`WorkItem main Role already has an active AgentRun: ${role.name}.`);
|
|
63
|
+
}
|
|
64
|
+
const workspace = role.name === "leader"
|
|
65
|
+
? store.getTaskWorkspace(taskId)
|
|
66
|
+
: store.getWorkItemWorkspace(taskId, item.id);
|
|
67
|
+
if (workspace === null) {
|
|
68
|
+
throw new Error(`WorkItem main workspace is missing: ${taskId}/${item.id}.`);
|
|
69
|
+
}
|
|
70
|
+
const visibleProjectIds = workspace.entries.map(({ projectId }) => projectId).sort();
|
|
71
|
+
const taskProjectIds = task.projectBindings.map(({ projectId }) => projectId).sort();
|
|
72
|
+
const writableProjectIds = workspace.entries
|
|
73
|
+
.filter(({ access }) => access === "write")
|
|
74
|
+
.map(({ projectId }) => projectId)
|
|
75
|
+
.sort();
|
|
76
|
+
if (!isDeepStrictEqual(visibleProjectIds, taskProjectIds)
|
|
77
|
+
|| !isDeepStrictEqual(writableProjectIds, [...item.writeProjectIds].sort())) {
|
|
78
|
+
throw new Error(`WorkItem main workspace does not match its approved scope: ${item.id}.`);
|
|
79
|
+
}
|
|
80
|
+
const effective = resolveEffectiveLaunch({
|
|
81
|
+
role,
|
|
82
|
+
purpose: "execution",
|
|
83
|
+
workspace,
|
|
84
|
+
workItemWriteProjectIds: item.writeProjectIds
|
|
85
|
+
});
|
|
86
|
+
const snapshot = freezeRunContextSnapshot(store, {
|
|
87
|
+
taskId,
|
|
88
|
+
roleName: role.name,
|
|
89
|
+
purpose: "execution",
|
|
90
|
+
workItemId: item.id,
|
|
91
|
+
sourceExecutionGroupId: group.id,
|
|
92
|
+
workspace
|
|
93
|
+
}, now, "leader", group.assignment.contextSnapshotRef, sourceRunIds);
|
|
94
|
+
const run = createRun(store.nextRunId(taskId), taskId, role.name, roleAgentSessionResumeMode(store.getTaskRoleSessionSet(taskId, role.name), effective.agentId, effective), createRunInput({
|
|
95
|
+
source: { type: "yui", channel: "workitem-dispatch" },
|
|
96
|
+
directive: synthesisDirective(group, producers),
|
|
97
|
+
contextSnapshotRef: contextSnapshotRef(snapshot),
|
|
98
|
+
deltaRefIds: contextSnapshotDeltaRefIds(store, snapshot)
|
|
99
|
+
}), now, {
|
|
100
|
+
workItemId: item.id,
|
|
101
|
+
sourceExecutionGroupId: group.id,
|
|
102
|
+
workspace,
|
|
103
|
+
effective
|
|
104
|
+
});
|
|
105
|
+
store.saveRun(run);
|
|
106
|
+
store.saveActiveRun(run);
|
|
107
|
+
enqueueRoleRunDispatch(store, {
|
|
108
|
+
taskId,
|
|
109
|
+
roleName: role.name,
|
|
110
|
+
runId: run.id,
|
|
111
|
+
reason: "workitem-synthesis-ready",
|
|
112
|
+
occurredAt: now
|
|
113
|
+
});
|
|
114
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "run.dispatched", {
|
|
115
|
+
runId: run.id,
|
|
116
|
+
role: run.roleName,
|
|
117
|
+
purpose: run.purpose,
|
|
118
|
+
mode: run.mode,
|
|
119
|
+
agent: `${run.effective.agentId}/${run.effective.adapterId}`,
|
|
120
|
+
component: run.effective.component,
|
|
121
|
+
effectiveRevision: String(run.effective.sourceDesiredRevision),
|
|
122
|
+
profileAccess: run.effective.profileAccess,
|
|
123
|
+
effectivePermission: run.effective.permission.strategy,
|
|
124
|
+
writeProjectIds: run.effective.writeProjectIds.join(",") || "none",
|
|
125
|
+
workItemId: item.id,
|
|
126
|
+
sourceExecutionGroupId: group.id
|
|
127
|
+
}, now));
|
|
128
|
+
return run;
|
|
129
|
+
}
|
|
130
|
+
function synthesisDirective(group, producers) {
|
|
131
|
+
return [
|
|
132
|
+
"Synthesize the explicitly selected Producer results in the supplied order.",
|
|
133
|
+
"Expand each exact source AgentRun from the frozen Context Snapshot and consume its original result text plus Core-authored system evidence.",
|
|
134
|
+
"Do not rerun, retry, append, or abandon any Lane. Form the final WorkItem result from these records.",
|
|
135
|
+
JSON.stringify({
|
|
136
|
+
schemaVersion: 1,
|
|
137
|
+
sourceExecutionGroupId: group.id,
|
|
138
|
+
producers
|
|
139
|
+
}, null, 2)
|
|
140
|
+
].join("\n\n");
|
|
141
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { isAbsolute, resolve } from "node:path";
|
|
4
|
-
import { supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
|
|
4
|
+
import { isAgentAdapterId, supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
|
|
5
5
|
import { ownedArgumentsForAdapter, validateAgentAdvancedArguments, validateAgentBaseArguments } from "../agent/argumentPolicy.js";
|
|
6
6
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
7
|
-
import { discoverClaudeConfiguration, discoverCodexConfiguration } from "./agentConfigurationProbe.js";
|
|
7
|
+
import { discoverAcpConfiguration, discoverClaudeConfiguration, discoverCodexConfiguration } from "./agentConfigurationProbe.js";
|
|
8
8
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
9
9
|
const SANDBOXES = ["read-only", "workspace-write", "danger-full-access"];
|
|
10
10
|
const APPROVALS = ["untrusted", "on-request", "never"];
|
|
@@ -61,6 +61,17 @@ class CodexAdapter extends BaseAdapter {
|
|
|
61
61
|
discoverConfiguration(input) {
|
|
62
62
|
return discoverCodexConfiguration(input);
|
|
63
63
|
}
|
|
64
|
+
compileNew(input) {
|
|
65
|
+
const launch = super.compileNew(input);
|
|
66
|
+
const developerInstructions = codexSessionInstructions(input);
|
|
67
|
+
return {
|
|
68
|
+
...launch,
|
|
69
|
+
codexThread: {
|
|
70
|
+
...codexThreadOptions(input, this.canonicalizeConfig(input.config)),
|
|
71
|
+
...(developerInstructions === undefined ? {} : { developerInstructions })
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
64
75
|
validateStructured(config) {
|
|
65
76
|
exact(config, ["adapterId", "model", "effort", "permission", "search", "profile",
|
|
66
77
|
"additionalDirectories", "advanced"], "Codex Agent config");
|
|
@@ -125,23 +136,13 @@ class CodexAdapter extends BaseAdapter {
|
|
|
125
136
|
"--config",
|
|
126
137
|
`projects={${JSON.stringify(resolve(input.workspace))}={trust_level="trusted"}}`
|
|
127
138
|
];
|
|
128
|
-
const instructions = input
|
|
129
|
-
|
|
130
|
-
...(input.skills === undefined || input.skills.length === 0
|
|
131
|
-
? []
|
|
132
|
-
: [
|
|
133
|
-
"Yui Role Skills are available at the paths below. Before performing work governed by one, read and follow its SKILL.md on demand; do not treat this list as a user message.",
|
|
134
|
-
...input.skills.map((skill) => `- ${skill.id}: ${skill.path}/SKILL.md`)
|
|
135
|
-
])
|
|
136
|
-
].filter((value) => value !== undefined && value.trim().length > 0) : [
|
|
137
|
-
`Yui managed Session. Read and follow the Session Manifest at ${input.sessionManifestPath} (digest ${input.sessionManifestDigest ?? "unknown"}). Load each Skill and Role Profile by its manifest path before acting; Task content is available only through the manifest's exact Context API.`
|
|
138
|
-
];
|
|
139
|
-
if (instructions.length === 0)
|
|
139
|
+
const instructions = codexSessionInstructions(input);
|
|
140
|
+
if (instructions === undefined)
|
|
140
141
|
return workspaceTrust;
|
|
141
142
|
return [
|
|
142
143
|
...workspaceTrust,
|
|
143
144
|
"--config",
|
|
144
|
-
`developer_instructions=${tomlString(instructions
|
|
145
|
+
`developer_instructions=${tomlString(instructions)}`
|
|
145
146
|
];
|
|
146
147
|
}
|
|
147
148
|
compileResume(input) {
|
|
@@ -261,6 +262,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
261
262
|
input.developerInstructions,
|
|
262
263
|
...(input.skills ?? []).map((skill) => [
|
|
263
264
|
`# Yui Skill: ${skill.id}`,
|
|
265
|
+
`Source: ${skill.path}/SKILL.md (resolve relative links from this directory).`,
|
|
264
266
|
skill.content
|
|
265
267
|
].join("\n\n"))
|
|
266
268
|
].filter((value) => value !== undefined && value.trim().length > 0);
|
|
@@ -300,6 +302,150 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
300
302
|
};
|
|
301
303
|
}
|
|
302
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* The Agent Client Protocol adapter.
|
|
307
|
+
*
|
|
308
|
+
* One adapter serves every ACP Agent. It deliberately compiles no arguments of
|
|
309
|
+
* its own: entering ACP mode is a per-product invocation detail that belongs in
|
|
310
|
+
* the Agent descriptor's `baseArgs`, and everything after startup is negotiated
|
|
311
|
+
* over the protocol. Adding a second ACP product therefore requires a new
|
|
312
|
+
* descriptor and no new code here.
|
|
313
|
+
*/
|
|
314
|
+
class AcpAdapter extends BaseAdapter {
|
|
315
|
+
id = "acp";
|
|
316
|
+
label = "Agent Client Protocol";
|
|
317
|
+
// The protocol version Yui implements. ACP Agents are versioned
|
|
318
|
+
// independently of their products, and `initialize` negotiates the real
|
|
319
|
+
// version at connect time, so no product's release number belongs here.
|
|
320
|
+
supportedVersion = "0.0.0";
|
|
321
|
+
capabilities = {
|
|
322
|
+
recover: true,
|
|
323
|
+
interrupt: true,
|
|
324
|
+
// ACP assigns the Session id in its `session/new` response.
|
|
325
|
+
nativeSessionDiscovery: "runtime",
|
|
326
|
+
preInputReadiness: driverPreInputReadiness("acp")
|
|
327
|
+
};
|
|
328
|
+
discoverConfiguration(input) {
|
|
329
|
+
return discoverAcpConfiguration(input);
|
|
330
|
+
}
|
|
331
|
+
validateStructured(config) {
|
|
332
|
+
exact(config, ["adapterId", "model", "effort", "permission", "additionalDirectories",
|
|
333
|
+
"settingsFile", "settingsSources", "advanced"], "ACP Agent config");
|
|
334
|
+
if (config.adapterId !== "acp")
|
|
335
|
+
throw new Error("ACP Agent config adapter is invalid.");
|
|
336
|
+
// Model and effort are session config options in ACP, so a value is legal
|
|
337
|
+
// here whatever this build knows about the product. Whether the Agent
|
|
338
|
+
// actually offers it is decided by the live option list at launch: the
|
|
339
|
+
// protocol enumerates the accepted values, and only that enumeration can
|
|
340
|
+
// answer it. Rejecting an unrecognised name here would require Yui to hold
|
|
341
|
+
// a model list per product, which is the fabricated authority this design
|
|
342
|
+
// avoids.
|
|
343
|
+
optionalText(config.model, "ACP model");
|
|
344
|
+
optionalText(config.effort, "ACP effort");
|
|
345
|
+
if (config.settingsFile !== undefined || config.settingsSources !== undefined) {
|
|
346
|
+
throw new Error("Yui's ACP client exposes no client-side settings configuration.");
|
|
347
|
+
}
|
|
348
|
+
// `additionalDirectories` is a real ACP session-lifecycle field, so a
|
|
349
|
+
// Project-backed workspace is configurable here. Whether it is actually
|
|
350
|
+
// sent is decided per connection: ACP requires Clients to send it only to
|
|
351
|
+
// an Agent that advertised `sessionCapabilities.additionalDirectories`, and
|
|
352
|
+
// only the live handshake knows that. Rejecting it here instead would make
|
|
353
|
+
// every multi-root Project launch fail before the Agent is even asked.
|
|
354
|
+
validatePaths(config.additionalDirectories, "ACP additional directory");
|
|
355
|
+
if (config.permission === undefined) {
|
|
356
|
+
throw new Error("ACP permission strategy is required.");
|
|
357
|
+
}
|
|
358
|
+
// `configured` carries the one mode id the user named; the other two
|
|
359
|
+
// strategies carry nothing, so an extra field would mean a caller expected a
|
|
360
|
+
// value this adapter never reads.
|
|
361
|
+
if (config.permission.strategy === "configured") {
|
|
362
|
+
exact(config.permission, ["strategy", "mode"], "ACP permission config");
|
|
363
|
+
// The mode is matched by exact id against what the Agent offers at launch.
|
|
364
|
+
// Yui keeps no per-product mode list, so any non-empty id is accepted here
|
|
365
|
+
// and verified there.
|
|
366
|
+
requireOneText(config.permission.mode, "ACP permission mode");
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
exact(config.permission, ["strategy"], "ACP permission config");
|
|
370
|
+
if (config.permission.strategy !== "default" && config.permission.strategy !== "bypass") {
|
|
371
|
+
throw new Error("ACP permission strategy is invalid.");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
advanced(this.id, config.advanced);
|
|
375
|
+
}
|
|
376
|
+
structuredArgs(_config) {
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
379
|
+
compileResume(input) {
|
|
380
|
+
// Reattaching is a `session/load` call inside the protocol, not a flag.
|
|
381
|
+
nativeId(input.nativeSessionId);
|
|
382
|
+
return this.compileNew(input);
|
|
383
|
+
}
|
|
384
|
+
compileManagedControl(input, _mode, _nativeSessionId) {
|
|
385
|
+
const config = this.canonicalizeConfig(input.config);
|
|
386
|
+
const bootstrap = acpSessionBootstrap(input);
|
|
387
|
+
const directories = config.additionalDirectories ?? [];
|
|
388
|
+
// What the Role asked for, in the form the Session applies. Only stated
|
|
389
|
+
// fields travel: an absent model is a request for the Agent's own default,
|
|
390
|
+
// and `default` permission deliberately sends no mode at all.
|
|
391
|
+
const desired = {
|
|
392
|
+
...(config.model === undefined ? {} : { model: config.model }),
|
|
393
|
+
...(config.effort === undefined ? {} : { effort: config.effort }),
|
|
394
|
+
...(config.permission.strategy === "configured"
|
|
395
|
+
? { permissionMode: config.permission.mode }
|
|
396
|
+
: config.permission.strategy === "bypass"
|
|
397
|
+
? { permissionBypass: true }
|
|
398
|
+
: {})
|
|
399
|
+
};
|
|
400
|
+
const hasDesired = Object.keys(desired).length > 0;
|
|
401
|
+
return {
|
|
402
|
+
...this.compileNew(input),
|
|
403
|
+
transport: "acp-stdio",
|
|
404
|
+
// ACP accepts no Yui flags, so everything a managed Session needs travels
|
|
405
|
+
// as protocol input. Omit the key entirely when there is nothing to say.
|
|
406
|
+
...(directories.length === 0 && bootstrap === undefined && !hasDesired ? {} : {
|
|
407
|
+
acpSession: {
|
|
408
|
+
...(directories.length === 0 ? {} : { additionalDirectories: [...directories] }),
|
|
409
|
+
...(bootstrap === undefined ? {} : { sessionBootstrap: bootstrap }),
|
|
410
|
+
...(hasDesired ? { desiredConfiguration: desired } : {})
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* The instructions a managed ACP Session must read before it acts.
|
|
418
|
+
*
|
|
419
|
+
* ACP defines no system prompt and no equivalent of `--append-system-prompt`,
|
|
420
|
+
* so unlike Codex and Claude this text cannot be delivered at launch. It is
|
|
421
|
+
* carried to the Session and prepended to the first prompt instead. The
|
|
422
|
+
* manifest form stays a pointer rather than an inlined Task: content is read
|
|
423
|
+
* through the manifest's own Context API, exactly as the other adapters do.
|
|
424
|
+
*/
|
|
425
|
+
function acpSessionBootstrap(input) {
|
|
426
|
+
const sections = input.sessionManifestPath === undefined
|
|
427
|
+
? [
|
|
428
|
+
input.developerInstructions,
|
|
429
|
+
...(input.skills === undefined || input.skills.length === 0 ? [] : [
|
|
430
|
+
[
|
|
431
|
+
"Yui Role Skills are available at the paths below. Before performing work "
|
|
432
|
+
+ "governed by one, read and follow its SKILL.md on demand; do not treat "
|
|
433
|
+
+ "this list as a user message.",
|
|
434
|
+
...input.skills.map((skill) => `- ${skill.id}: ${skill.path}/SKILL.md`)
|
|
435
|
+
].join("\n")
|
|
436
|
+
])
|
|
437
|
+
]
|
|
438
|
+
: [
|
|
439
|
+
`Yui managed Session. Read and follow the Session Manifest at `
|
|
440
|
+
+ `${input.sessionManifestPath} (digest ${input.sessionManifestDigest ?? "unknown"}). `
|
|
441
|
+
+ "Load each Skill and Role Profile by its manifest path before acting; Task "
|
|
442
|
+
+ "content is available only through the manifest's exact Context API."
|
|
443
|
+
];
|
|
444
|
+
const bootstrap = sections
|
|
445
|
+
.filter((value) => value !== undefined && value.trim().length > 0)
|
|
446
|
+
.join("\n\n");
|
|
447
|
+
return bootstrap.length === 0 ? undefined : bootstrap;
|
|
448
|
+
}
|
|
303
449
|
function driverPreInputReadiness(adapterId) {
|
|
304
450
|
const capability = builtinAgentDriverRegistry().requireByAdapterId(adapterId)
|
|
305
451
|
.capabilities.observation.preInputReadiness;
|
|
@@ -316,7 +462,7 @@ function driverPreInputReadiness(adapterId) {
|
|
|
316
462
|
});
|
|
317
463
|
}
|
|
318
464
|
const ADAPTERS = {
|
|
319
|
-
codex: new CodexAdapter(), claude: new ClaudeAdapter()
|
|
465
|
+
codex: new CodexAdapter(), claude: new ClaudeAdapter(), acp: new AcpAdapter()
|
|
320
466
|
};
|
|
321
467
|
function tomlString(value) {
|
|
322
468
|
if (value.includes("\0"))
|
|
@@ -354,6 +500,19 @@ function codexThreadOptions(input, config) {
|
|
|
354
500
|
...(Object.keys(threadConfig).length === 0 ? {} : { config: threadConfig })
|
|
355
501
|
};
|
|
356
502
|
}
|
|
503
|
+
function codexSessionInstructions(input) {
|
|
504
|
+
if (input.sessionManifestPath !== undefined) {
|
|
505
|
+
return `Yui managed Session. Read and follow the Session Manifest at ${input.sessionManifestPath} (digest ${input.sessionManifestDigest ?? "unknown"}). Load each Skill and Role Profile by its manifest path before acting; Task content is available only through the manifest's exact Context API.`;
|
|
506
|
+
}
|
|
507
|
+
const instructions = [
|
|
508
|
+
input.developerInstructions,
|
|
509
|
+
...(input.skills === undefined || input.skills.length === 0 ? [] : [
|
|
510
|
+
"Yui Role Skills are available at the paths below. Before performing work governed by one, read and follow its SKILL.md on demand; do not treat this list as a user message.",
|
|
511
|
+
...input.skills.map((skill) => `- ${skill.id}: ${skill.path}/SKILL.md`)
|
|
512
|
+
])
|
|
513
|
+
].filter((value) => value !== undefined && value.trim().length > 0);
|
|
514
|
+
return instructions.length === 0 ? undefined : instructions.join("\n");
|
|
515
|
+
}
|
|
357
516
|
function withoutCodexConfigOverride(argv, key) {
|
|
358
517
|
const filtered = [];
|
|
359
518
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -379,7 +538,10 @@ function sessionTitle(value) {
|
|
|
379
538
|
}
|
|
380
539
|
export { supportedAgentAdapterIds };
|
|
381
540
|
export function findAgentAdapter(id) {
|
|
382
|
-
|
|
541
|
+
// `ADAPTERS` is keyed by the catalog, so it already answers this question.
|
|
542
|
+
// A second name list here would strand a catalogued adapter that has a real
|
|
543
|
+
// implementation sitting one line above.
|
|
544
|
+
return isAgentAdapterId(id) ? ADAPTERS[id] : null;
|
|
383
545
|
}
|
|
384
546
|
export function resolveAgentAdapter(id) {
|
|
385
547
|
const adapter = findAgentAdapter(id);
|
|
@@ -450,6 +612,25 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
|
|
|
450
612
|
}, fields, at, warnings);
|
|
451
613
|
}
|
|
452
614
|
function baseline(id) {
|
|
615
|
+
// ACP negotiates its whole configurable surface per Session, so a static
|
|
616
|
+
// baseline can only say which axes exist — never which values one Agent
|
|
617
|
+
// accepts. Model, effort and mode are all `degraded` for exactly that reason:
|
|
618
|
+
// they are configurable and the enumeration comes from the live Session, so
|
|
619
|
+
// promising values here would be inventing them.
|
|
620
|
+
if (id === "acp")
|
|
621
|
+
return [
|
|
622
|
+
field("model", "enum", "degraded", true),
|
|
623
|
+
field("effort", "enum", "degraded", true),
|
|
624
|
+
field("permission.strategy", "enum", "available", false, ["default", "bypass", "configured"]),
|
|
625
|
+
// The exact mode ids belong to the Agent, and only a live Session lists
|
|
626
|
+
// them. `configured` carries whichever id the user names.
|
|
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
|
+
];
|
|
453
634
|
if (id === "codex")
|
|
454
635
|
return [
|
|
455
636
|
field("model", "enum", "degraded", true), field("effort", "enum", "unavailable", true),
|
|
@@ -472,6 +653,10 @@ function baseline(id) {
|
|
|
472
653
|
}
|
|
473
654
|
function fromHelp(id, help) {
|
|
474
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;
|
|
475
660
|
const replacements = id === "codex"
|
|
476
661
|
? [permissionStrategyField(help, "--dangerously-bypass-approvals-and-sandbox"),
|
|
477
662
|
choiceField("permission.sandbox", help, "--sandbox", SANDBOXES),
|
|
@@ -549,6 +734,12 @@ function compareVersions(leftVersion, rightVersion) {
|
|
|
549
734
|
return 0;
|
|
550
735
|
}
|
|
551
736
|
function missingRequiredCapabilities(id, help) {
|
|
737
|
+
// An ACP Agent declares its capabilities in the `initialize` handshake, not
|
|
738
|
+
// in `--help`. Demanding Claude's flags of it reported a working Agent as a
|
|
739
|
+
// broken one, so ACP is required to expose no flags at all here; the real
|
|
740
|
+
// check is `discoverAcpConfiguration`, which speaks the protocol.
|
|
741
|
+
if (id === "acp")
|
|
742
|
+
return [];
|
|
552
743
|
const required = id === "codex"
|
|
553
744
|
? [
|
|
554
745
|
[/(?:^|\s)--config(?:\s|[=<,]|$)/m, "--config"],
|
|
@@ -572,6 +763,12 @@ function missingRequiredCapabilities(id, help) {
|
|
|
572
763
|
}
|
|
573
764
|
function cloneConfig(config, paths) {
|
|
574
765
|
const advancedConfig = config.advanced?.rawArgs === undefined ? config.advanced : { rawArgs: [...config.advanced.rawArgs] };
|
|
766
|
+
if (config.adapterId === "acp") {
|
|
767
|
+
return { ...config,
|
|
768
|
+
permission: { ...config.permission },
|
|
769
|
+
...(paths === undefined ? {} : { additionalDirectories: [...paths] }),
|
|
770
|
+
...(advancedConfig === undefined ? {} : { advanced: advancedConfig }) };
|
|
771
|
+
}
|
|
575
772
|
if (config.adapterId === "codex")
|
|
576
773
|
return { ...config,
|
|
577
774
|
permission: { ...config.permission },
|
|
@@ -588,9 +785,15 @@ function cloneConfig(config, paths) {
|
|
|
588
785
|
...(advancedConfig === undefined ? {} : { advanced: advancedConfig }) };
|
|
589
786
|
}
|
|
590
787
|
export function defaultRoleAgentConfig(adapterId) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
788
|
+
// Keyed by adapter, not a codex/else ternary: defaulting an unrecognized
|
|
789
|
+
// adapter to Claude's shape produced a config whose `adapterId` disagreed
|
|
790
|
+
// with the binding it was created for.
|
|
791
|
+
//
|
|
792
|
+
// ACP defaults to `default` rather than `bypass` because Yui answers ACP
|
|
793
|
+
// permission requests itself, and the answer it must never invent is "yes".
|
|
794
|
+
return adapterId === "acp"
|
|
795
|
+
? { adapterId: "acp", permission: { strategy: "default" } }
|
|
796
|
+
: { adapterId, permission: { strategy: "bypass" } };
|
|
594
797
|
}
|
|
595
798
|
function validateSimplePermissionStrategy(value, label) {
|
|
596
799
|
if (value !== "default" && value !== "bypass") {
|
|
@@ -638,6 +841,10 @@ function optionalText(value, label) {
|
|
|
638
841
|
if (value !== undefined)
|
|
639
842
|
text(value, label);
|
|
640
843
|
}
|
|
844
|
+
/** A required non-empty single-line value. */
|
|
845
|
+
function requireOneText(value, label) {
|
|
846
|
+
text(value, label);
|
|
847
|
+
}
|
|
641
848
|
function optionalTexts(values, label) {
|
|
642
849
|
if (values === undefined)
|
|
643
850
|
return;
|
|
@@ -104,6 +104,26 @@ export function fallbackAgentConfigurationCatalog(agent) {
|
|
|
104
104
|
field("model", [], true),
|
|
105
105
|
field("effort", [], true)
|
|
106
106
|
];
|
|
107
|
+
// The fallback exists for when the probe could not run, so it must still be
|
|
108
|
+
// this Agent's own shape. Falling through to Claude's fields handed the
|
|
109
|
+
// caller a catalog whose `adapterId` contradicted the Agent it described.
|
|
110
|
+
if (agent.adapterId === "acp") {
|
|
111
|
+
return {
|
|
112
|
+
schemaVersion: 1,
|
|
113
|
+
agentId: agent.id,
|
|
114
|
+
adapterId: "acp",
|
|
115
|
+
models: [],
|
|
116
|
+
fields: [
|
|
117
|
+
...common,
|
|
118
|
+
field("permission.strategy", [choice("default"), choice("bypass"), choice("configured")], false),
|
|
119
|
+
// The mode ids come from a live Session, so an offline catalog can only
|
|
120
|
+
// say the axis exists. Listing candidates here would invent an Agent's
|
|
121
|
+
// vocabulary from a build-time guess.
|
|
122
|
+
field("permission.mode", [], true)
|
|
123
|
+
],
|
|
124
|
+
warnings: ["Runtime configuration catalog is unavailable."]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
107
127
|
return agent.adapterId === "codex"
|
|
108
128
|
? {
|
|
109
129
|
schemaVersion: 1,
|
|
@@ -183,9 +203,15 @@ function catalogFingerprint(input, environment) {
|
|
|
183
203
|
sourceName: binding.sourceName,
|
|
184
204
|
value: environment[binding.sourceName] ?? null
|
|
185
205
|
}));
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
206
|
+
// Which directory an ACP Agent keeps its own state in is that product's
|
|
207
|
+
// business, not the protocol's. Fingerprinting it against Claude's config
|
|
208
|
+
// root made unrelated Claude edits invalidate this cache; the executable
|
|
209
|
+
// path below is the honest identity for an Agent Yui only speaks to.
|
|
210
|
+
const nativeRoot = input.agent.adapterId === "acp"
|
|
211
|
+
? null
|
|
212
|
+
: input.agent.adapterId === "codex"
|
|
213
|
+
? environment.CODEX_HOME ?? join(environment.HOME ?? homedir(), ".codex")
|
|
214
|
+
: environment.CLAUDE_CONFIG_DIR ?? join(environment.HOME ?? homedir(), ".claude");
|
|
189
215
|
const context = input.config?.adapterId === "codex"
|
|
190
216
|
? { profile: input.config.profile ?? null }
|
|
191
217
|
: input.config?.adapterId === "claude"
|
|
@@ -195,6 +221,10 @@ function catalogFingerprint(input, environment) {
|
|
|
195
221
|
}
|
|
196
222
|
: null;
|
|
197
223
|
return createHash("sha256").update(JSON.stringify({
|
|
224
|
+
// The component, not just the plan: two ACP products answer the same
|
|
225
|
+
// handshake differently, so a cache keyed on the plan alone would serve one
|
|
226
|
+
// product's capabilities for the other.
|
|
227
|
+
component: input.agent.component,
|
|
198
228
|
adapterId: input.agent.adapterId,
|
|
199
229
|
command: input.agent.command,
|
|
200
230
|
baseArgs: input.agent.baseArgs,
|
|
@@ -239,7 +269,6 @@ function validateCatalog(value, agent) {
|
|
|
239
269
|
|| value.agentId !== agent.id
|
|
240
270
|
|| value.adapterId !== agent.adapterId
|
|
241
271
|
|| !Array.isArray(value.models)
|
|
242
|
-
|| value.models.length === 0
|
|
243
272
|
|| !Array.isArray(value.fields)
|
|
244
273
|
|| !Array.isArray(value.warnings)) {
|
|
245
274
|
throw new Error("Agent configuration model catalog is incomplete.");
|
|
@@ -248,6 +277,9 @@ function validateCatalog(value, agent) {
|
|
|
248
277
|
unique(models.map(({ value: model }) => model), "model");
|
|
249
278
|
const fields = value.fields.map(validateField);
|
|
250
279
|
unique(fields.map(({ key }) => key), "configuration field");
|
|
280
|
+
if (models.length === 0 && !modelAxisIsAccountedFor(fields)) {
|
|
281
|
+
throw new Error("Agent configuration model catalog is incomplete.");
|
|
282
|
+
}
|
|
251
283
|
const warnings = value.warnings.map((warning) => text(warning, "catalog warning"));
|
|
252
284
|
return {
|
|
253
285
|
schemaVersion: 1,
|
|
@@ -255,11 +287,101 @@ function validateCatalog(value, agent) {
|
|
|
255
287
|
adapterId: agent.adapterId,
|
|
256
288
|
...(typeof value.cliVersion === "string"
|
|
257
289
|
? { cliVersion: text(value.cliVersion, "CLI version") } : {}),
|
|
290
|
+
// Carried through rather than dropped. This is the only record of what the
|
|
291
|
+
// Agent on the other end actually agreed to, and for a plan whose products
|
|
292
|
+
// are interchangeable it is the one fact that tells them apart. Rebuilding
|
|
293
|
+
// the catalog without it silently erased the probe's answer on both the
|
|
294
|
+
// live path and the cache read-back, leaving every consumer unable to
|
|
295
|
+
// distinguish "negotiated nothing" from "never asked".
|
|
296
|
+
...(value.handshake === undefined
|
|
297
|
+
? {}
|
|
298
|
+
: { handshake: validateHandshake(value.handshake) }),
|
|
258
299
|
models,
|
|
259
300
|
fields,
|
|
260
301
|
warnings
|
|
261
302
|
};
|
|
262
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Whether an empty model list is an answer or a failure.
|
|
306
|
+
*
|
|
307
|
+
* Emptiness alone does not distinguish the two, so the `model` field's own
|
|
308
|
+
* contract decides. `available` is the field that carries a probe's explicit
|
|
309
|
+
* statement about an axis, and only a probe that states it has said anything
|
|
310
|
+
* about what an empty list means:
|
|
311
|
+
*
|
|
312
|
+
* - `available: false` — the axis does not exist for this Agent, and `reason`
|
|
313
|
+
* says why. Nothing to enumerate.
|
|
314
|
+
* - `available: true` — the axis exists and its values are deliberately
|
|
315
|
+
* enumerated later, with `allowCustom` letting a value be named before Yui has
|
|
316
|
+
* the list. This is ACP: models live in the `configOptions` a Session returns,
|
|
317
|
+
* so listing them at probe time would mean opening a real, possibly billed
|
|
318
|
+
* Session to populate a menu. The probe reports the axis, defers the values,
|
|
319
|
+
* and the configured model is verified against the Agent's own list at launch.
|
|
320
|
+
* - absent — the probe made no claim, which is the Codex and Claude shape. Those
|
|
321
|
+
* probes enumerate models into the top-level `models` array on success, so an
|
|
322
|
+
* empty array there means `model list` failed or returned nothing usable, and
|
|
323
|
+
* the catalog really is incomplete.
|
|
324
|
+
*
|
|
325
|
+
* So an unstated `available` with no models stays a rejection, which is what
|
|
326
|
+
* keeps a genuinely broken Codex or Claude discovery from passing as an answer.
|
|
327
|
+
*
|
|
328
|
+
* The earlier form of this check accepted only the first case. It therefore
|
|
329
|
+
* rejected a valid ACP catalog and replaced it with the fallback, discarding the
|
|
330
|
+
* live handshake and the probe's own reasons — the failure mode the
|
|
331
|
+
* `available`/`reason` pair exists to prevent.
|
|
332
|
+
*/
|
|
333
|
+
function modelAxisIsAccountedFor(fields) {
|
|
334
|
+
const model = fields.find(({ key }) => key === "model");
|
|
335
|
+
if (model === undefined)
|
|
336
|
+
return false;
|
|
337
|
+
if (model.available === undefined)
|
|
338
|
+
return model.choices.length > 0;
|
|
339
|
+
// Stated either way, the field itself explains the empty list.
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* A handshake observation, kept in whichever of its three states it arrived in.
|
|
344
|
+
*
|
|
345
|
+
* Absence is meaningful and is preserved by the caller: it means no live
|
|
346
|
+
* connection produced this catalog. The two present states are distinguished
|
|
347
|
+
* here rather than merged, because `unsupported` ("this plan negotiates
|
|
348
|
+
* nothing") and an `observed` result with empty capabilities ("the Agent
|
|
349
|
+
* answered, and advertised none") are different answers that would otherwise
|
|
350
|
+
* be indistinguishable. An unrecognised status is rejected instead of being
|
|
351
|
+
* coerced into either one.
|
|
352
|
+
*/
|
|
353
|
+
function validateHandshake(value) {
|
|
354
|
+
if (!record(value))
|
|
355
|
+
throw new Error("Agent handshake observation is invalid.");
|
|
356
|
+
if (value.status === "unsupported") {
|
|
357
|
+
return {
|
|
358
|
+
status: "unsupported",
|
|
359
|
+
reason: text(value.reason, "handshake reason")
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (value.status !== "observed") {
|
|
363
|
+
throw new Error(`Agent handshake status is unsupported: ${String(value.status)}.`);
|
|
364
|
+
}
|
|
365
|
+
if (!Number.isSafeInteger(value.protocolVersion)) {
|
|
366
|
+
throw new Error("Agent handshake protocol version is invalid.");
|
|
367
|
+
}
|
|
368
|
+
const capabilities = array(value.capabilities, "handshake capabilities")
|
|
369
|
+
.map((capability) => text(capability, "handshake capability"));
|
|
370
|
+
unique(capabilities, "handshake capability");
|
|
371
|
+
const authMethods = array(value.authMethods, "handshake authentication methods")
|
|
372
|
+
.map((method) => text(method, "handshake authentication method"));
|
|
373
|
+
unique(authMethods, "handshake authentication method");
|
|
374
|
+
return {
|
|
375
|
+
status: "observed",
|
|
376
|
+
protocolVersion: value.protocolVersion,
|
|
377
|
+
// `unknown` is a real answer here — the Agent connected but did not name
|
|
378
|
+
// itself — so it is stored as given and never replaced by a guess.
|
|
379
|
+
agentName: text(value.agentName, "handshake Agent name"),
|
|
380
|
+
agentVersion: text(value.agentVersion, "handshake Agent version"),
|
|
381
|
+
capabilities: [...capabilities].sort(),
|
|
382
|
+
authMethods: [...authMethods].sort()
|
|
383
|
+
};
|
|
384
|
+
}
|
|
263
385
|
function validateModel(value) {
|
|
264
386
|
if (!record(value) || typeof value.isDefault !== "boolean" || !Array.isArray(value.efforts)) {
|
|
265
387
|
throw new Error("Agent configuration model entry is invalid.");
|