@zq-silk/yui 0.15.7 → 0.15.8
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 +192 -399
- package/README.md +127 -1149
- 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/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +157 -70
- 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 +178 -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 +1 -7
- 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 +259 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1188 -710
- 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} +146 -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 +458 -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 +114 -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 +247 -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 +62 -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 +380 -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 +123 -0
- package/dist/resources/projectResourceService.js +421 -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 +1 -0
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/sqliteSchema.js +431 -5
- package/dist/storage/sqliteStore.js +375 -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 +301 -0
- package/dist/task/taskActivationService.js +392 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- 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 +120 -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 +353 -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 +118 -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 +80 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +94 -0
- package/docs/agent-runtime-drivers.md +91 -0
- package/docs/architecture/README.md +38 -0
- package/docs/architecture/capabilities-and-resources.md +79 -0
- package/docs/managed-turn-and-session-runtime.md +222 -0
- package/docs/observability/README.md +81 -0
- package/docs/plugin-sdk.md +290 -0
- package/docs/provider-runtime.md +163 -0
- package/docs/release-workflow.md +303 -0
- package/docs/roles-and-configuration.md +113 -0
- package/docs/sqlite-control-plane-design.md +76 -0
- package/docs/task-dag-semantics.md +57 -0
- package/docs/task-delivery.md +103 -0
- package/docs/task-local-identity.md +6 -6
- package/docs/testing/verification-levels.md +86 -0
- package/i18n/README.zh-CN.md +98 -739
- package/package.json +2 -2
- package/skills/yui-leader/SKILL.md +130 -103
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +33 -0
- package/skills/yui-operator/SKILL.md +30 -59
- 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,24 +1,32 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { chmodSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
3
|
+
import { tmpdir, homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { createConnection, createServer } from "node:net";
|
|
6
6
|
import { createInterface } from "node:readline";
|
|
7
|
+
import { assertAgentExecutionEnvironment } from "./executionEnvironment.js";
|
|
8
|
+
import { withSessionContextPointer } from "../context/sessionBootstrapManifest.js";
|
|
7
9
|
import { callController, controllerCallMayHaveApplied, ControllerClientError } from "../core/controllerClient.js";
|
|
8
10
|
import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
|
|
9
11
|
import { callFileTaskController } from "../controller/clientRuntime.js";
|
|
10
12
|
import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
|
|
11
|
-
import { publishStructuredProviderAccepted,
|
|
13
|
+
import { publishStructuredProviderAccepted, publishStructuredProviderAttachmentExit, publishStructuredProviderActivity, publishStructuredProviderInputSettlement, publishStructuredProviderInputObserved, publishStructuredProviderGoal, publishStructuredConversationRecoverability, publishStructuredProviderOpened, publishStructuredProviderStarted, publishStructuredProviderTerminal } from "../controller/structuredProviderObservation.js";
|
|
12
14
|
import { validateAgentHostLaunchPayload } from "./launchBroker.js";
|
|
13
|
-
import { ProviderDeliveryUnknownError, ProviderConversationMissingError, ProviderTurnBusyError, ProviderTurnRejectedError
|
|
15
|
+
import { ProviderDeliveryUnknownError, ProviderConversationMissingError, ProviderTurnBusyError, ProviderTurnRejectedError } from "./structuredProviderHost.js";
|
|
16
|
+
import { createAgentEndpointOwner } from "./agentEndpointOwnership.js";
|
|
14
17
|
import { sameProviderAuthorityFence, validateProviderAuthorityFence } from "./providerAuthorityFence.js";
|
|
15
18
|
import { validateRuntimeProcessExitObservation } from "./processExitObservation.js";
|
|
16
19
|
import { persistRuntimeProcessExitObservation, replayRuntimeProcessExitOutbox } from "./processExitOutbox.js";
|
|
17
|
-
import {
|
|
18
|
-
import { AGENT_HOST_CONTROL_TIMEOUT_MS, AGENT_HOST_READY_TIMEOUT_MS } from "./runtimeDeadlines.js";
|
|
20
|
+
import { AGENT_HOST_CLIENT_EXIT_GRACE_MS, AGENT_HOST_CONTROL_TIMEOUT_MS, AGENT_HOST_READY_TIMEOUT_MS, ENDPOINT_DRAIN_TIMEOUT_MS, PROVIDER_ACCEPT_TIMEOUT_MS } from "./runtimeDeadlines.js";
|
|
19
21
|
import { providerDeliveryFailure, providerDeliveryFailureFrom, redactAgentErrorText, serializeAgentErrorRaw } from "./agentError.js";
|
|
20
22
|
import { runCodexInteractiveHost } from "./codexInteractiveHost.js";
|
|
21
|
-
|
|
23
|
+
import { readAgentRunConfigurationObservation, unknownAgentRunConfiguration } from "./agentRunConfiguration.js";
|
|
24
|
+
import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
|
|
25
|
+
import { createSessionOwnerIdentity, readLinuxProcessIdentity } from "./sessionOwnerIdentity.js";
|
|
26
|
+
import { yuiTmuxServerName, yuiTmuxSessionName } from "../tmux/tmuxManager.js";
|
|
27
|
+
import { tmuxSocketDirectory } from "../tmux/tmuxSocketEndpoint.js";
|
|
28
|
+
import { createTaskEvent } from "../event/taskEvent.js";
|
|
29
|
+
export const AGENT_HOST_CONTROL_PROTOCOL = "yui-agent-host/v5";
|
|
22
30
|
const HOST_CONTROL_MAX_BYTES = 32 * 1024;
|
|
23
31
|
const CODEX_CLIENT_STABLE_MS = 5_000;
|
|
24
32
|
const MAX_CONSECUTIVE_CODEX_DISCONNECTS = 3;
|
|
@@ -28,20 +36,29 @@ export function serializeAgentHostLaunchControl(control) {
|
|
|
28
36
|
export async function runAgentHost(input) {
|
|
29
37
|
const hostInstanceId = randomUUID();
|
|
30
38
|
let hostSequence = 0;
|
|
31
|
-
let payload = await redeem(input.home, input.
|
|
39
|
+
let payload = await redeem(input.home, input.ticket);
|
|
32
40
|
if (payload.environment.YUI_SESSION_SCOPE === "global"
|
|
33
41
|
&& payload.environment.YUI_ADAPTER_ID === "codex"
|
|
34
42
|
&& payload.providerControl === undefined) {
|
|
35
43
|
return runCodexInteractiveHost(input.home, payload);
|
|
36
44
|
}
|
|
37
45
|
let session;
|
|
46
|
+
// This process really holds the Endpoint code its Session runs on. The lease
|
|
47
|
+
// spans the Session's Turns, so installing a newer generation does not change
|
|
48
|
+
// the implementation under a Session that is still using this one.
|
|
49
|
+
const endpointOwner = createAgentEndpointOwner();
|
|
50
|
+
let endpointLease;
|
|
51
|
+
/** The adapter this process actually pinned, so the drain below reports the
|
|
52
|
+
* implementation genuinely held rather than whichever one a later payload names. */
|
|
53
|
+
let endpointAdapterId;
|
|
38
54
|
let sessionPayload;
|
|
39
|
-
let
|
|
40
|
-
let
|
|
55
|
+
let activeRunPayload;
|
|
56
|
+
let lastTerminalPayload;
|
|
57
|
+
let lastTerminal;
|
|
58
|
+
let activeRunAttemptId;
|
|
41
59
|
let activeNativeTurnId;
|
|
42
60
|
let codexClientAttachedAt;
|
|
43
61
|
let consecutiveCodexDisconnects = 0;
|
|
44
|
-
let activationId;
|
|
45
62
|
let conversationRecoverability = "unknown";
|
|
46
63
|
let authority;
|
|
47
64
|
let hostStopRequested = false;
|
|
@@ -53,16 +70,55 @@ export async function runAgentHost(input) {
|
|
|
53
70
|
const updateSnapshot = (next) => {
|
|
54
71
|
snapshot = validateSnapshot(next);
|
|
55
72
|
};
|
|
56
|
-
const authorityFields = () =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
const authorityFields = () => ({
|
|
74
|
+
...(session === undefined ? {} : { endpointImplementation: session.configuration.implementation }),
|
|
75
|
+
...(authority === undefined ? {} : {
|
|
76
|
+
authorityEpoch: authority.epoch,
|
|
77
|
+
authorityOwner: authority.owner,
|
|
78
|
+
authorityHolderId: authority.holderId
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
const handleAccepted = (receipt) => {
|
|
82
|
+
const observedPayload = activeRunPayload;
|
|
83
|
+
if (observedPayload === undefined || receipt.acceptance !== "provider"
|
|
84
|
+
|| receipt.attemptId !== activeRunAttemptId
|
|
85
|
+
|| receipt.nativeSessionId !== session?.nativeSessionId)
|
|
86
|
+
return;
|
|
87
|
+
void enqueueSerialized(async () => {
|
|
88
|
+
if (activeRunPayload !== observedPayload || activeRunAttemptId !== receipt.attemptId
|
|
89
|
+
|| snapshot.attemptId === receipt.attemptId && snapshot.inputAcceptance === "provider")
|
|
90
|
+
return;
|
|
91
|
+
await publishStructuredProviderAccepted({
|
|
92
|
+
home: input.home, environment: observedPayload.environment, receipt
|
|
93
|
+
});
|
|
94
|
+
updateSnapshot(hostSnapshot("ready", {
|
|
95
|
+
adapterId: session.adapterId,
|
|
96
|
+
processInstanceId: session.processInstanceId,
|
|
97
|
+
nativeSessionId: receipt.nativeSessionId,
|
|
98
|
+
conversationId: receipt.conversationId,
|
|
99
|
+
attemptId: receipt.attemptId,
|
|
100
|
+
nativeTurnId: receipt.nativeTurnId,
|
|
101
|
+
inputAcceptance: "provider",
|
|
102
|
+
...authorityFields()
|
|
103
|
+
}));
|
|
104
|
+
signalRoleMailbox(input.home, observedPayload);
|
|
105
|
+
}).catch(() => { });
|
|
106
|
+
};
|
|
107
|
+
const handleActivity = (activity) => {
|
|
108
|
+
const observedPayload = activeRunPayload;
|
|
109
|
+
if (observedPayload === undefined || activity.attemptId !== activeRunAttemptId
|
|
110
|
+
|| activity.nativeSessionId !== session?.nativeSessionId)
|
|
111
|
+
return;
|
|
112
|
+
void enqueueSerialized(async () => {
|
|
113
|
+
if (activeRunPayload !== observedPayload || activity.attemptId !== activeRunAttemptId)
|
|
114
|
+
return;
|
|
115
|
+
await publishStructuredProviderActivity({ home: input.home, environment: observedPayload.environment, activity });
|
|
116
|
+
}).catch(() => { });
|
|
60
117
|
};
|
|
61
118
|
const handleStarted = (started, observedPayload = sessionPayload) => {
|
|
62
119
|
if (started.clientOwned || observedPayload === undefined)
|
|
63
120
|
return;
|
|
64
121
|
updateSnapshot(hostSnapshot("busy", {
|
|
65
|
-
runtimeGenerationId: observedPayload.runtimeGenerationId,
|
|
66
122
|
adapterId: observedPayload.providerControl?.adapterId,
|
|
67
123
|
processInstanceId: session?.processInstanceId,
|
|
68
124
|
nativeSessionId: started.nativeSessionId,
|
|
@@ -70,12 +126,10 @@ export async function runAgentHost(input) {
|
|
|
70
126
|
nativeTurnId: started.nativeTurnId,
|
|
71
127
|
...authorityFields()
|
|
72
128
|
}));
|
|
73
|
-
const directActivationId = activationId ?? observedPayload.runtimeGenerationId;
|
|
74
129
|
void enqueueSerialized(async () => {
|
|
75
130
|
await publishStructuredProviderStarted({
|
|
76
131
|
home: input.home,
|
|
77
132
|
environment: observedPayload.environment,
|
|
78
|
-
activationId: directActivationId,
|
|
79
133
|
started
|
|
80
134
|
});
|
|
81
135
|
signalRoleMailbox(input.home, observedPayload);
|
|
@@ -89,19 +143,16 @@ export async function runAgentHost(input) {
|
|
|
89
143
|
|| terminal.conversationId !== session.conversationId)
|
|
90
144
|
return;
|
|
91
145
|
// A direct Provider client shares the Session but still produces a Yui
|
|
92
|
-
//
|
|
146
|
+
// AgentRun audit record. It has no WorkItem ownership and no managed input
|
|
93
147
|
// fence; the runtime observer records only visible input/output.
|
|
94
|
-
const directActivationId = activationId ?? busyPayload.runtimeGenerationId;
|
|
95
148
|
void enqueueSerialized(async () => {
|
|
96
149
|
await publishStructuredProviderTerminal({
|
|
97
150
|
home: input.home,
|
|
98
151
|
environment: busyPayload.environment,
|
|
99
|
-
activationId: directActivationId,
|
|
100
152
|
terminal
|
|
101
153
|
});
|
|
102
154
|
if (session !== undefined && terminal.conversationId === session.conversationId) {
|
|
103
|
-
updateSnapshot(hostSnapshot(session.
|
|
104
|
-
runtimeGenerationId: busyPayload.runtimeGenerationId,
|
|
155
|
+
updateSnapshot(hostSnapshot(session.inspect().activeNativeTurnId === undefined ? "idle" : "busy", {
|
|
105
156
|
adapterId: session.adapterId,
|
|
106
157
|
processInstanceId: session.processInstanceId,
|
|
107
158
|
nativeSessionId: session.nativeSessionId,
|
|
@@ -113,18 +164,17 @@ export async function runAgentHost(input) {
|
|
|
113
164
|
}).catch(() => { });
|
|
114
165
|
return;
|
|
115
166
|
}
|
|
116
|
-
const terminalPayload =
|
|
117
|
-
if (terminal.attemptId !== undefined && terminal.attemptId !==
|
|
167
|
+
const terminalPayload = activeRunPayload;
|
|
168
|
+
if (terminal.attemptId !== undefined && terminal.attemptId !== activeRunAttemptId
|
|
118
169
|
&& sessionPayload !== undefined && terminal.nativeSessionId === session?.nativeSessionId) {
|
|
119
170
|
// A duplicate/late terminal retains the Driver's exact old attempt.
|
|
120
171
|
// The durable observer validates that binding; it must not alter the
|
|
121
|
-
// current Host occupancy or inherit the successor's managed
|
|
172
|
+
// current Host occupancy or inherit the successor's managed AgentRun.
|
|
122
173
|
const observedPayload = sessionPayload;
|
|
123
174
|
void enqueueSerialized(async () => {
|
|
124
175
|
await publishStructuredProviderTerminal({
|
|
125
176
|
home: input.home,
|
|
126
177
|
environment: observedPayload.environment,
|
|
127
|
-
activationId: activationId ?? observedPayload.runtimeGenerationId,
|
|
128
178
|
terminal
|
|
129
179
|
});
|
|
130
180
|
signalRoleMailbox(input.home, observedPayload);
|
|
@@ -132,20 +182,20 @@ export async function runAgentHost(input) {
|
|
|
132
182
|
return;
|
|
133
183
|
}
|
|
134
184
|
if (terminalPayload === undefined
|
|
135
|
-
|| terminal.attemptId !==
|
|
185
|
+
|| terminal.attemptId !== activeRunAttemptId
|
|
136
186
|
|| terminal.nativeSessionId !== session?.nativeSessionId
|
|
137
187
|
|| (terminal.nativeTurnId !== undefined && activeNativeTurnId !== undefined
|
|
138
188
|
&& terminal.nativeTurnId !== activeNativeTurnId))
|
|
139
189
|
return;
|
|
140
190
|
const terminalAttemptId = terminal.attemptId;
|
|
141
|
-
|
|
191
|
+
lastTerminalPayload = terminalPayload;
|
|
192
|
+
lastTerminal = terminal;
|
|
142
193
|
void enqueueSerialized(async () => {
|
|
143
|
-
if (
|
|
144
|
-
||
|
|
194
|
+
if (activeRunPayload !== terminalPayload
|
|
195
|
+
|| activeRunAttemptId !== terminalAttemptId)
|
|
145
196
|
return;
|
|
146
197
|
if (session !== undefined) {
|
|
147
198
|
updateSnapshot(hostSnapshot("settling", {
|
|
148
|
-
runtimeGenerationId: terminalPayload.runtimeGenerationId,
|
|
149
199
|
adapterId: terminalPayload.providerControl.adapterId,
|
|
150
200
|
processInstanceId: session.processInstanceId,
|
|
151
201
|
nativeSessionId: terminal.nativeSessionId,
|
|
@@ -159,19 +209,17 @@ export async function runAgentHost(input) {
|
|
|
159
209
|
await publishStructuredProviderTerminal({
|
|
160
210
|
home: input.home,
|
|
161
211
|
environment: terminalPayload.environment,
|
|
162
|
-
activationId: terminalActivationId,
|
|
163
212
|
terminal
|
|
164
213
|
});
|
|
165
|
-
if (
|
|
214
|
+
if (activeRunPayload !== terminalPayload)
|
|
166
215
|
return;
|
|
167
|
-
|
|
168
|
-
|
|
216
|
+
activeRunPayload = undefined;
|
|
217
|
+
activeRunAttemptId = undefined;
|
|
169
218
|
activeNativeTurnId = undefined;
|
|
170
219
|
if (session === undefined)
|
|
171
220
|
return;
|
|
172
221
|
const currentPayload = sessionPayload ?? terminalPayload;
|
|
173
|
-
updateSnapshot(hostSnapshot(session.
|
|
174
|
-
runtimeGenerationId: currentPayload.runtimeGenerationId,
|
|
222
|
+
updateSnapshot(hostSnapshot(session.inspect().activeNativeTurnId === undefined ? "idle" : "busy", {
|
|
175
223
|
adapterId: currentPayload.providerControl.adapterId,
|
|
176
224
|
processInstanceId: session.processInstanceId,
|
|
177
225
|
nativeSessionId: terminal.nativeSessionId,
|
|
@@ -182,7 +230,6 @@ export async function runAgentHost(input) {
|
|
|
182
230
|
}
|
|
183
231
|
catch (error) {
|
|
184
232
|
updateSnapshot(hostSnapshot("failed", {
|
|
185
|
-
runtimeGenerationId: terminalPayload.runtimeGenerationId,
|
|
186
233
|
adapterId: terminalPayload.providerControl.adapterId,
|
|
187
234
|
processInstanceId: session?.processInstanceId,
|
|
188
235
|
nativeSessionId: terminal.nativeSessionId,
|
|
@@ -195,16 +242,14 @@ export async function runAgentHost(input) {
|
|
|
195
242
|
}).catch(() => { });
|
|
196
243
|
};
|
|
197
244
|
const handleGoal = (goal) => {
|
|
198
|
-
const currentPayload =
|
|
245
|
+
const currentPayload = activeRunPayload ?? sessionPayload;
|
|
199
246
|
const currentSession = session;
|
|
200
247
|
if (currentPayload === undefined || currentSession === undefined)
|
|
201
248
|
return;
|
|
202
|
-
const currentActivationId = activationId ?? currentPayload.runtimeGenerationId;
|
|
203
249
|
void enqueueSerialized(async () => {
|
|
204
250
|
await publishStructuredProviderGoal({
|
|
205
251
|
home: input.home,
|
|
206
252
|
environment: currentPayload.environment,
|
|
207
|
-
activationId: currentActivationId,
|
|
208
253
|
conversationId: currentSession.conversationId,
|
|
209
254
|
goal
|
|
210
255
|
});
|
|
@@ -216,17 +261,26 @@ export async function runAgentHost(input) {
|
|
|
216
261
|
if (previousControl?.adapterId !== "codex" || authority === undefined) {
|
|
217
262
|
throw new Error("Codex client reconnect lost its Provider control identity.");
|
|
218
263
|
}
|
|
219
|
-
const ownedTurn =
|
|
220
|
-
||
|
|
264
|
+
const ownedTurn = activeRunPayload === undefined
|
|
265
|
+
|| activeRunAttemptId === undefined
|
|
221
266
|
|| activeNativeTurnId === undefined
|
|
222
267
|
? undefined
|
|
223
|
-
: { attemptId:
|
|
268
|
+
: { attemptId: activeRunAttemptId, turnId: activeNativeTurnId };
|
|
224
269
|
const reconnectPayload = {
|
|
225
270
|
...currentPayload,
|
|
226
|
-
|
|
271
|
+
command: disconnectedSession.configuration.command,
|
|
272
|
+
args: disconnectedSession.configuration.args,
|
|
273
|
+
cwd: disconnectedSession.configuration.cwd,
|
|
274
|
+
environment: activeRunPayload?.environment ?? currentPayload.environment,
|
|
227
275
|
providerControl: {
|
|
228
276
|
schemaVersion: 1,
|
|
229
277
|
adapterId: "codex",
|
|
278
|
+
// Reconnecting reaches the same product the disconnected Session was
|
|
279
|
+
// pinned to. Rebuilding the control without the component would quietly
|
|
280
|
+
// drop that identity halfway through a Session's life.
|
|
281
|
+
...(previousControl.component === undefined
|
|
282
|
+
? {}
|
|
283
|
+
: { component: previousControl.component }),
|
|
230
284
|
transport: "codex-app-server-proxy",
|
|
231
285
|
kind: "restore",
|
|
232
286
|
mode: "resume",
|
|
@@ -234,7 +288,8 @@ export async function runAgentHost(input) {
|
|
|
234
288
|
...(previousControl.sessionTitle === undefined
|
|
235
289
|
? {}
|
|
236
290
|
: { sessionTitle: previousControl.sessionTitle }),
|
|
237
|
-
codexThread:
|
|
291
|
+
codexThread: disconnectedSession.configuration.threadOptions,
|
|
292
|
+
endpointImplementation: disconnectedSession.configuration.implementation,
|
|
238
293
|
...(ownedTurn === undefined ? {} : { ownedTurn }),
|
|
239
294
|
authority
|
|
240
295
|
}
|
|
@@ -249,42 +304,61 @@ export async function runAgentHost(input) {
|
|
|
249
304
|
if (hostStopRequested)
|
|
250
305
|
return;
|
|
251
306
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
307
|
+
assertAgentExecutionEnvironment(input.home, reconnectPayload);
|
|
308
|
+
// Reattaching a dropped client continues the same Session on the same
|
|
309
|
+
// pinned generation; it never re-selects an implementation.
|
|
310
|
+
if (endpointLease === undefined) {
|
|
311
|
+
throw new Error("Codex client reconnect lost its Endpoint implementation lease.");
|
|
312
|
+
}
|
|
313
|
+
const started = await endpointLease.resume(reconnectPayload);
|
|
257
314
|
session = started.session;
|
|
258
315
|
sessionPayload = currentPayload;
|
|
316
|
+
started.session.events((event) => {
|
|
317
|
+
if (session !== started.session && event.type !== "terminal")
|
|
318
|
+
return;
|
|
319
|
+
if (event.type === "accepted")
|
|
320
|
+
handleAccepted(event.value);
|
|
321
|
+
else if (event.type === "activity")
|
|
322
|
+
handleActivity(event.value);
|
|
323
|
+
else if (event.type === "started")
|
|
324
|
+
handleStarted(event.value, currentPayload);
|
|
325
|
+
else if (event.type === "terminal")
|
|
326
|
+
handleTerminal(event.value);
|
|
327
|
+
else if (event.type === "goal")
|
|
328
|
+
handleGoal(event.value);
|
|
329
|
+
else
|
|
330
|
+
void enqueueSerialized(() => publishStructuredProviderInputObserved({
|
|
331
|
+
home: input.home, environment: currentPayload.environment, observed: event.value
|
|
332
|
+
})).catch(() => { });
|
|
333
|
+
});
|
|
259
334
|
conversationRecoverability = "recoverable";
|
|
260
335
|
codexClientAttachedAt = Date.now();
|
|
261
336
|
observeExit(started.session, currentPayload);
|
|
262
|
-
const reconnectState =
|
|
337
|
+
const reconnectState = activeRunPayload !== undefined
|
|
263
338
|
? ownedTurn === undefined ? "delivery-unknown" : "ready"
|
|
264
|
-
: started.session.
|
|
339
|
+
: started.session.inspect().activeNativeTurnId === undefined ? "idle" : "busy";
|
|
265
340
|
updateSnapshot(hostSnapshot(reconnectState, {
|
|
266
|
-
runtimeGenerationId: currentPayload.runtimeGenerationId,
|
|
267
341
|
adapterId: "codex",
|
|
268
342
|
processInstanceId: started.session.processInstanceId,
|
|
269
343
|
nativeSessionId: started.session.nativeSessionId,
|
|
270
344
|
conversationId: started.session.conversationId,
|
|
271
|
-
...(
|
|
345
|
+
...(activeRunAttemptId === undefined ? {} : { attemptId: activeRunAttemptId }),
|
|
272
346
|
...(activeNativeTurnId === undefined ? {} : { nativeTurnId: activeNativeTurnId }),
|
|
273
347
|
...authorityFields(),
|
|
274
|
-
...(ownedTurn !== undefined ||
|
|
348
|
+
...(ownedTurn !== undefined || activeRunPayload === undefined
|
|
275
349
|
? {}
|
|
276
350
|
: {
|
|
277
|
-
detail: "Codex client reattached, but the in-flight
|
|
351
|
+
detail: "Codex client reattached, but the in-flight AgentRun has no exact native identity."
|
|
278
352
|
})
|
|
279
353
|
}));
|
|
280
354
|
if (started.recoveredTerminal !== undefined) {
|
|
281
355
|
handleTerminal(started.recoveredTerminal);
|
|
282
356
|
}
|
|
283
|
-
if (ownedTurn === undefined && started.session.
|
|
357
|
+
if (ownedTurn === undefined && started.session.inspect().activeNativeTurnId !== undefined) {
|
|
284
358
|
handleStarted({
|
|
285
359
|
conversationId: started.session.conversationId,
|
|
286
360
|
nativeSessionId: started.session.nativeSessionId,
|
|
287
|
-
nativeTurnId: started.session.
|
|
361
|
+
nativeTurnId: started.session.inspect().activeNativeTurnId,
|
|
288
362
|
clientOwned: false,
|
|
289
363
|
observedAt: new Date().toISOString()
|
|
290
364
|
}, currentPayload);
|
|
@@ -298,12 +372,11 @@ export async function runAgentHost(input) {
|
|
|
298
372
|
}
|
|
299
373
|
}
|
|
300
374
|
updateSnapshot(hostSnapshot("failed", {
|
|
301
|
-
runtimeGenerationId: currentPayload.runtimeGenerationId,
|
|
302
375
|
adapterId: "codex",
|
|
303
376
|
processInstanceId: disconnectedSession.processInstanceId,
|
|
304
377
|
nativeSessionId: disconnectedSession.nativeSessionId,
|
|
305
378
|
conversationId: disconnectedSession.conversationId,
|
|
306
|
-
...(
|
|
379
|
+
...(activeRunAttemptId === undefined ? {} : { attemptId: activeRunAttemptId }),
|
|
307
380
|
...(activeNativeTurnId === undefined ? {} : { nativeTurnId: activeNativeTurnId }),
|
|
308
381
|
...authorityFields(),
|
|
309
382
|
detail: `Codex client could not reattach after bounded retries: ${errorText(lastError)}`
|
|
@@ -312,16 +385,13 @@ export async function runAgentHost(input) {
|
|
|
312
385
|
const observeExit = (providerSession, launched) => {
|
|
313
386
|
void providerSession.waitForExit().then((result) => enqueueSerialized(async () => {
|
|
314
387
|
const ownsCurrentSession = session === providerSession;
|
|
315
|
-
const currentPayload = ownsCurrentSession
|
|
316
|
-
|
|
317
|
-
? activationId ?? launched.runtimeGenerationId
|
|
318
|
-
: launched.runtimeGenerationId;
|
|
388
|
+
const currentPayload = ownsCurrentSession
|
|
389
|
+
? activeRunPayload ?? lastTerminalPayload ?? sessionPayload ?? launched : launched;
|
|
319
390
|
const exitAuthority = authorityFields();
|
|
320
|
-
const
|
|
391
|
+
const stopRequested = hostStopRequested || providerSession.inspect().cancellation === "requested";
|
|
321
392
|
const reconnectableCodexClient = ownsCurrentSession
|
|
322
393
|
&& providerSession.adapterId === "codex"
|
|
323
|
-
&& !hostStopRequested
|
|
324
|
-
&& stopReceipt === null;
|
|
394
|
+
&& !hostStopRequested;
|
|
325
395
|
if (reconnectableCodexClient) {
|
|
326
396
|
session = undefined;
|
|
327
397
|
if (codexClientAttachedAt !== undefined
|
|
@@ -331,12 +401,11 @@ export async function runAgentHost(input) {
|
|
|
331
401
|
consecutiveCodexDisconnects += 1;
|
|
332
402
|
if (consecutiveCodexDisconnects > MAX_CONSECUTIVE_CODEX_DISCONNECTS) {
|
|
333
403
|
updateSnapshot(hostSnapshot("failed", {
|
|
334
|
-
runtimeGenerationId: currentPayload.runtimeGenerationId,
|
|
335
404
|
adapterId: "codex",
|
|
336
405
|
processInstanceId: result.processInstanceId,
|
|
337
406
|
nativeSessionId: providerSession.nativeSessionId,
|
|
338
407
|
conversationId: providerSession.conversationId,
|
|
339
|
-
...(
|
|
408
|
+
...(activeRunAttemptId === undefined ? {} : { attemptId: activeRunAttemptId }),
|
|
340
409
|
...(activeNativeTurnId === undefined ? {} : { nativeTurnId: activeNativeTurnId }),
|
|
341
410
|
...exitAuthority,
|
|
342
411
|
detail: "Codex client repeatedly disconnected before reaching a stable attachment."
|
|
@@ -344,12 +413,11 @@ export async function runAgentHost(input) {
|
|
|
344
413
|
return;
|
|
345
414
|
}
|
|
346
415
|
updateSnapshot(hostSnapshot("starting", {
|
|
347
|
-
runtimeGenerationId: currentPayload.runtimeGenerationId,
|
|
348
416
|
adapterId: "codex",
|
|
349
417
|
processInstanceId: result.processInstanceId,
|
|
350
418
|
nativeSessionId: providerSession.nativeSessionId,
|
|
351
419
|
conversationId: providerSession.conversationId,
|
|
352
|
-
...(
|
|
420
|
+
...(activeRunAttemptId === undefined ? {} : { attemptId: activeRunAttemptId }),
|
|
353
421
|
...(activeNativeTurnId === undefined ? {} : { nativeTurnId: activeNativeTurnId }),
|
|
354
422
|
...exitAuthority,
|
|
355
423
|
detail: "Codex App Server proxy disconnected; attaching a replacement client."
|
|
@@ -359,28 +427,18 @@ export async function runAgentHost(input) {
|
|
|
359
427
|
}
|
|
360
428
|
if (ownsCurrentSession) {
|
|
361
429
|
session = undefined;
|
|
362
|
-
activationId = undefined;
|
|
363
430
|
conversationRecoverability = "unknown";
|
|
364
431
|
authority = undefined;
|
|
432
|
+
// The client is gone for good, so this Session stops holding the
|
|
433
|
+
// implementation. Keeping the reference would report work in progress
|
|
434
|
+
// that no longer exists; a later launch pins again explicitly.
|
|
435
|
+
const ending = endpointLease;
|
|
436
|
+
endpointLease = undefined;
|
|
437
|
+
await ending?.release().catch(() => undefined);
|
|
365
438
|
}
|
|
366
439
|
hostSequence += 1;
|
|
367
440
|
const observedAt = new Date().toISOString();
|
|
368
441
|
const failures = [];
|
|
369
|
-
try {
|
|
370
|
-
await publishStructuredProviderActivationTerminal({
|
|
371
|
-
home: input.home,
|
|
372
|
-
environment: currentPayload.environment,
|
|
373
|
-
conversationId: providerSession.conversationId,
|
|
374
|
-
nativeSessionId: providerSession.nativeSessionId,
|
|
375
|
-
activationId: currentActivationId,
|
|
376
|
-
status: stopReceipt !== null || hostStopRequested ? "ended" : "failed",
|
|
377
|
-
observedAt
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
catch (error) {
|
|
381
|
-
failures.push(`activation terminal: ${errorText(error)}`);
|
|
382
|
-
}
|
|
383
|
-
let exitPersisted = false;
|
|
384
442
|
try {
|
|
385
443
|
await persistAndSubmitExit(input.home, validateRuntimeProcessExitObservation({
|
|
386
444
|
schemaVersion: 2,
|
|
@@ -392,31 +450,39 @@ export async function runAgentHost(input) {
|
|
|
392
450
|
? {}
|
|
393
451
|
: { taskId: currentPayload.environment.YUI_TASK_ID }),
|
|
394
452
|
roleName: currentPayload.environment.YUI_ROLE ?? "unknown-role",
|
|
395
|
-
...(currentPayload.environment.
|
|
453
|
+
...(currentPayload.environment.YUI_RUN_ID === undefined
|
|
396
454
|
? {}
|
|
397
|
-
: {
|
|
398
|
-
runtimeGenerationId: currentPayload.runtimeGenerationId,
|
|
455
|
+
: { runId: currentPayload.environment.YUI_RUN_ID }),
|
|
399
456
|
...(providerSession.nativeSessionId.length === 0
|
|
400
457
|
? {}
|
|
401
458
|
: { nativeSessionId: providerSession.nativeSessionId }),
|
|
402
459
|
processKind: "provider-child",
|
|
403
460
|
...(result.code === null ? {} : { exitCode: result.code }),
|
|
404
461
|
...(result.signal === null ? {} : { signal: result.signal }),
|
|
405
|
-
...(
|
|
462
|
+
...(stopRequested ? { stopRequested: true } : {}),
|
|
406
463
|
observedAt
|
|
407
464
|
}));
|
|
408
|
-
exitPersisted = true;
|
|
409
465
|
}
|
|
410
466
|
catch (error) {
|
|
411
467
|
failures.push(`process exit: ${errorText(error)}`);
|
|
412
468
|
}
|
|
413
|
-
if (
|
|
414
|
-
|
|
469
|
+
if (ownsCurrentSession && currentPayload.environment.YUI_SESSION_SCOPE === "task") {
|
|
470
|
+
try {
|
|
471
|
+
await publishStructuredProviderAttachmentExit({
|
|
472
|
+
home: input.home, environment: currentPayload.environment,
|
|
473
|
+
nativeSessionId: providerSession.nativeSessionId,
|
|
474
|
+
attemptId: activeRunAttemptId ?? lastTerminal?.attemptId,
|
|
475
|
+
nativeTurnId: activeNativeTurnId ?? lastTerminal?.nativeTurnId,
|
|
476
|
+
failed: !stopRequested, observedAt
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
failures.push(`attachment exit: ${errorText(error)}`);
|
|
481
|
+
}
|
|
415
482
|
}
|
|
416
483
|
if (hostStopRequested || !ownsCurrentSession)
|
|
417
484
|
return;
|
|
418
485
|
updateSnapshot(hostSnapshot(failures.length === 0 ? "exited" : "failed", {
|
|
419
|
-
runtimeGenerationId: currentPayload.runtimeGenerationId,
|
|
420
486
|
adapterId: currentPayload.providerControl?.adapterId,
|
|
421
487
|
processInstanceId: result.processInstanceId,
|
|
422
488
|
nativeSessionId: providerSession.nativeSessionId,
|
|
@@ -424,12 +490,11 @@ export async function runAgentHost(input) {
|
|
|
424
490
|
...exitAuthority,
|
|
425
491
|
...(failures.length !== 0
|
|
426
492
|
? { detail: failures.join("; ") }
|
|
427
|
-
:
|
|
493
|
+
: activeRunPayload === undefined
|
|
428
494
|
? {}
|
|
429
|
-
: { detail: "Provider process exited before the active
|
|
495
|
+
: { detail: "Provider process exited before the active AgentRun reached a terminal boundary." })
|
|
430
496
|
}));
|
|
431
497
|
})).catch((error) => updateSnapshot(hostSnapshot("failed", {
|
|
432
|
-
runtimeGenerationId: launched.runtimeGenerationId,
|
|
433
498
|
adapterId: launched.providerControl?.adapterId,
|
|
434
499
|
processInstanceId: providerSession.processInstanceId,
|
|
435
500
|
...authorityFields(),
|
|
@@ -441,7 +506,7 @@ export async function runAgentHost(input) {
|
|
|
441
506
|
if (providerControl === undefined) {
|
|
442
507
|
throw new Error("Agent Host accepts only managed Provider control launches.");
|
|
443
508
|
}
|
|
444
|
-
if (
|
|
509
|
+
if (activeRunPayload !== undefined
|
|
445
510
|
|| ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
|
|
446
511
|
throw new Error("Agent Host still owns an unsettled Provider Turn.");
|
|
447
512
|
}
|
|
@@ -457,7 +522,6 @@ export async function runAgentHost(input) {
|
|
|
457
522
|
throw new Error("Agent Host launch carries a stale Provider authority fence.");
|
|
458
523
|
}
|
|
459
524
|
updateSnapshot(hostSnapshot("starting", {
|
|
460
|
-
runtimeGenerationId: next.runtimeGenerationId,
|
|
461
525
|
adapterId: providerControl.adapterId,
|
|
462
526
|
...(session === undefined ? {} : {
|
|
463
527
|
processInstanceId: session.processInstanceId,
|
|
@@ -471,28 +535,58 @@ export async function runAgentHost(input) {
|
|
|
471
535
|
if (session !== undefined) {
|
|
472
536
|
if (session.adapterId !== providerControl.adapterId
|
|
473
537
|
|| providerControl.mode !== "resume"
|
|
474
|
-
|| providerControl.nativeSessionId !== session.nativeSessionId
|
|
538
|
+
|| providerControl.nativeSessionId !== session.nativeSessionId
|
|
539
|
+
|| (providerControl.endpointImplementation !== undefined
|
|
540
|
+
&& (providerControl.endpointImplementation.id !== session.configuration.implementation.id
|
|
541
|
+
|| providerControl.endpointImplementation.generation !== session.configuration.implementation.generation))) {
|
|
475
542
|
throw new Error("Agent Host launch does not match its live Provider Conversation.");
|
|
476
543
|
}
|
|
477
544
|
sessionPayload = next;
|
|
478
545
|
}
|
|
479
546
|
else {
|
|
480
|
-
activationId = next.runtimeGenerationId;
|
|
481
|
-
conversationRecoverability = providerControl.adapterId === "codex"
|
|
482
|
-
? "recoverable"
|
|
483
|
-
: "unknown";
|
|
484
547
|
if (providerControl.kind === "restore" && providerControl.ownedTurn !== undefined) {
|
|
485
|
-
|
|
486
|
-
|
|
548
|
+
activeRunPayload = next;
|
|
549
|
+
activeRunAttemptId = providerControl.ownedTurn.attemptId;
|
|
487
550
|
activeNativeTurnId = providerControl.ownedTurn.turnId;
|
|
488
551
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
552
|
+
assertAgentExecutionEnvironment(input.home, next);
|
|
553
|
+
// Pin before opening: the Session holds this exact generation for every
|
|
554
|
+
// later Turn. A pinned reference naming code this process is not running
|
|
555
|
+
// fails here, rather than silently starting on a different generation.
|
|
556
|
+
endpointLease ??= endpointOwner.pin(providerControl.adapterId, providerControl.endpointImplementation);
|
|
557
|
+
endpointAdapterId = providerControl.adapterId;
|
|
558
|
+
const started = await (providerControl.mode === "new"
|
|
559
|
+
? endpointLease.open(next) : endpointLease.resume(next));
|
|
494
560
|
session = started.session;
|
|
495
561
|
sessionPayload = next;
|
|
562
|
+
recordOwnedProviderProcess(input.home, next.environment, started.session);
|
|
563
|
+
recordNativeConnection(input.home, next, started.session);
|
|
564
|
+
lastTerminalPayload = undefined;
|
|
565
|
+
lastTerminal = undefined;
|
|
566
|
+
// Recoverable means a later process can rebind this Conversation by
|
|
567
|
+
// its native id. The Endpoint answers that from the Driver capability
|
|
568
|
+
// or, where the protocol settles it per connection, from what this
|
|
569
|
+
// Agent actually negotiated — so it is read after the handshake, never
|
|
570
|
+
// predicted before it.
|
|
571
|
+
conversationRecoverability = started.session.conversationRecoverability;
|
|
572
|
+
started.session.events((event) => {
|
|
573
|
+
if (session !== started.session && event.type !== "terminal")
|
|
574
|
+
return;
|
|
575
|
+
if (event.type === "accepted")
|
|
576
|
+
handleAccepted(event.value);
|
|
577
|
+
else if (event.type === "activity")
|
|
578
|
+
handleActivity(event.value);
|
|
579
|
+
else if (event.type === "started")
|
|
580
|
+
handleStarted(event.value, next);
|
|
581
|
+
else if (event.type === "terminal")
|
|
582
|
+
handleTerminal(event.value);
|
|
583
|
+
else if (event.type === "goal")
|
|
584
|
+
handleGoal(event.value);
|
|
585
|
+
else
|
|
586
|
+
void enqueueSerialized(() => publishStructuredProviderInputObserved({
|
|
587
|
+
home: input.home, environment: next.environment, observed: event.value
|
|
588
|
+
})).catch(() => { });
|
|
589
|
+
});
|
|
496
590
|
recoveredGoal = started.goal;
|
|
497
591
|
if (started.session.adapterId === "codex") {
|
|
498
592
|
codexClientAttachedAt = Date.now();
|
|
@@ -503,51 +597,49 @@ export async function runAgentHost(input) {
|
|
|
503
597
|
handleTerminal(started.recoveredTerminal);
|
|
504
598
|
}
|
|
505
599
|
if ((providerControl.kind !== "restore" || providerControl.ownedTurn === undefined)
|
|
506
|
-
&& started.session.
|
|
600
|
+
&& started.session.inspect().activeNativeTurnId !== undefined) {
|
|
507
601
|
handleStarted({
|
|
508
602
|
conversationId: started.session.conversationId,
|
|
509
603
|
nativeSessionId: started.session.nativeSessionId,
|
|
510
|
-
nativeTurnId: started.session.
|
|
604
|
+
nativeTurnId: started.session.inspect().activeNativeTurnId,
|
|
511
605
|
clientOwned: false,
|
|
512
606
|
observedAt: new Date().toISOString()
|
|
513
607
|
}, next);
|
|
514
608
|
}
|
|
515
609
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (recoveredGoal !== undefined) {
|
|
610
|
+
if (!providerControl.sessionOnly)
|
|
611
|
+
await publishStructuredProviderOpened({
|
|
612
|
+
home: input.home,
|
|
613
|
+
environment: next.environment,
|
|
614
|
+
conversationId: session.conversationId,
|
|
615
|
+
nativeSessionId: session.nativeSessionId,
|
|
616
|
+
recoverability: conversationRecoverability,
|
|
617
|
+
observedAt: new Date().toISOString()
|
|
618
|
+
});
|
|
619
|
+
if (recoveredGoal !== undefined && !providerControl.sessionOnly) {
|
|
526
620
|
await publishStructuredProviderGoal({
|
|
527
621
|
home: input.home,
|
|
528
622
|
environment: next.environment,
|
|
529
|
-
activationId: activationId ?? next.runtimeGenerationId,
|
|
530
623
|
conversationId: session.conversationId,
|
|
531
624
|
goal: recoveredGoal
|
|
532
625
|
});
|
|
533
626
|
}
|
|
534
|
-
const
|
|
627
|
+
const restoredOwnedRun = providerControl.kind === "restore"
|
|
535
628
|
? providerControl.ownedTurn
|
|
536
629
|
: undefined;
|
|
537
|
-
const providerState =
|
|
630
|
+
const providerState = restoredOwnedRun !== undefined
|
|
538
631
|
? "ready"
|
|
539
|
-
: session.
|
|
632
|
+
: session.inspect().activeNativeTurnId === undefined ? "idle" : "busy";
|
|
540
633
|
updateSnapshot(hostSnapshot(providerState, {
|
|
541
|
-
runtimeGenerationId: next.runtimeGenerationId,
|
|
542
634
|
adapterId: providerControl.adapterId,
|
|
543
635
|
processInstanceId: session.processInstanceId,
|
|
544
636
|
nativeSessionId: session.nativeSessionId,
|
|
545
637
|
conversationId: session.conversationId,
|
|
546
|
-
...(
|
|
638
|
+
...(restoredOwnedRun === undefined
|
|
547
639
|
? {}
|
|
548
640
|
: {
|
|
549
|
-
attemptId:
|
|
550
|
-
nativeTurnId:
|
|
641
|
+
attemptId: restoredOwnedRun.attemptId,
|
|
642
|
+
nativeTurnId: restoredOwnedRun.turnId
|
|
551
643
|
}),
|
|
552
644
|
...authorityFields()
|
|
553
645
|
}));
|
|
@@ -559,13 +651,11 @@ export async function runAgentHost(input) {
|
|
|
559
651
|
home: input.home,
|
|
560
652
|
environment: next.environment,
|
|
561
653
|
conversationId: error.conversationId,
|
|
562
|
-
activationId: activationId ?? next.runtimeGenerationId,
|
|
563
654
|
recoverability: "unrecoverable",
|
|
564
655
|
observedAt: new Date().toISOString()
|
|
565
656
|
}).catch(() => { });
|
|
566
657
|
}
|
|
567
658
|
if (session === undefined) {
|
|
568
|
-
activationId = undefined;
|
|
569
659
|
conversationRecoverability = "unknown";
|
|
570
660
|
authority = undefined;
|
|
571
661
|
}
|
|
@@ -573,7 +663,6 @@ export async function runAgentHost(input) {
|
|
|
573
663
|
? "busy"
|
|
574
664
|
: error instanceof ProviderTurnRejectedError ? "rejected" : "failed";
|
|
575
665
|
updateSnapshot(hostSnapshot(state, {
|
|
576
|
-
runtimeGenerationId: next.runtimeGenerationId,
|
|
577
666
|
adapterId: providerControl.adapterId,
|
|
578
667
|
processInstanceId: session?.processInstanceId,
|
|
579
668
|
nativeSessionId: session?.nativeSessionId ?? providerControl.nativeSessionId,
|
|
@@ -581,8 +670,8 @@ export async function runAgentHost(input) {
|
|
|
581
670
|
...authorityFields(),
|
|
582
671
|
detail: errorText(error)
|
|
583
672
|
}));
|
|
584
|
-
|
|
585
|
-
|
|
673
|
+
activeRunPayload = undefined;
|
|
674
|
+
activeRunAttemptId = undefined;
|
|
586
675
|
activeNativeTurnId = undefined;
|
|
587
676
|
throw error;
|
|
588
677
|
}
|
|
@@ -593,85 +682,61 @@ export async function runAgentHost(input) {
|
|
|
593
682
|
return operation;
|
|
594
683
|
};
|
|
595
684
|
const enqueueDispatch = (next) => (enqueueSerialized(() => dispatch(next)));
|
|
596
|
-
const
|
|
685
|
+
const submitRun = async (request, operation) => {
|
|
597
686
|
if (session === undefined || sessionPayload === undefined) {
|
|
598
687
|
throw new Error("Agent Host has no live Provider Conversation.");
|
|
599
688
|
}
|
|
600
689
|
if (request.nativeSessionId !== session.nativeSessionId) {
|
|
601
|
-
throw new Error("Agent Host
|
|
602
|
-
}
|
|
603
|
-
// A superseded generation is a real identity conflict, and the writer
|
|
604
|
-
// fence does not catch it: a Turn can carry an old generation under a
|
|
605
|
-
// fence that is still current. Without this the Host registered the stale
|
|
606
|
-
// Turn, wrote it to the Provider, and adopted the old generation into its
|
|
607
|
-
// own snapshot. Same generation is contention, which is decided below.
|
|
608
|
-
if (request.runtimeGenerationId !== sessionPayload.runtimeGenerationId) {
|
|
609
|
-
operation.failure = providerDeliveryFailure({
|
|
610
|
-
detail: "Agent Host Turn targets a superseded runtime generation.",
|
|
611
|
-
errorName: "ProviderGenerationConflictError",
|
|
612
|
-
phase: "turn-submit",
|
|
613
|
-
hostState: snapshot.state,
|
|
614
|
-
expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
|
|
615
|
-
observedRuntimeGenerationId: request.runtimeGenerationId,
|
|
616
|
-
attemptId: request.turn.attemptId,
|
|
617
|
-
inputDisposition: "not-accepted",
|
|
618
|
-
registrationDisposition: "not-committed",
|
|
619
|
-
// A stale writer does not make the live Session unusable.
|
|
620
|
-
sessionDisposition: "recoverable"
|
|
621
|
-
});
|
|
622
|
-
throw new Error("Agent Host Turn targets a superseded runtime generation: expected "
|
|
623
|
-
+ `${sessionPayload.runtimeGenerationId}, observed ${request.runtimeGenerationId}.`);
|
|
690
|
+
throw new Error("Agent Host AgentRun targets a different Provider Conversation.");
|
|
624
691
|
}
|
|
625
692
|
if (authority === undefined
|
|
626
693
|
|| !sameProviderAuthorityFence(authority, request.authority)) {
|
|
627
694
|
throw new Error("Agent Host rejected a stale Provider writer fence.");
|
|
628
695
|
}
|
|
629
|
-
if (
|
|
696
|
+
if (activeRunPayload !== undefined || snapshot.state === "settling"
|
|
697
|
+
|| session.inspect().activeNativeTurnId !== undefined) {
|
|
630
698
|
operation.failure = providerDeliveryFailure({
|
|
631
699
|
detail: "Agent Host still owns an unsettled Provider Turn.",
|
|
632
700
|
errorName: "ProviderTurnBusyError",
|
|
633
701
|
phase: "turn-submit",
|
|
634
702
|
hostState: snapshot.state === "settling" ? "settling" : snapshot.state,
|
|
635
|
-
|
|
636
|
-
observedRuntimeGenerationId: request.runtimeGenerationId,
|
|
637
|
-
attemptId: request.turn.attemptId,
|
|
703
|
+
attemptId: request.run.attemptId,
|
|
638
704
|
inputDisposition: "not-accepted",
|
|
639
705
|
registrationDisposition: "not-committed",
|
|
640
706
|
// Busy is a healthy Session doing work, never a reason to stop it.
|
|
641
707
|
sessionDisposition: "recoverable"
|
|
642
708
|
});
|
|
643
|
-
throw new ProviderTurnBusyError("Agent Host still owns an unsettled Provider Turn.", request.
|
|
709
|
+
throw new ProviderTurnBusyError("Agent Host still owns an unsettled Provider Turn.", request.run.attemptId, activeNativeTurnId);
|
|
644
710
|
}
|
|
645
|
-
const {
|
|
711
|
+
const { YUI_RUN_ID: _launchRunId, ...baseEnvironment } = sessionPayload.environment;
|
|
646
712
|
// Steer attempts belong to the active native Turn. Never evict an unknown
|
|
647
|
-
// attempt while that
|
|
713
|
+
// attempt while that AgentRun remains active, but don't retain another AgentRun's
|
|
648
714
|
// completed steering history after a new exact submission begins.
|
|
649
715
|
recentSteerAttempts.clear();
|
|
650
|
-
|
|
716
|
+
activeRunPayload = {
|
|
651
717
|
...sessionPayload,
|
|
652
718
|
environment: {
|
|
653
719
|
...baseEnvironment,
|
|
654
|
-
...(request.
|
|
720
|
+
...(request.runId === undefined ? {} : { YUI_RUN_ID: request.runId })
|
|
655
721
|
}
|
|
656
722
|
};
|
|
657
|
-
|
|
723
|
+
activeRunAttemptId = request.run.attemptId;
|
|
658
724
|
activeNativeTurnId = undefined;
|
|
659
725
|
updateSnapshot(hostSnapshot("starting", {
|
|
660
|
-
runtimeGenerationId: request.runtimeGenerationId,
|
|
661
726
|
adapterId: session.adapterId,
|
|
662
727
|
processInstanceId: session.processInstanceId,
|
|
663
728
|
nativeSessionId: session.nativeSessionId,
|
|
664
729
|
conversationId: session.conversationId,
|
|
665
|
-
attemptId: request.
|
|
730
|
+
attemptId: request.run.attemptId,
|
|
666
731
|
...authorityFields()
|
|
667
732
|
}));
|
|
668
|
-
const
|
|
733
|
+
const durableRun = hostRunControlParams(sessionPayload, session.nativeSessionId, request.authority, request.run.attemptId, request.runId);
|
|
669
734
|
// Registration precedes the Provider write, so its failure modes are not
|
|
670
735
|
// the Provider's. A definite failure means the Provider never saw this
|
|
671
736
|
// input and the occupancy must be released; anything unconfirmed leaves
|
|
672
737
|
// durable state ambiguous and keeps it held.
|
|
673
738
|
try {
|
|
674
|
-
await beginDurableProviderTurn(input.home,
|
|
739
|
+
await beginDurableProviderTurn(input.home, durableRun);
|
|
675
740
|
}
|
|
676
741
|
catch (error) {
|
|
677
742
|
// Registration ends in one of three states and they are not
|
|
@@ -686,13 +751,13 @@ export async function runAgentHost(input) {
|
|
|
686
751
|
const registrationUnknown = !registrationSettled
|
|
687
752
|
&& (error instanceof ControllerAcknowledgementUnknownError
|
|
688
753
|
|| error instanceof ProviderDeliveryUnknownError);
|
|
689
|
-
updateSnapshot(hostSnapshot(registrationUnknown ? "delivery-unknown"
|
|
690
|
-
|
|
754
|
+
updateSnapshot(hostSnapshot(registrationUnknown ? "delivery-unknown"
|
|
755
|
+
: error instanceof ProviderTurnBusyError ? "busy" : "failed", {
|
|
691
756
|
adapterId: session.adapterId,
|
|
692
757
|
processInstanceId: session.processInstanceId,
|
|
693
758
|
nativeSessionId: session.nativeSessionId,
|
|
694
759
|
conversationId: session.conversationId,
|
|
695
|
-
attemptId: request.
|
|
760
|
+
attemptId: request.run.attemptId,
|
|
696
761
|
...authorityFields(),
|
|
697
762
|
detail: registrationUnknown
|
|
698
763
|
? `Provider Turn registration acknowledgement is unconfirmed; the Provider was not sent this input: ${errorText(error)}`
|
|
@@ -705,9 +770,7 @@ export async function runAgentHost(input) {
|
|
|
705
770
|
operation.failure = providerDeliveryFailureFrom(error, {
|
|
706
771
|
phase: "turn-submit",
|
|
707
772
|
hostState: snapshot.state,
|
|
708
|
-
|
|
709
|
-
observedRuntimeGenerationId: request.runtimeGenerationId,
|
|
710
|
-
attemptId: request.turn.attemptId,
|
|
773
|
+
attemptId: request.run.attemptId,
|
|
711
774
|
inputDisposition: "not-accepted",
|
|
712
775
|
registrationDisposition: registrationUnknown
|
|
713
776
|
? "unknown"
|
|
@@ -718,8 +781,8 @@ export async function runAgentHost(input) {
|
|
|
718
781
|
// The Provider provably holds nothing, and the durable record either
|
|
719
782
|
// never existed or is settled. Release the Session for the next
|
|
720
783
|
// attempt instead of stranding it in a false `starting`.
|
|
721
|
-
|
|
722
|
-
|
|
784
|
+
activeRunPayload = undefined;
|
|
785
|
+
activeRunAttemptId = undefined;
|
|
723
786
|
activeNativeTurnId = undefined;
|
|
724
787
|
}
|
|
725
788
|
throw error;
|
|
@@ -727,34 +790,38 @@ export async function runAgentHost(input) {
|
|
|
727
790
|
let providerAccepted = false;
|
|
728
791
|
let transportAccepted = false;
|
|
729
792
|
try {
|
|
730
|
-
|
|
793
|
+
assertAgentExecutionEnvironment(input.home, activeRunPayload);
|
|
794
|
+
const receipt = endpointReceipt(await session.submit({
|
|
795
|
+
...request.run,
|
|
796
|
+
boundedText: withSessionContextPointer(request.run.boundedText, activeRunPayload.environment),
|
|
797
|
+
inputRef: request.runId ?? request.run.attemptId
|
|
798
|
+
}), request.run.attemptId);
|
|
731
799
|
transportAccepted = true;
|
|
732
800
|
providerAccepted = receipt.acceptance === "provider";
|
|
733
801
|
activeNativeTurnId = receipt.nativeTurnId;
|
|
734
802
|
try {
|
|
735
803
|
await publishStructuredProviderAccepted({
|
|
736
804
|
home: input.home,
|
|
737
|
-
environment:
|
|
738
|
-
activationId: activationId ?? sessionPayload.runtimeGenerationId,
|
|
805
|
+
environment: activeRunPayload.environment,
|
|
739
806
|
receipt
|
|
740
807
|
});
|
|
741
808
|
}
|
|
742
809
|
catch (error) {
|
|
743
|
-
const unknown = new ProviderDeliveryUnknownError("Input submission returned a receipt but its durable acknowledgement could not be confirmed.", request.
|
|
810
|
+
const unknown = new ProviderDeliveryUnknownError("Input submission returned a receipt but its durable acknowledgement could not be confirmed.", request.run.attemptId, { cause: error });
|
|
744
811
|
// A resolve failure here must not escape before the snapshot is
|
|
745
812
|
// written: the Provider has already accepted, and losing that fact
|
|
746
|
-
// would report a definite non-acceptance for a live
|
|
747
|
-
await resolveProviderTurnSubmission(input.home,
|
|
813
|
+
// would report a definite non-acceptance for a live AgentRun.
|
|
814
|
+
await resolveProviderTurnSubmission(input.home, durableRun, unknown);
|
|
748
815
|
throw unknown;
|
|
749
816
|
}
|
|
750
817
|
updateSnapshot(hostSnapshot("ready", {
|
|
751
|
-
runtimeGenerationId: request.runtimeGenerationId,
|
|
752
818
|
adapterId: session.adapterId,
|
|
753
819
|
processInstanceId: session.processInstanceId,
|
|
754
820
|
nativeSessionId: receipt.nativeSessionId,
|
|
755
821
|
conversationId: receipt.conversationId,
|
|
756
822
|
attemptId: receipt.attemptId,
|
|
757
823
|
nativeTurnId: receipt.nativeTurnId,
|
|
824
|
+
inputAcceptance: receipt.acceptance,
|
|
758
825
|
...authorityFields()
|
|
759
826
|
}));
|
|
760
827
|
return snapshot;
|
|
@@ -764,7 +831,7 @@ export async function runAgentHost(input) {
|
|
|
764
831
|
let settlementUnknown = false;
|
|
765
832
|
if (!transportAccepted) {
|
|
766
833
|
try {
|
|
767
|
-
await resolveProviderTurnSubmission(input.home,
|
|
834
|
+
await resolveProviderTurnSubmission(input.home, durableRun, error);
|
|
768
835
|
}
|
|
769
836
|
catch (resolutionError) {
|
|
770
837
|
settlementUnknown = true;
|
|
@@ -779,21 +846,18 @@ export async function runAgentHost(input) {
|
|
|
779
846
|
? "busy"
|
|
780
847
|
: error instanceof ProviderTurnRejectedError ? "rejected" : "failed";
|
|
781
848
|
updateSnapshot(hostSnapshot(state, {
|
|
782
|
-
runtimeGenerationId: request.runtimeGenerationId,
|
|
783
849
|
adapterId: session.adapterId,
|
|
784
850
|
processInstanceId: session.processInstanceId,
|
|
785
851
|
nativeSessionId: session.nativeSessionId,
|
|
786
852
|
conversationId: session.conversationId,
|
|
787
|
-
attemptId: request.
|
|
853
|
+
attemptId: request.run.attemptId,
|
|
788
854
|
...authorityFields(),
|
|
789
855
|
detail: errorText(failureError)
|
|
790
856
|
}));
|
|
791
857
|
operation.failure = providerDeliveryFailureFrom(failureError, {
|
|
792
858
|
phase: "turn-submit",
|
|
793
859
|
hostState: state,
|
|
794
|
-
|
|
795
|
-
observedRuntimeGenerationId: request.runtimeGenerationId,
|
|
796
|
-
attemptId: request.turn.attemptId,
|
|
860
|
+
attemptId: request.run.attemptId,
|
|
797
861
|
// Acceptance is observed, never inferred from the error class.
|
|
798
862
|
inputDisposition: providerAccepted
|
|
799
863
|
? "accepted"
|
|
@@ -802,50 +866,28 @@ export async function runAgentHost(input) {
|
|
|
802
866
|
sessionDisposition: state === "failed" ? "unknown" : "recoverable"
|
|
803
867
|
});
|
|
804
868
|
if (state !== "delivery-unknown") {
|
|
805
|
-
|
|
806
|
-
|
|
869
|
+
activeRunPayload = undefined;
|
|
870
|
+
activeRunAttemptId = undefined;
|
|
807
871
|
activeNativeTurnId = undefined;
|
|
808
872
|
}
|
|
809
873
|
if (deliveryUnknown && !(failureError instanceof ProviderDeliveryUnknownError)) {
|
|
810
|
-
throw new ProviderDeliveryUnknownError("Provider accepted input but its durable acknowledgement could not be confirmed.", request.
|
|
874
|
+
throw new ProviderDeliveryUnknownError("Provider accepted input but its durable acknowledgement could not be confirmed.", request.run.attemptId, { cause: failureError });
|
|
811
875
|
}
|
|
812
876
|
throw failureError;
|
|
813
877
|
}
|
|
814
878
|
};
|
|
815
|
-
const
|
|
816
|
-
if (session === undefined || sessionPayload === undefined ||
|
|
879
|
+
const steerRun = async (request, operation) => {
|
|
880
|
+
if (session === undefined || sessionPayload === undefined || activeRunPayload === undefined) {
|
|
817
881
|
operation.failure = providerDeliveryFailure({
|
|
818
882
|
detail: "Agent Host has no active Provider Turn to steer.",
|
|
819
883
|
errorName: "ProviderTurnRejectedError",
|
|
820
884
|
phase: "turn-submit",
|
|
821
885
|
hostState: snapshot.state,
|
|
822
|
-
|
|
823
|
-
?? snapshot.runtimeGenerationId ?? "none",
|
|
824
|
-
observedRuntimeGenerationId: request.runtimeGenerationId,
|
|
825
|
-
attemptId: request.turn.attemptId,
|
|
826
|
-
inputDisposition: "not-accepted",
|
|
827
|
-
sessionDisposition: "recoverable"
|
|
828
|
-
});
|
|
829
|
-
throw new ProviderTurnRejectedError("Agent Host has no active Provider Turn to steer.", request.turn.attemptId);
|
|
830
|
-
}
|
|
831
|
-
// Steer carried no generation check, so a steer issued against a
|
|
832
|
-
// superseded activation was written into the current Provider Turn. The
|
|
833
|
-
// native Turn id alone does not catch it: a late steer can arrive while
|
|
834
|
-
// the id it names is still the active one under a newer generation.
|
|
835
|
-
if (request.runtimeGenerationId !== sessionPayload.runtimeGenerationId) {
|
|
836
|
-
operation.failure = providerDeliveryFailure({
|
|
837
|
-
detail: "Agent Host steer targets a superseded runtime generation.",
|
|
838
|
-
errorName: "ProviderGenerationConflictError",
|
|
839
|
-
phase: "turn-submit",
|
|
840
|
-
hostState: snapshot.state,
|
|
841
|
-
expectedRuntimeGenerationId: sessionPayload.runtimeGenerationId,
|
|
842
|
-
observedRuntimeGenerationId: request.runtimeGenerationId,
|
|
843
|
-
attemptId: request.turn.attemptId,
|
|
886
|
+
attemptId: request.run.attemptId,
|
|
844
887
|
inputDisposition: "not-accepted",
|
|
845
888
|
sessionDisposition: "recoverable"
|
|
846
889
|
});
|
|
847
|
-
throw new ProviderTurnRejectedError("Agent Host
|
|
848
|
-
+ `${sessionPayload.runtimeGenerationId}, observed ${request.runtimeGenerationId}.`, request.turn.attemptId);
|
|
890
|
+
throw new ProviderTurnRejectedError("Agent Host has no active Provider Turn to steer.", request.run.attemptId);
|
|
849
891
|
}
|
|
850
892
|
if (request.nativeSessionId !== session.nativeSessionId
|
|
851
893
|
|| request.nativeTurnId !== activeNativeTurnId) {
|
|
@@ -854,44 +896,35 @@ export async function runAgentHost(input) {
|
|
|
854
896
|
errorName: "ProviderTurnRejectedError",
|
|
855
897
|
phase: "turn-submit",
|
|
856
898
|
hostState: snapshot.state,
|
|
857
|
-
|
|
858
|
-
observedRuntimeGenerationId: request.runtimeGenerationId,
|
|
859
|
-
attemptId: request.turn.attemptId,
|
|
899
|
+
attemptId: request.run.attemptId,
|
|
860
900
|
inputDisposition: "not-accepted",
|
|
861
901
|
sessionDisposition: "recoverable"
|
|
862
902
|
});
|
|
863
|
-
throw new ProviderTurnRejectedError("Agent Host steer targets a different Provider Turn.", request.
|
|
903
|
+
throw new ProviderTurnRejectedError("Agent Host steer targets a different Provider Turn.", request.run.attemptId);
|
|
864
904
|
}
|
|
865
905
|
if (authority === undefined || !sameProviderAuthorityFence(authority, request.authority)) {
|
|
866
906
|
throw new Error("Agent Host rejected a stale Provider writer fence.");
|
|
867
907
|
}
|
|
868
|
-
if (recentSteerAttempts.has(request.turn.attemptId)) {
|
|
869
|
-
const previous = recentSteerAttempts.get(request.turn.attemptId);
|
|
870
|
-
if (previous === undefined)
|
|
871
|
-
return snapshot;
|
|
872
|
-
operation.failure = previous;
|
|
873
|
-
throw new ProviderDeliveryUnknownError(previous.detail, request.turn.attemptId);
|
|
874
|
-
}
|
|
875
908
|
try {
|
|
876
|
-
|
|
909
|
+
assertAgentExecutionEnvironment(input.home, sessionPayload);
|
|
910
|
+
const receipt = endpointReceipt(await session.steer({
|
|
911
|
+
...request.run,
|
|
912
|
+
boundedText: withSessionContextPointer(request.run.boundedText, sessionPayload.environment),
|
|
913
|
+
inputRef: request.run.attemptId
|
|
914
|
+
}), request.run.attemptId);
|
|
877
915
|
if (receipt.nativeTurnId !== request.nativeTurnId) {
|
|
878
|
-
throw new ProviderDeliveryUnknownError("Provider accepted steer against an unexpected native Turn.", request.
|
|
916
|
+
throw new ProviderDeliveryUnknownError("Provider accepted steer against an unexpected native Turn.", request.run.attemptId);
|
|
879
917
|
}
|
|
880
|
-
recentSteerAttempts.set(request.turn.attemptId, undefined);
|
|
881
918
|
}
|
|
882
919
|
catch (error) {
|
|
883
920
|
const unknown = error instanceof ProviderDeliveryUnknownError;
|
|
884
921
|
operation.failure = providerDeliveryFailureFrom(error, {
|
|
885
922
|
phase: "turn-submit",
|
|
886
923
|
hostState: snapshot.state,
|
|
887
|
-
|
|
888
|
-
observedRuntimeGenerationId: request.runtimeGenerationId,
|
|
889
|
-
attemptId: request.turn.attemptId,
|
|
924
|
+
attemptId: request.run.attemptId,
|
|
890
925
|
inputDisposition: unknown ? "unknown" : "not-accepted",
|
|
891
926
|
sessionDisposition: "unknown"
|
|
892
927
|
});
|
|
893
|
-
if (unknown)
|
|
894
|
-
recentSteerAttempts.set(request.turn.attemptId, operation.failure);
|
|
895
928
|
throw error;
|
|
896
929
|
}
|
|
897
930
|
return snapshot;
|
|
@@ -900,9 +933,9 @@ export async function runAgentHost(input) {
|
|
|
900
933
|
if (session === undefined || request.nativeSessionId !== session.nativeSessionId) {
|
|
901
934
|
throw new Error("Agent Host authority targets a different Provider Conversation.");
|
|
902
935
|
}
|
|
903
|
-
if (
|
|
936
|
+
if (activeRunPayload !== undefined
|
|
904
937
|
|| ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
|
|
905
|
-
throw new Error("Agent Host authority cannot transfer while a
|
|
938
|
+
throw new Error("Agent Host authority cannot transfer while a AgentRun is unsettled.");
|
|
906
939
|
}
|
|
907
940
|
const next = validateProviderAuthorityFence(request.authority);
|
|
908
941
|
if (authority !== undefined) {
|
|
@@ -914,7 +947,6 @@ export async function runAgentHost(input) {
|
|
|
914
947
|
}
|
|
915
948
|
authority = next;
|
|
916
949
|
updateSnapshot(hostSnapshot("idle", {
|
|
917
|
-
runtimeGenerationId: sessionPayload?.runtimeGenerationId ?? snapshot.runtimeGenerationId,
|
|
918
950
|
adapterId: session.adapterId,
|
|
919
951
|
processInstanceId: session.processInstanceId,
|
|
920
952
|
nativeSessionId: session.nativeSessionId,
|
|
@@ -926,48 +958,119 @@ export async function runAgentHost(input) {
|
|
|
926
958
|
};
|
|
927
959
|
const control = await openAgentHostControl(input.home, payload, () => snapshot, async (request) => {
|
|
928
960
|
if (request.type === "status") {
|
|
929
|
-
|
|
961
|
+
// Read the Agent's configuration at answer time, not from the stored
|
|
962
|
+
// snapshot. The Agent may have changed it since the last state
|
|
963
|
+
// transition, and a status request is exactly where a caller expects the
|
|
964
|
+
// current reading rather than the one that was true at launch. Nothing is
|
|
965
|
+
// sent to the Agent to obtain it: this reads what the Session has already
|
|
966
|
+
// been told.
|
|
967
|
+
return controlResult("status", session === undefined
|
|
968
|
+
? snapshot
|
|
969
|
+
: validateSnapshot({
|
|
970
|
+
...snapshot,
|
|
971
|
+
runConfiguration: session.runConfiguration
|
|
972
|
+
}));
|
|
973
|
+
}
|
|
974
|
+
if (request.type === "cancel") {
|
|
975
|
+
if (session === undefined || request.nativeSessionId !== session.nativeSessionId
|
|
976
|
+
|| request.attemptId !== activeRunAttemptId
|
|
977
|
+
|| authority === undefined || !sameProviderAuthorityFence(authority, request.authority)) {
|
|
978
|
+
throw new Error("Endpoint cancellation does not match the current Session and authority.");
|
|
979
|
+
}
|
|
980
|
+
// Cancellation remains callable while submit is awaiting native ack.
|
|
981
|
+
// The response never declares the shared native resource stopped.
|
|
982
|
+
const cancellation = await session.cancel(request.attemptId);
|
|
983
|
+
return Object.freeze({ ...controlResult("cancel-requested", snapshot), cancellation });
|
|
930
984
|
}
|
|
931
985
|
if (request.type === "submit-turn") {
|
|
932
|
-
const
|
|
986
|
+
const submission = enqueueSerialized(async () => {
|
|
933
987
|
const operation = {};
|
|
934
988
|
try {
|
|
935
|
-
return await
|
|
989
|
+
return await submitRun(request, operation);
|
|
936
990
|
}
|
|
937
991
|
catch (error) {
|
|
938
992
|
throw new AgentHostOperationError(error, snapshot, operation.failure);
|
|
939
993
|
}
|
|
940
994
|
});
|
|
941
|
-
return
|
|
995
|
+
return await observePendingHostSubmission(submission, () => snapshot);
|
|
942
996
|
}
|
|
943
997
|
if (request.type === "steer-turn") {
|
|
944
|
-
const
|
|
998
|
+
const previous = recentSteerAttempts.get(request.run.attemptId);
|
|
999
|
+
if (previous !== undefined) {
|
|
1000
|
+
if (previous.request.nativeSessionId !== request.nativeSessionId
|
|
1001
|
+
|| previous.request.nativeTurnId !== request.nativeTurnId
|
|
1002
|
+
|| previous.request.run.boundedText !== request.run.boundedText
|
|
1003
|
+
|| !sameProviderAuthorityFence(previous.request.authority, request.authority)) {
|
|
1004
|
+
throw new Error("Steer attempt identity cannot name another input or Provider fence.");
|
|
1005
|
+
}
|
|
1006
|
+
return controlResult(previous.outcome.result === "delivered" ? "accepted"
|
|
1007
|
+
: previous.outcome.result === "pending" ? "pending" : "rejected", snapshot, previous.outcome.failure);
|
|
1008
|
+
}
|
|
1009
|
+
// Reserve before queueing, so inspection is meaningful even while an
|
|
1010
|
+
// earlier Host operation owns the serialized dispatch lane.
|
|
1011
|
+
const attempt = { request, outcome: { result: "pending" } };
|
|
1012
|
+
recentSteerAttempts.set(request.run.attemptId, attempt);
|
|
1013
|
+
const sourcePayload = activeRunPayload ?? sessionPayload ?? payload;
|
|
1014
|
+
const settlement = {
|
|
1015
|
+
home: input.home,
|
|
1016
|
+
environment: sourcePayload.environment,
|
|
1017
|
+
nativeSessionId: request.nativeSessionId,
|
|
1018
|
+
nativeTurnId: request.nativeTurnId,
|
|
1019
|
+
attemptId: request.run.attemptId,
|
|
1020
|
+
boundedText: request.run.boundedText
|
|
1021
|
+
};
|
|
1022
|
+
const submission = enqueueSerialized(async () => {
|
|
945
1023
|
const operation = {};
|
|
1024
|
+
let providerAccepted = false;
|
|
946
1025
|
try {
|
|
947
|
-
|
|
1026
|
+
const accepted = await steerRun(request, operation);
|
|
1027
|
+
providerAccepted = true;
|
|
1028
|
+
await publishStructuredProviderInputSettlement({ ...settlement, status: "accepted" });
|
|
1029
|
+
attempt.outcome = { result: "delivered" };
|
|
1030
|
+
signalRoleMailbox(input.home, sessionPayload ?? payload);
|
|
1031
|
+
return accepted;
|
|
948
1032
|
}
|
|
949
1033
|
catch (error) {
|
|
950
|
-
|
|
1034
|
+
const unknown = providerAccepted || error instanceof ProviderDeliveryUnknownError;
|
|
1035
|
+
const failure = operation.failure ?? providerDeliveryFailureFrom(error, {
|
|
1036
|
+
phase: "turn-submit", attemptId: request.run.attemptId,
|
|
1037
|
+
inputDisposition: providerAccepted ? "accepted" : unknown ? "unknown" : "not-accepted"
|
|
1038
|
+
});
|
|
1039
|
+
attempt.outcome = {
|
|
1040
|
+
result: unknown ? "delivery-unknown" : "rejected",
|
|
1041
|
+
failure
|
|
1042
|
+
};
|
|
1043
|
+
// Persist a native disposition before replying. Accepted publication
|
|
1044
|
+
// already enqueued its fact before Controller apply; never replace
|
|
1045
|
+
// that fact with an "unknown" when only apply acknowledgement failed.
|
|
1046
|
+
if (!providerAccepted) {
|
|
1047
|
+
try {
|
|
1048
|
+
await publishStructuredProviderInputSettlement({
|
|
1049
|
+
...settlement, status: unknown ? "unknown" : "rejected", failure
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
catch (settlementError) {
|
|
1053
|
+
throw new AgentHostOperationError(new ProviderDeliveryUnknownError("Steer disposition could not be durably acknowledged.", request.run.attemptId, { cause: new AggregateError([error, settlementError]) }), snapshot, failure);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
signalRoleMailbox(input.home, sessionPayload ?? payload);
|
|
1057
|
+
throw new AgentHostOperationError(providerAccepted ? new ProviderDeliveryUnknownError("Steer acceptance was observed but its settlement acknowledgement is unknown.", request.run.attemptId, { cause: error }) : error, snapshot, failure);
|
|
951
1058
|
}
|
|
952
1059
|
});
|
|
953
|
-
return
|
|
1060
|
+
return await observePendingHostSubmission(submission, () => snapshot);
|
|
954
1061
|
}
|
|
955
1062
|
if (request.type === "set-authority") {
|
|
956
1063
|
const accepted = await enqueueSerialized(async () => setAuthority(request));
|
|
957
1064
|
return controlResult("accepted", accepted);
|
|
958
1065
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
// Conversation is usable yet is a separate fact the caller reads from
|
|
962
|
-
// `snapshot.state`; it is never an identity conflict.
|
|
963
|
-
if (snapshot.runtimeGenerationId === request.runtimeGenerationId
|
|
964
|
-
&& ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
|
|
965
|
-
return controlResult("active-same-generation", snapshot);
|
|
966
|
-
}
|
|
967
|
-
const redeemed = await redeem(input.home, request.runtimeGenerationId, request.ticket);
|
|
968
|
-
if (activeTurnPayload !== undefined
|
|
1066
|
+
const redeemed = await redeem(input.home, request.ticket);
|
|
1067
|
+
if (activeRunPayload !== undefined
|
|
969
1068
|
|| ["starting", "ready", "settling", "delivery-unknown"].includes(snapshot.state)) {
|
|
970
|
-
|
|
1069
|
+
if (redeemed.providerControl?.mode === "resume"
|
|
1070
|
+
&& redeemed.providerControl.nativeSessionId === snapshot.nativeSessionId) {
|
|
1071
|
+
return controlResult("accepted", snapshot);
|
|
1072
|
+
}
|
|
1073
|
+
return controlResult("busy", snapshot);
|
|
971
1074
|
}
|
|
972
1075
|
const accepted = await enqueueDispatch(redeemed);
|
|
973
1076
|
return controlResult("accepted", accepted);
|
|
@@ -977,7 +1080,7 @@ export async function runAgentHost(input) {
|
|
|
977
1080
|
: undefined;
|
|
978
1081
|
promptHuman = () => {
|
|
979
1082
|
if (humanConsole !== undefined && authority?.owner === "human"
|
|
980
|
-
&&
|
|
1083
|
+
&& activeRunPayload === undefined
|
|
981
1084
|
&& ["idle", "rejected", "failed"].includes(snapshot.state)) {
|
|
982
1085
|
humanConsole.setPrompt("yui(provider)> ");
|
|
983
1086
|
humanConsole.prompt();
|
|
@@ -998,19 +1101,18 @@ export async function runAgentHost(input) {
|
|
|
998
1101
|
if (boundedText.length === 0)
|
|
999
1102
|
return;
|
|
1000
1103
|
const attemptId = `human:${currentAuthority.holderId}:${randomUUID()}`;
|
|
1001
|
-
const
|
|
1104
|
+
const runControl = {
|
|
1002
1105
|
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
1003
1106
|
type: "submit-turn",
|
|
1004
|
-
runtimeGenerationId: currentPayload.runtimeGenerationId,
|
|
1005
1107
|
nativeSessionId: currentSession.nativeSessionId,
|
|
1006
1108
|
authority: currentAuthority,
|
|
1007
|
-
|
|
1109
|
+
run: {
|
|
1008
1110
|
attemptId,
|
|
1009
1111
|
boundedText
|
|
1010
1112
|
}
|
|
1011
1113
|
};
|
|
1012
|
-
await
|
|
1013
|
-
process.stdout.write("Provider accepted the human
|
|
1114
|
+
await submitRun(runControl, {});
|
|
1115
|
+
process.stdout.write("Provider accepted the human AgentRun; waiting for its terminal boundary.\n");
|
|
1014
1116
|
}).catch((error) => {
|
|
1015
1117
|
process.stderr.write(`Provider input failed: ${errorText(error)}\n`);
|
|
1016
1118
|
promptHuman();
|
|
@@ -1029,8 +1131,8 @@ export async function runAgentHost(input) {
|
|
|
1029
1131
|
if (hostStopRequested)
|
|
1030
1132
|
return;
|
|
1031
1133
|
hostStopRequested = true;
|
|
1032
|
-
session?.
|
|
1033
|
-
forceKillTimer = setTimeout(() => session?.
|
|
1134
|
+
session?.detach(signal);
|
|
1135
|
+
forceKillTimer = setTimeout(() => session?.detach("SIGKILL"), AGENT_HOST_CLIENT_EXIT_GRACE_MS);
|
|
1034
1136
|
forceKillTimer.unref();
|
|
1035
1137
|
stopResolve();
|
|
1036
1138
|
};
|
|
@@ -1050,11 +1152,111 @@ export async function runAgentHost(input) {
|
|
|
1050
1152
|
if (forceKillTimer !== undefined)
|
|
1051
1153
|
clearTimeout(forceKillTimer);
|
|
1052
1154
|
humanConsole?.close();
|
|
1053
|
-
|
|
1155
|
+
// Ask the client to exit, then hand back the Session's hold. The reference
|
|
1156
|
+
// is only actually returned once the client proves it exited, so the bounded
|
|
1157
|
+
// stop below waits on the real dependency instead of an assumed one.
|
|
1158
|
+
session?.detach();
|
|
1159
|
+
const adapterId = endpointAdapterId;
|
|
1160
|
+
await endpointLease?.release();
|
|
1161
|
+
endpointLease = undefined;
|
|
1162
|
+
if (adapterId !== undefined) {
|
|
1163
|
+
// Stop new acquisition and wait a bounded time for what is already
|
|
1164
|
+
// running. A timeout reports the references and pending effects that are
|
|
1165
|
+
// genuinely still in use; it never escalates to killing a Provider this
|
|
1166
|
+
// Endpoint does not own, and it never reports quiescence it cannot prove.
|
|
1167
|
+
const drain = await endpointOwner.stop(adapterId, ENDPOINT_DRAIN_TIMEOUT_MS);
|
|
1168
|
+
if (!drain.quiescent) {
|
|
1169
|
+
const detail = `Endpoint stop ${drain.timedOut ? "timed out" : "returned"} after ${drain.waitedMs}ms`
|
|
1170
|
+
+ ` (bound ${ENDPOINT_DRAIN_TIMEOUT_MS}ms); ${drain.references} reference(s), `
|
|
1171
|
+
+ `${drain.opening} opening client(s) and `
|
|
1172
|
+
+ `${drain.sessions.reduce((total, held) => total + held.pending.length, 0)} pending effect(s) `
|
|
1173
|
+
+ "may still be in use. Owned client resources are unknown.";
|
|
1174
|
+
updateSnapshot(hostSnapshot(snapshot.state, {
|
|
1175
|
+
...definedFields({
|
|
1176
|
+
adapterId,
|
|
1177
|
+
nativeSessionId: snapshot.nativeSessionId,
|
|
1178
|
+
conversationId: snapshot.conversationId
|
|
1179
|
+
}),
|
|
1180
|
+
...authorityFields(),
|
|
1181
|
+
detail
|
|
1182
|
+
}));
|
|
1183
|
+
// The snapshot is about to stop being reachable, and a stop this process
|
|
1184
|
+
// could not prove quiescent must remain diagnosable afterwards. Written
|
|
1185
|
+
// to the Host's own stream, which its pane and logs retain.
|
|
1186
|
+
process.stderr.write(`${detail}\n`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
// Final cleanup is bounded on its own deadline. The bounded stop above may
|
|
1190
|
+
// already have given up on a client that never proves exit, and waiting for
|
|
1191
|
+
// that same drain again would leave the control socket open and this function
|
|
1192
|
+
// never returning. Anything still held is reported, not silently released.
|
|
1193
|
+
const remaining = await endpointOwner.close(ENDPOINT_DRAIN_TIMEOUT_MS).catch(() => []);
|
|
1194
|
+
const held = remaining.filter((drain) => !drain.quiescent);
|
|
1195
|
+
if (held.length > 0) {
|
|
1196
|
+
const references = held.reduce((total, drain) => total + drain.references, 0);
|
|
1197
|
+
const opening = held.reduce((total, drain) => total + drain.opening, 0);
|
|
1198
|
+
process.stderr.write(`Endpoint cleanup returned with ${references} reference(s) and ${opening} opening client(s) `
|
|
1199
|
+
+ `still held across ${held.length} implementation(s) (bound ${ENDPOINT_DRAIN_TIMEOUT_MS}ms); `
|
|
1200
|
+
+ "those implementations stay detached and undisposed. Owned client resources are unknown.\n");
|
|
1201
|
+
}
|
|
1054
1202
|
await control.close();
|
|
1055
1203
|
void sessionPayload;
|
|
1056
1204
|
}
|
|
1057
1205
|
}
|
|
1206
|
+
/** Host-restart-independent OS custody. No secret or launch payload is stored. */
|
|
1207
|
+
function recordOwnedProviderProcess(home, environment, endpoint) {
|
|
1208
|
+
if (endpoint.ownedProcessId === undefined || environment.YUI_SESSION_SCOPE !== "task")
|
|
1209
|
+
return;
|
|
1210
|
+
const identity = readLinuxProcessIdentity(endpoint.ownedProcessId);
|
|
1211
|
+
if (identity === undefined)
|
|
1212
|
+
throw new Error("Dedicated Provider process identity was lost before registration.");
|
|
1213
|
+
const taskId = environment.YUI_TASK_ID;
|
|
1214
|
+
const roleName = environment.YUI_ROLE;
|
|
1215
|
+
const store = openCurrentTaskStore(home);
|
|
1216
|
+
try {
|
|
1217
|
+
store.saveSessionOwner(createSessionOwnerIdentity({
|
|
1218
|
+
owner: { scope: "task", taskId, roleName },
|
|
1219
|
+
agentId: environment.YUI_AGENT_ID, adapterId: endpoint.adapterId,
|
|
1220
|
+
nativeSessionId: endpoint.nativeSessionId,
|
|
1221
|
+
tmux: {
|
|
1222
|
+
serverName: yuiTmuxServerName(home),
|
|
1223
|
+
socketPath: join(tmuxSocketDirectory(environment), yuiTmuxServerName(home)),
|
|
1224
|
+
sessionName: yuiTmuxSessionName(home, taskId), windowName: roleName, panePid: process.pid
|
|
1225
|
+
},
|
|
1226
|
+
providerRoot: { pid: identity.pid, startIdentity: identity.startIdentity,
|
|
1227
|
+
processGroupId: identity.processGroupId, processSessionId: identity.processSessionId,
|
|
1228
|
+
attribution: "owned-child" },
|
|
1229
|
+
recordedAt: new Date()
|
|
1230
|
+
}));
|
|
1231
|
+
}
|
|
1232
|
+
finally {
|
|
1233
|
+
store.close();
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
function recordNativeConnection(home, payload, endpoint) {
|
|
1237
|
+
if (endpoint.adapterId !== "codex" || payload.environment.YUI_SESSION_SCOPE !== "task")
|
|
1238
|
+
return;
|
|
1239
|
+
const taskId = payload.environment.YUI_TASK_ID;
|
|
1240
|
+
const store = openCurrentTaskStore(home);
|
|
1241
|
+
try {
|
|
1242
|
+
if (store.listEvents(taskId).some(event => event.type === "runtime.native-connection-bound"
|
|
1243
|
+
&& event.payload.nativeSessionId === endpoint.nativeSessionId))
|
|
1244
|
+
return;
|
|
1245
|
+
const userHome = resolve(payload.cwd, payload.environment.HOME ?? homedir());
|
|
1246
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "runtime.native-connection-bound", {
|
|
1247
|
+
roleName: payload.environment.YUI_ROLE, agentId: payload.environment.YUI_AGENT_ID,
|
|
1248
|
+
nativeSessionId: endpoint.nativeSessionId,
|
|
1249
|
+
// Account location is needed after Controller/Host loss. Never retain
|
|
1250
|
+
// resolved credentials or duplicate the full process environment.
|
|
1251
|
+
home: userHome,
|
|
1252
|
+
codexHome: resolve(payload.cwd, payload.environment.CODEX_HOME ?? join(userHome, ".codex")),
|
|
1253
|
+
...(endpoint.nativeAccountHome === undefined ? {} : { nativeAccountHome: endpoint.nativeAccountHome })
|
|
1254
|
+
}, new Date()));
|
|
1255
|
+
}
|
|
1256
|
+
finally {
|
|
1257
|
+
store.close();
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1058
1260
|
export function agentHostControlSocketPath(input) {
|
|
1059
1261
|
const owner = input.scope === "task" ? input.taskId ?? "missing-task" : "global";
|
|
1060
1262
|
const homeDigest = createHash("sha256")
|
|
@@ -1075,7 +1277,7 @@ export function agentHostControlSocketPath(input) {
|
|
|
1075
1277
|
export async function sendAgentHostLaunchControl(input) {
|
|
1076
1278
|
return await sendAgentHostControl(input);
|
|
1077
1279
|
}
|
|
1078
|
-
export async function
|
|
1280
|
+
export async function sendAgentHostRunControl(input) {
|
|
1079
1281
|
return await sendAgentHostControl(input);
|
|
1080
1282
|
}
|
|
1081
1283
|
export async function sendAgentHostSteerControl(input) {
|
|
@@ -1084,6 +1286,9 @@ export async function sendAgentHostSteerControl(input) {
|
|
|
1084
1286
|
export async function sendAgentHostAuthorityControl(input) {
|
|
1085
1287
|
return await sendAgentHostControl(input);
|
|
1086
1288
|
}
|
|
1289
|
+
export async function sendAgentHostCancelControl(input) {
|
|
1290
|
+
return await sendAgentHostControl(input);
|
|
1291
|
+
}
|
|
1087
1292
|
export async function inspectAgentHost(input) {
|
|
1088
1293
|
const result = await sendAgentHostControl({
|
|
1089
1294
|
...input,
|
|
@@ -1097,9 +1302,9 @@ export async function waitForAgentHostLaunchAck(input) {
|
|
|
1097
1302
|
while (Date.now() < deadline) {
|
|
1098
1303
|
try {
|
|
1099
1304
|
const snapshot = await inspectAgentHost(input);
|
|
1100
|
-
|
|
1305
|
+
{
|
|
1101
1306
|
if (snapshot.state === "ready"
|
|
1102
|
-
|| (input.
|
|
1307
|
+
|| (input.requireRunAck !== true && snapshot.state === "idle"))
|
|
1103
1308
|
return snapshot;
|
|
1104
1309
|
if (snapshot.state === "delivery-unknown"
|
|
1105
1310
|
|| snapshot.state === "busy"
|
|
@@ -1120,7 +1325,7 @@ export async function waitForAgentHostLaunchAck(input) {
|
|
|
1120
1325
|
}
|
|
1121
1326
|
await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
|
|
1122
1327
|
}
|
|
1123
|
-
throw new Error(`Agent Host did not acknowledge Provider launch
|
|
1328
|
+
throw new Error(`Agent Host did not acknowledge Provider launch: ${errorText(lastError)}.`);
|
|
1124
1329
|
}
|
|
1125
1330
|
async function sendAgentHostControl(input) {
|
|
1126
1331
|
const path = agentHostControlSocketPath(input);
|
|
@@ -1158,9 +1363,8 @@ async function sendAgentHostControl(input) {
|
|
|
1158
1363
|
});
|
|
1159
1364
|
});
|
|
1160
1365
|
}
|
|
1161
|
-
async function redeem(home,
|
|
1366
|
+
async function redeem(home, ticket) {
|
|
1162
1367
|
const result = await callController(home, "runtime.launch-redeem", {
|
|
1163
|
-
runtimeGenerationId,
|
|
1164
1368
|
ticket,
|
|
1165
1369
|
hostPid: process.pid
|
|
1166
1370
|
});
|
|
@@ -1236,15 +1440,12 @@ export async function openAgentHostControl(home, payload, snapshot, dispatch) {
|
|
|
1236
1440
|
|| error instanceof ControllerAcknowledgementUnknownError;
|
|
1237
1441
|
// The failing operation already recorded the exact facts. Prefer its
|
|
1238
1442
|
// record; only synthesize one when the failure came from outside a
|
|
1239
|
-
//
|
|
1443
|
+
// AgentRun operation (a malformed control, say), where those facts do
|
|
1240
1444
|
// not exist.
|
|
1241
1445
|
const recorded = caught instanceof AgentHostOperationError ? caught.failure : undefined;
|
|
1242
1446
|
const failure = recorded ?? providerDeliveryFailureFrom(error, {
|
|
1243
1447
|
phase: controlRequestPhase(body),
|
|
1244
1448
|
hostState: busy ? "busy" : unknown ? "delivery-unknown" : current.state,
|
|
1245
|
-
...(current.runtimeGenerationId === undefined
|
|
1246
|
-
? {}
|
|
1247
|
-
: { observedRuntimeGenerationId: current.runtimeGenerationId }),
|
|
1248
1449
|
...(current.attemptId === undefined
|
|
1249
1450
|
? {}
|
|
1250
1451
|
: { attemptId: current.attemptId }),
|
|
@@ -1256,7 +1457,7 @@ export async function openAgentHostControl(home, payload, snapshot, dispatch) {
|
|
|
1256
1457
|
// A bare `rejected` here is indistinguishable from the Provider
|
|
1257
1458
|
// refusing the input. Carry the real cause and an explicit input
|
|
1258
1459
|
// disposition so no consumer has to guess from the message text.
|
|
1259
|
-
socket.end(`${JSON.stringify(boundControlResponse(controlResult(busy ? "
|
|
1460
|
+
socket.end(`${JSON.stringify(boundControlResponse(controlResult(busy ? "busy" : "rejected", validateSnapshot({
|
|
1260
1461
|
...current,
|
|
1261
1462
|
...(busy ? { state: "busy" } : {}),
|
|
1262
1463
|
...(unknown ? { state: "delivery-unknown" } : {}),
|
|
@@ -1294,7 +1495,10 @@ function boundControlResponse(result) {
|
|
|
1294
1495
|
// Every public response, including status and successful controls, crosses
|
|
1295
1496
|
// the same redaction/size boundary. An earlier failed operation may remain
|
|
1296
1497
|
// visible through an otherwise successful status request.
|
|
1297
|
-
result =
|
|
1498
|
+
result = {
|
|
1499
|
+
...controlResult(result.outcome, validateSnapshot(result.snapshot), result.failure === undefined ? undefined : providerDeliveryFailure(result.failure)),
|
|
1500
|
+
...(result.cancellation === undefined ? {} : { cancellation: result.cancellation })
|
|
1501
|
+
};
|
|
1298
1502
|
if (withinControlBound(result))
|
|
1299
1503
|
return result;
|
|
1300
1504
|
const clip = (text, chars) => {
|
|
@@ -1303,6 +1507,17 @@ function boundControlResponse(result) {
|
|
|
1303
1507
|
const head = Math.floor(chars * 0.75);
|
|
1304
1508
|
return `${text.slice(0, head)}…[truncated ${text.length - chars} chars: control byte bound]${text.slice(text.length - (chars - head))}`;
|
|
1305
1509
|
};
|
|
1510
|
+
// Never make an omitted option list look like a complete empty enumeration.
|
|
1511
|
+
if (result.snapshot.runConfiguration?.status === "observed") {
|
|
1512
|
+
result = controlResult(result.outcome, validateSnapshot({
|
|
1513
|
+
...result.snapshot,
|
|
1514
|
+
runConfiguration: unknownAgentRunConfiguration("The Agent's reported configuration is too large for the Agent Host "
|
|
1515
|
+
+ "control response, so it could not be carried in full and none of it "
|
|
1516
|
+
+ "is shown rather than part of it.")
|
|
1517
|
+
}), result.failure);
|
|
1518
|
+
if (withinControlBound(result))
|
|
1519
|
+
return result;
|
|
1520
|
+
}
|
|
1306
1521
|
let bounded = controlResult(result.outcome, {
|
|
1307
1522
|
...result.snapshot,
|
|
1308
1523
|
...(result.snapshot.detail === undefined ? {} : { detail: clip(result.snapshot.detail, 1200) })
|
|
@@ -1349,24 +1564,29 @@ function validateControl(control) {
|
|
|
1349
1564
|
}
|
|
1350
1565
|
if (control.type === "status")
|
|
1351
1566
|
return Object.freeze({ ...control });
|
|
1567
|
+
if (control.type === "cancel") {
|
|
1568
|
+
validateIdentity(control.nativeSessionId, "native Session id");
|
|
1569
|
+
validateIdentity(control.attemptId, "Provider input attempt id");
|
|
1570
|
+
validateProviderAuthorityFence(control.authority);
|
|
1571
|
+
return Object.freeze({ ...control });
|
|
1572
|
+
}
|
|
1352
1573
|
if (control.type === "submit-turn" || control.type === "steer-turn") {
|
|
1353
|
-
validateRuntimeGenerationId(control.runtimeGenerationId);
|
|
1354
1574
|
validateIdentity(control.nativeSessionId, "native Session id");
|
|
1355
|
-
if (control.type === "submit-turn" && control.
|
|
1356
|
-
validateIdentity(control.
|
|
1575
|
+
if (control.type === "submit-turn" && control.runId !== undefined) {
|
|
1576
|
+
validateIdentity(control.runId, "AgentRun id");
|
|
1357
1577
|
}
|
|
1358
1578
|
if (control.type === "steer-turn")
|
|
1359
1579
|
validateIdentity(control.nativeTurnId, "native Turn id");
|
|
1360
1580
|
validateProviderAuthorityFence(control.authority);
|
|
1361
|
-
validateIdentity(control.
|
|
1362
|
-
if (typeof control.
|
|
1363
|
-
|| control.
|
|
1364
|
-
|| Buffer.byteLength(control.
|
|
1581
|
+
validateIdentity(control.run.attemptId, "Provider input attempt id");
|
|
1582
|
+
if (typeof control.run.boundedText !== "string"
|
|
1583
|
+
|| control.run.boundedText.includes("\0")
|
|
1584
|
+
|| Buffer.byteLength(control.run.boundedText, "utf8") > 32 * 1024) {
|
|
1365
1585
|
throw new Error("Agent Host Provider input is invalid.");
|
|
1366
1586
|
}
|
|
1367
1587
|
return Object.freeze({
|
|
1368
1588
|
...control,
|
|
1369
|
-
|
|
1589
|
+
run: Object.freeze({ ...control.run })
|
|
1370
1590
|
});
|
|
1371
1591
|
}
|
|
1372
1592
|
if (control.type === "set-authority") {
|
|
@@ -1378,18 +1598,11 @@ function validateControl(control) {
|
|
|
1378
1598
|
}
|
|
1379
1599
|
if (control.type !== "launch")
|
|
1380
1600
|
throw new Error("Agent Host control type is invalid.");
|
|
1381
|
-
validateRuntimeGenerationId(control.runtimeGenerationId);
|
|
1382
1601
|
if (typeof control.ticket !== "string" || !/^[a-f0-9]{64}$/u.test(control.ticket)) {
|
|
1383
1602
|
throw new Error("Agent Host launch control ticket is invalid.");
|
|
1384
1603
|
}
|
|
1385
1604
|
return Object.freeze({ ...control });
|
|
1386
1605
|
}
|
|
1387
|
-
function validateRuntimeGenerationId(value) {
|
|
1388
|
-
if (typeof value !== "string" || value.length === 0
|
|
1389
|
-
|| value.length > 256 || value.includes("\0")) {
|
|
1390
|
-
throw new Error("Agent Host launch control identity is invalid.");
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
1606
|
function validateIdentity(value, label) {
|
|
1394
1607
|
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
1395
1608
|
throw new Error(`Agent Host ${label} is invalid.`);
|
|
@@ -1397,7 +1610,7 @@ function validateIdentity(value, label) {
|
|
|
1397
1610
|
}
|
|
1398
1611
|
function validateControlResult(result) {
|
|
1399
1612
|
if (result.protocol !== AGENT_HOST_CONTROL_PROTOCOL
|
|
1400
|
-
|| !["status", "accepted", "
|
|
1613
|
+
|| !["status", "accepted", "pending", "cancel-requested", "rejected", "busy"].includes(result.outcome)) {
|
|
1401
1614
|
throw new Error("Agent Host control response is invalid.");
|
|
1402
1615
|
}
|
|
1403
1616
|
return Object.freeze({
|
|
@@ -1431,6 +1644,10 @@ function validateSnapshot(snapshot) {
|
|
|
1431
1644
|
if (!Number.isFinite(Date.parse(snapshot.updatedAt))) {
|
|
1432
1645
|
throw new Error("Agent Host snapshot timestamp is invalid.");
|
|
1433
1646
|
}
|
|
1647
|
+
if (snapshot.inputAcceptance !== undefined
|
|
1648
|
+
&& snapshot.inputAcceptance !== "provider" && snapshot.inputAcceptance !== "transport") {
|
|
1649
|
+
throw new Error("Agent Host input acceptance evidence is invalid.");
|
|
1650
|
+
}
|
|
1434
1651
|
const authorityFields = [
|
|
1435
1652
|
snapshot.authorityEpoch,
|
|
1436
1653
|
snapshot.authorityOwner,
|
|
@@ -1451,6 +1668,12 @@ function validateSnapshot(snapshot) {
|
|
|
1451
1668
|
// to expose the original exception on their normal success paths.
|
|
1452
1669
|
return Object.freeze({
|
|
1453
1670
|
...snapshot,
|
|
1671
|
+
...(snapshot.runConfiguration === undefined
|
|
1672
|
+
? {}
|
|
1673
|
+
// Re-read through the same parser the client uses. A shape this build does
|
|
1674
|
+
// not recognize becomes an explicit `unknown` here rather than travelling
|
|
1675
|
+
// as a partial record that renders like a real answer.
|
|
1676
|
+
: { runConfiguration: readAgentRunConfigurationObservation(snapshot.runConfiguration) }),
|
|
1454
1677
|
...(snapshot.detail === undefined ? {} : { detail: redactAgentErrorText(snapshot.detail) })
|
|
1455
1678
|
});
|
|
1456
1679
|
}
|
|
@@ -1480,6 +1703,8 @@ function controlRequestPhase(body) {
|
|
|
1480
1703
|
const type = JSON.parse(body.trim()).type;
|
|
1481
1704
|
if (type === "submit-turn" || type === "steer-turn")
|
|
1482
1705
|
return "turn-submit";
|
|
1706
|
+
if (type === "cancel")
|
|
1707
|
+
return "host-stop";
|
|
1483
1708
|
if (type === "launch")
|
|
1484
1709
|
return "session-restore";
|
|
1485
1710
|
}
|
|
@@ -1494,6 +1719,30 @@ function definedFields(value) {
|
|
|
1494
1719
|
function errorText(error) {
|
|
1495
1720
|
return error instanceof Error ? error.message : String(error ?? "unknown error");
|
|
1496
1721
|
}
|
|
1722
|
+
/** Preserve the existing durable publication/failure path at the Host boundary. */
|
|
1723
|
+
function endpointReceipt(result, attemptId) {
|
|
1724
|
+
if (result.status === "accepted")
|
|
1725
|
+
return result.receipt;
|
|
1726
|
+
if (result.status === "pending") {
|
|
1727
|
+
throw result.error ?? new ProviderTurnBusyError("The same Endpoint input is still pending.", attemptId);
|
|
1728
|
+
}
|
|
1729
|
+
throw result.error;
|
|
1730
|
+
}
|
|
1731
|
+
async function observePendingHostSubmission(submission, snapshot) {
|
|
1732
|
+
let timer;
|
|
1733
|
+
try {
|
|
1734
|
+
return await Promise.race([
|
|
1735
|
+
submission.then((accepted) => controlResult("accepted", accepted)),
|
|
1736
|
+
new Promise((resolvePromise) => {
|
|
1737
|
+
timer = setTimeout(() => resolvePromise(controlResult("pending", snapshot())), PROVIDER_ACCEPT_TIMEOUT_MS);
|
|
1738
|
+
})
|
|
1739
|
+
]);
|
|
1740
|
+
}
|
|
1741
|
+
finally {
|
|
1742
|
+
if (timer !== undefined)
|
|
1743
|
+
clearTimeout(timer);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1497
1746
|
function signalRoleMailbox(home, payload) {
|
|
1498
1747
|
const taskId = payload.environment.YUI_TASK_ID;
|
|
1499
1748
|
const roleName = payload.environment.YUI_ROLE;
|
|
@@ -1511,14 +1760,13 @@ async function delay(milliseconds) {
|
|
|
1511
1760
|
timer.unref();
|
|
1512
1761
|
});
|
|
1513
1762
|
}
|
|
1514
|
-
function
|
|
1763
|
+
function hostRunControlParams(payload, nativeSessionId, authority, attemptId, runId) {
|
|
1515
1764
|
const environment = payload.environment;
|
|
1516
1765
|
return Object.freeze({
|
|
1517
1766
|
taskId: requiredEnvironment(environment.YUI_TASK_ID, "Task id"),
|
|
1518
1767
|
roleName: requiredEnvironment(environment.YUI_ROLE, "Role name"),
|
|
1519
|
-
...(
|
|
1768
|
+
...(runId === undefined ? {} : { runId: requiredEnvironment(runId, "AgentRun id") }),
|
|
1520
1769
|
agentId: requiredEnvironment(environment.YUI_AGENT_ID, "Agent id"),
|
|
1521
|
-
runtimeGenerationId: payload.runtimeGenerationId,
|
|
1522
1770
|
nativeSessionId: requiredEnvironment(nativeSessionId, "native Session id"),
|
|
1523
1771
|
attemptId,
|
|
1524
1772
|
authorityEpoch: authority.epoch,
|
|
@@ -1527,14 +1775,17 @@ function hostTurnControlParams(payload, nativeSessionId, authority, attemptId, t
|
|
|
1527
1775
|
observedAt: new Date().toISOString()
|
|
1528
1776
|
});
|
|
1529
1777
|
}
|
|
1530
|
-
async function beginDurableProviderTurn(home,
|
|
1778
|
+
async function beginDurableProviderTurn(home, durableRun) {
|
|
1531
1779
|
try {
|
|
1532
|
-
await callControllerIdempotently(home, "runtime.provider-turn-begin",
|
|
1780
|
+
await callControllerIdempotently(home, "runtime.provider-turn-begin", durableRun);
|
|
1533
1781
|
}
|
|
1534
1782
|
catch (error) {
|
|
1783
|
+
if (error instanceof ControllerClientError && error.code === "SESSION_BUSY") {
|
|
1784
|
+
throw new ProviderTurnBusyError(error.message, String(durableRun.attemptId));
|
|
1785
|
+
}
|
|
1535
1786
|
if (!(error instanceof ControllerAcknowledgementUnknownError))
|
|
1536
1787
|
throw error;
|
|
1537
|
-
await resolveProviderTurnSubmission(home,
|
|
1788
|
+
await resolveProviderTurnSubmission(home, durableRun, new Error(`Provider Turn intent acknowledgement failed before Provider write: ${errorText(error)}`, { cause: error }));
|
|
1538
1789
|
// The resolve is fenced on this exact attempt id, so its success proves
|
|
1539
1790
|
// the registration did commit and is now settled with nothing written to
|
|
1540
1791
|
// the Provider. That is a known outcome; reporting it as unknown would
|
|
@@ -1577,16 +1828,16 @@ class ControllerAcknowledgementUnknownError extends Error {
|
|
|
1577
1828
|
class ProviderTurnRegistrationSettledError extends Error {
|
|
1578
1829
|
name = "ProviderTurnRegistrationSettledError";
|
|
1579
1830
|
}
|
|
1580
|
-
async function resolveProviderTurnSubmission(home,
|
|
1581
|
-
const attemptId =
|
|
1831
|
+
async function resolveProviderTurnSubmission(home, durableRun, error) {
|
|
1832
|
+
const attemptId = durableRun.attemptId;
|
|
1582
1833
|
if (typeof attemptId !== "string") {
|
|
1583
1834
|
throw new Error("Provider Turn resolution has no attempt id.");
|
|
1584
1835
|
}
|
|
1585
1836
|
const request = {
|
|
1586
|
-
...
|
|
1837
|
+
...durableRun,
|
|
1587
1838
|
status: error instanceof ProviderDeliveryUnknownError
|
|
1588
1839
|
? "delivery-unknown"
|
|
1589
|
-
: "rejected",
|
|
1840
|
+
: error instanceof ProviderTurnBusyError ? "deferred" : "rejected",
|
|
1590
1841
|
reason: errorText(error),
|
|
1591
1842
|
raw: serializeAgentErrorRaw(error),
|
|
1592
1843
|
observedAt: new Date().toISOString()
|