@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
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a Yui Role's requested run configuration into ACP config option
|
|
3
|
+
* calls, and judging what the Agent answered.
|
|
4
|
+
*
|
|
5
|
+
* Two rules shape this module.
|
|
6
|
+
*
|
|
7
|
+
* The first is that the protocol does the work. ACP describes a Session's
|
|
8
|
+
* configurable surface as a list of `select` options, each with an id, an
|
|
9
|
+
* optional category naming the axis it belongs to, a current value and the
|
|
10
|
+
* complete enumeration of values it accepts. Model, reasoning effort and
|
|
11
|
+
* permission mode are all just options on that list, so resolving them is one
|
|
12
|
+
* generic lookup by category rather than three product branches. An Agent Yui
|
|
13
|
+
* has never seen is configured by the same code as one it ships knowledge of.
|
|
14
|
+
*
|
|
15
|
+
* The second is that a product-specific fact must be named, never inferred. One
|
|
16
|
+
* such fact is needed here: which mode value means "act without asking". No ACP
|
|
17
|
+
* field marks it, and its name is the Agent's own choice, so a value called
|
|
18
|
+
* `bypassPermissions` proves nothing on its own — a different Agent could use
|
|
19
|
+
* the same word for something narrower, or a narrower word for the same power.
|
|
20
|
+
* Guessing from the string would convert an unverified reading into granted
|
|
21
|
+
* authority. So this module does not decide it: it asks the execution component
|
|
22
|
+
* catalog, where every product is described, and a component that names no
|
|
23
|
+
* bypass value has its request reported as unsupported instead. That is a worse
|
|
24
|
+
* user experience and the only honest one. Nothing here enumerates which
|
|
25
|
+
* components exist, so a new ACP product is added by describing it there rather
|
|
26
|
+
* than by editing this file.
|
|
27
|
+
*
|
|
28
|
+
* Nothing here performs I/O: the caller owns the transport, this module owns
|
|
29
|
+
* the decision. That keeps the mapping testable against recorded option lists
|
|
30
|
+
* without a live Agent, and keeps the Session free of product knowledge.
|
|
31
|
+
*/
|
|
32
|
+
import { agentExecutionComponent } from "../agent/executionComponents.js";
|
|
33
|
+
import { ACP_MODE_CONFIG_ID } from "./acpProtocol.js";
|
|
34
|
+
/** ACP categories that name each configurable axis. */
|
|
35
|
+
const MODEL_CATEGORY = "model";
|
|
36
|
+
const EFFORT_CATEGORY = "thought_level";
|
|
37
|
+
const MODE_CATEGORY = "mode";
|
|
38
|
+
/** Only expose the axes Yui configures, never arbitrary peer credential/settings fields. */
|
|
39
|
+
export function acpRunConfigurationOptions(options) {
|
|
40
|
+
const selected = [
|
|
41
|
+
findOption(options, MODEL_CATEGORY, "model"),
|
|
42
|
+
findOption(options, EFFORT_CATEGORY, "effort"),
|
|
43
|
+
findOption(options, MODE_CATEGORY, ACP_MODE_CONFIG_ID)
|
|
44
|
+
];
|
|
45
|
+
return options.filter((option) => selected.includes(option));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Find the option describing one axis.
|
|
49
|
+
*
|
|
50
|
+
* Category is the primary key because it is the field ACP defines for exactly
|
|
51
|
+
* this purpose and it does not depend on an Agent's choice of id. The id is
|
|
52
|
+
* consulted only as a fallback, for an Agent that omits the optional category
|
|
53
|
+
* but uses the conventional id anyway. An Agent doing neither leaves the axis
|
|
54
|
+
* unconfigurable, which is reported rather than worked around.
|
|
55
|
+
*/
|
|
56
|
+
function findOption(options, category, fallbackId) {
|
|
57
|
+
return options.find((option) => option.category === category)
|
|
58
|
+
?? options.find((option) => option.id === fallbackId);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve one requested value against one axis.
|
|
62
|
+
*
|
|
63
|
+
* A value is sendable only when the Agent enumerated it. ACP requires a `select`
|
|
64
|
+
* option to carry its complete set of accepted values, so that list is
|
|
65
|
+
* authoritative: sending anything outside it would be sending a value the Agent
|
|
66
|
+
* has already said it does not take. Reporting the mismatch with the real
|
|
67
|
+
* enumeration is both more accurate and more useful than relaying whatever
|
|
68
|
+
* generic error the Agent would answer with.
|
|
69
|
+
*/
|
|
70
|
+
function resolveValue(field, requested, option, missingAxisReason) {
|
|
71
|
+
if (option === undefined) {
|
|
72
|
+
return Object.freeze({ field, requested, reason: missingAxisReason, offered: Object.freeze([]) });
|
|
73
|
+
}
|
|
74
|
+
const offered = Object.freeze(option.options.map(({ value }) => value));
|
|
75
|
+
const match = option.options.find(({ value }) => value === requested);
|
|
76
|
+
if (match === undefined) {
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
field,
|
|
79
|
+
requested,
|
|
80
|
+
reason: `ACP Agent option \`${option.id}\` does not offer the value \`${requested}\`.`,
|
|
81
|
+
offered
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return Object.freeze({
|
|
85
|
+
field,
|
|
86
|
+
configId: option.id,
|
|
87
|
+
value: match.value,
|
|
88
|
+
previousValue: option.currentValue
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function isRejection(value) {
|
|
92
|
+
return "reason" in value;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The order fields are applied in, and it is load-bearing.
|
|
96
|
+
*
|
|
97
|
+
* A model change is what redefines the other axes: selecting a model can reset
|
|
98
|
+
* the reasoning effort to that model's own default, and can change which effort
|
|
99
|
+
* values exist at all. So the model is settled first and everything that depends
|
|
100
|
+
* on it is resolved afterwards, against the list the Agent reports by then. The
|
|
101
|
+
* permission mode is last because it is the one axis whose wrong value grants
|
|
102
|
+
* authority rather than merely degrading quality — it is decided when the
|
|
103
|
+
* Session's shape has stopped moving.
|
|
104
|
+
*/
|
|
105
|
+
export const ACP_CONFIGURATION_ORDER = Object.freeze(["model", "effort", "permission"]);
|
|
106
|
+
/**
|
|
107
|
+
* Resolve exactly one requested field against the options the Agent reports
|
|
108
|
+
* right now.
|
|
109
|
+
*
|
|
110
|
+
* Per-field and stateless by design. The Session applies one field at a time and
|
|
111
|
+
* re-reads the Agent's complete list in between, so every resolution must be
|
|
112
|
+
* computed from the newest list rather than from anything remembered: a value
|
|
113
|
+
* that was correct before a model change may have been reset by it, and a value
|
|
114
|
+
* that was unavailable may have become offered. A batch plan computed once
|
|
115
|
+
* cannot express either, which is why this replaced one.
|
|
116
|
+
*/
|
|
117
|
+
export function resolveAcpConfigurationField(field, desired, options, component) {
|
|
118
|
+
const outcome = resolveRequest(field, desired, options, component);
|
|
119
|
+
if (outcome === undefined)
|
|
120
|
+
return UNREQUESTED;
|
|
121
|
+
if (isRejection(outcome))
|
|
122
|
+
return Object.freeze({ kind: "rejection", rejection: outcome });
|
|
123
|
+
// A value the Agent already holds needs no call. Keeping this distinct from a
|
|
124
|
+
// value Yui set means a launch never claims to have sent something it did not.
|
|
125
|
+
return outcome.previousValue === outcome.value
|
|
126
|
+
? Object.freeze({ kind: "satisfied", step: outcome })
|
|
127
|
+
: Object.freeze({ kind: "step", step: outcome });
|
|
128
|
+
}
|
|
129
|
+
const UNREQUESTED = Object.freeze({ kind: "unrequested" });
|
|
130
|
+
function resolveRequest(field, desired, options, component) {
|
|
131
|
+
if (field === "model") {
|
|
132
|
+
if (desired.model === undefined)
|
|
133
|
+
return undefined;
|
|
134
|
+
return resolveValue("model", desired.model, findOption(options, MODEL_CATEGORY, "model"), "ACP Agent offers no model config option for this Session, so the requested model "
|
|
135
|
+
+ "cannot be selected over the protocol; configure it in the Agent itself.");
|
|
136
|
+
}
|
|
137
|
+
if (field === "effort") {
|
|
138
|
+
if (desired.effort === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
return resolveValue("effort", desired.effort, findOption(options, EFFORT_CATEGORY, "effort"), "ACP Agent offers no reasoning-effort config option for this Session, so the "
|
|
141
|
+
+ "requested effort cannot be selected over the protocol.");
|
|
142
|
+
}
|
|
143
|
+
const permission = desired.permission;
|
|
144
|
+
// `default` deliberately produces nothing. Yui states no mode, so whatever the
|
|
145
|
+
// Agent chose for itself stands: an old Role's conservative default is never
|
|
146
|
+
// widened, and a permissive Agent is not narrowed behind the user's back.
|
|
147
|
+
if (permission.kind === "default")
|
|
148
|
+
return undefined;
|
|
149
|
+
const modeOption = findOption(options, MODE_CATEGORY, ACP_MODE_CONFIG_ID);
|
|
150
|
+
if (permission.kind === "mode") {
|
|
151
|
+
return resolveValue("permission", permission.modeId, modeOption, "ACP Agent offers no permission-mode config option for this Session, so the "
|
|
152
|
+
+ "requested mode cannot be applied.");
|
|
153
|
+
}
|
|
154
|
+
const bypassValue = agentExecutionComponent(component).bypassPermissionMode;
|
|
155
|
+
if (bypassValue === undefined) {
|
|
156
|
+
// The user asked for real elevation and Yui cannot name the value that
|
|
157
|
+
// grants it for this product. Selecting a mode by how its name reads would
|
|
158
|
+
// be a guess with authority attached, so the request stops here.
|
|
159
|
+
return Object.freeze({
|
|
160
|
+
field: "permission",
|
|
161
|
+
requested: "bypass",
|
|
162
|
+
reason: `Yui cannot map the bypass permission strategy onto execution component `
|
|
163
|
+
+ `${component}: no mode value is known to grant it, and Yui does not infer one `
|
|
164
|
+
+ `from a mode's name. Select an exact mode this Agent offers instead.`,
|
|
165
|
+
offered: Object.freeze(modeOption?.options.map(({ value }) => value) ?? [])
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return resolveValue("permission", bypassValue, modeOption, "ACP Agent offers no permission-mode config option for this Session, so the "
|
|
169
|
+
+ "requested bypass strategy cannot be applied.");
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Re-check every explicitly requested value against the Agent's final option
|
|
173
|
+
* list, after all calls have been made.
|
|
174
|
+
*
|
|
175
|
+
* This exists because confirming each step as it lands is not enough: a later
|
|
176
|
+
* call can reset an earlier axis. Selecting a model resets the reasoning effort
|
|
177
|
+
* on real Agents, so a Session can confirm `effort=high`, then confirm
|
|
178
|
+
* `model=b`, and end up running at that model's default effort with both
|
|
179
|
+
* per-step checks having passed. Only a check against the last complete list the
|
|
180
|
+
* Agent reported can see that, and it is the list the prompt would run under.
|
|
181
|
+
*
|
|
182
|
+
* Only stated values are checked. An unrequested axis has no expectation to
|
|
183
|
+
* violate, and `default` permission means Yui asked for nothing.
|
|
184
|
+
*
|
|
185
|
+
* Legacy `session/set_mode` peers answer with no option list, so their mode
|
|
186
|
+
* cannot be re-read; `acknowledged` outcomes carry that and are excluded here
|
|
187
|
+
* rather than being verified against a value Yui wrote into its own cache.
|
|
188
|
+
*/
|
|
189
|
+
export function verifyAcpConfiguration(desired, options, component, outcomes) {
|
|
190
|
+
const failures = [];
|
|
191
|
+
for (const field of ACP_CONFIGURATION_ORDER) {
|
|
192
|
+
const outcome = outcomes.find((candidate) => candidate.field === field);
|
|
193
|
+
if (outcome !== undefined && outcome.confirmation === "acknowledged")
|
|
194
|
+
continue;
|
|
195
|
+
const resolution = resolveAcpConfigurationField(field, desired, options, component);
|
|
196
|
+
if (resolution.kind === "unrequested" || resolution.kind === "satisfied")
|
|
197
|
+
continue;
|
|
198
|
+
if (resolution.kind === "rejection") {
|
|
199
|
+
// The axis or the value disappeared from the Agent's final list, so the
|
|
200
|
+
// request cannot be shown to hold even though a call for it succeeded.
|
|
201
|
+
failures.push(`Requested ${field} could not be confirmed against the ACP Agent's `
|
|
202
|
+
+ `final configuration. ${resolution.rejection.reason}`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
// The Agent reports a different current value than the one requested, which
|
|
206
|
+
// means something applied later moved this axis.
|
|
207
|
+
failures.push(`ACP Agent reports ${field} \`${resolution.step.previousValue}\` after `
|
|
208
|
+
+ `Yui applied this Session's configuration, but the launch requested `
|
|
209
|
+
+ `\`${resolution.step.value}\`.`);
|
|
210
|
+
}
|
|
211
|
+
return Object.freeze(failures);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Confirm that one applied step actually took effect.
|
|
215
|
+
*
|
|
216
|
+
* `session/set_config_option` answers with the Session's complete option list,
|
|
217
|
+
* so the proof is in that answer rather than in the call having not thrown. An
|
|
218
|
+
* Agent that accepts the call and reports a different current value has
|
|
219
|
+
* substituted something for what was asked, and that substitution must surface
|
|
220
|
+
* as a failure instead of passing as success.
|
|
221
|
+
*/
|
|
222
|
+
export function confirmAcpConfigurationStep(step, options) {
|
|
223
|
+
const option = options.find(({ id }) => id === step.configId);
|
|
224
|
+
if (option === undefined) {
|
|
225
|
+
return `ACP Agent stopped reporting config option \`${step.configId}\` after Yui set it, `
|
|
226
|
+
+ `so the requested ${step.field} cannot be confirmed.`;
|
|
227
|
+
}
|
|
228
|
+
if (option.currentValue !== step.value) {
|
|
229
|
+
return `ACP Agent accepted \`${step.configId}\` = \`${step.value}\` but reports `
|
|
230
|
+
+ `\`${option.currentValue}\`, so the requested ${step.field} was not applied.`;
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
/** A single readable diagnostic for every request the Agent cannot honour. */
|
|
235
|
+
export function describeAcpConfigurationRejections(rejections) {
|
|
236
|
+
return rejections
|
|
237
|
+
.map((rejection) => rejection.offered.length === 0
|
|
238
|
+
? rejection.reason
|
|
239
|
+
: `${rejection.reason} Offered values: ${rejection.offered.join(", ")}.`)
|
|
240
|
+
.join(" ");
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Read a launch payload's requested configuration into the form this module
|
|
244
|
+
* plans from.
|
|
245
|
+
*
|
|
246
|
+
* The payload keeps the two permission requests as separate fields because they
|
|
247
|
+
* travel as JSON; this collapses them into the one decision they represent.
|
|
248
|
+
* Neither present means `default`, which sends no mode at all.
|
|
249
|
+
*/
|
|
250
|
+
export function acpDesiredSessionConfiguration(options) {
|
|
251
|
+
return Object.freeze({
|
|
252
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
253
|
+
...(options.effort === undefined ? {} : { effort: options.effort }),
|
|
254
|
+
permission: options.permissionMode !== undefined
|
|
255
|
+
? Object.freeze({ kind: "mode", modeId: options.permissionMode })
|
|
256
|
+
: options.permissionBypass === true
|
|
257
|
+
? Object.freeze({ kind: "bypass" })
|
|
258
|
+
: Object.freeze({ kind: "default" })
|
|
259
|
+
});
|
|
260
|
+
}
|
|
@@ -41,6 +41,9 @@ export function validateAgentDriverCapabilities(input) {
|
|
|
41
41
|
throw new Error(`Agent Driver control capability ${name} must be boolean.`);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
if (!["native", "owned-process"].includes(String(control.interruptDelivery))) {
|
|
45
|
+
throw new Error("Agent Driver interrupt delivery capability is invalid.");
|
|
46
|
+
}
|
|
44
47
|
const lifecycle = input.lifecycle;
|
|
45
48
|
if (lifecycle === null || typeof lifecycle !== "object" || Array.isArray(lifecycle)) {
|
|
46
49
|
throw new Error("Agent Driver lifecycle capabilities must be an object.");
|
|
@@ -140,6 +143,7 @@ export function validateAgentDriverCapabilities(input) {
|
|
|
140
143
|
resume: control.resume,
|
|
141
144
|
sendTurn: control.sendTurn,
|
|
142
145
|
interrupt: control.interrupt,
|
|
146
|
+
interruptDelivery: control.interruptDelivery,
|
|
143
147
|
stop: control.stop
|
|
144
148
|
}),
|
|
145
149
|
conversation: Object.freeze({
|
|
@@ -190,9 +194,8 @@ export function managedRuntimeAdmission(capabilities) {
|
|
|
190
194
|
if (actual.observation.sessionIdentity !== "exact"
|
|
191
195
|
|| actual.conversation.persistentIdentity !== "exact")
|
|
192
196
|
missing.push("exact-session-identity");
|
|
193
|
-
if (actual
|
|
194
|
-
|
|
195
|
-
missing.push("exact-prompt-acceptance");
|
|
197
|
+
if (!provesPromptAcceptance(actual))
|
|
198
|
+
missing.push("provable-prompt-acceptance");
|
|
196
199
|
if (actual.lifecycle.host !== "persistent")
|
|
197
200
|
missing.push("persistent-agent-host");
|
|
198
201
|
if (actual.lifecycle.nativeConversationResume !== "exact") {
|
|
@@ -206,6 +209,30 @@ export function managedRuntimeAdmission(capabilities) {
|
|
|
206
209
|
? Object.freeze({ admitted: true })
|
|
207
210
|
: Object.freeze({ admitted: false, missing: Object.freeze(missing) });
|
|
208
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Whether a Turn's input can be proven either accepted or not accepted.
|
|
214
|
+
*
|
|
215
|
+
* Two different protocol shapes satisfy this, and neither is weaker than the
|
|
216
|
+
* other:
|
|
217
|
+
*
|
|
218
|
+
* - the provider announces acceptance separately from the result, so a Turn
|
|
219
|
+
* is known to be running before it finishes (`acceptance: "exact"`);
|
|
220
|
+
* - the provider answers the request itself with an exact structured
|
|
221
|
+
* terminal, so the reply that settles the Turn is also the proof its input
|
|
222
|
+
* was accepted (`bounded.structuredTerminal` with an exact turn lifecycle).
|
|
223
|
+
*
|
|
224
|
+
* In the second shape a long wait is genuinely pending rather than unproven:
|
|
225
|
+
* the correlated response is still owed, and it will say which way the Turn
|
|
226
|
+
* went. What must never be admitted is a provider that can leave input in a
|
|
227
|
+
* state with no reply and no acceptance, because nothing there can ever settle
|
|
228
|
+
* the Turn.
|
|
229
|
+
*/
|
|
230
|
+
function provesPromptAcceptance(actual) {
|
|
231
|
+
if (actual.observation.promptAcceptance === "exact" && actual.input.acceptance === "exact") {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
return actual.bounded.structuredTerminal && actual.observation.turnLifecycle === "exact";
|
|
235
|
+
}
|
|
209
236
|
export function boundedRuntimeAdmission(capabilities) {
|
|
210
237
|
const actual = validateAgentDriverCapabilities(capabilities);
|
|
211
238
|
const missing = [];
|
|
@@ -314,19 +341,11 @@ export function normalizeAgentDriverHookClassification(input) {
|
|
|
314
341
|
}
|
|
315
342
|
if (input.continuationId !== undefined) {
|
|
316
343
|
requireText(input.continuationId, "Agent Driver Hook continuation id");
|
|
317
|
-
if (!Number.isSafeInteger(input.continuationGeneration)
|
|
318
|
-
|| input.continuationGeneration < 1) {
|
|
319
|
-
throw new Error("Agent Driver Hook continuation generation is invalid.");
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
else if (input.continuationGeneration !== undefined) {
|
|
323
|
-
throw new Error("Agent Driver Hook continuation generation requires an id.");
|
|
324
344
|
}
|
|
325
345
|
return Object.freeze({
|
|
326
346
|
...(input.startupSession === undefined ? {} : { startupSession: input.startupSession }),
|
|
327
347
|
...(input.continuationId === undefined ? {} : {
|
|
328
348
|
continuationId: input.continuationId,
|
|
329
|
-
continuationGeneration: input.continuationGeneration
|
|
330
349
|
}),
|
|
331
350
|
terminal: input.terminal ?? false
|
|
332
351
|
});
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { builtinAgentDriverRegistry } from "./builtinAgentDrivers.js";
|
|
2
|
+
import { builtinAgentEndpointImplementation, requireBuiltinAgentEndpointImplementation } from "./agentEndpointIdentity.js";
|
|
3
|
+
import { CodexPreSubmissionError } from "./codexAppServerRuntime.js";
|
|
4
|
+
export { builtinAgentEndpointImplementation } from "./agentEndpointIdentity.js";
|
|
5
|
+
import { ProviderDeliveryUnknownError, ProviderTurnBusyError, ProviderTurnRejectedError, startStructuredProviderSession } from "./structuredProviderHost.js";
|
|
6
|
+
/**
|
|
7
|
+
* The only built-in managed execution factory. Product/protocol codecs remain
|
|
8
|
+
* private to runtime; the Host consumes the same boundary for both providers.
|
|
9
|
+
* An injected opener is useful for isolated protocol evidence, not a fallback.
|
|
10
|
+
*/
|
|
11
|
+
export function createAgentEndpointFactory(start = startStructuredProviderSession) {
|
|
12
|
+
const connect = async (payload, mode) => {
|
|
13
|
+
const control = payload.providerControl;
|
|
14
|
+
if (control === undefined || control.mode !== mode) {
|
|
15
|
+
throw new Error(`AgentEndpoint ${mode === "new" ? "open" : "resume"} requires matching Session intent.`);
|
|
16
|
+
}
|
|
17
|
+
const pinned = control.endpointImplementation;
|
|
18
|
+
const implementation = pinned === undefined ? builtinAgentEndpointImplementation(control.adapterId)
|
|
19
|
+
: Object.freeze({ ...requireBuiltinAgentEndpointImplementation(control.adapterId, pinned) });
|
|
20
|
+
// Clone before asynchronous startup; callers must not mutate the Session's
|
|
21
|
+
// effective invocation when configuration changes for a subsequent AgentRun.
|
|
22
|
+
const configuration = Object.freeze({
|
|
23
|
+
implementation,
|
|
24
|
+
command: payload.command,
|
|
25
|
+
args: Object.freeze([...payload.args]),
|
|
26
|
+
cwd: payload.cwd,
|
|
27
|
+
...(payload.executionEnvironment === undefined ? {} : {
|
|
28
|
+
executionEnvironment: freezeConfiguration(structuredClone(payload.executionEnvironment))
|
|
29
|
+
}),
|
|
30
|
+
...(control.codexThread === undefined ? {} : {
|
|
31
|
+
threadOptions: freezeConfiguration(structuredClone(control.codexThread))
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
let endpoint;
|
|
35
|
+
const openingEvents = [];
|
|
36
|
+
const emit = (event) => {
|
|
37
|
+
if (endpoint === undefined)
|
|
38
|
+
openingEvents.push(event);
|
|
39
|
+
else
|
|
40
|
+
endpoint.observe(event);
|
|
41
|
+
};
|
|
42
|
+
const opened = await start(payload, {
|
|
43
|
+
onAccepted: (value) => emit({ type: "accepted", value }),
|
|
44
|
+
onActivity: (value) => emit({ type: "activity", value }),
|
|
45
|
+
onStarted: (value) => emit({ type: "started", value }),
|
|
46
|
+
onTerminal: (value) => emit({ type: "terminal", value }),
|
|
47
|
+
onInput: (value) => emit({ type: "input", value }),
|
|
48
|
+
onGoal: (value) => emit({ type: "goal", value })
|
|
49
|
+
});
|
|
50
|
+
endpoint = new BuiltinAgentEndpoint(opened.session, configuration, opened.recoveredTerminal);
|
|
51
|
+
for (const event of openingEvents)
|
|
52
|
+
endpoint.observe(event);
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
session: endpoint,
|
|
55
|
+
...(opened.recoveredTerminal === undefined ? {} : { recoveredTerminal: opened.recoveredTerminal }),
|
|
56
|
+
...(opened.goal === undefined ? {} : { goal: opened.goal })
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
open: (payload) => connect(payload, "new"),
|
|
61
|
+
resume: (payload) => connect(payload, "resume")
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function freezeConfiguration(value) {
|
|
65
|
+
if (value !== null && typeof value === "object") {
|
|
66
|
+
for (const member of Object.values(value))
|
|
67
|
+
freezeConfiguration(member);
|
|
68
|
+
Object.freeze(value);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
class BuiltinAgentEndpoint {
|
|
73
|
+
driver;
|
|
74
|
+
configuration;
|
|
75
|
+
get ownedProcessId() { return this.driver.ownedProcessId; }
|
|
76
|
+
get nativeAccountHome() { return this.driver.nativeAccountHome; }
|
|
77
|
+
capabilities;
|
|
78
|
+
conversationRecoverability;
|
|
79
|
+
#listeners = new Set();
|
|
80
|
+
#openingEvents = [];
|
|
81
|
+
#attempts = new Map();
|
|
82
|
+
#terminals = new Map();
|
|
83
|
+
#attachment = "attached";
|
|
84
|
+
#cancellation = "not-requested";
|
|
85
|
+
constructor(driver, configuration, recoveredTerminal) {
|
|
86
|
+
this.driver = driver;
|
|
87
|
+
this.configuration = configuration;
|
|
88
|
+
if (recoveredTerminal?.clientOwned && recoveredTerminal.attemptId !== undefined) {
|
|
89
|
+
this.#terminals.set(recoveredTerminal.attemptId, cancellationProof(recoveredTerminal));
|
|
90
|
+
}
|
|
91
|
+
// The registered Driver, not the adapter name, states which control
|
|
92
|
+
// affordances this Session really has: a Session that sends a native
|
|
93
|
+
// cancel message must not be reported as one that can only kill its
|
|
94
|
+
// process, and vice versa.
|
|
95
|
+
const capabilities = builtinAgentDriverRegistry()
|
|
96
|
+
.requireByAdapterId(driver.adapterId)
|
|
97
|
+
.capabilities;
|
|
98
|
+
this.capabilities = Object.freeze({
|
|
99
|
+
steer: capabilities.input.steer === "fenced" ? "native" : "unsupported",
|
|
100
|
+
cancel: capabilities.control.interruptDelivery === "native"
|
|
101
|
+
? "native-interrupt"
|
|
102
|
+
: "owned-process"
|
|
103
|
+
});
|
|
104
|
+
// What the protocol permits is not always what this Agent agreed to. When
|
|
105
|
+
// the Session settled the question during its own handshake, that answer
|
|
106
|
+
// wins over the adapter-wide capability, which cannot see the difference
|
|
107
|
+
// between two Agents on the same adapter.
|
|
108
|
+
this.conversationRecoverability = driver.conversationRecoverability
|
|
109
|
+
?? (capabilities.lifecycle.nativeConversationResume === "exact"
|
|
110
|
+
&& capabilities.conversation.crossProcessResume
|
|
111
|
+
? "recoverable"
|
|
112
|
+
: "unknown");
|
|
113
|
+
void driver.waitForExit().then(() => { this.#attachment = "exited"; });
|
|
114
|
+
}
|
|
115
|
+
get adapterId() { return this.driver.adapterId; }
|
|
116
|
+
get nativeSessionId() { return this.driver.nativeSessionId; }
|
|
117
|
+
get conversationId() { return this.driver.conversationId; }
|
|
118
|
+
get processInstanceId() { return this.driver.processInstanceId; }
|
|
119
|
+
/**
|
|
120
|
+
* Delegated rather than copied at construction: the Session reads its own
|
|
121
|
+
* current state, and a value captured here would freeze the configuration as
|
|
122
|
+
* it stood when the connection opened.
|
|
123
|
+
*/
|
|
124
|
+
get runConfiguration() {
|
|
125
|
+
return this.driver.runConfiguration;
|
|
126
|
+
}
|
|
127
|
+
submit(input) {
|
|
128
|
+
return this.deliver(input, "submit");
|
|
129
|
+
}
|
|
130
|
+
steer(input) {
|
|
131
|
+
return this.deliver(input, "steer");
|
|
132
|
+
}
|
|
133
|
+
async deliver(input, operation) {
|
|
134
|
+
if (!input.attemptId || !input.inputRef)
|
|
135
|
+
throw new Error("Endpoint input requires attemptId and inputRef.");
|
|
136
|
+
const previous = this.#attempts.get(input.attemptId);
|
|
137
|
+
if (previous !== undefined) {
|
|
138
|
+
if (previous.input.inputRef !== input.inputRef || previous.input.boundedText !== input.boundedText
|
|
139
|
+
|| previous.operation !== operation) {
|
|
140
|
+
throw new Error("Endpoint attempt identity cannot be reused for different input or operation.");
|
|
141
|
+
}
|
|
142
|
+
// Busy is a proven non-write: the same durable request may be tried
|
|
143
|
+
// explicitly later. Unknown, accepted and in-flight input are never resent.
|
|
144
|
+
if (previous.disposition.status !== "pending" || previous.disposition.reason !== "native-busy") {
|
|
145
|
+
return previous.disposition;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (this.#attachment !== "attached") {
|
|
149
|
+
return { status: "not-submitted", error: new ProviderTurnRejectedError("Endpoint is detached.", input.attemptId) };
|
|
150
|
+
}
|
|
151
|
+
const attempt = {
|
|
152
|
+
input: Object.freeze({ ...input }),
|
|
153
|
+
operation,
|
|
154
|
+
disposition: { status: "pending", reason: "submitting" }
|
|
155
|
+
};
|
|
156
|
+
this.#attempts.set(input.attemptId, attempt);
|
|
157
|
+
try {
|
|
158
|
+
const receipt = await (operation === "submit"
|
|
159
|
+
? this.driver.submitTurn(input) : this.driver.steerTurn(input));
|
|
160
|
+
if (receipt.attemptId !== input.attemptId || receipt.nativeSessionId !== this.nativeSessionId
|
|
161
|
+
|| receipt.conversationId !== this.conversationId) {
|
|
162
|
+
throw new ProviderDeliveryUnknownError("Provider receipt does not match Endpoint input.", input.attemptId);
|
|
163
|
+
}
|
|
164
|
+
if (attempt.disposition.status !== "accepted") {
|
|
165
|
+
attempt.disposition = { status: "accepted", receipt };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (attempt.disposition.status === "accepted")
|
|
170
|
+
return attempt.disposition;
|
|
171
|
+
attempt.disposition = error instanceof ProviderTurnBusyError
|
|
172
|
+
? { status: "pending", reason: "native-busy", error }
|
|
173
|
+
: error instanceof ProviderTurnRejectedError || error instanceof CodexPreSubmissionError
|
|
174
|
+
? { status: "not-submitted", error }
|
|
175
|
+
: {
|
|
176
|
+
status: "unknown",
|
|
177
|
+
error: error instanceof ProviderDeliveryUnknownError ? error
|
|
178
|
+
: new ProviderDeliveryUnknownError("Provider submission outcome is unknown.", input.attemptId, { cause: error })
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return attempt.disposition;
|
|
182
|
+
}
|
|
183
|
+
inspect() {
|
|
184
|
+
return Object.freeze({
|
|
185
|
+
...(this.driver.activeTurnId === undefined ? {} : { activeNativeTurnId: this.driver.activeTurnId }),
|
|
186
|
+
submissions: Object.freeze([...this.#attempts].map(([attemptId, attempt]) => Object.freeze({
|
|
187
|
+
attemptId, inputRef: attempt.input.inputRef, disposition: attempt.disposition
|
|
188
|
+
}))),
|
|
189
|
+
attachment: this.#attachment,
|
|
190
|
+
cancellation: this.#cancellation,
|
|
191
|
+
resources: "unknown"
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
observe(value) {
|
|
195
|
+
if (value.type !== "goal"
|
|
196
|
+
&& (value.value.nativeSessionId !== this.nativeSessionId || value.value.conversationId !== this.conversationId))
|
|
197
|
+
return;
|
|
198
|
+
if (value.type === "goal" && value.value !== null && value.value.conversationId !== this.conversationId)
|
|
199
|
+
return;
|
|
200
|
+
if (value.type === "accepted") {
|
|
201
|
+
const attempt = this.#attempts.get(value.value.attemptId);
|
|
202
|
+
if (attempt === undefined || value.value.acceptance !== "provider")
|
|
203
|
+
return;
|
|
204
|
+
attempt.disposition = { status: "accepted", receipt: value.value };
|
|
205
|
+
}
|
|
206
|
+
if (value.type === "terminal" && value.value.clientOwned && value.value.attemptId !== undefined) {
|
|
207
|
+
const attempt = this.#attempts.get(value.value.attemptId);
|
|
208
|
+
if (attempt !== undefined) {
|
|
209
|
+
const prior = attempt.disposition;
|
|
210
|
+
if (prior.status === "accepted" && prior.receipt.nativeTurnId !== undefined
|
|
211
|
+
&& prior.receipt.nativeTurnId !== value.value.nativeTurnId)
|
|
212
|
+
return;
|
|
213
|
+
// An exactly correlated terminal is stronger evidence than pipe write
|
|
214
|
+
// or a lost acknowledgement. It never adopts the current attempt.
|
|
215
|
+
attempt.disposition = {
|
|
216
|
+
status: "accepted",
|
|
217
|
+
receipt: {
|
|
218
|
+
attemptId: value.value.attemptId,
|
|
219
|
+
nativeSessionId: this.nativeSessionId,
|
|
220
|
+
conversationId: this.conversationId,
|
|
221
|
+
...(value.value.nativeTurnId === undefined ? {} : { nativeTurnId: value.value.nativeTurnId }),
|
|
222
|
+
acceptedAt: value.value.observedAt,
|
|
223
|
+
acceptance: "provider"
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
this.#terminals.set(value.value.attemptId, cancellationProof(value.value));
|
|
228
|
+
}
|
|
229
|
+
const event = Object.freeze({
|
|
230
|
+
...value, implementation: this.configuration.implementation, processInstanceId: this.processInstanceId
|
|
231
|
+
});
|
|
232
|
+
if (this.#listeners.size === 0)
|
|
233
|
+
this.#openingEvents.push(event);
|
|
234
|
+
else
|
|
235
|
+
for (const listener of this.#listeners)
|
|
236
|
+
listener(event);
|
|
237
|
+
}
|
|
238
|
+
events(listener) {
|
|
239
|
+
this.#listeners.add(listener);
|
|
240
|
+
for (const event of this.#openingEvents.splice(0))
|
|
241
|
+
listener(event);
|
|
242
|
+
return () => { this.#listeners.delete(listener); };
|
|
243
|
+
}
|
|
244
|
+
async cancel(attemptId) {
|
|
245
|
+
this.#cancellation = "requested";
|
|
246
|
+
const status = await this.driver.cancelTurn(attemptId);
|
|
247
|
+
let terminal = this.#terminals.get(attemptId);
|
|
248
|
+
if (terminal === undefined && status !== "unknown") {
|
|
249
|
+
terminal = await new Promise(resolve => {
|
|
250
|
+
const timer = setTimeout(() => { this.#listeners.delete(listener); resolve(undefined); }, 8_000);
|
|
251
|
+
const listener = (event) => {
|
|
252
|
+
if (event.type !== "terminal" || !event.value.clientOwned || event.value.attemptId !== attemptId)
|
|
253
|
+
return;
|
|
254
|
+
clearTimeout(timer);
|
|
255
|
+
this.#listeners.delete(listener);
|
|
256
|
+
resolve(cancellationProof(event.value));
|
|
257
|
+
};
|
|
258
|
+
this.#listeners.add(listener);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (terminal === undefined || this.driver.activeTurnId !== undefined) {
|
|
262
|
+
return { status: "unknown", resources: "unknown" };
|
|
263
|
+
}
|
|
264
|
+
return Object.freeze({ status, resources: "unknown", terminal });
|
|
265
|
+
}
|
|
266
|
+
detach(signal = "SIGTERM") {
|
|
267
|
+
if (this.#attachment === "exited")
|
|
268
|
+
return;
|
|
269
|
+
this.#attachment = "detach-requested";
|
|
270
|
+
// The driver terminates only its owned proxy/stream process group.
|
|
271
|
+
this.driver.terminate(signal);
|
|
272
|
+
}
|
|
273
|
+
waitForExit() { return this.driver.waitForExit(); }
|
|
274
|
+
}
|
|
275
|
+
function cancellationProof(terminal) {
|
|
276
|
+
const { input: _input, output: _output, error: _error, rawError: _rawError, ...proof } = terminal;
|
|
277
|
+
return Object.freeze(proof);
|
|
278
|
+
}
|