@gajae-code/coding-agent 0.18.12 → 0.19.0
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/CHANGELOG.md +467 -0
- package/dist/types/capability/rule.d.ts +7 -2
- package/dist/types/cli/append-system-prompt.d.ts +6 -0
- package/dist/types/cli/args.d.ts +7 -2
- package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
- package/dist/types/cli/customize-doctor.d.ts +4 -3
- package/dist/types/cli/public-command-errors.d.ts +1 -0
- package/dist/types/cli/root-flags.d.ts +0 -12
- package/dist/types/cli/update-cli.d.ts +7 -2
- package/dist/types/cli-main.d.ts +0 -6
- package/dist/types/commands/launch.d.ts +0 -6
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +8 -0
- package/dist/types/commit/map-reduce/index.d.ts +4 -4
- package/dist/types/config/autorouting-tier-map.d.ts +23 -1
- package/dist/types/config/config-hot-reload.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +44 -2
- package/dist/types/config/model-profile-activation.d.ts +19 -5
- package/dist/types/config/model-registry.d.ts +16 -3
- package/dist/types/config/model-resolver.d.ts +5 -4
- package/dist/types/config/settings-schema.d.ts +11 -1
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/customization/import.d.ts +2 -0
- package/dist/types/customization/types.d.ts +1 -1
- package/dist/types/dap/session.d.ts +11 -2
- package/dist/types/discovery/helpers.d.ts +7 -2
- package/dist/types/edit/index.d.ts +5 -0
- package/dist/types/edit/modes/patch.d.ts +9 -0
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/export/ttsr.d.ts +19 -0
- package/dist/types/extensibility/extensions/types.d.ts +5 -1
- package/dist/types/extensibility/slash-commands.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/content.d.ts +5 -6
- package/dist/types/internal-urls/types.d.ts +2 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
- package/dist/types/main.d.ts +23 -18
- package/dist/types/migrate/action-planner.d.ts +10 -1
- package/dist/types/migrate/adapters/codex.d.ts +1 -1
- package/dist/types/migrate/adapters/index.d.ts +35 -3
- package/dist/types/migrate/hook-module.d.ts +23 -0
- package/dist/types/migrate/report.d.ts +2 -1
- package/dist/types/migrate/types.d.ts +69 -2
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +43 -0
- package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +15 -11
- package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
- package/dist/types/sdk/broker/broker-exit.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +22 -1
- package/dist/types/sdk/broker/ensure.d.ts +5 -2
- package/dist/types/sdk/broker/hop.d.ts +9 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
- package/dist/types/sdk/broker/session-index.d.ts +2 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/index.d.ts +21 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
- package/dist/types/sdk/host/control/operations.d.ts +5 -1
- package/dist/types/sdk/host/events.d.ts +4 -0
- package/dist/types/sdk/host/host.d.ts +2 -1
- package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
- package/dist/types/sdk/host/session-runtime.d.ts +8 -8
- package/dist/types/sdk/host/surface-policy.d.ts +2 -1
- package/dist/types/sdk/lifecycle/service.d.ts +12 -0
- package/dist/types/sdk/prompt-status.d.ts +11 -4
- package/dist/types/session/agent-session.d.ts +50 -8
- package/dist/types/session/session-manager.d.ts +16 -0
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +13 -1
- package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
- package/dist/types/ssh/ssh-executor.d.ts +8 -1
- package/dist/types/system-prompt.d.ts +3 -2
- package/dist/types/task/role-body.d.ts +11 -0
- package/dist/types/tools/awaiting-input.d.ts +28 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/descriptors.d.ts +13 -2
- package/dist/types/tools/fetch.d.ts +6 -0
- package/dist/types/tools/irc.d.ts +5 -3
- package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/file-mentions.d.ts +5 -0
- package/dist/types/utils/image-limits.d.ts +7 -0
- package/dist/types/utils/image-loading.d.ts +0 -1
- package/dist/types/utils/open.d.ts +0 -1
- package/dist/types/utils/pasted-image-loading.d.ts +1 -5
- package/dist/types/utils/prompt-suggestion.d.ts +2 -2
- package/dist/types/utils/shell-snapshot.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +13 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -7
- package/package.json +9 -8
- package/scripts/acp-conformance-agent.ts +12 -2
- package/scripts/generate-sdk-operation-inventory.ts +16 -0
- package/src/capability/fs.ts +31 -3
- package/src/capability/rule.ts +11 -2
- package/src/cli/append-system-prompt.ts +12 -0
- package/src/cli/args.ts +11 -7
- package/src/cli/customize-doctor-precedence.ts +31 -0
- package/src/cli/customize-doctor.ts +49 -39
- package/src/cli/fast-help.ts +0 -2
- package/src/cli/migrate-cli.ts +14 -3
- package/src/cli/public-command-errors.ts +2 -0
- package/src/cli/root-flags.ts +0 -2
- package/src/cli/update-cli.ts +247 -6
- package/src/commands/harness.ts +25 -15
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +81 -11
- package/src/commit/agentic/agent.ts +6 -6
- package/src/commit/analysis/conventional.ts +14 -5
- package/src/commit/map-reduce/index.ts +7 -7
- package/src/commit/pipeline.ts +12 -12
- package/src/config/autorouting-tier-map.ts +20 -1
- package/src/config/config-file.ts +106 -3
- package/src/config/config-hot-reload.ts +8 -0
- package/src/config/file-lock.ts +1431 -304
- package/src/config/keybindings.ts +154 -7
- package/src/config/model-profile-activation.ts +177 -39
- package/src/config/model-registry.ts +380 -55
- package/src/config/model-resolver.ts +49 -21
- package/src/config/settings-schema.ts +13 -1
- package/src/config/settings.ts +2 -0
- package/src/coordinator-mcp/server.ts +37 -2
- package/src/crash/sanitize.ts +3 -1
- package/src/customization/import.ts +276 -7
- package/src/customization/inventory.ts +24 -8
- package/src/customization/types.ts +1 -0
- package/src/dap/client.ts +3 -2
- package/src/dap/session.ts +161 -36
- package/src/debug/crash-diagnostics.ts +87 -5
- package/src/debug/report-bundle.ts +71 -2
- package/src/discovery/builtin.ts +4 -3
- package/src/discovery/helpers.ts +24 -14
- package/src/edit/index.ts +191 -5
- package/src/edit/modes/patch.ts +27 -0
- package/src/eval/py/runtime.ts +44 -4
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/html/index.ts +60 -20
- package/src/export/ttsr.ts +176 -1
- package/src/extensibility/extensions/loader.ts +2 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
- package/src/extensibility/extensions/types.ts +5 -1
- package/src/extensibility/hooks/loader.ts +10 -2
- package/src/extensibility/plugins/marketplace/manager.ts +36 -10
- package/src/extensibility/slash-commands.ts +5 -0
- package/src/gjc-runtime/gc-runtime.ts +4 -1
- package/src/gjc-runtime/launch-tmux.ts +23 -13
- package/src/gjc-runtime/ralplan-current-final.ts +294 -0
- package/src/gjc-runtime/ralplan-runtime.ts +95 -1
- package/src/gjc-runtime/state-writer.ts +109 -5
- package/src/gjc-runtime/tmux-sessions.ts +93 -13
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
- package/src/gjc-runtime/ultragoal-guard.ts +9 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
- package/src/harness-control-plane/finalize.ts +11 -0
- package/src/harness-control-plane/receipt-spool.ts +105 -28
- package/src/harness-control-plane/storage.ts +124 -6
- package/src/hindsight/content.ts +116 -16
- package/src/hindsight/mental-models.ts +22 -10
- package/src/hindsight/state.ts +7 -4
- package/src/hooks/native-skill-hook.ts +3 -1
- package/src/internal-urls/artifact-protocol.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +14 -14
- package/src/internal-urls/local-protocol.ts +28 -3
- package/src/internal-urls/types.ts +2 -0
- package/src/lsp/client.ts +2 -1
- package/src/lsp/clients/biome-client.ts +2 -0
- package/src/lsp/clients/swiftlint-client.ts +2 -0
- package/src/main.ts +330 -92
- package/src/memories/index.ts +25 -2
- package/src/migrate/action-planner.ts +517 -2
- package/src/migrate/adapters/claude-code.ts +141 -3
- package/src/migrate/adapters/codex.ts +57 -5
- package/src/migrate/adapters/index.ts +240 -5
- package/src/migrate/adapters/opencode.ts +4 -0
- package/src/migrate/executor.ts +141 -3
- package/src/migrate/hook-module.ts +193 -0
- package/src/migrate/report.ts +45 -1
- package/src/migrate/types.ts +73 -3
- package/src/modes/acp/acp-agent.ts +10 -1
- package/src/modes/components/agent-dashboard.ts +20 -4
- package/src/modes/components/customization/customization-dashboard.ts +7 -0
- package/src/modes/components/customization/import-wizard.ts +29 -3
- package/src/modes/components/model-selector.ts +157 -103
- package/src/modes/components/welcome.ts +3 -1
- package/src/modes/controllers/command-controller.ts +271 -16
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +31 -1
- package/src/modes/controllers/goal-mode-controller.ts +79 -10
- package/src/modes/controllers/input-controller.ts +2 -0
- package/src/modes/controllers/plan-mode-controller.ts +49 -4
- package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
- package/src/modes/controllers/selector-controller.ts +14 -1
- package/src/modes/interactive-mode.ts +55 -4
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +5 -1
- package/src/modes/utils/host-status.ts +21 -10
- package/src/prompts/agent-fragments/browser-aside.md +2 -2
- package/src/prompts/system/irc-incoming.md +3 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/runtime-mcp/manager.ts +8 -3
- package/src/runtime-mcp/project-host-values.ts +12 -8
- package/src/runtime-mcp/tool-bridge.ts +68 -4
- package/src/runtime-mcp/transports/stdio.ts +2 -1
- package/src/sdk/broker/broker-exit.ts +2 -0
- package/src/sdk/broker/broker.ts +143 -4
- package/src/sdk/broker/ensure.ts +74 -6
- package/src/sdk/broker/hop.ts +13 -1
- package/src/sdk/broker/lifecycle-ledger.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +102 -6
- package/src/sdk/broker/session-index.ts +29 -4
- package/src/sdk/broker/transport.ts +17 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +408 -14
- package/src/sdk/bus/session-control.ts +2 -0
- package/src/sdk/bus/slack-daemon.ts +105 -9
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -2
- package/src/sdk/bus/threaded-render.ts +20 -1
- package/src/sdk/cli/session-cli.ts +2 -0
- package/src/sdk/host/control/dispatch.ts +13 -1
- package/src/sdk/host/control/operations.ts +10 -1
- package/src/sdk/host/events.ts +67 -4
- package/src/sdk/host/host.ts +8 -2
- package/src/sdk/host/prompt-image-upload.ts +438 -0
- package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
- package/src/sdk/host/session-runtime.test.ts +1537 -24
- package/src/sdk/host/session-runtime.ts +332 -36
- package/src/sdk/host/surface-policy.ts +4 -0
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
- package/src/sdk/host/websocket-transport.ts +19 -7
- package/src/sdk/lifecycle/service.ts +14 -1
- package/src/sdk/prompt-status.ts +6 -4
- package/src/sdk/protocol/adapter-validation.ts +28 -1
- package/src/sdk/protocol/operation-inventory.generated.json +160 -0
- package/src/sdk/protocol/operation-registry.ts +33 -3
- package/src/sdk/session.ts +123 -36
- package/src/session/agent-session.ts +1614 -329
- package/src/session/history-storage.ts +182 -12
- package/src/session/internal/managed-session-scope.ts +47 -18
- package/src/session/internal/managed-session-storage.ts +71 -1
- package/src/session/session-manager.ts +90 -5
- package/src/session-import/redact.ts +53 -2
- package/src/skill-state/workflow-mutation-guard.ts +1498 -18
- package/src/slash-commands/builtin-registry.ts +6 -3
- package/src/slash-commands/helpers/mcp.ts +11 -3
- package/src/ssh/ssh-executor.ts +9 -2
- package/src/stt/recorder.ts +26 -9
- package/src/system-prompt.ts +61 -16
- package/src/task/executor.ts +12 -8
- package/src/task/role-body.ts +84 -0
- package/src/task/token-log-mode.test.ts +162 -0
- package/src/task/token-log.ts +91 -2
- package/src/tools/archive-reader.ts +650 -37
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-grep.ts +7 -2
- package/src/tools/auto-generated-guard.ts +12 -3
- package/src/tools/awaiting-input.ts +114 -0
- package/src/tools/bash-allowed-prefixes.ts +61 -1
- package/src/tools/bash-skill-urls.ts +58 -5
- package/src/tools/bash.ts +99 -26
- package/src/tools/browser/tab-worker.ts +39 -13
- package/src/tools/browser.ts +17 -7
- package/src/tools/cron.ts +10 -1
- package/src/tools/debug.ts +9 -2
- package/src/tools/descriptors.test.ts +4 -4
- package/src/tools/descriptors.ts +16 -4
- package/src/tools/eval.ts +15 -0
- package/src/tools/fetch.ts +90 -2
- package/src/tools/gh.ts +27 -12
- package/src/tools/image-gen.ts +20 -6
- package/src/tools/irc.ts +5 -3
- package/src/tools/monitor.ts +9 -1
- package/src/tools/output-meta.ts +51 -3
- package/src/tools/read.ts +9 -2
- package/src/tools/recipe/runner.ts +23 -12
- package/src/tools/render-utils.ts +2 -2
- package/src/tools/search.ts +3 -2
- package/src/tools/ssh.ts +274 -8
- package/src/tools/subagent.ts +10 -4
- package/src/tools/tmux-self-injection-guard.ts +3301 -122
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/ultragoal-ask-guard.ts +13 -1
- package/src/tools/write.ts +7 -0
- package/src/tui/tree-list.ts +15 -1
- package/src/utils/commit-message-generator.ts +3 -2
- package/src/utils/external-editor.ts +21 -7
- package/src/utils/file-mentions.ts +46 -6
- package/src/utils/git.ts +38 -7
- package/src/utils/image-limits.ts +8 -0
- package/src/utils/image-loading.ts +1 -1
- package/src/utils/open.ts +73 -4
- package/src/utils/pasted-image-loading.ts +9 -8
- package/src/utils/prompt-suggestion.ts +5 -4
- package/src/utils/shell-snapshot.ts +9 -1
- package/src/utils/title-generator.ts +65 -20
- package/src/web/search/index.ts +2 -1
- package/src/web/search/providers/codex.ts +29 -32
package/src/sdk/bus/index.ts
CHANGED
|
@@ -71,7 +71,7 @@ import {
|
|
|
71
71
|
type WorkflowGateTerminalController,
|
|
72
72
|
type WorkflowGateTerminalProof,
|
|
73
73
|
} from "../../modes/shared/agent-wire/workflow-gate-broker";
|
|
74
|
-
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
74
|
+
import type { AgentSessionEvent, QueuedRunStartFailure } from "../../session/agent-session";
|
|
75
75
|
import type { ClientBridge } from "../../session/client-bridge";
|
|
76
76
|
import {
|
|
77
77
|
createSessionWorkLease,
|
|
@@ -102,6 +102,7 @@ import {
|
|
|
102
102
|
registerAskAnswerSource,
|
|
103
103
|
registerWorkflowGateEmitterListener,
|
|
104
104
|
} from "../../tools/ask-answer-registry";
|
|
105
|
+
import type { AwaitingInputKind } from "../../tools/awaiting-input";
|
|
105
106
|
import { acpFinalTextFromMessage } from "../acp/final-text";
|
|
106
107
|
import { ensureBroker } from "../broker/ensure";
|
|
107
108
|
import { publishSessionHostRuntimeEvidence, type SessionHostRuntimePublication } from "../broker/lifecycle";
|
|
@@ -117,11 +118,13 @@ import {
|
|
|
117
118
|
type SessionSdkHost,
|
|
118
119
|
SessionSdkSessionRuntime,
|
|
119
120
|
shouldHostSdk,
|
|
121
|
+
TURN_STREAM_CAPABILITY,
|
|
120
122
|
verifyMasterCapabilityFrame,
|
|
121
123
|
} from "../host";
|
|
122
124
|
import { type AbortScope, type ControlSurface, dispatchControl, TypedControlError } from "../host/control";
|
|
123
125
|
import { BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD } from "../host/control/runtime-gate";
|
|
124
126
|
import { isAutoroutingInactive, markAutoroutingInactive } from "../host/internal-autorouting-state";
|
|
127
|
+
import { PromptImageUploadStore, retirePromptImageReservation } from "../host/prompt-image-upload";
|
|
125
128
|
import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "../host/query";
|
|
126
129
|
import { RESPONSE_CEILING_BYTES } from "../host/query/handlers";
|
|
127
130
|
import type { SdkFrame } from "../host/types";
|
|
@@ -1235,6 +1238,10 @@ export class PresentationArbiter {
|
|
|
1235
1238
|
interface SessionRuntime {
|
|
1236
1239
|
server: NotificationServer;
|
|
1237
1240
|
host: SessionSdkHost;
|
|
1241
|
+
imageUploads: PromptImageUploadStore | undefined;
|
|
1242
|
+
releaseAcceptedImage: (correlation: { commandId: string; turnId: string }) => void;
|
|
1243
|
+
releaseAcceptedImagesForRun: (owner: AgentTerminalOwnerContext) => void;
|
|
1244
|
+
releaseAcceptedImages: () => void;
|
|
1238
1245
|
getJoinedPromptCorrelations: (owner: AgentTerminalOwnerContext) => Array<{ commandId: string; turnId: string }>;
|
|
1239
1246
|
isPromptRunOwner: (correlation: { commandId: string; turnId: string }, owner: AgentTerminalOwnerContext) => boolean;
|
|
1240
1247
|
retireJoinedPromptOwners: () => void;
|
|
@@ -1421,6 +1428,12 @@ interface SessionRuntime {
|
|
|
1421
1428
|
string,
|
|
1422
1429
|
{ toolName: string; args?: unknown; pendingPhase?: "completed" | "failed" | "cancelled" }
|
|
1423
1430
|
>;
|
|
1431
|
+
/**
|
|
1432
|
+
* Running tool calls that reported an awaiting-input signal, keyed by tool call
|
|
1433
|
+
* id. An entry stays undelivered while notifications are inactive or policy is
|
|
1434
|
+
* provisional, and is dropped when the call ends; one notification per call.
|
|
1435
|
+
*/
|
|
1436
|
+
awaitingInput: Map<string, { toolName: string; kind: AwaitingInputKind; delivered: boolean }>;
|
|
1424
1437
|
/** Cancels the postmortem cleanup that emits `session_closed` on process teardown. */
|
|
1425
1438
|
cancelPostmortemCleanup: () => void;
|
|
1426
1439
|
/** Disposes side-turn resources when their owning logical session becomes unavailable. */
|
|
@@ -1431,6 +1444,48 @@ const SENSITIVE_MODEL_LABEL =
|
|
|
1431
1444
|
/(?:\b(?:https?|wss?):\/\/|\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b|\b(?:api[-_ ]?key|access[-_ ]?token|bearer|secret|password|account(?:\s*id)?|email|exception|stack trace)\b|\b(?:sk|pk|rk)-[A-Za-z0-9_-]{12,}\b)/i;
|
|
1432
1445
|
const TOOL_SUMMARY_MAX = 280;
|
|
1433
1446
|
|
|
1447
|
+
/** The `awaitingInput` signal a running bash update carries, validated at the extension boundary. */
|
|
1448
|
+
function readAwaitingInput(partialResult: unknown): { kind: AwaitingInputKind } | undefined {
|
|
1449
|
+
if (!partialResult || typeof partialResult !== "object") return undefined;
|
|
1450
|
+
const details = (partialResult as { details?: unknown }).details;
|
|
1451
|
+
if (!details || typeof details !== "object") return undefined;
|
|
1452
|
+
const signal = (details as { awaitingInput?: unknown }).awaitingInput;
|
|
1453
|
+
if (!signal || typeof signal !== "object") return undefined;
|
|
1454
|
+
const kind = (signal as { kind?: unknown }).kind;
|
|
1455
|
+
return kind === "browser_auth" || kind === "device_code" ? { kind } : undefined;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Deliver every undelivered awaiting-input notice once stable policy admits it.
|
|
1460
|
+
* The notice describes the present ("a command is waiting now"), so it goes out
|
|
1461
|
+
* on the live native leg only and never enters the replay ring: a client that
|
|
1462
|
+
* reconnects after the command exited must not receive it. The native server
|
|
1463
|
+
* still withholds it from connections without `awaiting_input_v1`.
|
|
1464
|
+
*/
|
|
1465
|
+
function deliverAwaitingInput(
|
|
1466
|
+
runtime: Pick<SessionRuntime, "server" | "awaitingInput" | "notificationsActive" | "policySuspended">,
|
|
1467
|
+
sessionId: string,
|
|
1468
|
+
): void {
|
|
1469
|
+
if (!runtime.notificationsActive || runtime.policySuspended) return;
|
|
1470
|
+
for (const [toolCallId, entry] of runtime.awaitingInput) {
|
|
1471
|
+
if (entry.delivered) continue;
|
|
1472
|
+
entry.delivered = true;
|
|
1473
|
+
try {
|
|
1474
|
+
runtime.server.pushFrame(
|
|
1475
|
+
JSON.stringify({
|
|
1476
|
+
type: "awaiting_input",
|
|
1477
|
+
sessionId,
|
|
1478
|
+
toolCallId,
|
|
1479
|
+
toolName: entry.toolName,
|
|
1480
|
+
kind: entry.kind,
|
|
1481
|
+
}),
|
|
1482
|
+
);
|
|
1483
|
+
} catch (e) {
|
|
1484
|
+
logger.warn(`notifications: awaiting_input failed: ${String(e)}`);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1434
1489
|
/** Stable projection of the tool-owned safe-display seam (never the full Tool surface). */
|
|
1435
1490
|
type SafeSummaryTool = Pick<Tool, "safeSummary" | "safeSummaryFields">;
|
|
1436
1491
|
|
|
@@ -2459,11 +2514,13 @@ function sdkQuerySurface(
|
|
|
2459
2514
|
}),
|
|
2460
2515
|
getRuntimeHost: () => SessionSdkHost | undefined = () => undefined,
|
|
2461
2516
|
primaryControlSurface: "cli" | "sdk" = "sdk",
|
|
2517
|
+
imageUploadsAvailable = false,
|
|
2462
2518
|
): SessionSurface {
|
|
2463
2519
|
return createSdkSurfaceFactory({
|
|
2464
2520
|
ctx,
|
|
2465
2521
|
id,
|
|
2466
2522
|
api,
|
|
2523
|
+
imageUploadsAvailable,
|
|
2467
2524
|
getInstalledDefinitions,
|
|
2468
2525
|
getLiveState,
|
|
2469
2526
|
configOverrides,
|
|
@@ -2612,6 +2669,19 @@ function sdkControlSurface(
|
|
|
2612
2669
|
pendingInteractive: Map<string, PendingInteractiveAsk>,
|
|
2613
2670
|
gatePresentations: PresentationArbiter | undefined,
|
|
2614
2671
|
api: ExtensionAPI,
|
|
2672
|
+
imageUploads: PromptImageUploadStore | undefined,
|
|
2673
|
+
imageUploadsAvailable: boolean,
|
|
2674
|
+
retainAcceptedImage: (
|
|
2675
|
+
correlation: { commandId: string; turnId: string },
|
|
2676
|
+
release: () => void,
|
|
2677
|
+
preflightSignal: AbortSignal,
|
|
2678
|
+
submissionSettled: Promise<void>,
|
|
2679
|
+
) => {
|
|
2680
|
+
accepted: () => void;
|
|
2681
|
+
promoted: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
2682
|
+
refused: () => boolean;
|
|
2683
|
+
},
|
|
2684
|
+
releaseAcceptedImage: (correlation: { commandId: string; turnId: string }) => void,
|
|
2615
2685
|
isBusy: () => boolean,
|
|
2616
2686
|
onPromptAccepted: (
|
|
2617
2687
|
correlation: { commandId: string; turnId: string },
|
|
@@ -2626,6 +2696,12 @@ function sdkControlSurface(
|
|
|
2626
2696
|
correlation: { commandId: string; turnId: string },
|
|
2627
2697
|
error: unknown,
|
|
2628
2698
|
) => void | Promise<void> = () => {},
|
|
2699
|
+
onQueuedRunStartFailed: (
|
|
2700
|
+
correlation: { commandId: string; turnId: string },
|
|
2701
|
+
failure: QueuedRunStartFailure,
|
|
2702
|
+
sdkRunToken: string | undefined,
|
|
2703
|
+
refuseImage: (() => boolean) | undefined,
|
|
2704
|
+
) => boolean,
|
|
2629
2705
|
onPromptAcceptFailed: (correlation: { commandId: string; turnId: string }) => void = () => {},
|
|
2630
2706
|
onPromptDiverted: (correlation: { commandId: string; turnId: string }) => void,
|
|
2631
2707
|
onPromptPromoted: (
|
|
@@ -2727,6 +2803,7 @@ function sdkControlSurface(
|
|
|
2727
2803
|
ctx,
|
|
2728
2804
|
id: ctx.sessionManager.getSessionId(),
|
|
2729
2805
|
api,
|
|
2806
|
+
imageUploadsAvailable,
|
|
2730
2807
|
}).policy;
|
|
2731
2808
|
const missingExpectedSessionAudits = new Set<"workflow.gate_answer" | "workflow.plan_approve">();
|
|
2732
2809
|
const auditMissingExpectedSessionId = (operation: "workflow.gate_answer" | "workflow.plan_approve") => {
|
|
@@ -2912,6 +2989,18 @@ function sdkControlSurface(
|
|
|
2912
2989
|
requesterConnectionId?: string,
|
|
2913
2990
|
clientRef?: string,
|
|
2914
2991
|
trackReconciliation = false,
|
|
2992
|
+
onCorrelation?: (
|
|
2993
|
+
correlation: { commandId: string; turnId: string },
|
|
2994
|
+
preflightSignal: AbortSignal,
|
|
2995
|
+
submissionSettled: Promise<void>,
|
|
2996
|
+
) =>
|
|
2997
|
+
| {
|
|
2998
|
+
accepted: () => void;
|
|
2999
|
+
promoted: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
3000
|
+
refused: () => boolean;
|
|
3001
|
+
}
|
|
3002
|
+
| undefined,
|
|
3003
|
+
resolveImages?: () => unknown,
|
|
2915
3004
|
) => {
|
|
2916
3005
|
const trimmedClientRef = typeof clientRef === "string" ? clientRef.trim() : undefined;
|
|
2917
3006
|
if (clientRef !== undefined && (!trimmedClientRef || trimmedClientRef.length > PROMPT_CLIENT_REF_MAX_LENGTH))
|
|
@@ -2957,6 +3046,8 @@ function sdkControlSurface(
|
|
|
2957
3046
|
code: "busy",
|
|
2958
3047
|
},
|
|
2959
3048
|
);
|
|
3049
|
+
// Staged bytes are one-shot: reserve reconciliation before consuming them.
|
|
3050
|
+
if (resolveImages) images = resolveImages();
|
|
2960
3051
|
} catch (error) {
|
|
2961
3052
|
admission.abandon();
|
|
2962
3053
|
if (trackReconciliation) releasePromptAdmission(trimmedClientRef);
|
|
@@ -2988,6 +3079,8 @@ function sdkControlSurface(
|
|
|
2988
3079
|
const submissionSettled = Promise.withResolvers<void>();
|
|
2989
3080
|
let cancellation: Promise<void> | undefined;
|
|
2990
3081
|
const correlation = { commandId, turnId };
|
|
3082
|
+
let queuedRunRefused = false;
|
|
3083
|
+
const imageLifecycle = onCorrelation?.(correlation, preflightController.signal, submissionSettled.promise);
|
|
2991
3084
|
const key = preflightKey(requesterConnectionId, correlation);
|
|
2992
3085
|
const settlePreflight = (result: PreflightTerminalResult) => {
|
|
2993
3086
|
if (preflightSettled) return;
|
|
@@ -3038,6 +3131,8 @@ function sdkControlSurface(
|
|
|
3038
3131
|
admission.accepted();
|
|
3039
3132
|
accepting = false;
|
|
3040
3133
|
accepted = true;
|
|
3134
|
+
// A late durable acceptance after direct abort cannot end image retention.
|
|
3135
|
+
if (!preflightController.signal.aborted) imageLifecycle?.accepted();
|
|
3041
3136
|
if (effectiveDeliverAs !== undefined) onPromptDiverted(correlation);
|
|
3042
3137
|
// #4743: an accepted run owns a durable terminal publication when it
|
|
3043
3138
|
// settles; teardown joins it via this latch (never-accepted preflights
|
|
@@ -3065,13 +3160,22 @@ function sdkControlSurface(
|
|
|
3065
3160
|
onPreflightAcceptCommit,
|
|
3066
3161
|
onPreflightAccepted,
|
|
3067
3162
|
onQueuedPromoted: promotion => {
|
|
3163
|
+
if (queuedRunRefused) return;
|
|
3164
|
+
if (promotion.startsOwnRun === true) imageLifecycle?.promoted(promotion);
|
|
3068
3165
|
admission.hooks.onQueuedPromoted(promotion);
|
|
3069
3166
|
onPromptPromoted(correlation, promotion, ctx.getActivePromptHandle());
|
|
3167
|
+
if (promotion.startsOwnRun !== true) imageLifecycle?.promoted(promotion);
|
|
3168
|
+
if (promotion.removed) releaseAcceptedImage(correlation);
|
|
3070
3169
|
},
|
|
3071
3170
|
onDispatchDisposition: disposition => {
|
|
3171
|
+
if (queuedRunRefused) return;
|
|
3072
3172
|
admission.hooks.onDispatchDisposition(disposition);
|
|
3073
3173
|
if (!disposition.startsOwnRun) onPromptDiverted(correlation);
|
|
3074
3174
|
},
|
|
3175
|
+
onQueuedRunStartFailed: failure => {
|
|
3176
|
+
if (queuedRunRefused) return;
|
|
3177
|
+
queuedRunRefused = onQueuedRunStartFailed(correlation, failure, sdkRunToken, imageLifecycle?.refused);
|
|
3178
|
+
},
|
|
3075
3179
|
preflightSignal: preflightController.signal,
|
|
3076
3180
|
...(sdkRunToken ? { sdkRunToken } : {}),
|
|
3077
3181
|
}),
|
|
@@ -3079,7 +3183,7 @@ function sdkControlSurface(
|
|
|
3079
3183
|
} catch (error) {
|
|
3080
3184
|
submissionSettled.resolve();
|
|
3081
3185
|
admission.abandon();
|
|
3082
|
-
if (accepted && !preflightController.signal.aborted)
|
|
3186
|
+
if (accepted && !queuedRunRefused && !preflightController.signal.aborted)
|
|
3083
3187
|
trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
|
|
3084
3188
|
else settlePreflight({ status: "rejected", error });
|
|
3085
3189
|
}
|
|
@@ -3101,7 +3205,7 @@ function sdkControlSurface(
|
|
|
3101
3205
|
},
|
|
3102
3206
|
error => {
|
|
3103
3207
|
admission.abandon();
|
|
3104
|
-
if (accepted && !preflightController.signal.aborted)
|
|
3208
|
+
if (accepted && !queuedRunRefused && !preflightController.signal.aborted)
|
|
3105
3209
|
trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
|
|
3106
3210
|
else settlePreflight({ status: "rejected", error });
|
|
3107
3211
|
submissionSettled.resolve();
|
|
@@ -3124,8 +3228,52 @@ function sdkControlSurface(
|
|
|
3124
3228
|
cancelPendingPreflights(): Promise<void>;
|
|
3125
3229
|
cancelPendingPreflightsForConnection(connectionId: string): Promise<void>;
|
|
3126
3230
|
} = {
|
|
3127
|
-
prompt: (text, images, clientRef) =>
|
|
3128
|
-
|
|
3231
|
+
prompt: async (text, images, clientRef, stagedImages) => {
|
|
3232
|
+
if (stagedImages !== undefined && images !== undefined)
|
|
3233
|
+
throw Object.assign(new Error("Direct and staged images cannot be mixed."), { code: "invalid_input" });
|
|
3234
|
+
const requesterConnectionId = controlRequesterContext.getStore();
|
|
3235
|
+
let reservation: { images: ImageContent[]; release: () => void } | undefined;
|
|
3236
|
+
let correlation: { commandId: string; turnId: string } | undefined;
|
|
3237
|
+
try {
|
|
3238
|
+
return await submitPrompt(
|
|
3239
|
+
text,
|
|
3240
|
+
images,
|
|
3241
|
+
false,
|
|
3242
|
+
undefined,
|
|
3243
|
+
true,
|
|
3244
|
+
requesterConnectionId,
|
|
3245
|
+
clientRef,
|
|
3246
|
+
true,
|
|
3247
|
+
(current, preflightSignal, submissionSettled) => {
|
|
3248
|
+
correlation = current;
|
|
3249
|
+
if (reservation)
|
|
3250
|
+
return retainAcceptedImage(current, reservation.release, preflightSignal, submissionSettled);
|
|
3251
|
+
},
|
|
3252
|
+
stagedImages === undefined
|
|
3253
|
+
? undefined
|
|
3254
|
+
: () => {
|
|
3255
|
+
if (!imageUploads)
|
|
3256
|
+
throw Object.assign(new Error("Staged images are unavailable."), {
|
|
3257
|
+
code: "image_upload_unavailable",
|
|
3258
|
+
});
|
|
3259
|
+
reservation = imageUploads.redeem(requesterConnectionId, stagedImages);
|
|
3260
|
+
return reservation.images;
|
|
3261
|
+
},
|
|
3262
|
+
);
|
|
3263
|
+
} catch (error) {
|
|
3264
|
+
if (correlation) releaseAcceptedImage(correlation);
|
|
3265
|
+
else reservation?.release();
|
|
3266
|
+
throw error;
|
|
3267
|
+
}
|
|
3268
|
+
},
|
|
3269
|
+
...(imageUploads
|
|
3270
|
+
? {
|
|
3271
|
+
imageBegin: (input: unknown) => imageUploads.begin(controlRequesterContext.getStore(), input),
|
|
3272
|
+
imageAppend: (input: unknown) => imageUploads.append(controlRequesterContext.getStore(), input),
|
|
3273
|
+
imageFinish: (input: unknown) => imageUploads.finish(controlRequesterContext.getStore(), input),
|
|
3274
|
+
imageDiscard: (input: unknown) => imageUploads.discard(controlRequesterContext.getStore(), input),
|
|
3275
|
+
}
|
|
3276
|
+
: {}),
|
|
3129
3277
|
steer: (text, clientRef) => sendSteer(text, clientRef),
|
|
3130
3278
|
followUp: text => submitPrompt(text, undefined, false, "followUp", false, controlRequesterContext.getStore()),
|
|
3131
3279
|
abort: async () => {
|
|
@@ -4348,6 +4496,8 @@ export function createNotificationsExtension(
|
|
|
4348
4496
|
* preflight outside the durable admission path (review thread P2).
|
|
4349
4497
|
*/
|
|
4350
4498
|
terminalAbortSeams?: {
|
|
4499
|
+
/** Definitive event-driven observation; never mutates the owning run. */
|
|
4500
|
+
waitForRunSettlement?: (handle: string) => Promise<RunSettlementProof>;
|
|
4351
4501
|
getTerminalRunOwnerForEvent?: (event: object) => AgentTerminalOwnerContext | undefined;
|
|
4352
4502
|
getRunOwnerDomain?: (handle: string) => RunCancellationDomain | undefined;
|
|
4353
4503
|
getTerminalTurnEpoch: () => number | undefined;
|
|
@@ -4637,6 +4787,7 @@ export function createNotificationsExtension(
|
|
|
4637
4787
|
if (reason === "session" && requestedRuntime) {
|
|
4638
4788
|
requestedRuntime.inboundFenced = true;
|
|
4639
4789
|
requestedRuntime.stopping = true;
|
|
4790
|
+
requestedRuntime.imageUploads?.close();
|
|
4640
4791
|
requestedRuntime.retireJoinedPromptOwners();
|
|
4641
4792
|
requestedRuntime.abortEphemeralTurns();
|
|
4642
4793
|
}
|
|
@@ -4815,6 +4966,7 @@ export function createNotificationsExtension(
|
|
|
4815
4966
|
cleanupRetries.set(id, rt);
|
|
4816
4967
|
throw new AggregateError(ownerReleaseFailures, `SDK notification runtime ${id} owner release failed.`);
|
|
4817
4968
|
}
|
|
4969
|
+
rt.releaseAcceptedImages();
|
|
4818
4970
|
if (cleanupRetries.get(id) === rt) cleanupRetries.delete(id);
|
|
4819
4971
|
return true;
|
|
4820
4972
|
}
|
|
@@ -5089,7 +5241,11 @@ export function createNotificationsExtension(
|
|
|
5089
5241
|
};
|
|
5090
5242
|
|
|
5091
5243
|
type HostConnectionIncarnation = { generation: number; closed: boolean };
|
|
5092
|
-
type HostCapabilityCacheEntry = {
|
|
5244
|
+
type HostCapabilityCacheEntry = {
|
|
5245
|
+
generation: number;
|
|
5246
|
+
capabilities: ReadonlySet<string>;
|
|
5247
|
+
nativeConfirmed: ReadonlySet<string>;
|
|
5248
|
+
};
|
|
5093
5249
|
const hostCapCache = new Map<string, HostCapabilityCacheEntry>();
|
|
5094
5250
|
const hostConnectionIncarnations = new Map<string, HostConnectionIncarnation>();
|
|
5095
5251
|
const hostAttachedConnections = new Set<string>();
|
|
@@ -5103,12 +5259,28 @@ export function createNotificationsExtension(
|
|
|
5103
5259
|
hostConnectionIncarnations.set(connectionId, incarnation);
|
|
5104
5260
|
return incarnation;
|
|
5105
5261
|
};
|
|
5106
|
-
const rememberHostCapabilities = (
|
|
5262
|
+
const rememberHostCapabilities = (
|
|
5263
|
+
connectionId: string,
|
|
5264
|
+
capabilities: readonly string[],
|
|
5265
|
+
source: "replay" | "native",
|
|
5266
|
+
): void => {
|
|
5107
5267
|
const incarnation = liveHostConnection(connectionId);
|
|
5108
5268
|
if (!incarnation) return;
|
|
5269
|
+
const previous = hostCapCache.get(connectionId);
|
|
5270
|
+
const merged =
|
|
5271
|
+
previous?.generation === incarnation.generation ? new Set(previous.capabilities) : new Set<string>();
|
|
5272
|
+
const nativeConfirmed =
|
|
5273
|
+
previous?.generation === incarnation.generation ? new Set(previous.nativeConfirmed) : new Set<string>();
|
|
5274
|
+
for (const capability of capabilities) merged.add(capability);
|
|
5275
|
+
// Replay claims enable stream routing immediately, but only native snapshots
|
|
5276
|
+
// can remove a socket from host demand. Merge delayed snapshots monotonically.
|
|
5277
|
+
if (source === "native") {
|
|
5278
|
+
for (const capability of capabilities) nativeConfirmed.add(capability);
|
|
5279
|
+
}
|
|
5109
5280
|
hostCapCache.set(connectionId, {
|
|
5110
5281
|
generation: incarnation.generation,
|
|
5111
|
-
capabilities:
|
|
5282
|
+
capabilities: merged,
|
|
5283
|
+
nativeConfirmed,
|
|
5112
5284
|
});
|
|
5113
5285
|
};
|
|
5114
5286
|
const liveHostCapabilities = (connectionId: string): ReadonlySet<string> | undefined => {
|
|
@@ -5130,6 +5302,17 @@ export function createNotificationsExtension(
|
|
|
5130
5302
|
hostCapCache.delete(connectionId);
|
|
5131
5303
|
hostAttachedConnections.delete(connectionId);
|
|
5132
5304
|
};
|
|
5305
|
+
const imageUploadsAvailable =
|
|
5306
|
+
typeof terminalAbortSeams?.waitForRunSettlement === "function" &&
|
|
5307
|
+
typeof terminalAbortSeams.getRunOwnerDomain === "function" &&
|
|
5308
|
+
typeof terminalAbortSeams.getTerminalRunOwnerForEvent === "function" &&
|
|
5309
|
+
typeof ctx.getActivePromptHandle === "function";
|
|
5310
|
+
const imageUploads = imageUploadsAvailable
|
|
5311
|
+
? new PromptImageUploadStore(connectionId => {
|
|
5312
|
+
const incarnation = hostConnectionIncarnations.get(connectionId);
|
|
5313
|
+
return incarnation !== undefined && !incarnation.closed;
|
|
5314
|
+
})
|
|
5315
|
+
: undefined;
|
|
5133
5316
|
|
|
5134
5317
|
const configOverrides = new Map<string, unknown>();
|
|
5135
5318
|
const configRevision = { current: 0 };
|
|
@@ -5145,6 +5328,115 @@ export function createNotificationsExtension(
|
|
|
5145
5328
|
const OWNED_SETTLEMENT_GRACE_MS = 500;
|
|
5146
5329
|
const promptSubmissionKey = (correlation: { commandId: string; turnId: string }) =>
|
|
5147
5330
|
`${correlation.commandId}:${correlation.turnId}`;
|
|
5331
|
+
type AcceptedImageReservation = {
|
|
5332
|
+
release: () => void;
|
|
5333
|
+
preflightSignal: AbortSignal;
|
|
5334
|
+
/** The producer can still retain ImageContent until settlement or exact run adoption. */
|
|
5335
|
+
submissionPending: boolean;
|
|
5336
|
+
/** A promoted queue entry needs its own run's proof before release authority transfers. */
|
|
5337
|
+
awaitingAdoption: boolean;
|
|
5338
|
+
accepted: boolean;
|
|
5339
|
+
releaseRequested: boolean;
|
|
5340
|
+
};
|
|
5341
|
+
const acceptedImages = new Map<string, AcceptedImageReservation>();
|
|
5342
|
+
// Absence means never consumed; a present undefined owner means execution
|
|
5343
|
+
// started without exact ownership evidence and must remain charged.
|
|
5344
|
+
const acceptedImageRunOwners = new Map<string, AgentTerminalOwnerContext | undefined>();
|
|
5345
|
+
const retiringImages = new Set<string>();
|
|
5346
|
+
const retireAcceptedImage = (key: string): void => {
|
|
5347
|
+
const reservation = acceptedImages.get(key);
|
|
5348
|
+
if (!reservation) return;
|
|
5349
|
+
if (reservation.submissionPending || reservation.awaitingAdoption) {
|
|
5350
|
+
reservation.releaseRequested = true;
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
if (retiringImages.has(key)) return;
|
|
5354
|
+
const discard = (): void => {
|
|
5355
|
+
if (acceptedImages.get(key) !== reservation) return;
|
|
5356
|
+
acceptedImages.delete(key);
|
|
5357
|
+
acceptedImageRunOwners.delete(key);
|
|
5358
|
+
retiringImages.delete(key);
|
|
5359
|
+
reservation.release();
|
|
5360
|
+
};
|
|
5361
|
+
const owner = acceptedImageRunOwners.get(key);
|
|
5362
|
+
if (!owner) {
|
|
5363
|
+
if (acceptedImageRunOwners.has(key)) {
|
|
5364
|
+
retiringImages.add(key);
|
|
5365
|
+
logger.warn("SDK image capacity retained for execution without an exact resource owner");
|
|
5366
|
+
return;
|
|
5367
|
+
}
|
|
5368
|
+
discard();
|
|
5369
|
+
return;
|
|
5370
|
+
}
|
|
5371
|
+
const waitForSettlement = terminalAbortSeams?.waitForRunSettlement;
|
|
5372
|
+
if (!waitForSettlement) return;
|
|
5373
|
+
retiringImages.add(key);
|
|
5374
|
+
retirePromptImageReservation(
|
|
5375
|
+
owner.resourceRunId,
|
|
5376
|
+
waitForSettlement,
|
|
5377
|
+
() => acceptedImages.get(key) === reservation,
|
|
5378
|
+
discard,
|
|
5379
|
+
);
|
|
5380
|
+
};
|
|
5381
|
+
const settleAcceptedImageSubmission = (key: string, reservation: AcceptedImageReservation): void => {
|
|
5382
|
+
if (acceptedImages.get(key) !== reservation || !reservation.submissionPending) return;
|
|
5383
|
+
reservation.submissionPending = false;
|
|
5384
|
+
if (reservation.releaseRequested) retireAcceptedImage(key);
|
|
5385
|
+
};
|
|
5386
|
+
const retainAcceptedImage = (
|
|
5387
|
+
correlation: { commandId: string; turnId: string },
|
|
5388
|
+
release: () => void,
|
|
5389
|
+
preflightSignal: AbortSignal,
|
|
5390
|
+
submissionSettled: Promise<void>,
|
|
5391
|
+
): {
|
|
5392
|
+
accepted: () => void;
|
|
5393
|
+
promoted: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
5394
|
+
refused: () => boolean;
|
|
5395
|
+
} => {
|
|
5396
|
+
const key = promptSubmissionKey(correlation);
|
|
5397
|
+
const reservation: AcceptedImageReservation = {
|
|
5398
|
+
release,
|
|
5399
|
+
preflightSignal,
|
|
5400
|
+
submissionPending: true,
|
|
5401
|
+
awaitingAdoption: false,
|
|
5402
|
+
accepted: false,
|
|
5403
|
+
releaseRequested: false,
|
|
5404
|
+
};
|
|
5405
|
+
acceptedImages.set(key, reservation);
|
|
5406
|
+
void submissionSettled.then(() => settleAcceptedImageSubmission(key, reservation));
|
|
5407
|
+
return {
|
|
5408
|
+
accepted: () => {
|
|
5409
|
+
reservation.accepted = true;
|
|
5410
|
+
if (reservation.preflightSignal.aborted) return;
|
|
5411
|
+
if (acceptedImageRunOwners.get(key) && !reservation.awaitingAdoption) {
|
|
5412
|
+
settleAcceptedImageSubmission(key, reservation);
|
|
5413
|
+
if (reservation.releaseRequested) retireAcceptedImage(key);
|
|
5414
|
+
}
|
|
5415
|
+
},
|
|
5416
|
+
promoted: promotion => {
|
|
5417
|
+
reservation.awaitingAdoption = promotion.startsOwnRun === true && promotion.removed !== true;
|
|
5418
|
+
if (!reservation.awaitingAdoption && reservation.releaseRequested) retireAcceptedImage(key);
|
|
5419
|
+
},
|
|
5420
|
+
refused: () => {
|
|
5421
|
+
if (acceptedImages.get(key) !== reservation || acceptedImageRunOwners.has(key)) return false;
|
|
5422
|
+
reservation.awaitingAdoption = false;
|
|
5423
|
+
retireAcceptedImage(key);
|
|
5424
|
+
return true;
|
|
5425
|
+
},
|
|
5426
|
+
};
|
|
5427
|
+
};
|
|
5428
|
+
const releaseAcceptedImage = (correlation: { commandId: string; turnId: string }): void =>
|
|
5429
|
+
retireAcceptedImage(promptSubmissionKey(correlation));
|
|
5430
|
+
const releaseAcceptedImagesForRun = (owner: AgentTerminalOwnerContext): void => {
|
|
5431
|
+
for (const [key, runOwner] of acceptedImageRunOwners) {
|
|
5432
|
+
if (!runOwner || runOwner.resourceRunId !== owner.resourceRunId || runOwner.domain !== owner.domain)
|
|
5433
|
+
continue;
|
|
5434
|
+
retireAcceptedImage(key);
|
|
5435
|
+
}
|
|
5436
|
+
};
|
|
5437
|
+
const releaseAcceptedImages = (): void => {
|
|
5438
|
+
for (const key of acceptedImages.keys()) retireAcceptedImage(key);
|
|
5439
|
+
};
|
|
5148
5440
|
type PromptLifecycleFrame =
|
|
5149
5441
|
| {
|
|
5150
5442
|
type: "agent_start" | "agent_end";
|
|
@@ -5850,7 +6142,7 @@ export function createNotificationsExtension(
|
|
|
5850
6142
|
);
|
|
5851
6143
|
};
|
|
5852
6144
|
const emitPromptEvent = (event: AgentSessionEvent) => {
|
|
5853
|
-
if (!runtime) return;
|
|
6145
|
+
if (!runtime || runtime.stopping || runtime.serverStopped || runtimes.get(id) !== runtime) return;
|
|
5854
6146
|
cleanupPromptRecords();
|
|
5855
6147
|
const handle = ctx.getActivePromptHandle();
|
|
5856
6148
|
const domain = handle ? terminalAbortSeams?.getRunOwnerDomain?.(handle) : undefined;
|
|
@@ -5863,12 +6155,14 @@ export function createNotificationsExtension(
|
|
|
5863
6155
|
)
|
|
5864
6156
|
)
|
|
5865
6157
|
correlations.unshift(root);
|
|
6158
|
+
const owners = new Set<string>();
|
|
5866
6159
|
for (const correlation of correlations) {
|
|
5867
6160
|
// Renewal is attributed exactly like the correlated delivery below.
|
|
5868
6161
|
renewPromptDeadline(correlation, event);
|
|
5869
6162
|
noteToolDispatchBoundary(correlation, event);
|
|
5870
6163
|
const submission = promptSubmissions.get(promptSubmissionKey(correlation));
|
|
5871
6164
|
if (!submission || submission.abandoned) continue;
|
|
6165
|
+
owners.add(submission.connectionId);
|
|
5872
6166
|
const frame = {
|
|
5873
6167
|
type: "event",
|
|
5874
6168
|
kind: event.type,
|
|
@@ -5881,6 +6175,25 @@ export function createNotificationsExtension(
|
|
|
5881
6175
|
}
|
|
5882
6176
|
deliverCorrelatedFrame(submission, frame);
|
|
5883
6177
|
}
|
|
6178
|
+
// Broker-created hosts use this bus, not the SDK-only extension. Observers
|
|
6179
|
+
// must opt into BOTH capabilities and receive no other client's correlation.
|
|
6180
|
+
// Keep cumulative partialResult snapshots and authoritative final result
|
|
6181
|
+
// intact, and off the durable lifecycle replay ring, like owner content.
|
|
6182
|
+
for (const connectionId of sdkRuntime?.connectionIdsWithCapabilities([
|
|
6183
|
+
TURN_STREAM_CAPABILITY,
|
|
6184
|
+
SESSION_HOST_OBSERVER_CAPABILITY,
|
|
6185
|
+
]) ?? []) {
|
|
6186
|
+
if (owners.has(connectionId)) continue;
|
|
6187
|
+
try {
|
|
6188
|
+
sdkRuntime?.sendFrameTo(connectionId, {
|
|
6189
|
+
type: "event",
|
|
6190
|
+
kind: event.type,
|
|
6191
|
+
payload: toAgentWireEventPayload(event),
|
|
6192
|
+
});
|
|
6193
|
+
} catch {
|
|
6194
|
+
// Content delivery is best-effort and must not interrupt execution.
|
|
6195
|
+
}
|
|
6196
|
+
}
|
|
5884
6197
|
};
|
|
5885
6198
|
/**
|
|
5886
6199
|
* Status of a registered deadline expiry attempt after an awaited
|
|
@@ -6200,6 +6513,7 @@ export function createNotificationsExtension(
|
|
|
6200
6513
|
logger.warn(`sdk: failed to cancel fenced preflight: ${String(error)}`),
|
|
6201
6514
|
);
|
|
6202
6515
|
fencedConnections.add(submission.connectionId);
|
|
6516
|
+
imageUploads?.disconnect(submission.connectionId);
|
|
6203
6517
|
host?.handleDisconnect(submission.connectionId);
|
|
6204
6518
|
for (const [key, owned] of promptSubmissions)
|
|
6205
6519
|
if (owned.connectionId === submission.connectionId) {
|
|
@@ -6232,6 +6546,21 @@ export function createNotificationsExtension(
|
|
|
6232
6546
|
submission.executionHandle = handle;
|
|
6233
6547
|
submission.preflightAbort = undefined;
|
|
6234
6548
|
}
|
|
6549
|
+
if (acceptedImages.has(key) && !acceptedImageRunOwners.has(key)) {
|
|
6550
|
+
acceptedImageRunOwners.set(key, undefined);
|
|
6551
|
+
const domain = handle ? terminalAbortSeams?.getRunOwnerDomain?.(handle) : undefined;
|
|
6552
|
+
if (handle && domain) acceptedImageRunOwners.set(key, { resourceRunId: handle, domain });
|
|
6553
|
+
}
|
|
6554
|
+
const imageReservation = acceptedImages.get(key);
|
|
6555
|
+
if (
|
|
6556
|
+
imageReservation?.accepted &&
|
|
6557
|
+
!imageReservation.preflightSignal.aborted &&
|
|
6558
|
+
acceptedImageRunOwners.get(key)
|
|
6559
|
+
) {
|
|
6560
|
+
imageReservation.awaitingAdoption = false;
|
|
6561
|
+
settleAcceptedImageSubmission(key, imageReservation);
|
|
6562
|
+
if (imageReservation.releaseRequested) retireAcceptedImage(key);
|
|
6563
|
+
}
|
|
6235
6564
|
};
|
|
6236
6565
|
const onPromptDiverted = (correlation: { commandId: string; turnId: string }) => {
|
|
6237
6566
|
const submission = promptSubmissions.get(promptSubmissionKey(correlation));
|
|
@@ -6419,6 +6748,7 @@ export function createNotificationsExtension(
|
|
|
6419
6748
|
return;
|
|
6420
6749
|
}
|
|
6421
6750
|
if (capture) capture.proof = proof;
|
|
6751
|
+
releaseAcceptedImage(correlation);
|
|
6422
6752
|
}
|
|
6423
6753
|
if (deadlineAttempt) {
|
|
6424
6754
|
// Durability BEFORE the durable terminal (#5583, #5623 review round 4).
|
|
@@ -6637,7 +6967,8 @@ export function createNotificationsExtension(
|
|
|
6637
6967
|
};
|
|
6638
6968
|
const recordPromptFailure = async (correlation: { commandId: string; turnId: string }, error: unknown) => {
|
|
6639
6969
|
const submission = promptSubmissions.get(promptSubmissionKey(correlation));
|
|
6640
|
-
if (!submission) return;
|
|
6970
|
+
if (!submission || submission.terminal || submission.failed) return;
|
|
6971
|
+
releaseAcceptedImage(correlation);
|
|
6641
6972
|
submission.failed = true;
|
|
6642
6973
|
removePendingPromptCorrelation(correlation);
|
|
6643
6974
|
if (
|
|
@@ -6647,6 +6978,29 @@ export function createNotificationsExtension(
|
|
|
6647
6978
|
runtime.activePromptCorrelation = undefined;
|
|
6648
6979
|
await emitPromptFailure(correlation, error);
|
|
6649
6980
|
};
|
|
6981
|
+
const recordQueuedRunStartFailure = (
|
|
6982
|
+
correlation: { commandId: string; turnId: string },
|
|
6983
|
+
failure: QueuedRunStartFailure,
|
|
6984
|
+
sdkRunToken: string | undefined,
|
|
6985
|
+
refuseImage: (() => boolean) | undefined,
|
|
6986
|
+
): boolean => {
|
|
6987
|
+
const key = promptSubmissionKey(correlation);
|
|
6988
|
+
const submission = promptSubmissions.get(key);
|
|
6989
|
+
// A present undefined owner is already-started execution, not no-start evidence.
|
|
6990
|
+
if (
|
|
6991
|
+
failure.disposition !== "not-started" ||
|
|
6992
|
+
(submission && submission.sdkRunToken !== sdkRunToken) ||
|
|
6993
|
+
submission?.executionHandle !== undefined ||
|
|
6994
|
+
acceptedImageRunOwners.has(key)
|
|
6995
|
+
)
|
|
6996
|
+
return false;
|
|
6997
|
+
// Reservation proof outlives the mutable receipt. The captured lifecycle
|
|
6998
|
+
// checks reservation identity and keeps the pending producer fenced.
|
|
6999
|
+
if (refuseImage ? !refuseImage() : !submission) return false;
|
|
7000
|
+
if (submission && !submission.terminal && !submission.failed)
|
|
7001
|
+
trackReconciliationProducer(recordPromptFailure(correlation, failure.error));
|
|
7002
|
+
return true;
|
|
7003
|
+
};
|
|
6650
7004
|
const acknowledgePrompt = (connectionId: string, correlation: { commandId: string; turnId: string }) => {
|
|
6651
7005
|
const key = promptSubmissionKey(correlation);
|
|
6652
7006
|
const submission = promptSubmissions.get(key);
|
|
@@ -6679,6 +7033,7 @@ export function createNotificationsExtension(
|
|
|
6679
7033
|
selector => kindReconciliation.lookupSteer(selector),
|
|
6680
7034
|
() => host,
|
|
6681
7035
|
options.primaryControlSurface ?? "sdk",
|
|
7036
|
+
imageUploadsAvailable,
|
|
6682
7037
|
),
|
|
6683
7038
|
id,
|
|
6684
7039
|
revisions,
|
|
@@ -6689,12 +7044,17 @@ export function createNotificationsExtension(
|
|
|
6689
7044
|
pendingInteractive,
|
|
6690
7045
|
gatePresentations,
|
|
6691
7046
|
api,
|
|
7047
|
+
imageUploads,
|
|
7048
|
+
imageUploadsAvailable,
|
|
7049
|
+
retainAcceptedImage,
|
|
7050
|
+
releaseAcceptedImage,
|
|
6692
7051
|
() =>
|
|
6693
7052
|
runtime?.busy === true ||
|
|
6694
7053
|
pendingPromptCorrelations.length > 0 ||
|
|
6695
7054
|
pendingPromptCorrelationsBySdkRunToken.size > 0,
|
|
6696
7055
|
recordPromptAccepted,
|
|
6697
7056
|
recordPromptFailure,
|
|
7057
|
+
recordQueuedRunStartFailure,
|
|
6698
7058
|
discardPromptAcceptance,
|
|
6699
7059
|
onPromptDiverted,
|
|
6700
7060
|
onPromptPromoted,
|
|
@@ -6758,6 +7118,7 @@ export function createNotificationsExtension(
|
|
|
6758
7118
|
await terminalizePrompt({ commandId, turnId }, cancellationOutcome, {
|
|
6759
7119
|
fence: hasExecutionHandle || !submission.preflightAbort,
|
|
6760
7120
|
});
|
|
7121
|
+
if (!hasExecutionHandle && submission.preflightAbort) releaseAcceptedImage({ commandId, turnId });
|
|
6761
7122
|
}
|
|
6762
7123
|
}
|
|
6763
7124
|
return { aborted: true, disposition: "cancelled" as const };
|
|
@@ -8188,6 +8549,10 @@ export function createNotificationsExtension(
|
|
|
8188
8549
|
runtime = {
|
|
8189
8550
|
server,
|
|
8190
8551
|
host,
|
|
8552
|
+
imageUploads,
|
|
8553
|
+
releaseAcceptedImage,
|
|
8554
|
+
releaseAcceptedImagesForRun,
|
|
8555
|
+
releaseAcceptedImages,
|
|
8191
8556
|
getJoinedPromptCorrelations,
|
|
8192
8557
|
isPromptRunOwner: (correlation, owner) => {
|
|
8193
8558
|
const handle = promptSubmissions.get(promptSubmissionKey(correlation))?.executionHandle;
|
|
@@ -8327,6 +8692,7 @@ export function createNotificationsExtension(
|
|
|
8327
8692
|
string,
|
|
8328
8693
|
{ toolName: string; args?: unknown; pendingPhase?: "completed" | "failed" | "cancelled" }
|
|
8329
8694
|
>(),
|
|
8695
|
+
awaitingInput: new Map(),
|
|
8330
8696
|
deferredInboundControls: [],
|
|
8331
8697
|
};
|
|
8332
8698
|
const initializedRuntime = runtime;
|
|
@@ -8446,7 +8812,18 @@ export function createNotificationsExtension(
|
|
|
8446
8812
|
sendEndpointStale(inbound.connectionId, typedFrame);
|
|
8447
8813
|
return;
|
|
8448
8814
|
}
|
|
8815
|
+
if (typedFrame.type === "control_request" && !liveHostConnection(inbound.connectionId)) {
|
|
8816
|
+
sendEndpointStale(inbound.connectionId, typedFrame);
|
|
8817
|
+
return;
|
|
8818
|
+
}
|
|
8449
8819
|
if (typedFrame.type === "event_replay") {
|
|
8820
|
+
const capabilities = typedFrame.capabilities;
|
|
8821
|
+
if (
|
|
8822
|
+
Array.isArray(capabilities) &&
|
|
8823
|
+
capabilities.every((capability: unknown) => typeof capability === "string")
|
|
8824
|
+
) {
|
|
8825
|
+
rememberHostCapabilities(inbound.connectionId, capabilities as string[], "replay");
|
|
8826
|
+
}
|
|
8450
8827
|
if (liveHostConnection(inbound.connectionId)) hostAttachedConnections.add(inbound.connectionId);
|
|
8451
8828
|
}
|
|
8452
8829
|
if (typedFrame.type === "ephemeral_turn" || typedFrame.type === "ephemeral_turn_cancel") return;
|
|
@@ -8491,7 +8868,7 @@ export function createNotificationsExtension(
|
|
|
8491
8868
|
const negotiated = tuple?.[1] ?? capabilities;
|
|
8492
8869
|
if (typeof id !== "string" || !Array.isArray(negotiated)) return;
|
|
8493
8870
|
const normalized = negotiated.filter((capability): capability is string => typeof capability === "string");
|
|
8494
|
-
rememberHostCapabilities(id, normalized);
|
|
8871
|
+
rememberHostCapabilities(id, normalized, "native");
|
|
8495
8872
|
if (liveHostCapabilities(id) === undefined) return;
|
|
8496
8873
|
hostAttachedConnections.add(id);
|
|
8497
8874
|
negotiatedCapabilitiesHandler?.(id, normalized);
|
|
@@ -8499,6 +8876,7 @@ export function createNotificationsExtension(
|
|
|
8499
8876
|
server.onConnectionClose((_err, connectionId) => {
|
|
8500
8877
|
if (!connectionId) return;
|
|
8501
8878
|
closeHostConnection(connectionId);
|
|
8879
|
+
imageUploads?.disconnect(connectionId);
|
|
8502
8880
|
connectionCloseHandler?.(connectionId);
|
|
8503
8881
|
void controlSurface
|
|
8504
8882
|
.cancelPendingPreflightsForConnection(connectionId)
|
|
@@ -9034,8 +9412,10 @@ export function createNotificationsExtension(
|
|
|
9034
9412
|
attachedClients: () => server.clientCount(),
|
|
9035
9413
|
observerClients: () => {
|
|
9036
9414
|
if (workLease.isHeld()) return 0;
|
|
9037
|
-
const observers = [...hostCapCache.
|
|
9038
|
-
|
|
9415
|
+
const observers = [...hostCapCache.entries()].filter(
|
|
9416
|
+
([connectionId, entry]) =>
|
|
9417
|
+
liveHostCapabilities(connectionId)?.has(SESSION_HOST_OBSERVER_CAPABILITY) &&
|
|
9418
|
+
entry.nativeConfirmed.has(SESSION_HOST_OBSERVER_CAPABILITY),
|
|
9039
9419
|
).length;
|
|
9040
9420
|
return Math.min(server.clientCount(), observers);
|
|
9041
9421
|
},
|
|
@@ -9490,6 +9870,7 @@ export function createNotificationsExtension(
|
|
|
9490
9870
|
runtime.gatePresentations?.activateDeferred(runtime.workflowGatePublicationEpoch);
|
|
9491
9871
|
flushPendingFinal(runtime, runtime.id);
|
|
9492
9872
|
flushPendingSettled(runtime, runtime.id);
|
|
9873
|
+
deliverAwaitingInput(runtime, runtime.id);
|
|
9493
9874
|
for (const processControl of runtime.deferredInboundControls.splice(0)) processControl();
|
|
9494
9875
|
},
|
|
9495
9876
|
reproveOwnership: async binding => {
|
|
@@ -10100,6 +10481,7 @@ export function createNotificationsExtension(
|
|
|
10100
10481
|
// The Agent's terminal owner is independent of delivery correlation. Never
|
|
10101
10482
|
// borrow a successor's run handle to settle a predecessor's SDK prompts.
|
|
10102
10483
|
const terminalOwner = terminalAbortSeams?.getTerminalRunOwnerForEvent?.(event);
|
|
10484
|
+
if (terminalOwner) rt.releaseAcceptedImagesForRun(terminalOwner);
|
|
10103
10485
|
const rootCorrelation = rt.activePromptCorrelation;
|
|
10104
10486
|
const correlations = terminalOwner ? rt.getJoinedPromptCorrelations(terminalOwner) : [];
|
|
10105
10487
|
if (
|
|
@@ -10116,6 +10498,7 @@ export function createNotificationsExtension(
|
|
|
10116
10498
|
// subsequent durable terminal claim fails. A fatal transport closure
|
|
10117
10499
|
// clears attribution, so an unrelated later agent_end cannot settle a
|
|
10118
10500
|
// predecessor's prompt correlation.
|
|
10501
|
+
if (terminalOwner) rt.releaseAcceptedImage(correlation);
|
|
10119
10502
|
const assistants = (Array.isArray(event.messages) ? [...event.messages].reverse() : []).filter(
|
|
10120
10503
|
message => message && typeof message === "object" && (message as { role?: unknown }).role === "assistant",
|
|
10121
10504
|
) as Array<{ stopReason?: unknown; errorKind?: unknown; errorCode?: unknown }>;
|
|
@@ -10391,8 +10774,18 @@ export function createNotificationsExtension(
|
|
|
10391
10774
|
});
|
|
10392
10775
|
|
|
10393
10776
|
api.on("tool_execution_update", (event, ctx) => {
|
|
10394
|
-
const
|
|
10777
|
+
const id = sessionId(ctx);
|
|
10778
|
+
const rt = runtimes.get(id);
|
|
10395
10779
|
rt?.emitPromptEvent(event);
|
|
10780
|
+
if (!rt) return;
|
|
10781
|
+
const awaitingInput = readAwaitingInput(event.partialResult);
|
|
10782
|
+
if (!awaitingInput || rt.awaitingInput.has(event.toolCallId)) return;
|
|
10783
|
+
// Notify-only and content-free (prompt kind only, never the URL or code), so
|
|
10784
|
+
// it is delivered under redaction exactly like the idle marker. While policy
|
|
10785
|
+
// is provisional it stays pending; activation delivers it if the call is
|
|
10786
|
+
// still running.
|
|
10787
|
+
rt.awaitingInput.set(event.toolCallId, { toolName: event.toolName, kind: awaitingInput.kind, delivered: false });
|
|
10788
|
+
deliverAwaitingInput(rt, id);
|
|
10396
10789
|
});
|
|
10397
10790
|
|
|
10398
10791
|
api.on("tool_execution_end", (event, ctx) => {
|
|
@@ -10400,6 +10793,7 @@ export function createNotificationsExtension(
|
|
|
10400
10793
|
const rt = runtimes.get(id);
|
|
10401
10794
|
if (!rt) return;
|
|
10402
10795
|
rt.emitPromptEvent(event);
|
|
10796
|
+
rt.awaitingInput.delete(event.toolCallId);
|
|
10403
10797
|
const inFlight = rt.inFlightTools.get(event.toolCallId);
|
|
10404
10798
|
if (!inFlight) return;
|
|
10405
10799
|
if (!rt.notificationsActive) {
|