@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
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
import { YUI_VERSION } from "../version.js";
|
|
2
|
+
/**
|
|
3
|
+
* Preserve the native non-interactive Codex identity used by our historical
|
|
4
|
+
* execution adapter. A proxy's environment cannot set the daemon's originator;
|
|
5
|
+
* the daemon derives it from initialization (and can retain its first identity).
|
|
6
|
+
* Keep Yui's title/version explicit and do not opt into unsupported attestation.
|
|
7
|
+
*/
|
|
8
|
+
export function codexClientInitialization() {
|
|
9
|
+
return {
|
|
10
|
+
clientInfo: { name: "codex_exec", title: "Yui", version: YUI_VERSION },
|
|
11
|
+
capabilities: { experimentalApi: true, requestAttestation: false }
|
|
12
|
+
};
|
|
13
|
+
}
|
|
1
14
|
export class CodexAppServerRequestError extends Error {
|
|
2
15
|
code;
|
|
3
16
|
data;
|
|
@@ -8,6 +21,10 @@ export class CodexAppServerRequestError extends Error {
|
|
|
8
21
|
this.data = data;
|
|
9
22
|
}
|
|
10
23
|
}
|
|
24
|
+
/** The pre-submit read failed; no mutation request was issued. */
|
|
25
|
+
export class CodexPreSubmissionError extends Error {
|
|
26
|
+
name = "CodexPreSubmissionError";
|
|
27
|
+
}
|
|
11
28
|
/**
|
|
12
29
|
* Continuable Codex integration over App Server. A transport connection is
|
|
13
30
|
* deliberately not an Activation identity: callers supply the persisted
|
|
@@ -41,11 +58,11 @@ export class CodexAppServerRuntime {
|
|
|
41
58
|
name: text(input.name, "Codex thread name")
|
|
42
59
|
});
|
|
43
60
|
}
|
|
44
|
-
async readConversation(conversationId) {
|
|
61
|
+
async readConversation(conversationId, options = {}) {
|
|
45
62
|
const id = text(conversationId, "Codex thread id");
|
|
46
63
|
const result = await this.transport.request("thread/read", {
|
|
47
64
|
threadId: id,
|
|
48
|
-
includeTurns: true
|
|
65
|
+
includeTurns: options.includeTurns ?? true
|
|
49
66
|
});
|
|
50
67
|
return parseThreadSnapshot(result, id, "unknown");
|
|
51
68
|
}
|
|
@@ -84,22 +101,26 @@ export class CodexAppServerRuntime {
|
|
|
84
101
|
const requestedThreadId = text(input.conversationId, "Codex thread id");
|
|
85
102
|
let threadId = requestedThreadId;
|
|
86
103
|
try {
|
|
87
|
-
|
|
104
|
+
// Submission needs current availability, not historical Turns. Some
|
|
105
|
+
// App Server transports expose status without supporting history reads.
|
|
106
|
+
const snapshot = await this.readConversation(requestedThreadId, { includeTurns: false });
|
|
88
107
|
threadId = snapshot.threadId;
|
|
89
|
-
if (input.expectedNoActiveTurn
|
|
108
|
+
if (input.expectedNoActiveTurn
|
|
109
|
+
&& (snapshot.status === "active" || snapshot.activeTurnId !== undefined)) {
|
|
90
110
|
return {
|
|
91
111
|
status: "busy",
|
|
92
|
-
activeTurnId: snapshot.activeTurnId,
|
|
93
|
-
reason:
|
|
112
|
+
...(snapshot.activeTurnId === undefined ? {} : { activeTurnId: snapshot.activeTurnId }),
|
|
113
|
+
reason: snapshot.activeTurnId === undefined
|
|
114
|
+
? "Provider Conversation has an active Turn."
|
|
115
|
+
: `active-turn:${snapshot.activeTurnId}`
|
|
94
116
|
};
|
|
95
117
|
}
|
|
118
|
+
if (snapshot.status !== "idle" && snapshot.status !== "active") {
|
|
119
|
+
throw new Error(`Codex Session availability is ${snapshot.status}; no input was submitted.`);
|
|
120
|
+
}
|
|
96
121
|
}
|
|
97
122
|
catch (error) {
|
|
98
|
-
|
|
99
|
-
// exact response proves there cannot be an active Turn, so its first
|
|
100
|
-
// mutation can proceed without weakening unknown-delivery handling.
|
|
101
|
-
if (!codexAppServerErrorIsUnmaterialized(error))
|
|
102
|
-
throw error;
|
|
123
|
+
throw new CodexPreSubmissionError("Codex Session inspection failed before input submission.", { cause: error });
|
|
103
124
|
}
|
|
104
125
|
try {
|
|
105
126
|
const result = await this.transport.request("turn/start", {
|
|
@@ -126,7 +147,9 @@ export class CodexAppServerRuntime {
|
|
|
126
147
|
async steerTurn(input) {
|
|
127
148
|
const threadId = text(input.conversationId, "Codex thread id");
|
|
128
149
|
const expectedTurnId = text(input.expectedTurnId, "Codex expected Turn id");
|
|
129
|
-
const snapshot = await this.readConversation(threadId)
|
|
150
|
+
const snapshot = await this.readConversation(threadId).catch((error) => {
|
|
151
|
+
throw new CodexPreSubmissionError("Codex Session inspection failed before native steer.", { cause: error });
|
|
152
|
+
});
|
|
130
153
|
if (snapshot.activeTurnId !== expectedTurnId) {
|
|
131
154
|
return {
|
|
132
155
|
status: "not-accepted",
|
|
@@ -215,9 +238,7 @@ export class CodexAppServerRuntime {
|
|
|
215
238
|
input.providerNamespace,
|
|
216
239
|
input.accountScope,
|
|
217
240
|
input.conversationId,
|
|
218
|
-
|
|
219
|
-
continuation.continuationId,
|
|
220
|
-
continuation.generation
|
|
241
|
+
continuation.continuationId
|
|
221
242
|
].join("\u0000"),
|
|
222
243
|
...state
|
|
223
244
|
});
|
|
@@ -233,14 +254,14 @@ export class CodexAppServerRuntime {
|
|
|
233
254
|
return { quality: "exact", continuations: Object.freeze(observed) };
|
|
234
255
|
}
|
|
235
256
|
}
|
|
236
|
-
/** thread/closed
|
|
257
|
+
/** thread/closed unloads the client attachment; the durable thread remains resumable. */
|
|
237
258
|
export function codexNotificationBoundary(input) {
|
|
238
259
|
const conversationId = optionalId(input.params.threadId)
|
|
239
260
|
?? optionalId(objectMember(input.params, "thread")?.id);
|
|
240
261
|
const turnId = optionalId(input.params.turnId)
|
|
241
262
|
?? optionalId(objectMember(input.params, "turn")?.id);
|
|
242
263
|
if (input.method === "thread/closed")
|
|
243
|
-
return { kind: "
|
|
264
|
+
return { kind: "attachment-closed", conversationId };
|
|
244
265
|
if (input.method === "thread/goal/updated")
|
|
245
266
|
return { kind: "goal-updated", conversationId, turnId };
|
|
246
267
|
if (input.method === "thread/goal/cleared")
|
|
@@ -481,30 +502,17 @@ function optionalTurnStatus(value) {
|
|
|
481
502
|
}
|
|
482
503
|
function classifyMutationError(error) {
|
|
483
504
|
if (error instanceof CodexAppServerRequestError) {
|
|
484
|
-
if (codexAppServerErrorIsBusy(error)) {
|
|
485
|
-
return { status: "busy", reason: error.message };
|
|
486
|
-
}
|
|
487
505
|
if (["INVALID_PARAMS", "NOT_FOUND", "TURN_NOT_ACTIVE", -32602].includes(error.code)) {
|
|
488
506
|
return { status: "not-accepted", reason: error.message };
|
|
489
507
|
}
|
|
490
508
|
}
|
|
491
509
|
return { status: "unknown", reason: error instanceof Error ? error.message : String(error) };
|
|
492
510
|
}
|
|
493
|
-
function codexAppServerErrorIsBusy(error) {
|
|
494
|
-
return /\b(active turn|turn (?:is )?(?:already )?(?:active|in progress|running)|already has an active)\b/iu
|
|
495
|
-
.test(error.message);
|
|
496
|
-
}
|
|
497
511
|
function isNotLoaded(error) {
|
|
498
512
|
return error instanceof CodexAppServerRequestError
|
|
499
513
|
&& (String(error.code).toLowerCase().includes("not_loaded")
|
|
500
514
|
|| error.message.toLowerCase().includes("not loaded"));
|
|
501
515
|
}
|
|
502
|
-
function codexAppServerErrorIsUnmaterialized(error) {
|
|
503
|
-
if (!(error instanceof CodexAppServerRequestError) || Number(error.code) !== -32600) {
|
|
504
|
-
return false;
|
|
505
|
-
}
|
|
506
|
-
return /\bthread\b.*\bnot materialized yet\b.*\bbefore first user message\b/iu.test(error.message);
|
|
507
|
-
}
|
|
508
516
|
export function codexAppServerErrorIsMissing(error) {
|
|
509
517
|
if (!(error instanceof CodexAppServerRequestError))
|
|
510
518
|
return false;
|
|
@@ -7,7 +7,7 @@ import { openCodexInteractiveConnection } from "./structuredProviderHost.js";
|
|
|
7
7
|
/**
|
|
8
8
|
* Keep the native TUI and its transparent App Server attachment in one pane.
|
|
9
9
|
* The existing Host acknowledgement carries the ID from that TUI's exact
|
|
10
|
-
* thread/start or thread/resume response, before any user/model
|
|
10
|
+
* thread/start or thread/resume response, before any user/model AgentRun.
|
|
11
11
|
*
|
|
12
12
|
* Codex 0.150.1 cannot resume a pre-created empty Thread: no rollout exists
|
|
13
13
|
* until its first message. Observing the TUI's own startup avoids creating a
|
|
@@ -29,8 +29,7 @@ export async function runCodexInteractiveHost(home, payload) {
|
|
|
29
29
|
}
|
|
30
30
|
const expectedId = environment.YUI_NATIVE_SESSION_ID;
|
|
31
31
|
let snapshot = {
|
|
32
|
-
schemaVersion: 2, state: "starting", adapterId: "codex",
|
|
33
|
-
runtimeGenerationId: payload.runtimeGenerationId, updatedAt: new Date().toISOString()
|
|
32
|
+
schemaVersion: 2, state: "starting", adapterId: "codex", updatedAt: new Date().toISOString()
|
|
34
33
|
};
|
|
35
34
|
const control = await openAgentHostControl(home, payload, () => snapshot, async (request) => {
|
|
36
35
|
if (request.type !== "status") {
|
|
@@ -59,8 +58,8 @@ export async function runCodexInteractiveHost(home, payload) {
|
|
|
59
58
|
stop();
|
|
60
59
|
};
|
|
61
60
|
try {
|
|
62
|
-
// This proxy is a disposable client, not the daemon.
|
|
63
|
-
//
|
|
61
|
+
// This proxy is a disposable client, not the daemon. All requests belong
|
|
62
|
+
// to the TUI; its startup carries the reserved launch's per-Thread options.
|
|
64
63
|
connection = await openCodexInteractiveConnection({
|
|
65
64
|
command: payload.command, args: [...baseArgs, "app-server", "proxy"],
|
|
66
65
|
environment, cwd: payload.cwd
|
|
@@ -105,6 +104,7 @@ export async function runCodexInteractiveHost(home, payload) {
|
|
|
105
104
|
throw new Error("Codex TUI startup does not match the reserved Session.");
|
|
106
105
|
}
|
|
107
106
|
startupRequestId = message.id;
|
|
107
|
+
message.params = codexInteractiveStartupParameters(payload, params);
|
|
108
108
|
}
|
|
109
109
|
void connection.send(message).catch(fail);
|
|
110
110
|
}
|
|
@@ -159,7 +159,7 @@ export async function runCodexInteractiveHost(home, payload) {
|
|
|
159
159
|
args.splice(remoteIndex + 2, 0, "--remote-auth-token-env", "YUI_CODEX_REMOTE_AUTH_TOKEN");
|
|
160
160
|
child = spawn(payload.command, args, {
|
|
161
161
|
cwd: payload.cwd,
|
|
162
|
-
env: { ...environment, YUI_CODEX_REMOTE_AUTH_TOKEN: token },
|
|
162
|
+
env: { ...localCodexTuiEnvironment(environment), YUI_CODEX_REMOTE_AUTH_TOKEN: token },
|
|
163
163
|
stdio: "inherit"
|
|
164
164
|
});
|
|
165
165
|
for (const signal of signals)
|
|
@@ -183,6 +183,41 @@ export async function runCodexInteractiveHost(home, payload) {
|
|
|
183
183
|
await control.close();
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
+
/** The remote TUI can omit local launch configuration. Apply the reserved
|
|
187
|
+
* workspace and managed identity on its actual startup, never by starting a
|
|
188
|
+
* second Thread or changing daemon-wide configuration. */
|
|
189
|
+
export function codexInteractiveStartupParameters(payload, params) {
|
|
190
|
+
const options = payload.interactiveCodexThread;
|
|
191
|
+
if (options === undefined)
|
|
192
|
+
throw new Error("Global Codex launch is missing its per-Thread options.");
|
|
193
|
+
const config = params.config === undefined || params.config === null ? {} : jsonObject(params.config);
|
|
194
|
+
const shell = config.shell_environment_policy === undefined ? {} : jsonObject(config.shell_environment_policy);
|
|
195
|
+
const set = shell.set === undefined ? {} : jsonObject(shell.set);
|
|
196
|
+
return {
|
|
197
|
+
...params,
|
|
198
|
+
...options,
|
|
199
|
+
cwd: payload.cwd,
|
|
200
|
+
config: {
|
|
201
|
+
...config,
|
|
202
|
+
...options.config,
|
|
203
|
+
shell_environment_policy: {
|
|
204
|
+
...shell,
|
|
205
|
+
set: {
|
|
206
|
+
...set,
|
|
207
|
+
...Object.fromEntries(Object.entries(payload.environment).filter(([name]) => name.startsWith("YUI_")))
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** Our loopback relay must stay local, even when the TUI inherits HTTP proxies. */
|
|
214
|
+
export function localCodexTuiEnvironment(environment) {
|
|
215
|
+
const bypass = [
|
|
216
|
+
"127.0.0.1", "localhost", "::1",
|
|
217
|
+
environment.NO_PROXY, environment.no_proxy
|
|
218
|
+
].filter(Boolean).join(",");
|
|
219
|
+
return { ...environment, NO_PROXY: bypass, no_proxy: bypass };
|
|
220
|
+
}
|
|
186
221
|
function jsonObject(value) {
|
|
187
222
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
188
223
|
throw new Error("Invalid Codex App Server object.");
|
|
@@ -11,7 +11,7 @@ export function foldContinuationObservation(existing, raw) {
|
|
|
11
11
|
const base = existing ?? createProviderContinuation({
|
|
12
12
|
taskId: observation.fence.taskId,
|
|
13
13
|
roleName: observation.fence.roleName,
|
|
14
|
-
|
|
14
|
+
runId: observation.fence.runId,
|
|
15
15
|
identity,
|
|
16
16
|
...(observation.fence.parentContinuationId === undefined
|
|
17
17
|
? {}
|
|
@@ -116,17 +116,13 @@ export function applyContinuationObservationAtomically(store, raw, factRef) {
|
|
|
116
116
|
}
|
|
117
117
|
function continuationIdentity(observation) {
|
|
118
118
|
const fence = observation.fence;
|
|
119
|
-
if (fence.taskId === undefined || fence.
|
|
120
|
-
|| fence.conversationId === undefined || fence.activationId === undefined
|
|
121
|
-
|| fence.continuationId === undefined) {
|
|
119
|
+
if (fence.taskId === undefined || fence.runId === undefined || fence.conversationId === undefined || fence.continuationId === undefined) {
|
|
122
120
|
throw new Error("Continuation observation fence is incomplete.");
|
|
123
121
|
}
|
|
124
122
|
return {
|
|
125
123
|
providerNamespace: fence.driverId,
|
|
126
124
|
accountScope: fence.agentId,
|
|
127
125
|
conversationId: fence.conversationId,
|
|
128
|
-
activationId: fence.activationId,
|
|
129
126
|
continuationId: fence.continuationId,
|
|
130
|
-
generation: fence.continuationGeneration
|
|
131
127
|
};
|
|
132
128
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
|
|
3
|
+
import { createProjectResources } from "../resources/projectResourceService.js";
|
|
4
|
+
/** Revalidate the adopted owner; a saved path alone never authorizes execution. */
|
|
5
|
+
export function assertExecutionEnvironmentCurrent(store, taskId, snapshot) {
|
|
6
|
+
if (snapshot.taskId !== taskId)
|
|
7
|
+
throw new Error("Execution environment belongs to another Task.");
|
|
8
|
+
const current = createProjectResources(store).resolveExecutionEnvironment(taskId, snapshot.preparationId);
|
|
9
|
+
if (!isDeepStrictEqual(current, snapshot)) {
|
|
10
|
+
throw new Error("Execution environment changed; select an adopted environment and start a new Session.");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Last local check before native process creation or another Provider input. */
|
|
14
|
+
export function assertAgentExecutionEnvironment(home, payload) {
|
|
15
|
+
const snapshot = payload.executionEnvironment;
|
|
16
|
+
if (snapshot === undefined)
|
|
17
|
+
return;
|
|
18
|
+
if (payload.environment.YUI_SESSION_SCOPE !== "task"
|
|
19
|
+
|| payload.environment.YUI_TASK_ID !== snapshot.taskId
|
|
20
|
+
|| payload.cwd !== snapshot.directory.path) {
|
|
21
|
+
throw new Error("Agent launch does not match its adopted execution environment.");
|
|
22
|
+
}
|
|
23
|
+
const store = openCurrentTaskStore(home);
|
|
24
|
+
try {
|
|
25
|
+
assertExecutionEnvironmentCurrent(store, snapshot.taskId, snapshot);
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
store.close();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -4,15 +4,15 @@ export function projectFirstProgressAdvisory(input) {
|
|
|
4
4
|
: [...(input.sessions.history ?? []), ...Object.values(input.sessions.sessions)]
|
|
5
5
|
.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
6
6
|
const unique = [...new Map(sessions.map((session) => [
|
|
7
|
-
|
|
7
|
+
session.nativeSessionId,
|
|
8
8
|
session
|
|
9
9
|
])).values()];
|
|
10
|
-
const
|
|
11
|
-
const progress =
|
|
10
|
+
const firstSessionAt = unique[0]?.createdAt;
|
|
11
|
+
const progress = firstSessionAt === undefined
|
|
12
12
|
? []
|
|
13
13
|
: [
|
|
14
14
|
...input.events
|
|
15
|
-
.filter((event) => typeof event.payload.
|
|
15
|
+
.filter((event) => typeof event.payload.leaderRunId === "string")
|
|
16
16
|
.map((event) => ({ at: event.createdAt, ref: `event:${event.id}` })),
|
|
17
17
|
...input.workItems
|
|
18
18
|
.filter((item) => item.status !== "retired")
|
|
@@ -20,21 +20,21 @@ export function projectFirstProgressAdvisory(input) {
|
|
|
20
20
|
...input.reviewRounds.map((round) => ({ at: round.createdAt, ref: `review-round:${round.id}` })),
|
|
21
21
|
...input.integrations.map((attempt) => ({ at: attempt.createdAt, ref: `integration-attempt:${attempt.id}` }))
|
|
22
22
|
]
|
|
23
|
-
.filter(({ at }) => at >=
|
|
23
|
+
.filter(({ at }) => at >= firstSessionAt)
|
|
24
24
|
.sort((left, right) => left.at.localeCompare(right.at) || left.ref.localeCompare(right.ref));
|
|
25
25
|
const firstProgressAt = progress[0]?.at;
|
|
26
|
-
const
|
|
26
|
+
const sessionsBeforeFirstProgress = unique.filter((session) => (firstProgressAt === undefined || session.createdAt <= firstProgressAt)).length;
|
|
27
27
|
const attentionRecommended = firstProgressAt === undefined
|
|
28
|
-
&&
|
|
28
|
+
&& sessionsBeforeFirstProgress >= 2;
|
|
29
29
|
return Object.freeze({
|
|
30
30
|
attentionRecommended,
|
|
31
|
-
|
|
32
|
-
...(
|
|
31
|
+
sessionsBeforeFirstProgress,
|
|
32
|
+
...(firstSessionAt === undefined ? {} : { firstSessionAt }),
|
|
33
33
|
...(firstProgressAt === undefined ? {} : { firstProgressAt }),
|
|
34
34
|
reason: attentionRecommended
|
|
35
|
-
? `${
|
|
35
|
+
? `${sessionsBeforeFirstProgress} fresh Leader Sessions produced no first durable progress; Operator attention may be useful before another Session.`
|
|
36
36
|
: firstProgressAt !== undefined
|
|
37
37
|
? `First durable progress was recorded at ${firstProgressAt}.`
|
|
38
|
-
: "Fewer than two Leader
|
|
38
|
+
: "Fewer than two Leader Sessions exist before first durable progress."
|
|
39
39
|
});
|
|
40
40
|
}
|
package/dist/runtime/index.js
CHANGED
|
@@ -5,16 +5,17 @@ export { createRuntimeBinding } from "./runtimeBinding.js";
|
|
|
5
5
|
export { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
6
6
|
export { createSessionLaunchRequest } from "./sessionLaunchRequest.js";
|
|
7
7
|
export { DEFAULT_RECENT_TURN_ID_LIMIT, hasRecentTurnId, rememberRecentTurnId, validateRecentTurnIds } from "./recentTurnIds.js";
|
|
8
|
-
export { promptPushOutcome,
|
|
8
|
+
export { promptPushOutcome, RuntimeHostContentionError, RuntimeHostUnavailableError, RuntimeLaunchError } from "./ports.js";
|
|
9
9
|
export { AgentHostPromptPushAdapter, TmuxSessionHost } from "./tmuxAdapters.js";
|
|
10
10
|
export { FileTaskRuntimeIsolation, YUI_TASK_RUNTIME_ISOLATION_DESCRIPTOR, YUI_TASK_RUNTIME_SERVICE_NAMESPACE, assertTaskRuntimeIsolationPreflight, createTaskRuntimeIsolationDescriptor, parseTaskRuntimeIsolationDescriptor, planTaskRuntimeCleanup, taskRuntimeIsolationEnvironment, taskRuntimeIsolationFingerprint } from "./taskRuntimeIsolation.js";
|
|
11
|
-
export { createSessionOwnerIdentity,
|
|
11
|
+
export { createSessionOwnerIdentity, isLinuxProcessLive, listOwnedProcessTree, readLinuxProcessIdentity } from "./sessionOwnerIdentity.js";
|
|
12
12
|
export { FileSessionOwnerRegistry } from "./sessionOwnerRegistry.js";
|
|
13
13
|
export { formatRuntimeLaunchDiagnostic, redactLaunchArgument, redactLaunchText, RuntimeLaunchFailure, toRuntimeLaunchFailure } from "./launchDiagnostics.js";
|
|
14
14
|
export { DEFAULT_FORCED_GRACE_MS, DEFAULT_GRACEFUL_GRACE_MS, terminateSessionOwners } from "./sessionTerminationGuard.js";
|
|
15
15
|
export { ProviderContinuationReconciliationService } from "./providerContinuationReconciliationService.js";
|
|
16
16
|
export { codexNotificationBoundary, codexAppServerErrorIsMissing, CodexAppServerRequestError, CodexAppServerRuntime } from "./codexAppServerRuntime.js";
|
|
17
|
-
export { createProviderRuntimeBinding, acceptProviderTurn, beginProviderTurn,
|
|
17
|
+
export { createProviderRuntimeBinding, acceptProviderTurn, beginProviderTurn, currentProviderAuthority, currentProviderConversation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, managedProviderTurnId, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, validateProviderRuntimeBinding } from "./providerRuntimeIdentity.js";
|
|
18
18
|
export { FencedProviderControl } from "./providerControl.js";
|
|
19
19
|
export { sameProviderAuthorityFence, validateProviderAuthorityFence } from "./providerAuthorityFence.js";
|
|
20
20
|
export { reconcileSessionOwners } from "./sessionReconciliation.js";
|
|
21
|
+
export { createAgentEndpointFactory, builtinAgentEndpointImplementation } from "./agentEndpoint.js";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export const PROVIDER_MESSAGE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2
|
+
/**
|
|
3
|
+
* Newline-delimited JSON over a child process's stdio. This is a plain shared
|
|
4
|
+
* transport: it carries whole JSON objects and never interprets them, so any
|
|
5
|
+
* line-framed Provider protocol can sit on top of it.
|
|
6
|
+
*/
|
|
7
|
+
export class JsonLineChannel {
|
|
8
|
+
child;
|
|
9
|
+
mirror;
|
|
10
|
+
#listeners = new Set();
|
|
11
|
+
#closeListeners = new Set();
|
|
12
|
+
#buffer = "";
|
|
13
|
+
#closedError;
|
|
14
|
+
constructor(child, mirror) {
|
|
15
|
+
this.child = child;
|
|
16
|
+
this.mirror = mirror;
|
|
17
|
+
child.stdout.setEncoding("utf8");
|
|
18
|
+
child.stdout.on("data", (chunk) => this.#receive(chunk));
|
|
19
|
+
child.once("error", (error) => this.#close(error));
|
|
20
|
+
child.once("close", (code, signal) => this.#close(new Error(`Provider process exited (code=${code ?? "none"}, signal=${signal ?? "none"}).`)));
|
|
21
|
+
}
|
|
22
|
+
/** The transport's terminal failure, once it has one. */
|
|
23
|
+
get closedError() {
|
|
24
|
+
return this.#closedError;
|
|
25
|
+
}
|
|
26
|
+
onMessage(listener) {
|
|
27
|
+
this.#listeners.add(listener);
|
|
28
|
+
return () => this.#listeners.delete(listener);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Observe transport closure. A listener registered after the channel already
|
|
32
|
+
* closed is called immediately, so a caller cannot lose the loss of the pipe
|
|
33
|
+
* to a registration race and then wait forever for a reply that cannot come.
|
|
34
|
+
*/
|
|
35
|
+
onClose(listener) {
|
|
36
|
+
if (this.#closedError !== undefined) {
|
|
37
|
+
listener(this.#closedError);
|
|
38
|
+
return () => { };
|
|
39
|
+
}
|
|
40
|
+
this.#closeListeners.add(listener);
|
|
41
|
+
return () => this.#closeListeners.delete(listener);
|
|
42
|
+
}
|
|
43
|
+
async send(message) {
|
|
44
|
+
if (this.#closedError !== undefined)
|
|
45
|
+
throw this.#closedError;
|
|
46
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
47
|
+
if (Buffer.byteLength(line, "utf8") > PROVIDER_MESSAGE_MAX_BYTES) {
|
|
48
|
+
throw new Error("Provider request exceeds its message bound.");
|
|
49
|
+
}
|
|
50
|
+
await new Promise((resolvePromise, reject) => {
|
|
51
|
+
this.child.stdin.write(line, "utf8", (error) => {
|
|
52
|
+
if (error === null || error === undefined)
|
|
53
|
+
resolvePromise();
|
|
54
|
+
else
|
|
55
|
+
reject(error);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
#receive(chunk) {
|
|
60
|
+
this.mirror("stdout", chunk);
|
|
61
|
+
this.#buffer += chunk;
|
|
62
|
+
if (Buffer.byteLength(this.#buffer, "utf8") > PROVIDER_MESSAGE_MAX_BYTES) {
|
|
63
|
+
this.#close(new Error("Provider response line exceeds its message bound."));
|
|
64
|
+
terminateProcessGroup(this.child, "SIGTERM");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (;;) {
|
|
68
|
+
const newline = this.#buffer.indexOf("\n");
|
|
69
|
+
if (newline < 0)
|
|
70
|
+
return;
|
|
71
|
+
const line = this.#buffer.slice(0, newline).trim();
|
|
72
|
+
this.#buffer = this.#buffer.slice(newline + 1);
|
|
73
|
+
if (line.length === 0)
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(line);
|
|
77
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
78
|
+
continue;
|
|
79
|
+
for (const listener of this.#listeners)
|
|
80
|
+
listener(parsed);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
#close(error) {
|
|
88
|
+
if (this.#closedError !== undefined)
|
|
89
|
+
return;
|
|
90
|
+
this.#closedError = error;
|
|
91
|
+
for (const listener of this.#closeListeners)
|
|
92
|
+
listener(error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function terminateProcessGroup(child, signal) {
|
|
96
|
+
if (child.pid === undefined)
|
|
97
|
+
return;
|
|
98
|
+
// Managed Providers are spawned detached and therefore own a process group.
|
|
99
|
+
// Kill that exact group so a CLI helper cannot outlive the Agent Host. The
|
|
100
|
+
// direct-child fallback covers embedded runtimes that cannot create setsid.
|
|
101
|
+
try {
|
|
102
|
+
process.kill(-child.pid, signal);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error.code !== "ESRCH")
|
|
106
|
+
throw error;
|
|
107
|
+
child.kill(signal);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { resolve, isAbsolute } from "node:path";
|
|
3
3
|
import { validateProviderAuthorityFence } from "./providerAuthorityFence.js";
|
|
4
4
|
import { AGENT_HOST_LAUNCH_TICKET_TTL_MS } from "./runtimeDeadlines.js";
|
|
5
|
+
import { validateAgentEndpointImplementation } from "./agentEndpointIdentity.js";
|
|
6
|
+
import { validateExecutionEnvironmentSnapshot } from "../resources/projectResource.js";
|
|
7
|
+
import { isAgentAdapterId } from "../agent/adapterCatalog.js";
|
|
8
|
+
import { adapterIdForExecutionComponent, isAgentExecutionComponentId } from "../agent/executionComponents.js";
|
|
9
|
+
import { agentTransportForAdapter } from "../agent/connectionPlan.js";
|
|
10
|
+
import { resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
5
11
|
const brokers = new Map();
|
|
6
12
|
/** One Controller-process broker per canonical Home. Payloads never hit disk or tmux. */
|
|
7
13
|
export function launchBrokerForHome(home) {
|
|
@@ -17,30 +23,27 @@ export class LaunchBroker {
|
|
|
17
23
|
#reservations = new Map();
|
|
18
24
|
reserve(payload) {
|
|
19
25
|
validatePayload(payload);
|
|
20
|
-
if (this.#reservations.has(payload.runtimeGenerationId)) {
|
|
21
|
-
throw new Error(`Launch payload is already reserved: ${payload.runtimeGenerationId}.`);
|
|
22
|
-
}
|
|
23
26
|
const ticket = randomBytes(32).toString("hex");
|
|
24
|
-
this.#reservations.set(
|
|
27
|
+
this.#reservations.set(ticket, Object.freeze({
|
|
25
28
|
ticket,
|
|
26
29
|
payload,
|
|
27
30
|
createdAt: Date.now()
|
|
28
31
|
}));
|
|
29
|
-
return Object.freeze({
|
|
32
|
+
return Object.freeze({ ticket });
|
|
30
33
|
}
|
|
31
|
-
redeem(
|
|
32
|
-
const reservation = this.#reservations.get(
|
|
34
|
+
redeem(ticket) {
|
|
35
|
+
const reservation = this.#reservations.get(ticket);
|
|
33
36
|
if (reservation === undefined || reservation.ticket !== ticket) {
|
|
34
37
|
throw new Error("Launch ticket is invalid or already consumed.");
|
|
35
38
|
}
|
|
36
|
-
this.#reservations.delete(
|
|
39
|
+
this.#reservations.delete(ticket);
|
|
37
40
|
if (Date.now() - reservation.createdAt > AGENT_HOST_LAUNCH_TICKET_TTL_MS) {
|
|
38
41
|
throw new Error("Launch ticket expired before redemption.");
|
|
39
42
|
}
|
|
40
43
|
return reservation.payload;
|
|
41
44
|
}
|
|
42
|
-
revoke(
|
|
43
|
-
this.#reservations.delete(
|
|
45
|
+
revoke(ticket) {
|
|
46
|
+
this.#reservations.delete(ticket);
|
|
44
47
|
}
|
|
45
48
|
pendingCount() {
|
|
46
49
|
return this.#reservations.size;
|
|
@@ -55,7 +58,6 @@ export function validateAgentHostLaunchPayload(value) {
|
|
|
55
58
|
function validatePayload(payload) {
|
|
56
59
|
if (payload.schemaVersion !== 2)
|
|
57
60
|
throw new Error("Agent Host launch payload version is invalid.");
|
|
58
|
-
text(payload.runtimeGenerationId, "runtimeGenerationId");
|
|
59
61
|
text(payload.command, "command");
|
|
60
62
|
text(payload.cwd, "cwd");
|
|
61
63
|
if (!Array.isArray(payload.args))
|
|
@@ -70,6 +72,14 @@ function validatePayload(payload) {
|
|
|
70
72
|
throw new Error("Agent Host launch environment value is invalid.");
|
|
71
73
|
}
|
|
72
74
|
}
|
|
75
|
+
if (payload.executionEnvironment !== undefined) {
|
|
76
|
+
const adopted = validateExecutionEnvironmentSnapshot(payload.executionEnvironment);
|
|
77
|
+
if (payload.environment.YUI_SESSION_SCOPE !== "task"
|
|
78
|
+
|| payload.environment.YUI_TASK_ID !== adopted.taskId
|
|
79
|
+
|| payload.cwd !== adopted.directory.path) {
|
|
80
|
+
throw new Error("Agent Host execution environment does not match its Task and cwd.");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
73
83
|
if (payload.childLifecycle !== "persistent" && payload.childLifecycle !== "per-turn") {
|
|
74
84
|
throw new Error("Agent Host child lifecycle is invalid.");
|
|
75
85
|
}
|
|
@@ -83,18 +93,37 @@ function validatePayload(payload) {
|
|
|
83
93
|
function validateProviderControl(control) {
|
|
84
94
|
if (control.schemaVersion !== 1)
|
|
85
95
|
throw new Error("Agent Host Provider control version is invalid.");
|
|
86
|
-
if (control.
|
|
96
|
+
if (control.endpointImplementation !== undefined)
|
|
97
|
+
validateAgentEndpointImplementation(control.endpointImplementation);
|
|
98
|
+
if (!isAgentAdapterId(control.adapterId)) {
|
|
87
99
|
throw new Error("Agent Host Provider control adapter is invalid.");
|
|
88
100
|
}
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
// The connection plan owns the protocol/transport pair. Re-deriving it here
|
|
102
|
+
// is what keeps a control from naming a carrier its adapter does not speak.
|
|
103
|
+
if (control.transport !== agentTransportForAdapter(control.adapterId)) {
|
|
91
104
|
throw new Error("Agent Host Provider control transport does not match its adapter.");
|
|
92
105
|
}
|
|
106
|
+
// The component determines its plan, so a control naming both must have them
|
|
107
|
+
// agree. Letting them drift would let a launch apply one product's
|
|
108
|
+
// configuration decisions to another product's Session.
|
|
109
|
+
if (control.component !== undefined) {
|
|
110
|
+
if (!isAgentExecutionComponentId(control.component)) {
|
|
111
|
+
throw new Error("Agent Host Provider control execution component is invalid.");
|
|
112
|
+
}
|
|
113
|
+
if (adapterIdForExecutionComponent(control.component) !== control.adapterId) {
|
|
114
|
+
throw new Error("Agent Host Provider control execution component does not match its adapter.");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
93
117
|
if ((control.adapterId === "codex") !== (control.codexThread !== undefined)) {
|
|
94
118
|
throw new Error("Agent Host Provider thread settings do not match its adapter.");
|
|
95
119
|
}
|
|
96
120
|
if (control.codexThread !== undefined)
|
|
97
121
|
validateCodexThreadOptions(control.codexThread);
|
|
122
|
+
if (control.adapterId !== "acp" && control.acpSession !== undefined) {
|
|
123
|
+
throw new Error("Agent Host ACP session settings do not match its adapter.");
|
|
124
|
+
}
|
|
125
|
+
if (control.acpSession !== undefined)
|
|
126
|
+
validateAcpSessionOptions(control.acpSession);
|
|
98
127
|
if (control.mode !== "new" && control.mode !== "resume") {
|
|
99
128
|
throw new Error("Agent Host Provider control mode is invalid.");
|
|
100
129
|
}
|
|
@@ -114,7 +143,11 @@ function validateProviderControl(control) {
|
|
|
114
143
|
text(control.ownedTurn.attemptId, "owned Provider input attemptId");
|
|
115
144
|
text(control.ownedTurn.turnId, "owned Provider Turn id");
|
|
116
145
|
}
|
|
117
|
-
|
|
146
|
+
// Resume always needs the id being resumed. A new launch needs one only from
|
|
147
|
+
// an Agent that accepts a caller-chosen Session id; the others report theirs
|
|
148
|
+
// once they answer, so demanding it up front rejects a valid launch.
|
|
149
|
+
const requiresNativeSessionId = control.mode === "resume"
|
|
150
|
+
|| resolveAgentAdapter(control.adapterId).capabilities.nativeSessionDiscovery === "preallocated";
|
|
118
151
|
if (requiresNativeSessionId !== (control.nativeSessionId !== undefined)) {
|
|
119
152
|
throw new Error("Agent Host Provider resume identity is inconsistent.");
|
|
120
153
|
}
|
|
@@ -155,6 +188,48 @@ function validateCodexThreadOptions(options) {
|
|
|
155
188
|
throw new Error("Agent Host Codex thread config is invalid.");
|
|
156
189
|
}
|
|
157
190
|
}
|
|
191
|
+
function validateAcpSessionOptions(options) {
|
|
192
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
193
|
+
throw new Error("Agent Host ACP session settings are invalid.");
|
|
194
|
+
}
|
|
195
|
+
if (options.additionalDirectories !== undefined) {
|
|
196
|
+
if (!Array.isArray(options.additionalDirectories)) {
|
|
197
|
+
throw new Error("Agent Host ACP additional workspace roots are invalid.");
|
|
198
|
+
}
|
|
199
|
+
// ACP requires each additional root to be absolute. Rejecting a relative
|
|
200
|
+
// path here keeps an invalid request from reaching the Agent at all.
|
|
201
|
+
for (const root of options.additionalDirectories) {
|
|
202
|
+
if (!isAbsolute(text(root, "ACP additional workspace root"))) {
|
|
203
|
+
throw new Error("Agent Host ACP additional workspace root must be absolute.");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (options.sessionBootstrap !== undefined) {
|
|
208
|
+
text(options.sessionBootstrap, "ACP session bootstrap");
|
|
209
|
+
}
|
|
210
|
+
const desired = options.desiredConfiguration;
|
|
211
|
+
if (desired !== undefined) {
|
|
212
|
+
if (desired === null || typeof desired !== "object" || Array.isArray(desired)) {
|
|
213
|
+
throw new Error("Agent Host ACP session configuration is invalid.");
|
|
214
|
+
}
|
|
215
|
+
if (desired.model !== undefined)
|
|
216
|
+
text(desired.model, "ACP session model");
|
|
217
|
+
if (desired.effort !== undefined)
|
|
218
|
+
text(desired.effort, "ACP session effort");
|
|
219
|
+
if (desired.permissionMode !== undefined) {
|
|
220
|
+
text(desired.permissionMode, "ACP session permission mode");
|
|
221
|
+
}
|
|
222
|
+
if (desired.permissionBypass !== undefined && typeof desired.permissionBypass !== "boolean") {
|
|
223
|
+
throw new Error("Agent Host ACP session permission bypass is invalid.");
|
|
224
|
+
}
|
|
225
|
+
// Naming an exact mode and asking for bypass are two different requests, and
|
|
226
|
+
// a payload carrying both leaves it ambiguous which one the user made.
|
|
227
|
+
if (desired.permissionMode !== undefined && desired.permissionBypass === true) {
|
|
228
|
+
throw new Error("Agent Host ACP session configuration cannot request both a named permission "
|
|
229
|
+
+ "mode and the bypass strategy.");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
158
233
|
function text(value, label) {
|
|
159
234
|
if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
|
|
160
235
|
throw new Error(`Agent Host ${label} is invalid.`);
|