@scotthuang/agent-knock-knock 0.13.2 → 0.13.4
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 +36 -0
- package/README.md +27 -6
- package/dist/src/callback-outbox-policy.js +27 -6
- package/dist/src/callback-outbox-policy.js.map +1 -1
- package/dist/src/callback-outbox-service.d.ts +4 -30
- package/dist/src/callback-outbox-service.js +21 -154
- package/dist/src/callback-outbox-service.js.map +1 -1
- package/dist/src/callback-outbox-settlement.js +13 -8
- package/dist/src/callback-outbox-settlement.js.map +1 -1
- package/dist/src/callback-route-service.d.ts +46 -0
- package/dist/src/callback-route-service.js +155 -0
- package/dist/src/callback-route-service.js.map +1 -0
- package/dist/src/claude-terminal-agent-adapter.js +4 -0
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli-core.js +9 -1
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-process-incarnation.d.ts +8 -0
- package/dist/src/codex-process-incarnation.js +36 -0
- package/dist/src/codex-process-incarnation.js.map +1 -0
- package/dist/src/codex-terminal-agent-adapter.js +4 -0
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/deferred-foreground-conflict-policy.d.ts +22 -0
- package/dist/src/deferred-foreground-conflict-policy.js +52 -0
- package/dist/src/deferred-foreground-conflict-policy.js.map +1 -0
- package/dist/src/durable-notification-kernel.d.ts +94 -0
- package/dist/src/durable-notification-kernel.js +68 -0
- package/dist/src/durable-notification-kernel.js.map +1 -0
- package/dist/src/herdr-socket-transport.d.ts +43 -0
- package/dist/src/herdr-socket-transport.js +170 -0
- package/dist/src/herdr-socket-transport.js.map +1 -0
- package/dist/src/herdr-terminal-control-provider.d.ts +2 -45
- package/dist/src/herdr-terminal-control-provider.js +8 -173
- package/dist/src/herdr-terminal-control-provider.js.map +1 -1
- package/dist/src/host-adapter-capabilities.d.ts +35 -0
- package/dist/src/host-adapter-capabilities.js +146 -0
- package/dist/src/host-adapter-capabilities.js.map +1 -0
- package/dist/src/host-adapter.d.ts +9 -3
- package/dist/src/host-adapter.js +33 -9
- package/dist/src/host-adapter.js.map +1 -1
- package/dist/src/host-bridge-tools.d.ts +3 -4
- package/dist/src/host-bridge-tools.js +49 -92
- package/dist/src/host-bridge-tools.js.map +1 -1
- package/dist/src/host-bridge.js +5 -5
- package/dist/src/host-bridge.js.map +1 -1
- package/dist/src/host-monitor-reconciliation.d.ts +6 -0
- package/dist/src/host-monitor-reconciliation.js +96 -0
- package/dist/src/host-monitor-reconciliation.js.map +1 -0
- package/dist/src/managed-session.js +2 -18
- package/dist/src/managed-session.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +13 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.js +635 -3
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/openclaw-list-projection.d.ts +2 -0
- package/dist/src/openclaw-list-projection.js +3 -0
- package/dist/src/openclaw-list-projection.js.map +1 -0
- package/dist/src/openclaw-model-facing-field-policy.d.ts +2 -0
- package/dist/src/openclaw-model-facing-field-policy.js +3 -0
- package/dist/src/openclaw-model-facing-field-policy.js.map +1 -0
- package/dist/src/openclaw-plugin-command-adapter.d.ts +6 -29
- package/dist/src/openclaw-plugin-command-adapter.js +7 -2370
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +2 -121
- package/dist/src/openclaw-plugin-helpers.js +2 -1164
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +2 -510
- package/dist/src/openclaw-plugin-schemas.js +2 -519
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-plugin-supervisor.d.ts +4 -5
- package/dist/src/openclaw-plugin-supervisor.js +5 -94
- package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
- package/dist/src/openclaw-plugin-value-helpers.d.ts +2 -0
- package/dist/src/openclaw-plugin-value-helpers.js +3 -0
- package/dist/src/openclaw-plugin-value-helpers.js.map +1 -0
- package/dist/src/openclaw-private-authority-offers.d.ts +4 -39
- package/dist/src/openclaw-private-authority-offers.js +3 -258
- package/dist/src/openclaw-private-authority-offers.js.map +1 -1
- package/dist/src/semantic-private-authority-offers.d.ts +41 -0
- package/dist/src/semantic-private-authority-offers.js +261 -0
- package/dist/src/semantic-private-authority-offers.js.map +1 -0
- package/dist/src/semantic-tool-arguments.d.ts +9 -0
- package/dist/src/semantic-tool-arguments.js +64 -0
- package/dist/src/semantic-tool-arguments.js.map +1 -0
- package/dist/src/semantic-tool-catalog.d.ts +48 -0
- package/dist/src/semantic-tool-catalog.js +105 -0
- package/dist/src/semantic-tool-catalog.js.map +1 -0
- package/dist/src/semantic-tool-command-helpers.d.ts +145 -0
- package/dist/src/semantic-tool-command-helpers.js +1272 -0
- package/dist/src/semantic-tool-command-helpers.js.map +1 -0
- package/dist/src/semantic-tool-list-projection.d.ts +10 -0
- package/dist/src/semantic-tool-list-projection.js +497 -0
- package/dist/src/semantic-tool-list-projection.js.map +1 -0
- package/dist/src/semantic-tool-model-facing-field-policy.d.ts +3 -0
- package/dist/src/semantic-tool-model-facing-field-policy.js +66 -0
- package/dist/src/semantic-tool-model-facing-field-policy.js.map +1 -0
- package/dist/src/semantic-tool-presentation.d.ts +37 -0
- package/dist/src/semantic-tool-presentation.js +680 -0
- package/dist/src/semantic-tool-presentation.js.map +1 -0
- package/dist/src/semantic-tool-private-authority.d.ts +39 -0
- package/dist/src/semantic-tool-private-authority.js +602 -0
- package/dist/src/semantic-tool-private-authority.js.map +1 -0
- package/dist/src/semantic-tool-relay.d.ts +16 -0
- package/dist/src/semantic-tool-relay.js +175 -0
- package/dist/src/semantic-tool-relay.js.map +1 -0
- package/dist/src/semantic-tool-runtime.d.ts +10 -0
- package/dist/src/semantic-tool-runtime.js +1157 -0
- package/dist/src/semantic-tool-runtime.js.map +1 -0
- package/dist/src/semantic-tool-schemas.d.ts +563 -0
- package/dist/src/semantic-tool-schemas.js +573 -0
- package/dist/src/semantic-tool-schemas.js.map +1 -0
- package/dist/src/semantic-tool-value-helpers.d.ts +5 -0
- package/dist/src/semantic-tool-value-helpers.js +27 -0
- package/dist/src/semantic-tool-value-helpers.js.map +1 -0
- package/dist/src/store-event-presentation.d.ts +17 -0
- package/dist/src/store-event-presentation.js +33 -0
- package/dist/src/store-event-presentation.js.map +1 -0
- package/dist/src/store.d.ts +3 -16
- package/dist/src/store.js +2 -30
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +3 -17
- package/dist/src/terminal-action-projection.js +13 -41
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +7 -0
- package/dist/src/terminal-agent-adapter.js +8 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +35 -332
- package/dist/src/terminal-agent-bridge.js +293 -2798
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-approval-cli-adapter.d.ts +17 -0
- package/dist/src/terminal-approval-cli-adapter.js +921 -0
- package/dist/src/terminal-approval-cli-adapter.js.map +1 -0
- package/dist/src/terminal-command-cli-adapter.d.ts +2 -429
- package/dist/src/terminal-command-cli-adapter.js +344 -6061
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-command-cli-ports.d.ts +443 -0
- package/dist/src/terminal-command-cli-ports.js +2 -0
- package/dist/src/terminal-command-cli-ports.js.map +1 -0
- package/dist/src/terminal-command-dispatch-preparation.d.ts +81 -0
- package/dist/src/terminal-command-dispatch-preparation.js +209 -0
- package/dist/src/terminal-command-dispatch-preparation.js.map +1 -0
- package/dist/src/terminal-command-dispatch-transport.d.ts +35 -0
- package/dist/src/terminal-command-dispatch-transport.js +859 -0
- package/dist/src/terminal-command-dispatch-transport.js.map +1 -0
- package/dist/src/terminal-command-foreground-proof.d.ts +32 -0
- package/dist/src/terminal-command-foreground-proof.js +72 -0
- package/dist/src/terminal-command-foreground-proof.js.map +1 -0
- package/dist/src/terminal-command-send-preflight.d.ts +41 -0
- package/dist/src/terminal-command-send-preflight.js +86 -0
- package/dist/src/terminal-command-send-preflight.js.map +1 -0
- package/dist/src/terminal-composer-classifier.d.ts +55 -0
- package/dist/src/terminal-composer-classifier.js +638 -0
- package/dist/src/terminal-composer-classifier.js.map +1 -0
- package/dist/src/terminal-control-ref.d.ts +23 -0
- package/dist/src/terminal-control-ref.js +32 -0
- package/dist/src/terminal-control-ref.js.map +1 -1
- package/dist/src/terminal-dispatch-recovery-authority.d.ts +56 -0
- package/dist/src/terminal-dispatch-recovery-authority.js +251 -0
- package/dist/src/terminal-dispatch-recovery-authority.js.map +1 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +1 -9
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js +5 -240
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
- package/dist/src/terminal-handoff-cli-adapter.d.ts +1 -11
- package/dist/src/terminal-handoff-cli-adapter.js +2 -39
- package/dist/src/terminal-handoff-cli-adapter.js.map +1 -1
- package/dist/src/terminal-human-explicit-send-cli-adapter.d.ts +25 -0
- package/dist/src/terminal-human-explicit-send-cli-adapter.js +903 -0
- package/dist/src/terminal-human-explicit-send-cli-adapter.js.map +1 -0
- package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -6
- package/dist/src/terminal-identity-authority-cli-adapter.js +3 -33
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-cli-adapter.js +209 -210
- package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-core.d.ts +0 -2
- package/dist/src/terminal-interaction-core.js +4 -3
- package/dist/src/terminal-interaction-core.js.map +1 -1
- package/dist/src/terminal-interaction-protocol.d.ts +21 -0
- package/dist/src/terminal-interaction-protocol.js +74 -0
- package/dist/src/terminal-interaction-protocol.js.map +1 -1
- package/dist/src/terminal-interaction-response-bridge.d.ts +126 -0
- package/dist/src/terminal-interaction-response-bridge.js +579 -0
- package/dist/src/terminal-interaction-response-bridge.js.map +1 -0
- package/dist/src/terminal-interaction-response-transaction.d.ts +44 -0
- package/dist/src/terminal-interaction-response-transaction.js +56 -0
- package/dist/src/terminal-interaction-response-transaction.js.map +1 -0
- package/dist/src/terminal-list-action-policy.d.ts +43 -0
- package/dist/src/terminal-list-action-policy.js +164 -0
- package/dist/src/terminal-list-action-policy.js.map +1 -0
- package/dist/src/terminal-list-cli-adapter.d.ts +6 -36
- package/dist/src/terminal-list-cli-adapter.js +314 -1711
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-facts.d.ts +129 -0
- package/dist/src/terminal-list-facts.js +179 -0
- package/dist/src/terminal-list-facts.js.map +1 -0
- package/dist/src/terminal-list-inventory.d.ts +59 -0
- package/dist/src/terminal-list-inventory.js +194 -0
- package/dist/src/terminal-list-inventory.js.map +1 -0
- package/dist/src/terminal-list-ownership-service.d.ts +118 -0
- package/dist/src/terminal-list-ownership-service.js +1003 -0
- package/dist/src/terminal-list-ownership-service.js.map +1 -0
- package/dist/src/terminal-list-projection-service.d.ts +49 -0
- package/dist/src/terminal-list-projection-service.js +407 -0
- package/dist/src/terminal-list-projection-service.js.map +1 -0
- package/dist/src/terminal-list-renderer.d.ts +12 -11
- package/dist/src/terminal-list-renderer.js +237 -104
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-managed-send-cli-adapter.d.ts +33 -0
- package/dist/src/terminal-managed-send-cli-adapter.js +994 -0
- package/dist/src/terminal-managed-send-cli-adapter.js.map +1 -0
- package/dist/src/terminal-managed-turn-list-action-policy.d.ts +47 -0
- package/dist/src/terminal-managed-turn-list-action-policy.js +64 -0
- package/dist/src/terminal-managed-turn-list-action-policy.js.map +1 -0
- package/dist/src/terminal-model-control-action-policy.d.ts +60 -0
- package/dist/src/terminal-model-control-action-policy.js +96 -0
- package/dist/src/terminal-model-control-action-policy.js.map +1 -0
- package/dist/src/terminal-model-control-availability.d.ts +68 -0
- package/dist/src/terminal-model-control-availability.js +157 -0
- package/dist/src/terminal-model-control-availability.js.map +1 -0
- package/dist/src/terminal-model-control-bridge.d.ts +48 -0
- package/dist/src/terminal-model-control-bridge.js +259 -0
- package/dist/src/terminal-model-control-bridge.js.map +1 -0
- package/dist/src/terminal-model-control-profile.d.ts +55 -0
- package/dist/src/terminal-model-control-profile.js +137 -0
- package/dist/src/terminal-model-control-profile.js.map +1 -0
- package/dist/src/terminal-model-control-subject.d.ts +34 -0
- package/dist/src/terminal-model-control-subject.js +58 -0
- package/dist/src/terminal-model-control-subject.js.map +1 -0
- package/dist/src/terminal-model-control-transaction.d.ts +19 -0
- package/dist/src/terminal-model-control-transaction.js +39 -0
- package/dist/src/terminal-model-control-transaction.js.map +1 -0
- package/dist/src/terminal-model-control.d.ts +331 -0
- package/dist/src/terminal-model-control.js +1686 -0
- package/dist/src/terminal-model-control.js.map +1 -0
- package/dist/src/terminal-monitor-application-service.js +50 -17
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-native-inspection-bridge.d.ts +211 -0
- package/dist/src/terminal-native-inspection-bridge.js +1127 -0
- package/dist/src/terminal-native-inspection-bridge.js.map +1 -0
- package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -0
- package/dist/src/terminal-runtime-cli-adapter.js +59 -17
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
- package/dist/src/terminal-submission-retry-application.d.ts +24 -0
- package/dist/src/terminal-submission-retry-application.js +402 -0
- package/dist/src/terminal-submission-retry-application.js.map +1 -0
- package/dist/src/terminal-submission-retry-ports.d.ts +30 -0
- package/dist/src/terminal-submission-retry-ports.js +2 -0
- package/dist/src/terminal-submission-retry-ports.js.map +1 -0
- package/dist/src/terminal-submission-retry-reconciliation.d.ts +236 -0
- package/dist/src/terminal-submission-retry-reconciliation.js +1130 -0
- package/dist/src/terminal-submission-retry-reconciliation.js.map +1 -0
- package/dist/src/terminal-submission-retry-transport.d.ts +58 -0
- package/dist/src/terminal-submission-retry-transport.js +675 -0
- package/dist/src/terminal-submission-retry-transport.js.map +1 -0
- package/dist/src/terminal-text-submission-bridge.d.ts +200 -0
- package/dist/src/terminal-text-submission-bridge.js +632 -0
- package/dist/src/terminal-text-submission-bridge.js.map +1 -0
- package/dist/src/terminal-user-explicit-send-policy.d.ts +29 -0
- package/dist/src/terminal-user-explicit-send-policy.js +46 -0
- package/dist/src/terminal-user-explicit-send-policy.js.map +1 -0
- package/dist/src/terminal-watch-cli-adapter.js +91 -143
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-codec.d.ts +9 -0
- package/dist/src/terminal-watch-codec.js +1051 -0
- package/dist/src/terminal-watch-codec.js.map +1 -0
- package/dist/src/terminal-watch-interaction-response-store.d.ts +19 -0
- package/dist/src/terminal-watch-interaction-response-store.js +87 -0
- package/dist/src/terminal-watch-interaction-response-store.js.map +1 -0
- package/dist/src/terminal-watch-record.d.ts +319 -0
- package/dist/src/terminal-watch-record.js +742 -0
- package/dist/src/terminal-watch-record.js.map +1 -0
- package/dist/src/terminal-watch-service.js +48 -27
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +3 -279
- package/dist/src/terminal-watch-store.js +5 -1779
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/quickstart-herdr.md +1 -1
- package/docs/quickstart-tmux.md +27 -2
- package/openclaw.plugin.json +13 -1
- package/package.json +7 -2
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +54 -11
|
@@ -1,351 +1,18 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { formatTerminalConversationId, parseTerminalConversationId, terminalControlCapabilitiesForAdapter, terminalApprovalActionForDecision, terminalApprovalChoices } from "./terminal-agent-adapter.js";
|
|
3
3
|
import { enrichActiveProcessesWithTerminalControl, TerminalControlInputNotSentError } from "./terminal-control-provider.js";
|
|
4
|
-
import { hasCanonicalTerminalEndpoint, sameTerminalControlIncarnation,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// an exact frame has been observed, always allow the required stable/final
|
|
17
|
-
// captures to complete instead of letting the first I/O consume their budget.
|
|
18
|
-
const CODEX_MULTILINE_SETTLE_TIMEOUT_MS = 5_000;
|
|
19
|
-
const CODEX_MULTILINE_STABLE_CAPTURES = 2;
|
|
20
|
-
const CODEX_EXACT_CANDIDATE_GRACE_CAPTURES = 8;
|
|
21
|
-
const CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES = 240;
|
|
22
|
-
const CODEX_COMPOSER_MARKER = /^[›»](?:\s|$)/u;
|
|
23
|
-
const CODEX_COMPOSER_FOOTER = /^(?:gpt-[\w.-]+(?:\s|$)|[-\w.]+ default ·)/u;
|
|
24
|
-
const CODEX_COMPLETE_COMPOSER_FOOTER = /^(?:gpt-[\w.-]+(?:\s+\S+)?|[-\w.]+ default)\s+·\s+\S.*$/u;
|
|
25
|
-
// Keep every exact slash-completion shape closed per behavior profile so a
|
|
26
|
-
// version adding another matching command cannot silently become an authorized
|
|
27
|
-
// native command surface.
|
|
28
|
-
const CODEX_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
29
|
-
"codex-tui-0.146.0": [
|
|
30
|
-
" /status show current session configuration and token usage"
|
|
31
|
-
],
|
|
32
|
-
"codex-tui-0.146.1": [
|
|
33
|
-
" /status show current session configuration and token usage"
|
|
34
|
-
],
|
|
35
|
-
"codex-tui-0.147.0": [
|
|
36
|
-
" /status show current session configuration and token usage",
|
|
37
|
-
" /statusline configure which items appear in the status line"
|
|
38
|
-
],
|
|
39
|
-
"codex-tui-0.148.0": [
|
|
40
|
-
" /status show current session configuration and token usage",
|
|
41
|
-
" /statusline configure which items appear in the status line"
|
|
42
|
-
],
|
|
43
|
-
"codex-tui-0.149.1": [
|
|
44
|
-
" /status show current session configuration and token usage",
|
|
45
|
-
" /statusline configure which items appear in the status line"
|
|
46
|
-
],
|
|
47
|
-
"codex-tui-0.150.1": [
|
|
48
|
-
" /status show current session configuration and token usage",
|
|
49
|
-
" /statusline configure which items appear in the status line"
|
|
50
|
-
],
|
|
51
|
-
"codex-tui-0.151.0": [
|
|
52
|
-
" /status show current session configuration and token usage",
|
|
53
|
-
" /statusline configure which items appear in the status line"
|
|
54
|
-
],
|
|
55
|
-
"codex-tui-0.153.0": [
|
|
56
|
-
" /status show current session configuration and token usage",
|
|
57
|
-
" /statusline configure which items appear in the status line"
|
|
58
|
-
],
|
|
59
|
-
"codex-tui-0.153.4": [
|
|
60
|
-
" /status show current session configuration and token usage",
|
|
61
|
-
" /statusline configure which items appear in the status line"
|
|
62
|
-
],
|
|
63
|
-
"codex-tui-0.154.0": [
|
|
64
|
-
" /status show current session configuration and token usage",
|
|
65
|
-
" /statusline configure which items appear in the status line"
|
|
66
|
-
],
|
|
67
|
-
"codex-tui-generic-v1": [
|
|
68
|
-
" /status show current session configuration and token usage",
|
|
69
|
-
" /statusline configure which items appear in the status line"
|
|
70
|
-
]
|
|
71
|
-
};
|
|
72
|
-
// Codex's verified `/status` profiles are exercised against its canonical
|
|
73
|
-
// 80-column status surface. Narrower layouts can truncate the 36-character
|
|
74
|
-
// Session UUID after a dynamically sized label column. Exact provider geometry
|
|
75
|
-
// is required before input; ANSI visible-buffer width is only a conservative
|
|
76
|
-
// fallback diagnostic and never upgrades unknown geometry to safe.
|
|
77
|
-
const CODEX_NATIVE_STATUS_MIN_VIEWPORT_BY_PROFILE = {
|
|
78
|
-
"codex-tui-0.146.0": 80,
|
|
79
|
-
"codex-tui-0.146.1": 80,
|
|
80
|
-
"codex-tui-0.147.0": 80,
|
|
81
|
-
"codex-tui-0.148.0": 80,
|
|
82
|
-
"codex-tui-0.149.1": 80,
|
|
83
|
-
"codex-tui-0.150.1": 80,
|
|
84
|
-
"codex-tui-0.151.0": 80,
|
|
85
|
-
"codex-tui-0.153.0": 80,
|
|
86
|
-
"codex-tui-0.153.4": 80,
|
|
87
|
-
"codex-tui-0.154.0": 80,
|
|
88
|
-
"codex-tui-generic-v1": 80
|
|
89
|
-
};
|
|
90
|
-
const CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
91
|
-
"claude-code-2.1.218-native-status": [
|
|
92
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
93
|
-
"/statusline Set up Claude Code's status line UI",
|
|
94
|
-
"/ide Manage IDE integrations and show status",
|
|
95
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
96
|
-
],
|
|
97
|
-
"claude-code-2.1.226-native-status": [
|
|
98
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
99
|
-
"/statusline Set up Claude Code's status line UI",
|
|
100
|
-
"/ide Manage IDE integrations and show status",
|
|
101
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
102
|
-
],
|
|
103
|
-
"claude-code-2.1.237-native-status": [
|
|
104
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
105
|
-
"/statusline Set up Claude Code's status line UI",
|
|
106
|
-
"/ide Manage IDE integrations and show status",
|
|
107
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
108
|
-
],
|
|
109
|
-
"claude-code-2.1.251-native-status": [
|
|
110
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
111
|
-
"/statusline Set up Claude Code's status line UI",
|
|
112
|
-
"/ide Manage IDE integrations and show status",
|
|
113
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
114
|
-
],
|
|
115
|
-
"claude-code-2.1.259-native-status": [
|
|
116
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
117
|
-
"/statusline Set up Claude Code's status line UI",
|
|
118
|
-
"/ide Manage IDE integrations and show status",
|
|
119
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
120
|
-
],
|
|
121
|
-
"claude-code-2.1.263-native-status": [
|
|
122
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
123
|
-
"/statusline Set up Claude Code's status line UI",
|
|
124
|
-
"/ide Manage IDE integrations and show status",
|
|
125
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
126
|
-
],
|
|
127
|
-
"claude-code-2.1.266-native-status": [
|
|
128
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
129
|
-
"/statusline Set up Claude Code's status line UI",
|
|
130
|
-
"/ide Manage IDE integrations and show status",
|
|
131
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
132
|
-
],
|
|
133
|
-
"claude-code-2.1.267-native-status": [
|
|
134
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
135
|
-
"/statusline Set up Claude Code's status line UI",
|
|
136
|
-
"/ide Manage IDE integrations and show status",
|
|
137
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
138
|
-
],
|
|
139
|
-
"claude-code-unverified-native-status-v1": [
|
|
140
|
-
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
141
|
-
"/statusline Set up Claude Code's status line UI",
|
|
142
|
-
"/ide Manage IDE integrations and show status",
|
|
143
|
-
"/usage Show session cost, plan usage, and activity stats"
|
|
144
|
-
]
|
|
145
|
-
};
|
|
146
|
-
const CLAUDE_NATIVE_STATUS_SETTLE_BY_PROFILE = {
|
|
147
|
-
"claude-code-2.1.218-native-status": {
|
|
148
|
-
minimumStableMs: 80,
|
|
149
|
-
maximumSettleMs: 2_000
|
|
150
|
-
},
|
|
151
|
-
"claude-code-2.1.226-native-status": {
|
|
152
|
-
minimumStableMs: 80,
|
|
153
|
-
maximumSettleMs: 5_000
|
|
154
|
-
},
|
|
155
|
-
"claude-code-2.1.237-native-status": {
|
|
156
|
-
minimumStableMs: 80,
|
|
157
|
-
maximumSettleMs: 5_000
|
|
158
|
-
},
|
|
159
|
-
"claude-code-2.1.251-native-status": {
|
|
160
|
-
minimumStableMs: 80,
|
|
161
|
-
maximumSettleMs: 5_000
|
|
162
|
-
},
|
|
163
|
-
"claude-code-2.1.259-native-status": {
|
|
164
|
-
minimumStableMs: 80,
|
|
165
|
-
maximumSettleMs: 5_000
|
|
166
|
-
},
|
|
167
|
-
"claude-code-2.1.263-native-status": {
|
|
168
|
-
minimumStableMs: 80,
|
|
169
|
-
maximumSettleMs: 5_000
|
|
170
|
-
},
|
|
171
|
-
"claude-code-2.1.266-native-status": {
|
|
172
|
-
minimumStableMs: 80,
|
|
173
|
-
maximumSettleMs: 5_000
|
|
174
|
-
},
|
|
175
|
-
"claude-code-2.1.267-native-status": {
|
|
176
|
-
minimumStableMs: 80,
|
|
177
|
-
maximumSettleMs: 5_000
|
|
178
|
-
},
|
|
179
|
-
"claude-code-unverified-native-status-v1": {
|
|
180
|
-
minimumStableMs: 80,
|
|
181
|
-
maximumSettleMs: 5_000
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
const CODEX_LARGE_PASTE_CHAR_THRESHOLD = 1_000;
|
|
185
|
-
const TERMINAL_INTERACTION_TTL_MS = 10 * 60 * 1_000;
|
|
186
|
-
const TERMINAL_INTERACTION_IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
187
|
-
const TERMINAL_INTERACTION_FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/u;
|
|
188
|
-
/** A terminal send failed at a boundary that proves input never started. */
|
|
189
|
-
export class TerminalInputNotStartedError extends Error {
|
|
190
|
-
code = "AKK_TERMINAL_INPUT_NOT_STARTED";
|
|
191
|
-
constructor(message, options = {}) {
|
|
192
|
-
super(message, options);
|
|
193
|
-
this.name = "TerminalInputNotStartedError";
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Text is already in the terminal composer, but this bridge invocation proved
|
|
198
|
-
* that it never called the Enter transport boundary.
|
|
199
|
-
*
|
|
200
|
-
* Callers may persist this as narrow recovery evidence. It says nothing about
|
|
201
|
-
* whether the draft remains exact or whether a later human action submitted
|
|
202
|
-
* it; those facts must be re-observed under the terminal lock.
|
|
203
|
-
*/
|
|
204
|
-
export class TerminalEnterDispatchNotAttemptedError extends Error {
|
|
205
|
-
code = "AKK_TERMINAL_ENTER_DISPATCH_NOT_ATTEMPTED";
|
|
206
|
-
stage = "enter_not_attempted";
|
|
207
|
-
constructor(message, options = {}) {
|
|
208
|
-
super(message, options);
|
|
209
|
-
this.name = "TerminalEnterDispatchNotAttemptedError";
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* The caller's one-shot Enter reservation was invoked, so this recovery
|
|
214
|
-
* attempt is permanently consumed even though C-m was not proven dispatched.
|
|
215
|
-
*/
|
|
216
|
-
export class TerminalEnterDispatchReservedError extends Error {
|
|
217
|
-
code = "AKK_TERMINAL_ENTER_DISPATCH_RESERVED";
|
|
218
|
-
stage = "enter_reserved";
|
|
219
|
-
doNotRetry = true;
|
|
220
|
-
constructor(message, options = {}) {
|
|
221
|
-
super(message, options);
|
|
222
|
-
this.name = "TerminalEnterDispatchReservedError";
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* A fail-closed native-inspection submission result.
|
|
227
|
-
*
|
|
228
|
-
* Only `not_started` proves that a caller may safely retry. Once text has
|
|
229
|
-
* reached the composer, or Enter has been attempted, automated retries could
|
|
230
|
-
* duplicate input or execute a different command after terminal drift.
|
|
231
|
-
*/
|
|
232
|
-
export class NativeInspectionSubmissionError extends Error {
|
|
233
|
-
stage;
|
|
234
|
-
code = "AKK_NATIVE_INSPECTION_SUBMISSION_FAILED";
|
|
235
|
-
doNotRetry;
|
|
236
|
-
constructor(stage, message, options = {}) {
|
|
237
|
-
super(message, options);
|
|
238
|
-
this.stage = stage;
|
|
239
|
-
this.name = "NativeInspectionSubmissionError";
|
|
240
|
-
this.doNotRetry = stage !== "not_started";
|
|
241
|
-
this.diagnostic = options.diagnostic;
|
|
242
|
-
}
|
|
243
|
-
diagnostic;
|
|
244
|
-
}
|
|
245
|
-
class NativeInspectionDiagnosticError extends Error {
|
|
246
|
-
diagnostic;
|
|
247
|
-
constructor(diagnostic, message, options = {}) {
|
|
248
|
-
super(message, options);
|
|
249
|
-
this.diagnostic = diagnostic;
|
|
250
|
-
this.name = "NativeInspectionDiagnosticError";
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
/** A verified modal could not be dismissed across one exact key attempt. */
|
|
254
|
-
export class NativeInspectionDismissalError extends Error {
|
|
255
|
-
code = "AKK_NATIVE_INSPECTION_DISMISSAL_FAILED";
|
|
256
|
-
doNotRetry = true;
|
|
257
|
-
constructor(message, options = {}) {
|
|
258
|
-
super(message, options);
|
|
259
|
-
this.name = "NativeInspectionDismissalError";
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* A response reservation or terminal input may already have happened. The
|
|
264
|
-
* same interaction must never be dispatched automatically after this error.
|
|
265
|
-
*/
|
|
266
|
-
export class TerminalInteractionDispatchReservedError extends Error {
|
|
267
|
-
stage;
|
|
268
|
-
code = "AKK_TERMINAL_INTERACTION_DISPATCH_RESERVED";
|
|
269
|
-
doNotRetry = true;
|
|
270
|
-
constructor(stage, message, options = {}) {
|
|
271
|
-
super(message, options);
|
|
272
|
-
this.stage = stage;
|
|
273
|
-
this.name = "TerminalInteractionDispatchReservedError";
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* A durable response reservation exists, but the bridge proved that no
|
|
278
|
-
* terminal text or key delivery was attempted. The caller must discard the
|
|
279
|
-
* one-shot offer and obtain a fresh status projection before trying again.
|
|
280
|
-
*/
|
|
281
|
-
export class TerminalInteractionInputNotStartedError extends Error {
|
|
282
|
-
code = "AKK_TERMINAL_INTERACTION_INPUT_NOT_STARTED";
|
|
283
|
-
stage = "input_not_started";
|
|
284
|
-
doNotRetry = true;
|
|
285
|
-
requiresFreshOffer = true;
|
|
286
|
-
constructor(message, options = {}) {
|
|
287
|
-
super(message, options);
|
|
288
|
-
this.name = "TerminalInteractionInputNotStartedError";
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
function terminalSendComposerRequirements(agent, multiline, options) {
|
|
292
|
-
const exactEmptyRetryAuthority = options.requireExactEmptyComposerAfterBeforeText;
|
|
293
|
-
const requireExactEmptyComposerBeforeText = options.requireExactEmptyComposerBeforeText === true;
|
|
294
|
-
const userExplicitEnterAfterTextWithoutComposerVeto = options.userExplicitEnterAfterTextWithoutComposerVeto === true;
|
|
295
|
-
if (userExplicitEnterAfterTextWithoutComposerVeto &&
|
|
296
|
-
(agent !== "codex" || !requireExactEmptyComposerBeforeText)) {
|
|
297
|
-
throw new TerminalInputNotStartedError("composer-veto-free Enter requires Codex and an exact-empty pre-text user-explicit boundary");
|
|
298
|
-
}
|
|
299
|
-
const requireExactComposer = !userExplicitEnterAfterTextWithoutComposerVeto &&
|
|
300
|
-
((agent === "codex" && multiline) ||
|
|
301
|
-
options.requireExactComposerBeforeEnter === true ||
|
|
302
|
-
requireExactEmptyComposerBeforeText ||
|
|
303
|
-
exactEmptyRetryAuthority !== undefined);
|
|
304
|
-
if (exactEmptyRetryAuthority !== undefined &&
|
|
305
|
-
(agent !== "codex" ||
|
|
306
|
-
options.beforeText === undefined ||
|
|
307
|
-
!/^[0-9a-f]{64}$/u.test(exactEmptyRetryAuthority.preliminaryComposerDigest))) {
|
|
308
|
-
throw new TerminalInputNotStartedError("exact-empty replacement requires Codex, a reservation hook, and a preliminary composer digest");
|
|
309
|
-
}
|
|
310
|
-
return {
|
|
311
|
-
exactEmptyRetryAuthority,
|
|
312
|
-
requireExactEmptyComposerBeforeText,
|
|
313
|
-
requireExactComposer
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
async function recordTerminalSendStage(options, event, preserveUserInput, previousError) {
|
|
317
|
-
try {
|
|
318
|
-
await options.onTransportStage?.(event);
|
|
319
|
-
return previousError;
|
|
320
|
-
}
|
|
321
|
-
catch (error) {
|
|
322
|
-
if (!preserveUserInput)
|
|
323
|
-
throw error;
|
|
324
|
-
return previousError ?? error;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
async function runTerminalSendBeforeEnter(options, context, preserveUserInput, previousError) {
|
|
328
|
-
try {
|
|
329
|
-
await options.beforeEnter?.(context);
|
|
330
|
-
return previousError;
|
|
331
|
-
}
|
|
332
|
-
catch (error) {
|
|
333
|
-
if (!preserveUserInput)
|
|
334
|
-
throw error;
|
|
335
|
-
return previousError ?? error;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
async function waitForUserExplicitCodexEnter(input) {
|
|
339
|
-
if (!input.enabled)
|
|
340
|
-
return;
|
|
341
|
-
await input.sleep(Math.max(0, CODEX_PASTE_ENTER_SETTLE_MS -
|
|
342
|
-
(input.nowMs() - input.textInjectedAt)));
|
|
343
|
-
}
|
|
344
|
-
function throwPostTextHookFailureAfterUserEnter(enabled, error) {
|
|
345
|
-
if (!enabled || error === undefined)
|
|
346
|
-
return;
|
|
347
|
-
throw new TerminalEnterDispatchReservedError("user-explicit Codex Enter was dispatched but post-text bookkeeping failed; do not retry", { cause: error });
|
|
348
|
-
}
|
|
4
|
+
import { hasCanonicalTerminalEndpoint, sameTerminalControlIncarnation, terminalEndpointFromControlRef, terminalEndpointIdentityKey } from "./terminal-control-ref.js";
|
|
5
|
+
import { terminalInteractionPublicCompatibilityProjection } from "./terminal-interaction-protocol.js";
|
|
6
|
+
import { captureTerminalInteractionRuntimeOffer, exactCurrentModelControlSurface, TerminalInteractionResponseBridge } from "./terminal-interaction-response-bridge.js";
|
|
7
|
+
import { discoverTerminalModelOptions, inspectTerminalModelControlResidual, repairTerminalModelControlResidual, switchTerminalModel, terminalModelControlPlanConforms } from "./terminal-model-control.js";
|
|
8
|
+
import { codexActiveWriterViewerVisible, codexBlockingModalVisible, currentCodexComposerCapture, exactClaudeComposerCapture, exactClaudeInjectedPastePlaceholderCapture, exactClaudeModelControlComposerCapture, exactTerminalComposerCapture, inspectCodexAsyncQuestionInputMode } from "./terminal-composer-classifier.js";
|
|
9
|
+
import { assertTerminalMutationCapabilities, isExactClaudeIdleComposer, stripTerminalEscapeSequences, TerminalNativeInspectionBridge } from "./terminal-native-inspection-bridge.js";
|
|
10
|
+
import { createTerminalModelControlPorts } from "./terminal-model-control-bridge.js";
|
|
11
|
+
import { CODEX_MULTILINE_SETTLE_POLL_MS, CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES, CODEX_PASTE_ENTER_SETTLE_MS, TerminalEnterDispatchNotAttemptedError, TerminalEnterDispatchReservedError, TerminalInputNotStartedError, TerminalTextSubmissionBridge } from "./terminal-text-submission-bridge.js";
|
|
12
|
+
export { TerminalEnterDispatchNotAttemptedError, TerminalEnterDispatchReservedError, TerminalInputNotStartedError } from "./terminal-text-submission-bridge.js";
|
|
13
|
+
export { captureTerminalInteractionRuntimeOffer, TerminalInteractionDispatchReservedError, TerminalInteractionInputNotStartedError } from "./terminal-interaction-response-bridge.js";
|
|
14
|
+
export { inspectCodexAsyncQuestionInputMode } from "./terminal-composer-classifier.js";
|
|
15
|
+
export { exactCodexReadyStyledComposerCapture, isExactClaudeIdleComposer, isExactClaudeNativeInspectionIdleComposer, NativeInspectionDismissalError, NativeInspectionSubmissionError } from "./terminal-native-inspection-bridge.js";
|
|
349
16
|
function terminalApprovalPreflight(input) {
|
|
350
17
|
const { adapter, inspection, decision } = input;
|
|
351
18
|
if (!inspection.approval.approvable) {
|
|
@@ -647,274 +314,227 @@ export class TerminalAgentBridge {
|
|
|
647
314
|
};
|
|
648
315
|
}
|
|
649
316
|
}
|
|
650
|
-
async
|
|
317
|
+
async modelOptions(agent, terminalControl, agentVersion, plan, options = {}) {
|
|
651
318
|
const adapter = this.registry.require(agent);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
transport: [
|
|
665
|
-
"stable_resource_resolution",
|
|
666
|
-
"text_delivery",
|
|
667
|
-
"key_delivery",
|
|
668
|
-
...(requireExactComposer || exactEmptyRetryAuthority
|
|
669
|
-
? ["screen_capture"]
|
|
670
|
-
: [])
|
|
671
|
-
]
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
catch (error) {
|
|
675
|
-
throw new TerminalInputNotStartedError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
676
|
-
}
|
|
677
|
-
const normalized = text.trimEnd();
|
|
678
|
-
if (!normalized) {
|
|
679
|
-
throw new TerminalInputNotStartedError("terminal message is empty");
|
|
680
|
-
}
|
|
681
|
-
let verifiedForText;
|
|
682
|
-
try {
|
|
683
|
-
verifiedForText = await this.verifyTerminalIdentity(adapter.agent, terminalControl, options.runtime);
|
|
684
|
-
}
|
|
685
|
-
catch (error) {
|
|
686
|
-
throw new TerminalInputNotStartedError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
687
|
-
}
|
|
688
|
-
if (!verifiedForText.capabilities.includes("send_keys")) {
|
|
689
|
-
throw new TerminalInputNotStartedError(`${adapter.displayName} terminal input capability changed before injection`);
|
|
690
|
-
}
|
|
691
|
-
let composerVerifiedImmediatelyBeforeText = false;
|
|
692
|
-
if (exactEmptyRetryAuthority) {
|
|
693
|
-
try {
|
|
694
|
-
// Invoking this retry-only hook consumes the durable attempt. The
|
|
695
|
-
// independently identity-fenced recapture closes the human-edit race
|
|
696
|
-
// before any physical text delivery.
|
|
697
|
-
await options.beforeText?.({
|
|
698
|
-
agent: adapter.agent,
|
|
699
|
-
terminalControl: verifiedForText,
|
|
700
|
-
multiline,
|
|
701
|
-
text: normalized
|
|
702
|
-
});
|
|
703
|
-
const finalEmpty = await this.captureCodexComposerSnapshot(adapter, verifiedForText, normalized, options.runtime);
|
|
704
|
-
if (finalEmpty.state !== "exact_empty" ||
|
|
705
|
-
finalEmpty.digest !==
|
|
706
|
-
exactEmptyRetryAuthority.preliminaryComposerDigest ||
|
|
707
|
-
!sameTerminalControlIdentity(verifiedForText, finalEmpty.terminalControl)) {
|
|
708
|
-
const reason = "reason" in finalEmpty
|
|
709
|
-
? finalEmpty.reason
|
|
710
|
-
: `Codex composer is ${finalEmpty.state}`;
|
|
711
|
-
throw new Error(`reserved Codex replacement lost its exact-empty composer: ${reason}`);
|
|
712
|
-
}
|
|
713
|
-
verifiedForText = finalEmpty.terminalControl;
|
|
714
|
-
}
|
|
715
|
-
catch (error) {
|
|
716
|
-
throw new TerminalEnterDispatchReservedError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
else {
|
|
720
|
-
try {
|
|
721
|
-
await options.beforeText?.({
|
|
722
|
-
agent: adapter.agent,
|
|
723
|
-
terminalControl: verifiedForText,
|
|
724
|
-
multiline,
|
|
725
|
-
text: normalized
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
catch (error) {
|
|
729
|
-
throw new TerminalInputNotStartedError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
730
|
-
}
|
|
731
|
-
if (requireExactEmptyComposerBeforeText) {
|
|
732
|
-
try {
|
|
733
|
-
verifiedForText = await this.verifyExactEmptyComposerBeforeText(adapter, verifiedForText, normalized, options.runtime, options.allowWorkingComposerForUserExplicit === true);
|
|
734
|
-
composerVerifiedImmediatelyBeforeText = true;
|
|
735
|
-
}
|
|
736
|
-
catch (error) {
|
|
737
|
-
throw new TerminalInputNotStartedError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
if (!composerVerifiedImmediatelyBeforeText) {
|
|
742
|
-
try {
|
|
743
|
-
const reverifiedForText = await this.verifyTerminalIdentity(adapter.agent, verifiedForText, options.runtime);
|
|
744
|
-
if (!sameTerminalControlIdentity(verifiedForText, reverifiedForText)) {
|
|
745
|
-
throw new Error("terminal identity changed after the final pre-text check");
|
|
746
|
-
}
|
|
747
|
-
verifiedForText = reverifiedForText;
|
|
748
|
-
}
|
|
749
|
-
catch (error) {
|
|
750
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
751
|
-
if (exactEmptyRetryAuthority) {
|
|
752
|
-
throw new TerminalEnterDispatchReservedError(message, {
|
|
753
|
-
cause: error
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
throw new TerminalInputNotStartedError(message, { cause: error });
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
try {
|
|
760
|
-
await this.terminalProvider.sendText(this.terminalProvider.endpoint(verifiedForText), normalized);
|
|
761
|
-
}
|
|
762
|
-
catch (error) {
|
|
763
|
-
if (exactEmptyRetryAuthority) {
|
|
764
|
-
throw new TerminalEnterDispatchReservedError("reserved Codex replacement text delivery outcome is uncertain; do not retry", { cause: error });
|
|
765
|
-
}
|
|
766
|
-
if (error instanceof TerminalControlInputNotSentError) {
|
|
767
|
-
throw new TerminalInputNotStartedError(error.message, {
|
|
768
|
-
cause: error
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
throw error;
|
|
772
|
-
}
|
|
773
|
-
const textInjectedAt = this.nowMs();
|
|
774
|
-
const preserveUserInput = options.userExplicitEnterAfterTextWithoutComposerVeto === true;
|
|
775
|
-
const allowClaudeInjectedPastePlaceholder = claudeInjectedPasteProofEligible({
|
|
776
|
-
agent: adapter.agent,
|
|
777
|
-
multiline,
|
|
778
|
-
composerVerifiedImmediatelyBeforeText,
|
|
779
|
-
terminalControl: verifiedForText
|
|
319
|
+
assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan);
|
|
320
|
+
assertTerminalMutationCapabilities({
|
|
321
|
+
provider: this.terminalProvider,
|
|
322
|
+
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
323
|
+
semantic: ["send_keys", "screen_status"],
|
|
324
|
+
transport: [
|
|
325
|
+
"stable_resource_resolution",
|
|
326
|
+
"screen_capture",
|
|
327
|
+
"ansi_capture",
|
|
328
|
+
"text_delivery",
|
|
329
|
+
"key_delivery"
|
|
330
|
+
]
|
|
780
331
|
});
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
332
|
+
const result = await discoverTerminalModelOptions({
|
|
333
|
+
agent,
|
|
334
|
+
agentVersion,
|
|
335
|
+
plan,
|
|
336
|
+
terminalControl,
|
|
337
|
+
ports: this.modelControlPorts(adapter, plan, options),
|
|
338
|
+
initialResidual: options.initialResidual
|
|
339
|
+
});
|
|
340
|
+
return {
|
|
341
|
+
terminalControl: result.terminalControl,
|
|
342
|
+
catalog: result.catalog
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
async setModel(agent, terminalControl, agentVersion, plan, expectedCatalogFingerprint, request, options = {}) {
|
|
346
|
+
const adapter = this.registry.require(agent);
|
|
347
|
+
assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan);
|
|
348
|
+
assertTerminalMutationCapabilities({
|
|
349
|
+
provider: this.terminalProvider,
|
|
350
|
+
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
351
|
+
semantic: ["send_keys", "screen_status"],
|
|
352
|
+
transport: [
|
|
353
|
+
"stable_resource_resolution",
|
|
354
|
+
"screen_capture",
|
|
355
|
+
"ansi_capture",
|
|
356
|
+
"text_delivery",
|
|
357
|
+
"key_delivery"
|
|
358
|
+
]
|
|
359
|
+
});
|
|
360
|
+
const result = await switchTerminalModel({
|
|
361
|
+
agent,
|
|
362
|
+
agentVersion,
|
|
363
|
+
plan,
|
|
364
|
+
terminalControl,
|
|
365
|
+
expectedCatalogFingerprint,
|
|
366
|
+
request,
|
|
367
|
+
ports: this.modelControlPorts(adapter, plan, options)
|
|
368
|
+
});
|
|
369
|
+
return {
|
|
370
|
+
...result,
|
|
371
|
+
terminalControl: result.terminalControl
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
async inspectModelControlResidual(agent, terminalControl, agentVersion, plan, options = {}) {
|
|
375
|
+
const adapter = this.registry.require(agent);
|
|
376
|
+
assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan);
|
|
377
|
+
assertTerminalMutationCapabilities({
|
|
378
|
+
provider: this.terminalProvider,
|
|
379
|
+
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
380
|
+
semantic: ["screen_status"],
|
|
381
|
+
transport: ["stable_resource_resolution", "screen_capture", "ansi_capture"]
|
|
382
|
+
});
|
|
383
|
+
return inspectTerminalModelControlResidual({
|
|
384
|
+
plan,
|
|
385
|
+
terminalControl,
|
|
386
|
+
ports: this.modelControlPorts(adapter, plan, options)
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
async repairModelControlResidual(agent, terminalControl, agentVersion, plan, expectedResidualFingerprint, options = {}) {
|
|
390
|
+
const adapter = this.registry.require(agent);
|
|
391
|
+
assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan);
|
|
392
|
+
assertTerminalMutationCapabilities({
|
|
393
|
+
provider: this.terminalProvider,
|
|
394
|
+
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
395
|
+
semantic: ["send_keys", "screen_status"],
|
|
396
|
+
transport: [
|
|
397
|
+
"stable_resource_resolution",
|
|
398
|
+
"screen_capture",
|
|
399
|
+
"ansi_capture",
|
|
400
|
+
"key_delivery"
|
|
401
|
+
]
|
|
402
|
+
});
|
|
403
|
+
const result = await repairTerminalModelControlResidual({
|
|
404
|
+
plan,
|
|
405
|
+
terminalControl,
|
|
406
|
+
expectedResidualFingerprint,
|
|
407
|
+
ports: this.modelControlPorts(adapter, plan, options)
|
|
408
|
+
});
|
|
409
|
+
return {
|
|
410
|
+
...result,
|
|
411
|
+
terminalControl: result.terminalControl
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
modelControlPorts(adapter, plan, options) {
|
|
415
|
+
if (!options.beforeInput) {
|
|
416
|
+
throw new Error("terminal model control requires current-snapshot authority before every input");
|
|
855
417
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
418
|
+
return createTerminalModelControlPorts({
|
|
419
|
+
adapter,
|
|
420
|
+
plan,
|
|
421
|
+
runtime: options.runtime,
|
|
422
|
+
beforeInput: options.beforeInput,
|
|
423
|
+
loadCodexCatalog: options.loadCodexCatalog,
|
|
424
|
+
runtimePorts: {
|
|
425
|
+
verifyTerminalIdentity: (agent, control, runtime) => this.verifyTerminalIdentity(agent, control, runtime),
|
|
426
|
+
captureStyled: (control) => this.terminalProvider.capture(this.terminalProvider.endpoint(control), { scrollbackLines: 160, preserveEscapes: true }),
|
|
427
|
+
sendText: (control, text) => this.terminalProvider.sendText(this.terminalProvider.endpoint(control), text),
|
|
428
|
+
sendKeys: (control, keys) => this.terminalProvider.sendKeys(this.terminalProvider.endpoint(control), keys),
|
|
429
|
+
sleep: this.sleep
|
|
430
|
+
},
|
|
431
|
+
classifiers: {
|
|
432
|
+
stripEscapes: stripTerminalEscapeSequences,
|
|
433
|
+
sameIdentity: sameTerminalControlIdentity,
|
|
434
|
+
currentCodexComposer: currentCodexComposerCapture,
|
|
435
|
+
inspectCodexAsyncQuestionInputMode,
|
|
436
|
+
codexActiveWriterViewerVisible,
|
|
437
|
+
isExactClaudeIdleComposer,
|
|
438
|
+
exactClaudeModelControlComposer: exactClaudeModelControlComposerCapture,
|
|
439
|
+
exactTerminalComposer: exactTerminalComposerCapture
|
|
862
440
|
}
|
|
863
|
-
|
|
864
|
-
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
textSubmissionBridge() {
|
|
444
|
+
return new TerminalTextSubmissionBridge({
|
|
445
|
+
runtime: {
|
|
446
|
+
preflight: (terminalControl, operation) => {
|
|
447
|
+
const terminal = this.terminalProvider.endpoint(terminalControl);
|
|
448
|
+
switch (operation) {
|
|
449
|
+
case "send":
|
|
450
|
+
assertTerminalMutationCapabilities({
|
|
451
|
+
provider: this.terminalProvider,
|
|
452
|
+
terminal,
|
|
453
|
+
semantic: ["send_keys"],
|
|
454
|
+
transport: [
|
|
455
|
+
"stable_resource_resolution",
|
|
456
|
+
"text_delivery",
|
|
457
|
+
"key_delivery"
|
|
458
|
+
]
|
|
459
|
+
});
|
|
460
|
+
return;
|
|
461
|
+
case "send_with_composer":
|
|
462
|
+
assertTerminalMutationCapabilities({
|
|
463
|
+
provider: this.terminalProvider,
|
|
464
|
+
terminal,
|
|
465
|
+
semantic: ["send_keys", "screen_status"],
|
|
466
|
+
transport: [
|
|
467
|
+
"stable_resource_resolution",
|
|
468
|
+
"text_delivery",
|
|
469
|
+
"key_delivery",
|
|
470
|
+
"screen_capture"
|
|
471
|
+
]
|
|
472
|
+
});
|
|
473
|
+
return;
|
|
474
|
+
case "observe_composer":
|
|
475
|
+
assertTerminalMutationCapabilities({
|
|
476
|
+
provider: this.terminalProvider,
|
|
477
|
+
terminal,
|
|
478
|
+
semantic: ["screen_status"],
|
|
479
|
+
transport: ["stable_resource_resolution", "screen_capture"]
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
verifyIdentity: (agent, control, runtime) => this.verifyTerminalIdentity(agent, control, runtime),
|
|
484
|
+
captureInspection: (adapter, control, options) => this.captureInspection(adapter, control, options),
|
|
485
|
+
captureStyled: (control) => this.terminalProvider.capture(this.terminalProvider.endpoint(control), {
|
|
486
|
+
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES,
|
|
487
|
+
preserveEscapes: true
|
|
488
|
+
}),
|
|
489
|
+
deliverText: (control, text) => this.terminalProvider.sendText(this.terminalProvider.endpoint(control), text),
|
|
490
|
+
dispatchEnter: (control) => this.terminalProvider.sendKeys(this.terminalProvider.endpoint(control), ["C-m"]),
|
|
491
|
+
nowMs: () => this.nowMs(),
|
|
492
|
+
sleep: (milliseconds) => this.sleep(milliseconds)
|
|
493
|
+
},
|
|
494
|
+
classifiers: {
|
|
495
|
+
sameIdentity: sameTerminalControlIdentity,
|
|
496
|
+
stripEscapes: stripTerminalEscapeSequences,
|
|
497
|
+
codexBlockingModalVisible,
|
|
498
|
+
inspectCodexAsyncQuestionInputMode,
|
|
499
|
+
currentCodexComposer: currentCodexComposerCapture,
|
|
500
|
+
exactTerminalComposer: exactTerminalComposerCapture,
|
|
501
|
+
exactClaudeComposer: exactClaudeComposerCapture,
|
|
502
|
+
exactClaudeInjectedPastePlaceholder: exactClaudeInjectedPastePlaceholderCapture
|
|
865
503
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
interactionResponseBridge() {
|
|
507
|
+
return new TerminalInteractionResponseBridge(this.terminalProvider, {
|
|
508
|
+
captureInspection: (agent, control, runtime, scrollbackLines) => this.captureInspection(this.registry.require(agent), control, {
|
|
509
|
+
runtime,
|
|
510
|
+
scrollbackLines
|
|
511
|
+
}),
|
|
512
|
+
verifyIdentity: (agent, control, runtime) => this.verifyTerminalIdentity(agent, control, runtime),
|
|
513
|
+
now: () => this.now(),
|
|
514
|
+
sleep: (milliseconds) => this.sleep(milliseconds)
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
nativeInspectionBridge() {
|
|
518
|
+
return new TerminalNativeInspectionBridge({
|
|
519
|
+
registry: this.registry,
|
|
520
|
+
terminalProvider: this.terminalProvider,
|
|
521
|
+
runtime: {
|
|
522
|
+
captureInspection: (adapter, control, options) => this.captureInspection(adapter, control, options),
|
|
523
|
+
verifyIdentity: (agent, control, runtime) => this.verifyTerminalIdentity(agent, control, runtime),
|
|
524
|
+
statusFromInspection: (adapter, control, inspection, options) => statusFromInspection(adapter, control, inspection, options),
|
|
525
|
+
nowMs: () => this.nowMs(),
|
|
526
|
+
sleep: (milliseconds) => this.sleep(milliseconds)
|
|
883
527
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
terminalControl: verifiedForEnter,
|
|
890
|
-
multiline
|
|
891
|
-
};
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
async send(agent, terminalControl, text, options = {}) {
|
|
531
|
+
const adapter = this.registry.require(agent);
|
|
532
|
+
return this.textSubmissionBridge().send(adapter, terminalControl, text, options);
|
|
892
533
|
}
|
|
893
534
|
/** Observe only the current, live Codex composer without exposing its text. */
|
|
894
535
|
async observeCodexComposer(terminalControl, expectedText, options = {}) {
|
|
895
536
|
const adapter = this.registry.require("codex");
|
|
896
|
-
|
|
897
|
-
if (!normalized) {
|
|
898
|
-
return {
|
|
899
|
-
state: "unavailable",
|
|
900
|
-
reason: "expected Codex draft is empty"
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
try {
|
|
904
|
-
assertTerminalMutationCapabilities({
|
|
905
|
-
provider: this.terminalProvider,
|
|
906
|
-
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
907
|
-
semantic: ["screen_status"],
|
|
908
|
-
transport: ["stable_resource_resolution", "screen_capture"]
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
catch (error) {
|
|
912
|
-
return {
|
|
913
|
-
state: "unavailable",
|
|
914
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
return this.settleCodexComposerObservation(adapter, terminalControl, normalized, options.runtime, { minimumStableMs: CODEX_MULTILINE_SETTLE_POLL_MS });
|
|
537
|
+
return this.textSubmissionBridge().observeCodexComposer(adapter, terminalControl, expectedText, options.runtime);
|
|
918
538
|
}
|
|
919
539
|
/**
|
|
920
540
|
* Honor one explicit user Send against the current Codex terminal. The
|
|
@@ -1102,6 +722,7 @@ export class TerminalAgentBridge {
|
|
|
1102
722
|
*/
|
|
1103
723
|
async submitExactCodexDraft(terminalControl, expectedText, options) {
|
|
1104
724
|
const adapter = this.registry.require("codex");
|
|
725
|
+
const textSubmission = this.textSubmissionBridge();
|
|
1105
726
|
const normalized = expectedText.trimEnd();
|
|
1106
727
|
if (!normalized) {
|
|
1107
728
|
throw new TerminalEnterDispatchNotAttemptedError("expected Codex draft is empty");
|
|
@@ -1118,7 +739,7 @@ export class TerminalAgentBridge {
|
|
|
1118
739
|
"key_delivery"
|
|
1119
740
|
]
|
|
1120
741
|
});
|
|
1121
|
-
const observation = await
|
|
742
|
+
const observation = await textSubmission.settleCodexComposerObservation(adapter, terminalControl, normalized, options.runtime, {
|
|
1122
743
|
minimumStableMs: CODEX_PASTE_ENTER_SETTLE_MS,
|
|
1123
744
|
requiredState: "exact_draft"
|
|
1124
745
|
});
|
|
@@ -1146,7 +767,7 @@ export class TerminalAgentBridge {
|
|
|
1146
767
|
text: normalized,
|
|
1147
768
|
composerDigest: preliminaryObservation.digest
|
|
1148
769
|
});
|
|
1149
|
-
const recaptured = await
|
|
770
|
+
const recaptured = await textSubmission.captureCodexComposerSnapshot(adapter, preliminaryObservation.terminalControl, normalized, options.runtime);
|
|
1150
771
|
if (recaptured.state !== "exact_draft" ||
|
|
1151
772
|
recaptured.digest !== preliminaryObservation.digest ||
|
|
1152
773
|
!sameTerminalControlIdentity(preliminaryObservation.terminalControl, recaptured.terminalControl)) {
|
|
@@ -1175,66 +796,6 @@ export class TerminalAgentBridge {
|
|
|
1175
796
|
enterCount: 1
|
|
1176
797
|
};
|
|
1177
798
|
}
|
|
1178
|
-
async verifyExactComposerBeforeEnter(adapter, terminalControl, expectedText, runtime, allowWorkingComposer = false) {
|
|
1179
|
-
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
1180
|
-
runtime,
|
|
1181
|
-
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
1182
|
-
});
|
|
1183
|
-
const exactComposer = exactTerminalComposerCapture(adapter.agent, captured.screen, expectedText);
|
|
1184
|
-
if (captured.inspection.approval.blocked ||
|
|
1185
|
-
!(["idle", "unknown"].includes(captured.inspection.activity.state) ||
|
|
1186
|
-
(allowWorkingComposer &&
|
|
1187
|
-
captured.inspection.activity.state === "working")) ||
|
|
1188
|
-
!exactComposer) {
|
|
1189
|
-
throw new Error(`${adapter.displayName} composer was not exact and idle immediately before Enter`);
|
|
1190
|
-
}
|
|
1191
|
-
const verifiedImmediatelyBeforeEnter = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, runtime);
|
|
1192
|
-
if (!sameTerminalControlIdentity(captured.terminalControl, verifiedImmediatelyBeforeEnter)) {
|
|
1193
|
-
throw new Error("terminal control identity changed after the final exact composer capture");
|
|
1194
|
-
}
|
|
1195
|
-
return verifiedImmediatelyBeforeEnter;
|
|
1196
|
-
}
|
|
1197
|
-
claudeInjectedPasteProofPorts(adapter) {
|
|
1198
|
-
return {
|
|
1199
|
-
nowMs: this.nowMs,
|
|
1200
|
-
sleep: this.sleep,
|
|
1201
|
-
capture: (terminalControl, runtime) => this.captureInspection(adapter, terminalControl, {
|
|
1202
|
-
runtime,
|
|
1203
|
-
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
1204
|
-
}),
|
|
1205
|
-
verifyIdentity: (terminalControl, runtime) => this.verifyTerminalIdentity(adapter.agent, terminalControl, runtime),
|
|
1206
|
-
exactDraft: exactClaudeComposerCapture,
|
|
1207
|
-
exactInjectedPastePlaceholder: exactClaudeInjectedPastePlaceholderCapture
|
|
1208
|
-
};
|
|
1209
|
-
}
|
|
1210
|
-
async verifyExactEmptyComposerBeforeText(adapter, terminalControl, expectedText, runtime, allowWorkingComposer = false) {
|
|
1211
|
-
if (adapter.agent === "codex") {
|
|
1212
|
-
const snapshot = await this.captureCodexComposerSnapshot(adapter, terminalControl, expectedText, runtime);
|
|
1213
|
-
if (snapshot.state !== "exact_empty") {
|
|
1214
|
-
const reason = "reason" in snapshot
|
|
1215
|
-
? snapshot.reason
|
|
1216
|
-
: `Codex composer is ${snapshot.state}`;
|
|
1217
|
-
throw new Error(`Codex composer was not exactly empty immediately before text: ${reason}`);
|
|
1218
|
-
}
|
|
1219
|
-
return snapshot.terminalControl;
|
|
1220
|
-
}
|
|
1221
|
-
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
1222
|
-
runtime,
|
|
1223
|
-
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
1224
|
-
});
|
|
1225
|
-
if (captured.inspection.approval.blocked ||
|
|
1226
|
-
!(["idle", "unknown"].includes(captured.inspection.activity.state) ||
|
|
1227
|
-
(allowWorkingComposer &&
|
|
1228
|
-
captured.inspection.activity.state === "working")) ||
|
|
1229
|
-
!exactTerminalComposerCapture(adapter.agent, captured.screen, "")) {
|
|
1230
|
-
throw new Error(`${adapter.displayName} composer was not exactly empty immediately before text`);
|
|
1231
|
-
}
|
|
1232
|
-
const verified = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, runtime);
|
|
1233
|
-
if (!sameTerminalControlIdentity(captured.terminalControl, verified)) {
|
|
1234
|
-
throw new Error("terminal control identity changed after the final empty composer capture");
|
|
1235
|
-
}
|
|
1236
|
-
return verified;
|
|
1237
|
-
}
|
|
1238
799
|
/**
|
|
1239
800
|
* Submit one closed, adapter-owned native inspection command.
|
|
1240
801
|
*
|
|
@@ -1243,182 +804,19 @@ export class TerminalAgentBridge {
|
|
|
1243
804
|
* draft untouched instead of issuing the legacy best-effort C-u cleanup.
|
|
1244
805
|
*/
|
|
1245
806
|
async submitNativeInspection(agent, terminalControl, plan, options = {}) {
|
|
1246
|
-
return this.
|
|
807
|
+
return this.nativeInspectionBridge().submitNativeInspection(agent, terminalControl, plan, options);
|
|
1247
808
|
}
|
|
1248
809
|
/**
|
|
1249
810
|
* Submit Codex's closed, version-profiled `/status` probe.
|
|
1250
811
|
*
|
|
1251
812
|
* Unlike the generic native-inspection entry point, callers provide only
|
|
1252
|
-
* the detected Codex version, never a command or plan.
|
|
1253
|
-
* exact empty (or fully dim replace-on-type) ANSI composer before injecting
|
|
1254
|
-
* text, then crosses Codex's paste suppression window under the same exact
|
|
1255
|
-
* composer and terminal-identity fences used by native inspection.
|
|
813
|
+
* the detected Codex version, never a command or plan.
|
|
1256
814
|
*/
|
|
1257
815
|
async submitCodexStatusProbe(terminalControl, agentVersion, options = {}) {
|
|
1258
|
-
|
|
1259
|
-
let plan;
|
|
1260
|
-
try {
|
|
1261
|
-
const capability = adapter.probeNativeInspection?.(agentVersion);
|
|
1262
|
-
if (capability?.status !== "supported" ||
|
|
1263
|
-
capability.statusInspection !== true) {
|
|
1264
|
-
throw new NativeInspectionDiagnosticError("unsupported_profile", capability?.reason ??
|
|
1265
|
-
`Codex ${agentVersion} has no closed /status behavior profile`);
|
|
1266
|
-
}
|
|
1267
|
-
const planned = adapter.planNativeInspection?.({ kind: "status" }, capability);
|
|
1268
|
-
if (!planned) {
|
|
1269
|
-
throw new NativeInspectionDiagnosticError("unsupported_profile", `Codex ${agentVersion} did not produce a closed /status plan`);
|
|
1270
|
-
}
|
|
1271
|
-
plan = planned;
|
|
1272
|
-
}
|
|
1273
|
-
catch (error) {
|
|
1274
|
-
throw nativeInspectionSubmissionError("not_started", error, "unsupported_profile");
|
|
1275
|
-
}
|
|
1276
|
-
const result = await this.submitClosedNativeInspection("codex", terminalControl, plan, options, { requireCodexReadyComposer: true });
|
|
1277
|
-
if (!result.preTextScreenDigest) {
|
|
1278
|
-
throw new Error("Codex /status pre-text composer evidence is missing");
|
|
1279
|
-
}
|
|
1280
|
-
return {
|
|
1281
|
-
...result,
|
|
1282
|
-
agent: "codex",
|
|
1283
|
-
preTextScreenDigest: result.preTextScreenDigest,
|
|
1284
|
-
observationBaselineDigest: bareDigestFromNativeInspectionScreenFingerprint(result.preEnterScreenDigest),
|
|
1285
|
-
observationScrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
1286
|
-
};
|
|
1287
|
-
}
|
|
1288
|
-
async submitClosedNativeInspection(agent, terminalControl, plan, options, safety = {}) {
|
|
1289
|
-
const adapter = this.registry.require(agent);
|
|
1290
|
-
try {
|
|
1291
|
-
assertClosedStatusInspectionPlan(adapter, terminalControl, plan);
|
|
1292
|
-
assertTerminalMutationCapabilities({
|
|
1293
|
-
provider: this.terminalProvider,
|
|
1294
|
-
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
1295
|
-
semantic: ["send_keys", "screen_status"],
|
|
1296
|
-
transport: [
|
|
1297
|
-
"stable_resource_resolution",
|
|
1298
|
-
"screen_capture",
|
|
1299
|
-
"text_delivery",
|
|
1300
|
-
"key_delivery",
|
|
1301
|
-
...(safety.requireCodexReadyComposer
|
|
1302
|
-
? ["ansi_capture"]
|
|
1303
|
-
: [])
|
|
1304
|
-
]
|
|
1305
|
-
});
|
|
1306
|
-
}
|
|
1307
|
-
catch (error) {
|
|
1308
|
-
throw nativeInspectionSubmissionError("not_started", error, "capability_unavailable");
|
|
1309
|
-
}
|
|
1310
|
-
let verifiedForText;
|
|
1311
|
-
try {
|
|
1312
|
-
verifiedForText = await this.verifyTerminalIdentity(adapter.agent, terminalControl, options.runtime);
|
|
1313
|
-
}
|
|
1314
|
-
catch (error) {
|
|
1315
|
-
throw nativeInspectionSubmissionError("not_started", error, "identity_unverified");
|
|
1316
|
-
}
|
|
1317
|
-
let preTextScreenDigest;
|
|
1318
|
-
if (safety.requireCodexReadyComposer) {
|
|
1319
|
-
try {
|
|
1320
|
-
const ready = await this.captureCodexReadyComposer(adapter, verifiedForText, plan, options.runtime);
|
|
1321
|
-
verifiedForText = ready.terminalControl;
|
|
1322
|
-
preTextScreenDigest = ready.screenDigest;
|
|
1323
|
-
}
|
|
1324
|
-
catch (error) {
|
|
1325
|
-
throw nativeInspectionSubmissionError("not_started", error, "composer_not_ready");
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
try {
|
|
1329
|
-
if (!verifiedForText.capabilities.includes("send_keys")) {
|
|
1330
|
-
throw new TerminalControlInputNotSentError(`${adapter.displayName} native inspection input capability changed`);
|
|
1331
|
-
}
|
|
1332
|
-
await this.terminalProvider.sendText(this.terminalProvider.endpoint(verifiedForText), plan.command);
|
|
1333
|
-
}
|
|
1334
|
-
catch (error) {
|
|
1335
|
-
if (error instanceof TerminalControlInputNotSentError) {
|
|
1336
|
-
throw nativeInspectionSubmissionError("not_started", error, "text_delivery_unproven");
|
|
1337
|
-
}
|
|
1338
|
-
// The transport cannot prove whether an untyped failure happened before
|
|
1339
|
-
// or after tmux accepted the literal input. Fail closed as injected.
|
|
1340
|
-
throw nativeInspectionSubmissionError("text_injected", error, "text_delivery_unproven");
|
|
1341
|
-
}
|
|
1342
|
-
let settled;
|
|
1343
|
-
try {
|
|
1344
|
-
settled = await this.settleNativeInspectionComposer(adapter, terminalControl, plan, options.runtime);
|
|
1345
|
-
await options.beforeEnter?.({
|
|
1346
|
-
agent: adapter.agent,
|
|
1347
|
-
terminalControl: settled.terminalControl,
|
|
1348
|
-
plan,
|
|
1349
|
-
preEnterScreenDigest: settled.screenDigest,
|
|
1350
|
-
materialization: settled.materialization
|
|
1351
|
-
});
|
|
1352
|
-
if (safety.requireCodexReadyComposer) {
|
|
1353
|
-
settled = {
|
|
1354
|
-
...settled,
|
|
1355
|
-
terminalControl: await this.assertFinalCodexStatusViewport(settled.terminalControl, plan, options.runtime)
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
// Viewport inspection may await provider I/O and the PTY may receive
|
|
1359
|
-
// human input while that proof is in flight. Keep the exact composer
|
|
1360
|
-
// capture as the final substantive asynchronous evidence before the
|
|
1361
|
-
// single Enter attempt.
|
|
1362
|
-
settled = await this.revalidateNativeInspectionComposer(adapter, settled.terminalControl, plan, settled.materialization, options.runtime);
|
|
1363
|
-
}
|
|
1364
|
-
catch (error) {
|
|
1365
|
-
throw nativeInspectionSubmissionError("text_injected", error, "composer_not_exact");
|
|
1366
|
-
}
|
|
1367
|
-
try {
|
|
1368
|
-
// Exactly one Enter attempt. Any error is submission-uncertain and must
|
|
1369
|
-
// never trigger a fallback executable or a blind second Enter.
|
|
1370
|
-
await this.terminalProvider.sendKeys(this.terminalProvider.endpoint(settled.terminalControl), ["C-m"]);
|
|
1371
|
-
}
|
|
1372
|
-
catch (error) {
|
|
1373
|
-
throw nativeInspectionSubmissionError("enter_uncertain", error, "enter_uncertain");
|
|
1374
|
-
}
|
|
1375
|
-
return {
|
|
1376
|
-
stage: "enter_dispatched",
|
|
1377
|
-
agent: adapter.agent,
|
|
1378
|
-
terminalControl: settled.terminalControl,
|
|
1379
|
-
command: plan.command,
|
|
1380
|
-
behaviorProfile: plan.behaviorProfile,
|
|
1381
|
-
preEnterScreenDigest: settled.screenDigest,
|
|
1382
|
-
preEnterEvidenceInventory: settled.evidenceInventory,
|
|
1383
|
-
materialization: settled.materialization,
|
|
1384
|
-
enterCount: 1,
|
|
1385
|
-
...(preTextScreenDigest ? { preTextScreenDigest } : {})
|
|
1386
|
-
};
|
|
816
|
+
return this.nativeInspectionBridge().submitCodexStatusProbe(terminalControl, agentVersion, options);
|
|
1387
817
|
}
|
|
1388
818
|
async observeNativeInspection(agent, terminalControl, request, options = {}) {
|
|
1389
|
-
|
|
1390
|
-
if (!adapter.observeNativeInspection) {
|
|
1391
|
-
throw new Error(`${adapter.displayName} native inspection observation is not supported`);
|
|
1392
|
-
}
|
|
1393
|
-
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
1394
|
-
runtime: options.runtime,
|
|
1395
|
-
scrollbackLines: options.scrollbackLines
|
|
1396
|
-
});
|
|
1397
|
-
const screenDigest = nativeInspectionScreenFingerprint(captured.screen);
|
|
1398
|
-
const adapterObservation = adapter.observeNativeInspection({
|
|
1399
|
-
operation: request.operation,
|
|
1400
|
-
previousScreenFingerprint: request.previousScreenFingerprint,
|
|
1401
|
-
preEnterEvidenceInventory: request.preEnterEvidenceInventory,
|
|
1402
|
-
expectedNativeThreadId: request.expectedNativeThreadId,
|
|
1403
|
-
expectedAgentVersion: request.expectedAgentVersion,
|
|
1404
|
-
expectedCwd: request.expectedCwd,
|
|
1405
|
-
screen: captured.screen
|
|
1406
|
-
});
|
|
1407
|
-
const observation = adapter.agent === "claude" &&
|
|
1408
|
-
options.runtime?.requireExactClaudeAgentRow === true &&
|
|
1409
|
-
adapterObservation.status === "observed" &&
|
|
1410
|
-
adapterObservation.evidence === "claude_status_panel"
|
|
1411
|
-
? {
|
|
1412
|
-
...adapterObservation,
|
|
1413
|
-
evidence: "claude_status_panel+claude_agents_exact_pid"
|
|
1414
|
-
}
|
|
1415
|
-
: adapterObservation;
|
|
1416
|
-
return {
|
|
1417
|
-
terminalControl: captured.terminalControl,
|
|
1418
|
-
status: statusFromInspection(adapter, captured.terminalControl, captured.inspection, { screen: captured.screen, runtime: options.runtime }),
|
|
1419
|
-
screenDigest,
|
|
1420
|
-
observation
|
|
1421
|
-
};
|
|
819
|
+
return this.nativeInspectionBridge().observeNativeInspection(agent, terminalControl, request, options);
|
|
1422
820
|
}
|
|
1423
821
|
/**
|
|
1424
822
|
* Dismiss one exact adapter-owned modal result after re-observing the same
|
|
@@ -1426,507 +824,7 @@ export class TerminalAgentBridge {
|
|
|
1426
824
|
* attempt and no automated retry across an uncertain dismissal boundary.
|
|
1427
825
|
*/
|
|
1428
826
|
async dismissNativeInspection(agent, terminalControl, plan, request, expectedEvidenceFingerprint, options = {}) {
|
|
1429
|
-
|
|
1430
|
-
try {
|
|
1431
|
-
assertClosedStatusInspectionPlan(adapter, terminalControl, plan);
|
|
1432
|
-
assertClosedNativeInspectionDismissal(plan);
|
|
1433
|
-
}
|
|
1434
|
-
catch (error) {
|
|
1435
|
-
throw new NativeInspectionDismissalError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
1436
|
-
}
|
|
1437
|
-
const observeExactPanel = async (control) => {
|
|
1438
|
-
const captured = await this.captureInspection(adapter, control, {
|
|
1439
|
-
runtime: options.runtime,
|
|
1440
|
-
scrollbackLines: options.scrollbackLines
|
|
1441
|
-
});
|
|
1442
|
-
const observation = adapter.observeNativeInspection?.({
|
|
1443
|
-
operation: request.operation,
|
|
1444
|
-
previousScreenFingerprint: request.previousScreenFingerprint,
|
|
1445
|
-
preEnterEvidenceInventory: request.preEnterEvidenceInventory,
|
|
1446
|
-
expectedNativeThreadId: request.expectedNativeThreadId,
|
|
1447
|
-
expectedAgentVersion: request.expectedAgentVersion,
|
|
1448
|
-
expectedCwd: request.expectedCwd,
|
|
1449
|
-
screen: captured.screen
|
|
1450
|
-
});
|
|
1451
|
-
if (!observation ||
|
|
1452
|
-
observation.status !== "observed" ||
|
|
1453
|
-
observation.result?.kind !== "native_status" ||
|
|
1454
|
-
observation.evidenceFingerprint !== expectedEvidenceFingerprint) {
|
|
1455
|
-
throw new Error(observation?.reason ??
|
|
1456
|
-
"the exact fresh native status panel changed before dismissal");
|
|
1457
|
-
}
|
|
1458
|
-
const verified = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, options.runtime);
|
|
1459
|
-
if (!sameTerminalControlIdentity(captured.terminalControl, verified)) {
|
|
1460
|
-
throw new Error("terminal control identity changed before native status dismissal");
|
|
1461
|
-
}
|
|
1462
|
-
return verified;
|
|
1463
|
-
};
|
|
1464
|
-
try {
|
|
1465
|
-
let verified = await observeExactPanel(terminalControl);
|
|
1466
|
-
await options.beforeDismiss?.({
|
|
1467
|
-
agent: adapter.agent,
|
|
1468
|
-
terminalControl: verified,
|
|
1469
|
-
plan,
|
|
1470
|
-
evidenceFingerprint: expectedEvidenceFingerprint
|
|
1471
|
-
});
|
|
1472
|
-
verified = await observeExactPanel(verified);
|
|
1473
|
-
try {
|
|
1474
|
-
await this.terminalProvider.sendKeys(this.terminalProvider.endpoint(verified), plan.expectedResult.dismissal.keys);
|
|
1475
|
-
}
|
|
1476
|
-
catch (error) {
|
|
1477
|
-
throw new NativeInspectionDismissalError("native status modal dismissal outcome is uncertain; do not retry automatically", { cause: error });
|
|
1478
|
-
}
|
|
1479
|
-
return {
|
|
1480
|
-
stage: "dismiss_dispatched",
|
|
1481
|
-
agent: adapter.agent,
|
|
1482
|
-
terminalControl: verified,
|
|
1483
|
-
keys: plan.expectedResult.dismissal.keys,
|
|
1484
|
-
dismissCount: 1
|
|
1485
|
-
};
|
|
1486
|
-
}
|
|
1487
|
-
catch (error) {
|
|
1488
|
-
if (error instanceof NativeInspectionDismissalError) {
|
|
1489
|
-
throw error;
|
|
1490
|
-
}
|
|
1491
|
-
throw new NativeInspectionDismissalError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
async captureCodexReadyComposer(adapter, terminalControl, plan, runtime) {
|
|
1495
|
-
if (adapter.agent !== "codex") {
|
|
1496
|
-
throw new NativeInspectionDiagnosticError("unsupported_profile", "the closed Codex /status probe requires the Codex adapter");
|
|
1497
|
-
}
|
|
1498
|
-
const minimumViewport = CODEX_NATIVE_STATUS_MIN_VIEWPORT_BY_PROFILE[plan.behaviorProfile];
|
|
1499
|
-
if (minimumViewport === undefined) {
|
|
1500
|
-
throw new NativeInspectionDiagnosticError("unsupported_profile", `Codex ${plan.behaviorProfile} has no exact /status viewport profile`);
|
|
1501
|
-
}
|
|
1502
|
-
const captureReady = async (control) => {
|
|
1503
|
-
const verified = await this.verifyTerminalIdentity(adapter.agent, control, runtime);
|
|
1504
|
-
const endpoint = this.terminalProvider.endpoint(verified);
|
|
1505
|
-
const viewportInspector = this.terminalProvider.inspectViewport;
|
|
1506
|
-
let exactViewport;
|
|
1507
|
-
let viewportUnavailableReason = "terminal provider has no exact viewport inspector";
|
|
1508
|
-
if (viewportInspector) {
|
|
1509
|
-
let viewport;
|
|
1510
|
-
try {
|
|
1511
|
-
viewport = await viewportInspector.call(this.terminalProvider, endpoint);
|
|
1512
|
-
}
|
|
1513
|
-
catch (error) {
|
|
1514
|
-
throw new NativeInspectionDiagnosticError("viewport_unavailable", `Codex /status viewport inspection failed before terminal input: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1515
|
-
}
|
|
1516
|
-
if (viewport) {
|
|
1517
|
-
if (!Number.isSafeInteger(viewport.columns) ||
|
|
1518
|
-
viewport.columns <= 0 ||
|
|
1519
|
-
!Number.isSafeInteger(viewport.rows) ||
|
|
1520
|
-
viewport.rows <= 0) {
|
|
1521
|
-
throw new NativeInspectionDiagnosticError("viewport_unavailable", "Codex /status viewport inspector returned invalid geometry");
|
|
1522
|
-
}
|
|
1523
|
-
exactViewport = viewport.columns;
|
|
1524
|
-
}
|
|
1525
|
-
else {
|
|
1526
|
-
viewportUnavailableReason =
|
|
1527
|
-
"terminal provider could not prove exact viewport geometry";
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
const styledScreen = await this.terminalProvider.capture(endpoint, { scrollbackLines: 40, preserveEscapes: true });
|
|
1531
|
-
const plainScreen = stripTerminalEscapeSequences(styledScreen);
|
|
1532
|
-
const inspection = adapter.inspectScreen({ screen: plainScreen, runtime });
|
|
1533
|
-
assertNativeInspectionComposerSafe(inspection, adapter.displayName);
|
|
1534
|
-
const composer = exactCodexReadyStyledComposerCapture(styledScreen);
|
|
1535
|
-
if (!composer) {
|
|
1536
|
-
throw new NativeInspectionDiagnosticError("composer_not_ready", "Codex composer contains non-placeholder input or is not at the exact idle prompt");
|
|
1537
|
-
}
|
|
1538
|
-
const inferredViewport = inferCodexVisibleViewportColumns(styledScreen);
|
|
1539
|
-
const observedViewport = exactViewport ?? inferredViewport;
|
|
1540
|
-
if ((observedViewport !== undefined && observedViewport < minimumViewport) ||
|
|
1541
|
-
hasTruncatedCodexStatusSessionLine(plainScreen)) {
|
|
1542
|
-
throw new NativeInspectionDiagnosticError("viewport_too_narrow", `Codex /status requires a proven viewport of at least ` +
|
|
1543
|
-
`${minimumViewport} columns to preserve the complete Session UUID` +
|
|
1544
|
-
`${observedViewport === undefined
|
|
1545
|
-
? ""
|
|
1546
|
-
: `; observed ${observedViewport}`}; widen or zoom the pane before retrying`);
|
|
1547
|
-
}
|
|
1548
|
-
if (exactViewport === undefined) {
|
|
1549
|
-
throw new NativeInspectionDiagnosticError("viewport_unavailable", `Codex /status requires exact terminal viewport geometry before input; ` +
|
|
1550
|
-
viewportUnavailableReason +
|
|
1551
|
-
`${inferredViewport === undefined
|
|
1552
|
-
? ""
|
|
1553
|
-
: ` (ANSI fallback estimated ${inferredViewport} columns)`}`);
|
|
1554
|
-
}
|
|
1555
|
-
const reverified = await this.verifyTerminalIdentity(adapter.agent, verified, runtime);
|
|
1556
|
-
if (!sameTerminalControlIdentity(verified, reverified)) {
|
|
1557
|
-
throw new NativeInspectionDiagnosticError("identity_unverified", "terminal control identity changed after the Codex pre-text composer capture");
|
|
1558
|
-
}
|
|
1559
|
-
return {
|
|
1560
|
-
terminalControl: reverified,
|
|
1561
|
-
screenDigest: nativeInspectionScreenFingerprint(styledScreen),
|
|
1562
|
-
composerDigest: composer.digest
|
|
1563
|
-
};
|
|
1564
|
-
};
|
|
1565
|
-
const first = await captureReady(terminalControl);
|
|
1566
|
-
const second = await captureReady(first.terminalControl);
|
|
1567
|
-
if (second.composerDigest !== first.composerDigest) {
|
|
1568
|
-
throw new NativeInspectionDiagnosticError("composer_not_ready", "Codex empty composer changed across its stable pre-text captures");
|
|
1569
|
-
}
|
|
1570
|
-
return {
|
|
1571
|
-
terminalControl: second.terminalControl,
|
|
1572
|
-
screenDigest: second.screenDigest
|
|
1573
|
-
};
|
|
1574
|
-
}
|
|
1575
|
-
async assertFinalCodexStatusViewport(terminalControl, plan, runtime) {
|
|
1576
|
-
const minimumViewport = CODEX_NATIVE_STATUS_MIN_VIEWPORT_BY_PROFILE[plan.behaviorProfile];
|
|
1577
|
-
if (minimumViewport === undefined) {
|
|
1578
|
-
throw new NativeInspectionDiagnosticError("unsupported_profile", `Codex ${plan.behaviorProfile} has no exact /status viewport profile`);
|
|
1579
|
-
}
|
|
1580
|
-
const verified = await this.verifyTerminalIdentity("codex", terminalControl, runtime);
|
|
1581
|
-
const viewportInspector = this.terminalProvider.inspectViewport;
|
|
1582
|
-
if (!viewportInspector) {
|
|
1583
|
-
throw new NativeInspectionDiagnosticError("viewport_unavailable", "Codex /status requires exact terminal viewport geometry immediately before Enter");
|
|
1584
|
-
}
|
|
1585
|
-
let viewport;
|
|
1586
|
-
try {
|
|
1587
|
-
viewport = await viewportInspector.call(this.terminalProvider, this.terminalProvider.endpoint(verified));
|
|
1588
|
-
}
|
|
1589
|
-
catch (error) {
|
|
1590
|
-
throw new NativeInspectionDiagnosticError("viewport_unavailable", `Codex /status viewport inspection failed immediately before Enter: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1591
|
-
}
|
|
1592
|
-
if (!viewport ||
|
|
1593
|
-
!Number.isSafeInteger(viewport.columns) ||
|
|
1594
|
-
viewport.columns <= 0 ||
|
|
1595
|
-
!Number.isSafeInteger(viewport.rows) ||
|
|
1596
|
-
viewport.rows <= 0) {
|
|
1597
|
-
throw new NativeInspectionDiagnosticError("viewport_unavailable", "Codex /status exact terminal viewport became unavailable immediately before Enter");
|
|
1598
|
-
}
|
|
1599
|
-
if (viewport.columns < minimumViewport) {
|
|
1600
|
-
throw new NativeInspectionDiagnosticError("viewport_too_narrow", `Codex /status viewport narrowed to ${viewport.columns} columns before Enter; ` +
|
|
1601
|
-
`at least ${minimumViewport} are required to preserve the complete Session UUID`);
|
|
1602
|
-
}
|
|
1603
|
-
const reverified = await this.verifyTerminalIdentity("codex", verified, runtime);
|
|
1604
|
-
if (!sameTerminalControlIdentity(verified, reverified)) {
|
|
1605
|
-
throw new NativeInspectionDiagnosticError("identity_unverified", "terminal control identity changed after the final Codex viewport proof");
|
|
1606
|
-
}
|
|
1607
|
-
return reverified;
|
|
1608
|
-
}
|
|
1609
|
-
async settleNativeInspectionComposer(adapter, terminalControl, plan, runtime) {
|
|
1610
|
-
const startedAt = this.nowMs();
|
|
1611
|
-
const settleTimeoutMs = plan.composer.maximumSettleMs;
|
|
1612
|
-
let stableDigest;
|
|
1613
|
-
let stableKind;
|
|
1614
|
-
let stableSince;
|
|
1615
|
-
let stableCaptures = 0;
|
|
1616
|
-
let capturesBeyondDeadline = 0;
|
|
1617
|
-
let lastMismatchDiagnostic = "composer_not_exact";
|
|
1618
|
-
while (true) {
|
|
1619
|
-
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
1620
|
-
runtime,
|
|
1621
|
-
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
1622
|
-
});
|
|
1623
|
-
assertNativeInspectionComposerSafe(captured.inspection);
|
|
1624
|
-
const materialized = exactNativeInspectionComposerCapture(adapter.agent, captured.screen, plan);
|
|
1625
|
-
const now = this.nowMs();
|
|
1626
|
-
if (materialized) {
|
|
1627
|
-
if (materialized.digest === stableDigest &&
|
|
1628
|
-
materialized.kind === stableKind) {
|
|
1629
|
-
stableCaptures += 1;
|
|
1630
|
-
}
|
|
1631
|
-
else {
|
|
1632
|
-
stableDigest = materialized.digest;
|
|
1633
|
-
stableKind = materialized.kind;
|
|
1634
|
-
stableSince = now;
|
|
1635
|
-
stableCaptures = 1;
|
|
1636
|
-
}
|
|
1637
|
-
const stableForMs = stableSince === undefined ? 0 : now - stableSince;
|
|
1638
|
-
if (stableCaptures >= CODEX_MULTILINE_STABLE_CAPTURES &&
|
|
1639
|
-
stableForMs >= plan.composer.minimumStableMs) {
|
|
1640
|
-
const evidence = {
|
|
1641
|
-
kind: materialized.kind,
|
|
1642
|
-
digest: materialized.digest,
|
|
1643
|
-
stableForMs,
|
|
1644
|
-
stableCaptures
|
|
1645
|
-
};
|
|
1646
|
-
return this.revalidateNativeInspectionComposer(adapter, captured.terminalControl, plan, evidence, runtime);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
else {
|
|
1650
|
-
lastMismatchDiagnostic = adapter.agent === "codex"
|
|
1651
|
-
? codexNativeInspectionComposerMismatchDiagnostic(captured.screen, plan)
|
|
1652
|
-
: "composer_not_exact";
|
|
1653
|
-
stableDigest = undefined;
|
|
1654
|
-
stableKind = undefined;
|
|
1655
|
-
stableSince = undefined;
|
|
1656
|
-
stableCaptures = 0;
|
|
1657
|
-
}
|
|
1658
|
-
const elapsed = this.nowMs() - startedAt;
|
|
1659
|
-
const remaining = settleTimeoutMs - elapsed;
|
|
1660
|
-
if (remaining <= 0 && !materialized) {
|
|
1661
|
-
break;
|
|
1662
|
-
}
|
|
1663
|
-
if (remaining <= 0) {
|
|
1664
|
-
capturesBeyondDeadline += 1;
|
|
1665
|
-
if (capturesBeyondDeadline > CODEX_EXACT_CANDIDATE_GRACE_CAPTURES) {
|
|
1666
|
-
break;
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
const remainingStableMs = plan.composer.minimumStableMs -
|
|
1670
|
-
(stableSince === undefined ? 0 : this.nowMs() - stableSince);
|
|
1671
|
-
await this.sleep(Math.min(CODEX_MULTILINE_SETTLE_POLL_MS, remaining > 0
|
|
1672
|
-
? remaining
|
|
1673
|
-
: Math.max(1, remainingStableMs)));
|
|
1674
|
-
}
|
|
1675
|
-
throw new NativeInspectionDiagnosticError(lastMismatchDiagnostic, lastMismatchDiagnostic === "composer_viewport_truncated"
|
|
1676
|
-
? `${adapter.displayName} /status slash popup was truncated by the viewport; widen or zoom the pane before retrying manually`
|
|
1677
|
-
: `${adapter.displayName} /status composer did not become exact, idle, and stable before the bounded submit deadline`);
|
|
1678
|
-
}
|
|
1679
|
-
async revalidateNativeInspectionComposer(adapter, terminalControl, plan, expected, runtime) {
|
|
1680
|
-
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
1681
|
-
runtime,
|
|
1682
|
-
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES
|
|
1683
|
-
});
|
|
1684
|
-
assertNativeInspectionComposerSafe(captured.inspection);
|
|
1685
|
-
const materialized = exactNativeInspectionComposerCapture(adapter.agent, captured.screen, plan);
|
|
1686
|
-
if (!materialized ||
|
|
1687
|
-
materialized.digest !== expected.digest ||
|
|
1688
|
-
materialized.kind !== expected.kind) {
|
|
1689
|
-
throw new NativeInspectionDiagnosticError("composer_drift", `${adapter.displayName} /status composer changed after its stable pre-submit capture`);
|
|
1690
|
-
}
|
|
1691
|
-
const baseline = adapter.observeNativeInspection?.({
|
|
1692
|
-
operation: plan.operation,
|
|
1693
|
-
screen: captured.screen
|
|
1694
|
-
});
|
|
1695
|
-
if (!baseline ||
|
|
1696
|
-
baseline.status === "ambiguous" ||
|
|
1697
|
-
!Array.isArray(baseline.evidenceInventory)) {
|
|
1698
|
-
throw new NativeInspectionDiagnosticError("evidence_unproven", baseline?.reason ??
|
|
1699
|
-
`${adapter.displayName} /status pre-Enter evidence inventory was not proven`);
|
|
1700
|
-
}
|
|
1701
|
-
const verifiedImmediatelyBeforeEnter = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, runtime);
|
|
1702
|
-
if (!sameTerminalControlIdentity(captured.terminalControl, verifiedImmediatelyBeforeEnter)) {
|
|
1703
|
-
throw new NativeInspectionDiagnosticError("identity_unverified", `terminal control identity changed after the final ${adapter.displayName} /status composer capture`);
|
|
1704
|
-
}
|
|
1705
|
-
return {
|
|
1706
|
-
terminalControl: verifiedImmediatelyBeforeEnter,
|
|
1707
|
-
screenDigest: nativeInspectionScreenFingerprint(captured.screen),
|
|
1708
|
-
evidenceInventory: baseline.evidenceInventory,
|
|
1709
|
-
materialization: expected
|
|
1710
|
-
};
|
|
1711
|
-
}
|
|
1712
|
-
async settleCodexMultilineComposer(adapter, terminalControl, expectedText, runtime, options = {}) {
|
|
1713
|
-
const observation = await this.settleCodexComposerObservation(adapter, terminalControl, expectedText, runtime, {
|
|
1714
|
-
minimumStableMs: CODEX_PASTE_ENTER_SETTLE_MS,
|
|
1715
|
-
requiredState: "exact_draft",
|
|
1716
|
-
allowOpaqueLargePastePlaceholder: options.allowOpaqueLargePastePlaceholder === true,
|
|
1717
|
-
allowWorkingComposer: options.allowWorkingComposer === true
|
|
1718
|
-
});
|
|
1719
|
-
if (observation.state === "exact_draft") {
|
|
1720
|
-
return observation.terminalControl;
|
|
1721
|
-
}
|
|
1722
|
-
if (observation.state === "working" ||
|
|
1723
|
-
observation.state === "approval_or_modal") {
|
|
1724
|
-
throw new Error("Codex became busy or blocked while its multiline composer was settling");
|
|
1725
|
-
}
|
|
1726
|
-
if (observation.state === "identity_drift") {
|
|
1727
|
-
throw new Error(observation.reason);
|
|
1728
|
-
}
|
|
1729
|
-
throw new Error("Codex multiline composer did not become exact, idle, and stable before the bounded submit deadline");
|
|
1730
|
-
}
|
|
1731
|
-
async settleCodexComposerObservation(adapter, terminalControl, expectedText, runtime, options) {
|
|
1732
|
-
const startedAt = this.nowMs();
|
|
1733
|
-
let candidate;
|
|
1734
|
-
let stableSince;
|
|
1735
|
-
let stableCaptures = 0;
|
|
1736
|
-
let finishObservedExactCandidate = false;
|
|
1737
|
-
let capturesBeyondDeadline = 0;
|
|
1738
|
-
let control = terminalControl;
|
|
1739
|
-
while (true) {
|
|
1740
|
-
const snapshot = await this.captureCodexComposerSnapshot(adapter, control, expectedText, runtime, {
|
|
1741
|
-
allowOpaqueLargePastePlaceholder: options.allowOpaqueLargePastePlaceholder === true,
|
|
1742
|
-
classifyOpaqueLargePasteAsDifferent: options.classifyOpaqueLargePasteAsDifferent === true,
|
|
1743
|
-
allowWorkingComposer: options.allowWorkingComposer === true
|
|
1744
|
-
});
|
|
1745
|
-
if (snapshot.state === "identity_drift" ||
|
|
1746
|
-
(snapshot.state === "unavailable" && snapshot.retryable !== true)) {
|
|
1747
|
-
return snapshot;
|
|
1748
|
-
}
|
|
1749
|
-
if (this.nowMs() - startedAt > CODEX_MULTILINE_SETTLE_TIMEOUT_MS) {
|
|
1750
|
-
capturesBeyondDeadline += 1;
|
|
1751
|
-
if (capturesBeyondDeadline > CODEX_EXACT_CANDIDATE_GRACE_CAPTURES) {
|
|
1752
|
-
return {
|
|
1753
|
-
state: "unavailable",
|
|
1754
|
-
reason: "Codex exact composer did not stabilize within its bounded capture grace"
|
|
1755
|
-
};
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
control = snapshot.terminalControl;
|
|
1759
|
-
const sameCandidate = candidate !== undefined &&
|
|
1760
|
-
candidate.state === snapshot.state &&
|
|
1761
|
-
candidate.digest === snapshot.digest &&
|
|
1762
|
-
sameTerminalControlIdentity(candidate.terminalControl, snapshot.terminalControl);
|
|
1763
|
-
if (sameCandidate) {
|
|
1764
|
-
stableCaptures += 1;
|
|
1765
|
-
}
|
|
1766
|
-
else {
|
|
1767
|
-
candidate = snapshot;
|
|
1768
|
-
stableSince = this.nowMs();
|
|
1769
|
-
stableCaptures = 1;
|
|
1770
|
-
finishObservedExactCandidate = snapshot.state === "exact_draft";
|
|
1771
|
-
}
|
|
1772
|
-
const stableForMs = stableSince === undefined
|
|
1773
|
-
? 0
|
|
1774
|
-
: this.nowMs() - stableSince;
|
|
1775
|
-
if (stableCaptures >= CODEX_MULTILINE_STABLE_CAPTURES &&
|
|
1776
|
-
stableForMs >= options.minimumStableMs &&
|
|
1777
|
-
snapshot.state !== "unavailable" &&
|
|
1778
|
-
(options.requiredState === undefined ||
|
|
1779
|
-
snapshot.state === options.requiredState ||
|
|
1780
|
-
snapshot.state === "working" ||
|
|
1781
|
-
snapshot.state === "approval_or_modal")) {
|
|
1782
|
-
// The caller's authorization hook must see evidence from a final,
|
|
1783
|
-
// independently identity-fenced recapture, not the polling capture.
|
|
1784
|
-
const finalSnapshot = await this.captureCodexComposerSnapshot(adapter, snapshot.terminalControl, expectedText, runtime, {
|
|
1785
|
-
allowOpaqueLargePastePlaceholder: options.allowOpaqueLargePastePlaceholder === true,
|
|
1786
|
-
classifyOpaqueLargePasteAsDifferent: options.classifyOpaqueLargePasteAsDifferent === true,
|
|
1787
|
-
allowWorkingComposer: options.allowWorkingComposer === true
|
|
1788
|
-
});
|
|
1789
|
-
if (finalSnapshot.state === snapshot.state &&
|
|
1790
|
-
"digest" in finalSnapshot &&
|
|
1791
|
-
finalSnapshot.digest === snapshot.digest &&
|
|
1792
|
-
sameTerminalControlIdentity(snapshot.terminalControl, finalSnapshot.terminalControl)) {
|
|
1793
|
-
return {
|
|
1794
|
-
...finalSnapshot,
|
|
1795
|
-
stableCaptures: stableCaptures + 1
|
|
1796
|
-
};
|
|
1797
|
-
}
|
|
1798
|
-
if (finalSnapshot.state === "identity_drift" ||
|
|
1799
|
-
(finalSnapshot.state === "unavailable" &&
|
|
1800
|
-
finalSnapshot.retryable !== true)) {
|
|
1801
|
-
return finalSnapshot;
|
|
1802
|
-
}
|
|
1803
|
-
if (options.requiredState !== undefined &&
|
|
1804
|
-
snapshot.state === options.requiredState) {
|
|
1805
|
-
return {
|
|
1806
|
-
state: "unavailable",
|
|
1807
|
-
reason: "Codex composer changed after its stable final recapture"
|
|
1808
|
-
};
|
|
1809
|
-
}
|
|
1810
|
-
candidate = finalSnapshot;
|
|
1811
|
-
control = finalSnapshot.terminalControl;
|
|
1812
|
-
stableSince = this.nowMs();
|
|
1813
|
-
stableCaptures = 1;
|
|
1814
|
-
finishObservedExactCandidate = finalSnapshot.state === "exact_draft";
|
|
1815
|
-
}
|
|
1816
|
-
const elapsed = this.nowMs() - startedAt;
|
|
1817
|
-
if (elapsed > CODEX_MULTILINE_SETTLE_TIMEOUT_MS &&
|
|
1818
|
-
!finishObservedExactCandidate) {
|
|
1819
|
-
return {
|
|
1820
|
-
state: "unavailable",
|
|
1821
|
-
reason: options.requiredState
|
|
1822
|
-
? `Codex composer did not become ${options.requiredState} before the bounded observation deadline`
|
|
1823
|
-
: "Codex composer did not become stable before the bounded observation deadline"
|
|
1824
|
-
};
|
|
1825
|
-
}
|
|
1826
|
-
if (elapsed > CODEX_MULTILINE_SETTLE_TIMEOUT_MS &&
|
|
1827
|
-
options.requiredState !== undefined &&
|
|
1828
|
-
snapshot.state !== options.requiredState) {
|
|
1829
|
-
if (snapshot.state !== "unavailable") {
|
|
1830
|
-
return {
|
|
1831
|
-
...snapshot,
|
|
1832
|
-
stableCaptures
|
|
1833
|
-
};
|
|
1834
|
-
}
|
|
1835
|
-
return {
|
|
1836
|
-
state: "unavailable",
|
|
1837
|
-
reason: snapshot.reason
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1840
|
-
const remainingStableMs = options.minimumStableMs - stableForMs;
|
|
1841
|
-
await this.sleep(Math.max(1, Math.min(CODEX_MULTILINE_SETTLE_POLL_MS, remainingStableMs > 0
|
|
1842
|
-
? remainingStableMs
|
|
1843
|
-
: CODEX_MULTILINE_SETTLE_POLL_MS)));
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
async captureCodexComposerSnapshot(adapter, terminalControl, expectedText, runtime, options = {}) {
|
|
1847
|
-
let verifiedBefore;
|
|
1848
|
-
try {
|
|
1849
|
-
verifiedBefore = await this.verifyTerminalIdentity(adapter.agent, terminalControl, runtime);
|
|
1850
|
-
}
|
|
1851
|
-
catch (error) {
|
|
1852
|
-
return {
|
|
1853
|
-
state: "identity_drift",
|
|
1854
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
1855
|
-
};
|
|
1856
|
-
}
|
|
1857
|
-
let styledScreen;
|
|
1858
|
-
try {
|
|
1859
|
-
styledScreen = await this.terminalProvider.capture(this.terminalProvider.endpoint(verifiedBefore), {
|
|
1860
|
-
scrollbackLines: CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES,
|
|
1861
|
-
preserveEscapes: true
|
|
1862
|
-
});
|
|
1863
|
-
}
|
|
1864
|
-
catch (error) {
|
|
1865
|
-
return {
|
|
1866
|
-
state: "unavailable",
|
|
1867
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
1868
|
-
};
|
|
1869
|
-
}
|
|
1870
|
-
const screen = stripTerminalEscapeSequences(styledScreen);
|
|
1871
|
-
let inspection;
|
|
1872
|
-
try {
|
|
1873
|
-
inspection = adapter.inspectScreen({ screen, runtime });
|
|
1874
|
-
}
|
|
1875
|
-
catch (error) {
|
|
1876
|
-
return {
|
|
1877
|
-
state: "unavailable",
|
|
1878
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
1879
|
-
};
|
|
1880
|
-
}
|
|
1881
|
-
let verifiedAfter;
|
|
1882
|
-
try {
|
|
1883
|
-
verifiedAfter = await this.verifyTerminalIdentity(adapter.agent, verifiedBefore, runtime);
|
|
1884
|
-
}
|
|
1885
|
-
catch (error) {
|
|
1886
|
-
return {
|
|
1887
|
-
state: "identity_drift",
|
|
1888
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
1889
|
-
};
|
|
1890
|
-
}
|
|
1891
|
-
if (!sameTerminalControlIdentity(verifiedBefore, verifiedAfter)) {
|
|
1892
|
-
return {
|
|
1893
|
-
state: "identity_drift",
|
|
1894
|
-
reason: "terminal control identity changed across the Codex composer capture"
|
|
1895
|
-
};
|
|
1896
|
-
}
|
|
1897
|
-
const screenDigest = createHash("sha256").update(styledScreen).digest("hex");
|
|
1898
|
-
if (inspection.approval.blocked ||
|
|
1899
|
-
inspection.activity.state === "awaiting_approval" ||
|
|
1900
|
-
codexBlockingModalVisible(screen)) {
|
|
1901
|
-
return {
|
|
1902
|
-
state: "approval_or_modal",
|
|
1903
|
-
terminalControl: verifiedAfter,
|
|
1904
|
-
digest: screenDigest
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
if (inspection.activity.state === "working" &&
|
|
1908
|
-
options.allowWorkingComposer !== true) {
|
|
1909
|
-
return {
|
|
1910
|
-
state: "working",
|
|
1911
|
-
terminalControl: verifiedAfter,
|
|
1912
|
-
digest: screenDigest
|
|
1913
|
-
};
|
|
1914
|
-
}
|
|
1915
|
-
const composer = currentCodexComposerCapture(styledScreen, expectedText, options.allowOpaqueLargePastePlaceholder === true, options.classifyOpaqueLargePasteAsDifferent === true);
|
|
1916
|
-
if (!composer) {
|
|
1917
|
-
return {
|
|
1918
|
-
state: "unavailable",
|
|
1919
|
-
reason: "the current live Codex composer could not be proven",
|
|
1920
|
-
retryable: true,
|
|
1921
|
-
terminalControl: verifiedAfter,
|
|
1922
|
-
digest: screenDigest
|
|
1923
|
-
};
|
|
1924
|
-
}
|
|
1925
|
-
return {
|
|
1926
|
-
state: composer.state,
|
|
1927
|
-
terminalControl: verifiedAfter,
|
|
1928
|
-
digest: composer.digest
|
|
1929
|
-
};
|
|
827
|
+
return this.nativeInspectionBridge().dismissNativeInspection(agent, terminalControl, plan, request, expectedEvidenceFingerprint, options);
|
|
1930
828
|
}
|
|
1931
829
|
/**
|
|
1932
830
|
* Prove that the exact managed draft is still present after Enter dispatch.
|
|
@@ -2276,114 +1174,7 @@ export class TerminalAgentBridge {
|
|
|
2276
1174
|
};
|
|
2277
1175
|
}
|
|
2278
1176
|
async respondInteraction(agent, terminalControl, response, options) {
|
|
2279
|
-
|
|
2280
|
-
...options.runtime,
|
|
2281
|
-
agentVersion: options.agentVersion
|
|
2282
|
-
};
|
|
2283
|
-
const optionReason = terminalInteractionOptionPreflightReason(options, runtime);
|
|
2284
|
-
if (optionReason) {
|
|
2285
|
-
return blockedTerminalInteractionResponse(response, optionReason);
|
|
2286
|
-
}
|
|
2287
|
-
const first = await this.captureTerminalInteractionOffer(agent, terminalControl, runtime, options.scrollbackLines).catch((error) => error);
|
|
2288
|
-
if (first instanceof Error) {
|
|
2289
|
-
return blockedTerminalInteractionResponse(response, first.message);
|
|
2290
|
-
}
|
|
2291
|
-
const firstReason = terminalInteractionOfferPreflightReason(first.offer, options, this.now());
|
|
2292
|
-
if (firstReason || !first.offer) {
|
|
2293
|
-
return blockedTerminalInteractionResponse(response, firstReason ?? "native questionnaire has no safe interaction offer", first.offer);
|
|
2294
|
-
}
|
|
2295
|
-
const validatedResponse = validateAnyTerminalInteractionResponse(response, first.offer.projection, {
|
|
2296
|
-
now: this.now(),
|
|
2297
|
-
// The capture immediately above is the authoritative freshness proof.
|
|
2298
|
-
// A wall-clock bucket boundary must not override exact live terminal
|
|
2299
|
-
// evidence before any input has been sent.
|
|
2300
|
-
allowExpiredForLiveRecapture: true
|
|
2301
|
-
});
|
|
2302
|
-
const planReason = terminalInteractionPlanPreflightReason(validatedResponse, first.offer.actionPlan);
|
|
2303
|
-
if (planReason) {
|
|
2304
|
-
return blockedTerminalInteractionResponse(validatedResponse, planReason, first.offer);
|
|
2305
|
-
}
|
|
2306
|
-
const capabilityReason = terminalInteractionCapabilityReason(this.terminalProvider, first.terminalControl, first.offer.actionPlan);
|
|
2307
|
-
if (capabilityReason) {
|
|
2308
|
-
return blockedTerminalInteractionResponse(validatedResponse, capabilityReason, first.offer);
|
|
2309
|
-
}
|
|
2310
|
-
const authorization = await authorizeTerminalInteraction(options.authorize, interactionHookContext(agent, first.terminalControl, first.offer, validatedResponse, runtime));
|
|
2311
|
-
if (!authorization.approved) {
|
|
2312
|
-
return blockedTerminalInteractionResponse(validatedResponse, authorization.reason ?? "terminal interaction response was not authorized", first.offer);
|
|
2313
|
-
}
|
|
2314
|
-
const afterAuthorization = await this.captureTerminalInteractionOffer(agent, first.terminalControl, runtime, options.scrollbackLines).catch((error) => error);
|
|
2315
|
-
if (afterAuthorization instanceof Error) {
|
|
2316
|
-
return blockedTerminalInteractionResponse(validatedResponse, afterAuthorization.message, first.offer);
|
|
2317
|
-
}
|
|
2318
|
-
const afterAuthorizationReason = terminalInteractionOfferPreflightReason(afterAuthorization.offer, options, this.now());
|
|
2319
|
-
if (afterAuthorizationReason) {
|
|
2320
|
-
return blockedTerminalInteractionResponse(validatedResponse, afterAuthorizationReason, first.offer);
|
|
2321
|
-
}
|
|
2322
|
-
if (!sameTerminalInteractionOffer(first, afterAuthorization, {
|
|
2323
|
-
// expires_at is generated from AKK wall-clock freshness buckets, not
|
|
2324
|
-
// from the native prompt. All semantic projection and action-plan fields
|
|
2325
|
-
// still have to match exactly.
|
|
2326
|
-
ignoreExpiresAt: true
|
|
2327
|
-
})) {
|
|
2328
|
-
return blockedTerminalInteractionResponse(validatedResponse, "native questionnaire changed after authorization", first.offer);
|
|
2329
|
-
}
|
|
2330
|
-
await reserveTerminalInteractionDispatch(options.beforeDispatch, interactionHookContext(agent, afterAuthorization.terminalControl, afterAuthorization.offer, validatedResponse, runtime));
|
|
2331
|
-
const finalCapture = await this.captureTerminalInteractionOffer(agent, afterAuthorization.terminalControl, runtime, options.scrollbackLines).catch((error) => {
|
|
2332
|
-
if (options.beforeDispatch) {
|
|
2333
|
-
throw terminalInteractionInputNotStartedError(error, "cannot recapture native questionnaire after dispatch reservation");
|
|
2334
|
-
}
|
|
2335
|
-
return error;
|
|
2336
|
-
});
|
|
2337
|
-
if (finalCapture instanceof Error) {
|
|
2338
|
-
return blockedTerminalInteractionResponse(validatedResponse, finalCapture.message, first.offer);
|
|
2339
|
-
}
|
|
2340
|
-
if (!sameTerminalInteractionOffer(afterAuthorization, finalCapture, {
|
|
2341
|
-
// The projection expiry is bucketed and may roll over during this final,
|
|
2342
|
-
// bounded recapture even though the questionnaire is exact. Prompt,
|
|
2343
|
-
// action plan, terminal identity, and owner remain exact-match fences.
|
|
2344
|
-
ignoreExpiresAt: true
|
|
2345
|
-
})) {
|
|
2346
|
-
if (options.beforeDispatch) {
|
|
2347
|
-
throw new TerminalInteractionInputNotStartedError("native questionnaire changed after dispatch reservation");
|
|
2348
|
-
}
|
|
2349
|
-
return blockedTerminalInteractionResponse(validatedResponse, "native questionnaire changed before dispatch", first.offer);
|
|
2350
|
-
}
|
|
2351
|
-
const verified = await this.verifyTerminalIdentity(agent, finalCapture.terminalControl, runtime).catch((error) => {
|
|
2352
|
-
if (options.beforeDispatch) {
|
|
2353
|
-
throw terminalInteractionInputNotStartedError(error, "terminal identity could not be verified immediately before interaction dispatch");
|
|
2354
|
-
}
|
|
2355
|
-
throw terminalInteractionReservedError("key_uncertain", error, "terminal identity could not be verified immediately before interaction dispatch");
|
|
2356
|
-
});
|
|
2357
|
-
if (!sameTerminalControlIdentity(finalCapture.terminalControl, verified)) {
|
|
2358
|
-
if (options.beforeDispatch) {
|
|
2359
|
-
throw new TerminalInteractionInputNotStartedError("terminal identity changed after the final questionnaire capture");
|
|
2360
|
-
}
|
|
2361
|
-
throw new TerminalInteractionDispatchReservedError("key_uncertain", "terminal identity changed after the final questionnaire capture");
|
|
2362
|
-
}
|
|
2363
|
-
const answer = validatedResponse.answers[0];
|
|
2364
|
-
const outcome = await dispatchTerminalInteractionAnswer({
|
|
2365
|
-
agent,
|
|
2366
|
-
provider: this.terminalProvider,
|
|
2367
|
-
terminalControl: verified,
|
|
2368
|
-
actionPlan: finalCapture.offer.actionPlan,
|
|
2369
|
-
answer,
|
|
2370
|
-
sleep: this.sleep,
|
|
2371
|
-
reverifyTerminalIdentity: async (currentControl) => {
|
|
2372
|
-
const reverified = await this.verifyTerminalIdentity(agent, currentControl, runtime);
|
|
2373
|
-
if (!sameTerminalControlIdentity(currentControl, reverified)) {
|
|
2374
|
-
throw new Error("terminal identity changed between interaction text and Enter");
|
|
2375
|
-
}
|
|
2376
|
-
return reverified;
|
|
2377
|
-
}
|
|
2378
|
-
});
|
|
2379
|
-
return {
|
|
2380
|
-
responded: true,
|
|
2381
|
-
blocked: false,
|
|
2382
|
-
interactionId: validatedResponse.interaction_id,
|
|
2383
|
-
questionId: answer.question_id,
|
|
2384
|
-
responseKind: answer.response_kind,
|
|
2385
|
-
outcome
|
|
2386
|
-
};
|
|
1177
|
+
return this.interactionResponseBridge().respond(agent, terminalControl, response, options);
|
|
2387
1178
|
}
|
|
2388
1179
|
async monitorPoll(options) {
|
|
2389
1180
|
const adapter = this.registry.require(options.agent);
|
|
@@ -2455,25 +1246,6 @@ export class TerminalAgentBridge {
|
|
|
2455
1246
|
})
|
|
2456
1247
|
};
|
|
2457
1248
|
}
|
|
2458
|
-
async captureTerminalInteractionOffer(agent, terminalControl, runtime, scrollbackLines) {
|
|
2459
|
-
const adapter = this.registry.require(agent);
|
|
2460
|
-
const captured = await this.captureInspection(adapter, terminalControl, {
|
|
2461
|
-
scrollbackLines,
|
|
2462
|
-
runtime
|
|
2463
|
-
});
|
|
2464
|
-
return {
|
|
2465
|
-
terminalControl: captured.terminalControl,
|
|
2466
|
-
inspection: captured.inspection,
|
|
2467
|
-
offer: captureTerminalInteractionRuntimeOffer({
|
|
2468
|
-
agent,
|
|
2469
|
-
terminalControl: captured.terminalControl,
|
|
2470
|
-
screen: captured.screen,
|
|
2471
|
-
runtime,
|
|
2472
|
-
now: this.now(),
|
|
2473
|
-
approvalBlocked: captured.inspection.approval.blocked
|
|
2474
|
-
})
|
|
2475
|
-
};
|
|
2476
|
-
}
|
|
2477
1249
|
async verifyTerminalIdentity(agent, terminalControl, runtime) {
|
|
2478
1250
|
let verifiedControl = terminalControl;
|
|
2479
1251
|
if (this.verifyIdentity) {
|
|
@@ -2500,938 +1272,27 @@ export class TerminalAgentBridge {
|
|
|
2500
1272
|
return this.terminalProvider.toControlRef(resolvedEndpoint, verifiedControl.capabilities);
|
|
2501
1273
|
}
|
|
2502
1274
|
}
|
|
2503
|
-
function
|
|
2504
|
-
|
|
2505
|
-
!terminal.capabilities.includes(capability));
|
|
2506
|
-
const missingTransport = transport.filter((capability) => !provider.providerCapabilities.includes(capability));
|
|
2507
|
-
if (missingSemantic.length === 0 && missingTransport.length === 0) {
|
|
2508
|
-
return;
|
|
2509
|
-
}
|
|
2510
|
-
const missing = [
|
|
2511
|
-
...missingSemantic.map((value) => `terminal:${value}`),
|
|
2512
|
-
...missingTransport.map((value) => `provider:${value}`)
|
|
2513
|
-
];
|
|
2514
|
-
throw new TerminalControlInputNotSentError(`terminal action capability preflight failed for ` +
|
|
2515
|
-
`${terminal.identity.providerKind}:${terminal.route.label}: ` +
|
|
2516
|
-
missing.join(", "));
|
|
2517
|
-
}
|
|
2518
|
-
function nativeInspectionSubmissionError(stage, error, fallbackDiagnostic) {
|
|
2519
|
-
const diagnostic = error instanceof NativeInspectionSubmissionError
|
|
2520
|
-
? error.diagnostic
|
|
2521
|
-
: error instanceof NativeInspectionDiagnosticError
|
|
2522
|
-
? error.diagnostic
|
|
2523
|
-
: fallbackDiagnostic;
|
|
2524
|
-
if (error instanceof NativeInspectionSubmissionError) {
|
|
2525
|
-
const stageRank = {
|
|
2526
|
-
not_started: 0,
|
|
2527
|
-
text_injected: 1,
|
|
2528
|
-
enter_uncertain: 2
|
|
2529
|
-
};
|
|
2530
|
-
if (stageRank[error.stage] >= stageRank[stage]) {
|
|
2531
|
-
return error;
|
|
2532
|
-
}
|
|
2533
|
-
return new NativeInspectionSubmissionError(stage, error.message, {
|
|
2534
|
-
cause: error,
|
|
2535
|
-
diagnostic
|
|
2536
|
-
});
|
|
2537
|
-
}
|
|
2538
|
-
return new NativeInspectionSubmissionError(stage, error instanceof Error ? error.message : String(error), { cause: error, diagnostic });
|
|
2539
|
-
}
|
|
2540
|
-
function assertClosedStatusInspectionPlan(adapter, terminalControl, plan) {
|
|
2541
|
-
if (!terminalControl.capabilities.includes("send_keys")) {
|
|
2542
|
-
throw new Error(`${adapter.displayName} terminal input is not supported`);
|
|
2543
|
-
}
|
|
2544
|
-
if (!terminalControl.capabilities.includes("screen_status")) {
|
|
2545
|
-
throw new Error(`${adapter.displayName} terminal screen inspection is not supported`);
|
|
2546
|
-
}
|
|
2547
|
-
const codexProfile = adapter.agent === "codex" &&
|
|
2548
|
-
CODEX_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile] !== undefined;
|
|
2549
|
-
const claudeProfile = adapter.agent === "claude" &&
|
|
2550
|
-
CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile] !== undefined;
|
|
2551
|
-
const expectedSettle = codexProfile
|
|
2552
|
-
? { minimumStableMs: CODEX_PASTE_ENTER_SETTLE_MS, maximumSettleMs: 2_000 }
|
|
2553
|
-
: CLAUDE_NATIVE_STATUS_SETTLE_BY_PROFILE[plan.behaviorProfile];
|
|
2554
|
-
const exactPresentation = codexProfile
|
|
2555
|
-
? plan.expectedResult.presentation === "inline" &&
|
|
2556
|
-
plan.expectedResult.dismissal === undefined &&
|
|
2557
|
-
plan.composer.minimumStableMs === expectedSettle?.minimumStableMs &&
|
|
2558
|
-
plan.composer.maximumSettleMs === expectedSettle.maximumSettleMs
|
|
2559
|
-
: claudeProfile
|
|
2560
|
-
? plan.expectedResult.presentation === "modal" &&
|
|
2561
|
-
plan.composer.minimumStableMs === expectedSettle?.minimumStableMs &&
|
|
2562
|
-
plan.composer.maximumSettleMs === expectedSettle.maximumSettleMs &&
|
|
2563
|
-
plan.expectedResult.dismissal?.expected === "idle_empty_composer" &&
|
|
2564
|
-
JSON.stringify(plan.expectedResult.dismissal.keys) ===
|
|
2565
|
-
JSON.stringify(["Escape"])
|
|
2566
|
-
: false;
|
|
2567
|
-
if (plan.operation.kind !== "status" ||
|
|
2568
|
-
plan.command !== "/status" ||
|
|
2569
|
-
plan.effect !== "read_only" ||
|
|
2570
|
-
plan.requiresIdle !== true ||
|
|
2571
|
-
plan.composer.kind !== "exact" ||
|
|
2572
|
-
!Number.isFinite(plan.composer.minimumStableMs) ||
|
|
2573
|
-
plan.composer.minimumStableMs < 0 ||
|
|
2574
|
-
!Number.isFinite(plan.composer.maximumSettleMs) ||
|
|
2575
|
-
plan.composer.maximumSettleMs < plan.composer.minimumStableMs ||
|
|
2576
|
-
plan.expectedResult.kind !== "native_status" ||
|
|
2577
|
-
!exactPresentation) {
|
|
2578
|
-
throw new Error("refusing a non-closed native inspection plan");
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
function assertClosedNativeInspectionDismissal(plan) {
|
|
2582
|
-
if (plan.expectedResult.presentation !== "modal" ||
|
|
2583
|
-
plan.expectedResult.dismissal?.expected !== "idle_empty_composer" ||
|
|
2584
|
-
JSON.stringify(plan.expectedResult.dismissal.keys) !==
|
|
2585
|
-
JSON.stringify(["Escape"])) {
|
|
2586
|
-
throw new Error("native inspection has no closed modal dismissal plan");
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
function assertNativeInspectionComposerSafe(inspection, displayName = "terminal agent") {
|
|
2590
|
-
if (inspection.approval.blocked ||
|
|
2591
|
-
inspection.activity.state === "awaiting_approval" ||
|
|
2592
|
-
inspection.activity.state === "working") {
|
|
2593
|
-
throw new NativeInspectionDiagnosticError("composer_not_ready", `${displayName} became busy or blocked while its /status composer was settling`);
|
|
2594
|
-
}
|
|
2595
|
-
// Codex's generic activity parser deliberately reports a non-empty slash
|
|
2596
|
-
// composer as unknown. At this stage the caller has already proved an idle,
|
|
2597
|
-
// empty styled composer under the terminal lock; the exact-current composer
|
|
2598
|
-
// capture below is the stronger continuation proof after AKK injected only
|
|
2599
|
-
// the adapter-owned /status command.
|
|
1275
|
+
async function terminalSettleDelay(milliseconds) {
|
|
1276
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
2600
1277
|
}
|
|
2601
|
-
function
|
|
2602
|
-
return
|
|
2603
|
-
? exactCodexNativeInspectionComposerCapture(screen, plan)
|
|
2604
|
-
: exactClaudeNativeInspectionComposerCapture(screen, plan);
|
|
1278
|
+
function sameTerminalControlIdentity(left, right) {
|
|
1279
|
+
return sameTerminalControlIncarnation(left, right);
|
|
2605
1280
|
}
|
|
2606
|
-
function
|
|
2607
|
-
|
|
2608
|
-
let currentComposerIndex = -1;
|
|
2609
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
2610
|
-
if (CODEX_COMPOSER_MARKER.test(lines[index])) {
|
|
2611
|
-
currentComposerIndex = index;
|
|
2612
|
-
break;
|
|
2613
|
-
}
|
|
2614
|
-
}
|
|
2615
|
-
if (currentComposerIndex < 0) {
|
|
1281
|
+
export function terminalApprovalFingerprint(agent, terminalControl, inspection, options = {}) {
|
|
1282
|
+
if (!inspection.approval.approvable) {
|
|
2616
1283
|
return undefined;
|
|
2617
1284
|
}
|
|
2618
|
-
const
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
if (
|
|
1285
|
+
const decision = options.decision ?? "approve_once";
|
|
1286
|
+
const choices = terminalApprovalChoices(inspection.approval);
|
|
1287
|
+
const action = terminalApprovalActionForDecision(inspection.approval, decision);
|
|
1288
|
+
if (!action) {
|
|
2622
1289
|
return undefined;
|
|
2623
1290
|
}
|
|
2624
|
-
const
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
}
|
|
2630
|
-
const popupRows = region.slice(1).filter((line) => line.trim().length > 0);
|
|
2631
|
-
let kind;
|
|
2632
|
-
if (popupRows.length === 0) {
|
|
2633
|
-
kind = "exact_slash_composer";
|
|
2634
|
-
}
|
|
2635
|
-
else if (JSON.stringify(popupRows.map((line) => line.trimEnd())) ===
|
|
2636
|
-
JSON.stringify(CODEX_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile])) {
|
|
2637
|
-
kind = "exact_slash_popup";
|
|
2638
|
-
}
|
|
2639
|
-
else {
|
|
2640
|
-
return undefined;
|
|
2641
|
-
}
|
|
2642
|
-
return {
|
|
2643
|
-
kind,
|
|
2644
|
-
digest: createHash("sha256").update(region.join("\n")).digest("hex")
|
|
2645
|
-
};
|
|
2646
|
-
}
|
|
2647
|
-
function exactClaudeNativeInspectionComposerCapture(screen, plan) {
|
|
2648
|
-
const frame = exactClaudeComposerFrame(screen);
|
|
2649
|
-
if (!frame) {
|
|
2650
|
-
return undefined;
|
|
2651
|
-
}
|
|
2652
|
-
const { lines, openIndex, closeIndex, composerRows, trailing } = frame;
|
|
2653
|
-
if (composerRows.length !== 1 ||
|
|
2654
|
-
!/^\s*❯(?:\s|$)/u.test(composerRows[0]) ||
|
|
2655
|
-
composerRows[0].replace(/^\s*❯\s?/u, "").trimEnd() !== plan.command) {
|
|
2656
|
-
return undefined;
|
|
2657
|
-
}
|
|
2658
|
-
let kind;
|
|
2659
|
-
if (trailing.length === 0 || claudeNativeInspectionTrailingIsFooter(trailing)) {
|
|
2660
|
-
kind = "exact_slash_composer";
|
|
2661
|
-
}
|
|
2662
|
-
else {
|
|
2663
|
-
const suggestions = [];
|
|
2664
|
-
for (const line of trailing) {
|
|
2665
|
-
const trimmed = line.trim();
|
|
2666
|
-
if (trimmed.startsWith("/")) {
|
|
2667
|
-
suggestions.push(trimmed.replace(/\s+/gu, " "));
|
|
2668
|
-
}
|
|
2669
|
-
else if (suggestions.length > 0) {
|
|
2670
|
-
suggestions[suggestions.length - 1] +=
|
|
2671
|
-
` ${trimmed.replace(/\s+/gu, " ")}`;
|
|
2672
|
-
}
|
|
2673
|
-
else {
|
|
2674
|
-
return undefined;
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
if (!closedClaudeNativeStatusSuggestionsMatch(suggestions, CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile])) {
|
|
2678
|
-
return undefined;
|
|
2679
|
-
}
|
|
2680
|
-
kind = "exact_slash_popup";
|
|
2681
|
-
}
|
|
2682
|
-
return {
|
|
2683
|
-
kind,
|
|
2684
|
-
digest: createHash("sha256")
|
|
2685
|
-
.update(lines.slice(openIndex, closeIndex + 1).concat(trailing).join("\n"))
|
|
2686
|
-
.digest("hex")
|
|
2687
|
-
};
|
|
2688
|
-
}
|
|
2689
|
-
/**
|
|
2690
|
-
* Claude truncates a suggestion row with a Unicode ellipsis at narrow pane
|
|
2691
|
-
* widths. Keep authorization closed by accepting only an exact ordered row or
|
|
2692
|
-
* an explicit ellipsis whose preceding text is an exact, non-trivial prefix of
|
|
2693
|
-
* that same profiled row. A caller still cannot introduce, omit, or reorder a
|
|
2694
|
-
* slash command.
|
|
2695
|
-
*/
|
|
2696
|
-
function closedClaudeNativeStatusSuggestionsMatch(observed, expected) {
|
|
2697
|
-
if (!expected || observed.length !== expected.length) {
|
|
2698
|
-
return false;
|
|
2699
|
-
}
|
|
2700
|
-
return observed.every((row, index) => {
|
|
2701
|
-
const exact = expected[index];
|
|
2702
|
-
if (row === exact) {
|
|
2703
|
-
return true;
|
|
2704
|
-
}
|
|
2705
|
-
if (!row.endsWith("…")) {
|
|
2706
|
-
return false;
|
|
2707
|
-
}
|
|
2708
|
-
const prefix = row.slice(0, -1);
|
|
2709
|
-
const commandEnd = exact.indexOf(" ");
|
|
2710
|
-
return (commandEnd > 0 &&
|
|
2711
|
-
prefix.length >= commandEnd + 12 &&
|
|
2712
|
-
exact.startsWith(prefix));
|
|
2713
|
-
});
|
|
2714
|
-
}
|
|
2715
|
-
/**
|
|
2716
|
-
* Prove Claude Code's exact current idle input frame. This is shared by every
|
|
2717
|
-
* automated-input path: a loose or historical `❯` prompt is not authority to
|
|
2718
|
-
* inject text into the terminal.
|
|
2719
|
-
*/
|
|
2720
|
-
export function isExactClaudeIdleComposer(screen) {
|
|
2721
|
-
const frame = exactClaudeComposerFrame(screen);
|
|
2722
|
-
if (!frame) {
|
|
2723
|
-
return false;
|
|
2724
|
-
}
|
|
2725
|
-
return (frame.composerRows.length === 1 &&
|
|
2726
|
-
/^\s*❯\s*$/u.test(frame.composerRows[0]) &&
|
|
2727
|
-
(frame.trailing.length === 0 ||
|
|
2728
|
-
claudeNativeInspectionTrailingIsFooter(frame.trailing)));
|
|
2729
|
-
}
|
|
2730
|
-
/**
|
|
2731
|
-
* Compatibility export retained for callers that adopted the native-status
|
|
2732
|
-
* name before the same exact-frame proof was reused by lifecycle handoff.
|
|
2733
|
-
*/
|
|
2734
|
-
export function isExactClaudeNativeInspectionIdleComposer(screen) {
|
|
2735
|
-
return isExactClaudeIdleComposer(screen);
|
|
2736
|
-
}
|
|
2737
|
-
function exactClaudeComposerFrame(screen) {
|
|
2738
|
-
const lines = screen.replace(/\r\n?/gu, "\n").replace(/\u00a0/gu, " ")
|
|
2739
|
-
.split("\n");
|
|
2740
|
-
const dividerIndexes = lines
|
|
2741
|
-
.map((line, index) => /^\s*[─━]{8,}\s*$/u.test(line) ? index : -1)
|
|
2742
|
-
.filter((index) => index >= 0);
|
|
2743
|
-
if (dividerIndexes.length < 2) {
|
|
2744
|
-
return undefined;
|
|
2745
|
-
}
|
|
2746
|
-
const closeIndex = dividerIndexes.at(-1);
|
|
2747
|
-
const openIndex = dividerIndexes.at(-2);
|
|
2748
|
-
return {
|
|
2749
|
-
lines,
|
|
2750
|
-
openIndex,
|
|
2751
|
-
closeIndex,
|
|
2752
|
-
composerRows: lines.slice(openIndex + 1, closeIndex)
|
|
2753
|
-
.filter((line) => line.trim().length > 0),
|
|
2754
|
-
trailing: lines.slice(closeIndex + 1)
|
|
2755
|
-
.filter((line) => line.trim().length > 0)
|
|
2756
|
-
};
|
|
2757
|
-
}
|
|
2758
|
-
function claudeNativeInspectionTrailingIsFooter(lines) {
|
|
2759
|
-
return lines.length <= 2 && lines.every((line) => /^\s*(?:[⏵⏴⏸]{1,2}|\?)\s*.*(?:manual mode|shift\+tab|accept edits|bypass permissions|for shortcuts|← for agents)/iu
|
|
2760
|
-
.test(line));
|
|
2761
|
-
}
|
|
2762
|
-
function nativeInspectionScreenFingerprint(screen) {
|
|
2763
|
-
return `sha256:${createHash("sha256").update(screen).digest("hex")}`;
|
|
2764
|
-
}
|
|
2765
|
-
function bareDigestFromNativeInspectionScreenFingerprint(fingerprint) {
|
|
2766
|
-
const match = /^sha256:([0-9a-f]{64})$/u.exec(fingerprint);
|
|
2767
|
-
if (!match) {
|
|
2768
|
-
throw new Error("native inspection screen fingerprint is malformed");
|
|
2769
|
-
}
|
|
2770
|
-
return match[1];
|
|
2771
|
-
}
|
|
2772
|
-
function stripTerminalEscapeSequences(value) {
|
|
2773
|
-
return value.replace(/\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/gu, "");
|
|
2774
|
-
}
|
|
2775
|
-
export function exactCodexReadyStyledComposerCapture(screen) {
|
|
2776
|
-
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
2777
|
-
while (lines.length > 0 &&
|
|
2778
|
-
stripTerminalEscapeSequences(lines.at(-1) ?? "").trim().length === 0) {
|
|
2779
|
-
lines.pop();
|
|
2780
|
-
}
|
|
2781
|
-
const composerLine = [...lines.slice(-12)].reverse().find((line) => CODEX_COMPOSER_MARKER.test(stripTerminalEscapeSequences(line).trimEnd()));
|
|
2782
|
-
if (composerLine === undefined) {
|
|
2783
|
-
return undefined;
|
|
2784
|
-
}
|
|
2785
|
-
let dim = false;
|
|
2786
|
-
const visible = [];
|
|
2787
|
-
for (let index = 0; index < composerLine.length;) {
|
|
2788
|
-
if (composerLine[index] === "\x1b") {
|
|
2789
|
-
const escape = /^(?:\x1B\[([0-9;]*)m|\x1B\][^\x07]*(?:\x07|\x1B\\))/u
|
|
2790
|
-
.exec(composerLine.slice(index));
|
|
2791
|
-
if (escape) {
|
|
2792
|
-
if (escape[1] !== undefined) {
|
|
2793
|
-
const codes = escape[1] === ""
|
|
2794
|
-
? [0]
|
|
2795
|
-
: escape[1].split(";").map((value) => Number(value));
|
|
2796
|
-
for (let codeIndex = 0; codeIndex < codes.length; codeIndex += 1) {
|
|
2797
|
-
const code = codes[codeIndex];
|
|
2798
|
-
if ([38, 48, 58].includes(code) &&
|
|
2799
|
-
codes[codeIndex + 1] === 2) {
|
|
2800
|
-
codeIndex += 4;
|
|
2801
|
-
continue;
|
|
2802
|
-
}
|
|
2803
|
-
if ([38, 48, 58].includes(code) &&
|
|
2804
|
-
codes[codeIndex + 1] === 5) {
|
|
2805
|
-
codeIndex += 2;
|
|
2806
|
-
continue;
|
|
2807
|
-
}
|
|
2808
|
-
if (code === 0 || code === 22) {
|
|
2809
|
-
dim = false;
|
|
2810
|
-
}
|
|
2811
|
-
else if (code === 2) {
|
|
2812
|
-
dim = true;
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
}
|
|
2816
|
-
index += escape[0].length;
|
|
2817
|
-
continue;
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
const codePoint = composerLine.codePointAt(index);
|
|
2821
|
-
if (codePoint === undefined) {
|
|
2822
|
-
break;
|
|
2823
|
-
}
|
|
2824
|
-
const character = String.fromCodePoint(codePoint);
|
|
2825
|
-
visible.push({ character, dim });
|
|
2826
|
-
index += character.length;
|
|
2827
|
-
}
|
|
2828
|
-
const promptIndex = visible.findIndex(({ character }) => character === "›" || character === "»");
|
|
2829
|
-
if (promptIndex < 0) {
|
|
2830
|
-
return undefined;
|
|
2831
|
-
}
|
|
2832
|
-
const content = visible.slice(promptIndex + 1)
|
|
2833
|
-
.filter(({ character }) => !/^\s$/u.test(character));
|
|
2834
|
-
if (content.length > 0 && !content.every((entry) => entry.dim)) {
|
|
2835
|
-
return undefined;
|
|
2836
|
-
}
|
|
2837
|
-
return {
|
|
2838
|
-
digest: createHash("sha256").update(composerLine).digest("hex")
|
|
2839
|
-
};
|
|
2840
|
-
}
|
|
2841
|
-
/**
|
|
2842
|
-
* Infer a viewport only from fixed-width visible-buffer rows. Trimmed captures
|
|
2843
|
-
* deliberately return undefined: a short content row is not proof of a short
|
|
2844
|
-
* terminal. This keeps the fallback provider-neutral and fail-closed only on
|
|
2845
|
-
* positive geometry evidence.
|
|
2846
|
-
*/
|
|
2847
|
-
function inferCodexVisibleViewportColumns(screen) {
|
|
2848
|
-
const rows = screen.replace(/\r\n?/gu, "\n").split("\n")
|
|
2849
|
-
.map(stripTerminalEscapeSequences);
|
|
2850
|
-
const widthOneRows = rows.filter((row) => /^[\x20-\x7e›»·─━╭╮╰╯│]*$/u.test(row));
|
|
2851
|
-
const maxWidth = widthOneRows.reduce((maximum, row) => Math.max(maximum, Array.from(row).length), 0);
|
|
2852
|
-
if (maxWidth < 20) {
|
|
2853
|
-
return undefined;
|
|
2854
|
-
}
|
|
2855
|
-
const paddedAtMax = widthOneRows.filter((row) => row.endsWith(" ") && Array.from(row).length === maxWidth);
|
|
2856
|
-
const composerAtMax = paddedAtMax.some((row) => CODEX_COMPOSER_MARKER.test(row.trimEnd()));
|
|
2857
|
-
return paddedAtMax.length >= 3 && composerAtMax
|
|
2858
|
-
? maxWidth
|
|
2859
|
-
: undefined;
|
|
2860
|
-
}
|
|
2861
|
-
function hasTruncatedCodexStatusSessionLine(screen) {
|
|
2862
|
-
return screen.replace(/\r\n?/gu, "\n").split("\n").some((line) => {
|
|
2863
|
-
const match = /^\s*│\s*Session:\s*([^│\s]+).*│?\s*$/iu.exec(line);
|
|
2864
|
-
if (!match) {
|
|
2865
|
-
return false;
|
|
2866
|
-
}
|
|
2867
|
-
return !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu
|
|
2868
|
-
.test(match[1]);
|
|
2869
|
-
});
|
|
2870
|
-
}
|
|
2871
|
-
function codexNativeInspectionComposerMismatchDiagnostic(screen, plan) {
|
|
2872
|
-
const expectedRows = CODEX_NATIVE_STATUS_POPUP_BY_PROFILE[plan.behaviorProfile];
|
|
2873
|
-
if (!expectedRows) {
|
|
2874
|
-
return "composer_not_exact";
|
|
2875
|
-
}
|
|
2876
|
-
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
2877
|
-
let composerIndex = -1;
|
|
2878
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
2879
|
-
if (CODEX_COMPOSER_MARKER.test(lines[index])) {
|
|
2880
|
-
composerIndex = index;
|
|
2881
|
-
break;
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
if (composerIndex < 0 ||
|
|
2885
|
-
lines[composerIndex].replace(/^[›»]\s?/u, "").trimEnd() !== plan.command) {
|
|
2886
|
-
return "composer_not_exact";
|
|
2887
|
-
}
|
|
2888
|
-
const footerIndex = lines.findIndex((line, index) => index > composerIndex && CODEX_COMPOSER_FOOTER.test(line.trim()));
|
|
2889
|
-
const popupRows = lines.slice(composerIndex + 1, footerIndex < 0 ? lines.length : footerIndex).filter((line) => line.trim().length > 0);
|
|
2890
|
-
if (popupRows.length === 0) {
|
|
2891
|
-
return "composer_not_exact";
|
|
2892
|
-
}
|
|
2893
|
-
const logicalRows = [];
|
|
2894
|
-
let observedTruncation = false;
|
|
2895
|
-
for (const row of popupRows) {
|
|
2896
|
-
const trimmed = row.trim().replace(/\s+/gu, " ");
|
|
2897
|
-
if (trimmed.startsWith("/")) {
|
|
2898
|
-
logicalRows.push(trimmed);
|
|
2899
|
-
}
|
|
2900
|
-
else if (logicalRows.length > 0) {
|
|
2901
|
-
logicalRows[logicalRows.length - 1] += ` ${trimmed}`;
|
|
2902
|
-
observedTruncation = true;
|
|
2903
|
-
}
|
|
2904
|
-
else {
|
|
2905
|
-
return "composer_not_exact";
|
|
2906
|
-
}
|
|
2907
|
-
observedTruncation ||= trimmed.endsWith("…");
|
|
2908
|
-
}
|
|
2909
|
-
const normalizedExpected = expectedRows.map((row) => row.trim().replace(/\s+/gu, " "));
|
|
2910
|
-
const everyKnownPrefix = logicalRows.length <= normalizedExpected.length &&
|
|
2911
|
-
logicalRows.every((row, index) => {
|
|
2912
|
-
const withoutEllipsis = row.endsWith("…")
|
|
2913
|
-
? row.slice(0, -1).trimEnd()
|
|
2914
|
-
: row;
|
|
2915
|
-
return normalizedExpected[index]?.startsWith(withoutEllipsis) === true;
|
|
2916
|
-
});
|
|
2917
|
-
return observedTruncation && everyKnownPrefix
|
|
2918
|
-
? "composer_viewport_truncated"
|
|
2919
|
-
: "composer_not_exact";
|
|
2920
|
-
}
|
|
2921
|
-
function codexBlockingModalVisible(screen) {
|
|
2922
|
-
const tail = screen.replace(/\r\n?/gu, "\n").split("\n").slice(-80)
|
|
2923
|
-
.join("\n");
|
|
2924
|
-
return /\b(?:press|use)\s+(?:esc|escape)\s+to\s+(?:cancel|close|dismiss)\b/iu
|
|
2925
|
-
.test(tail) ||
|
|
2926
|
-
/\besc\s+to\s+cancel\b/iu.test(tail) ||
|
|
2927
|
-
codexActiveWriterViewerVisible(tail) ||
|
|
2928
|
-
["expanded", "ambiguous"].includes(inspectCodexAsyncQuestionInputMode(tail));
|
|
2929
|
-
}
|
|
2930
|
-
function codexActiveWriterViewerVisible(styledScreen) {
|
|
2931
|
-
const lines = stripTerminalEscapeSequences(styledScreen)
|
|
2932
|
-
.replace(/\r\n?/gu, "\n")
|
|
2933
|
-
.split("\n")
|
|
2934
|
-
.slice(-80);
|
|
2935
|
-
let titleIndex = -1;
|
|
2936
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
2937
|
-
if (/^ {2}🔒(?:\s|$)/u.test(lines[index])) {
|
|
2938
|
-
titleIndex = index;
|
|
2939
|
-
break;
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
2942
|
-
if (titleIndex < 0) {
|
|
2943
|
-
return false;
|
|
2944
|
-
}
|
|
2945
|
-
const laterMainComposer = lines.findIndex((line, index) => index > titleIndex && CODEX_COMPOSER_MARKER.test(line));
|
|
2946
|
-
if (laterMainComposer >= 0) {
|
|
2947
|
-
return false;
|
|
2948
|
-
}
|
|
2949
|
-
const frame = lines.slice(titleIndex, titleIndex + 12)
|
|
2950
|
-
.map((line) => line.trim().replace(/\s+/gu, " "))
|
|
2951
|
-
.filter((line) => line.length > 0)
|
|
2952
|
-
.join(" ");
|
|
2953
|
-
return /🔒\s+This conversation is open in another app\s+\S+ to Retry/iu
|
|
2954
|
-
.test(frame) &&
|
|
2955
|
-
/Close it there and press\s+\S+\s+to continue here\./iu.test(frame) &&
|
|
2956
|
-
/\S+ retry\s+\S+ exit(?:\s+\S+ transcript)?/iu.test(frame);
|
|
2957
|
-
}
|
|
2958
|
-
const CODEX_ASYNC_QUESTION_FOOTER_LABEL = /\s(submit|skip|main prompt|prev question|next question|queued messages)(?=\s{2,}|\s*$)/giu;
|
|
2959
|
-
function codexStructuredAsyncFooterEvidence(lines, evidenceFloor) {
|
|
2960
|
-
let strongest = { index: -1, labelCount: 0 };
|
|
2961
|
-
for (let start = evidenceFloor + 1; start < lines.length; start += 1) {
|
|
2962
|
-
const labels = new Set();
|
|
2963
|
-
for (let end = start; end < Math.min(lines.length, start + 5); end += 1) {
|
|
2964
|
-
const line = lines[end];
|
|
2965
|
-
if (!/^ {2}(?![ ↳?])\S/u.test(line)) {
|
|
2966
|
-
break;
|
|
2967
|
-
}
|
|
2968
|
-
for (const match of line.matchAll(CODEX_ASYNC_QUESTION_FOOTER_LABEL)) {
|
|
2969
|
-
labels.add(match[1].toLowerCase());
|
|
2970
|
-
}
|
|
2971
|
-
if (labels.size > strongest.labelCount) {
|
|
2972
|
-
strongest = { index: end, labelCount: labels.size };
|
|
2973
|
-
}
|
|
2974
|
-
if (labels.size >= 2) {
|
|
2975
|
-
return strongest;
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
2979
|
-
return strongest;
|
|
2980
|
-
}
|
|
2981
|
-
function codexMainComposerFooterIndex(lines, composerIndex) {
|
|
2982
|
-
for (let index = lines.length - 1; index > composerIndex; index -= 1) {
|
|
2983
|
-
// A real statusline starts at column zero. Draft continuations are inset,
|
|
2984
|
-
// even when their text happens to look exactly like a model statusline.
|
|
2985
|
-
if (CODEX_COMPLETE_COMPOSER_FOOTER.test(lines[index].trimEnd())) {
|
|
2986
|
-
return index;
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
return -1;
|
|
2990
|
-
}
|
|
2991
|
-
/**
|
|
2992
|
-
* Classify Codex 0.154's non-blocking inline-question surface by current input
|
|
2993
|
-
* ownership. The shared `Queued follow-up inputs` heading is not sufficient:
|
|
2994
|
-
* ordinary queued messages use it too. An exact collapsed summary, after any
|
|
2995
|
-
* ordinary queue preview is removed, proves Send-safe ownership even when the
|
|
2996
|
-
* Composer is outside the viewport. Positive editor evidence without a
|
|
2997
|
-
* complete shape remains fail-closed because paste plus Enter would otherwise
|
|
2998
|
-
* submit the user's task as an answer.
|
|
2999
|
-
*/
|
|
3000
|
-
export function inspectCodexAsyncQuestionInputMode(styledScreen) {
|
|
3001
|
-
const lines = stripTerminalEscapeSequences(styledScreen)
|
|
3002
|
-
.replace(/\r\n?/gu, "\n")
|
|
3003
|
-
.replace(/\u00a0/gu, " ")
|
|
3004
|
-
.split("\n")
|
|
3005
|
-
.slice(-80);
|
|
3006
|
-
while (lines.length > 0 && lines.at(-1)?.trim().length === 0) {
|
|
3007
|
-
lines.pop();
|
|
3008
|
-
}
|
|
3009
|
-
const normalized = lines.map((line) => line.trim().replace(/\s+/gu, " "));
|
|
3010
|
-
let mainComposerIndex = -1;
|
|
3011
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
3012
|
-
// The main Composer marker is column zero. Async option and inline-text
|
|
3013
|
-
// markers are inset by the menu surface, even after ANSI is removed.
|
|
3014
|
-
if (CODEX_COMPOSER_MARKER.test(lines[index])) {
|
|
3015
|
-
mainComposerIndex = index;
|
|
3016
|
-
break;
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
let headerIndex = -1;
|
|
3020
|
-
let wrappedHeaderRows = 1;
|
|
3021
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
3022
|
-
if (/^•\s+Queued follow-up inputs\s*$/u.test(lines[index]) ||
|
|
3023
|
-
/^•\s+Queued follow-up inpu…\s*$/u.test(lines[index]) ||
|
|
3024
|
-
(/^•\s+Queued follow-up\s*$/u.test(lines[index]) &&
|
|
3025
|
-
/^ {2}inputs\s*$/u.test(lines[index + 1] ?? ""))) {
|
|
3026
|
-
headerIndex = index;
|
|
3027
|
-
wrappedHeaderRows = /^•\s+Queued follow-up\s*$/u.test(lines[index])
|
|
3028
|
-
? 2
|
|
3029
|
-
: 1;
|
|
3030
|
-
break;
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
|
-
const headerBodyIndex = headerIndex < 0
|
|
3034
|
-
? -1
|
|
3035
|
-
: headerIndex + wrappedHeaderRows;
|
|
3036
|
-
let questionEvidenceStart = headerBodyIndex;
|
|
3037
|
-
if (headerIndex >= 0) {
|
|
3038
|
-
let cursor = headerBodyIndex;
|
|
3039
|
-
while (cursor < lines.length && lines[cursor].trim().length === 0) {
|
|
3040
|
-
cursor += 1;
|
|
3041
|
-
}
|
|
3042
|
-
let sawQueuedPreview = false;
|
|
3043
|
-
while (cursor < lines.length) {
|
|
3044
|
-
if (/^ {2}↳\s/u.test(lines[cursor])) {
|
|
3045
|
-
sawQueuedPreview = true;
|
|
3046
|
-
cursor += 1;
|
|
3047
|
-
while (cursor < lines.length) {
|
|
3048
|
-
if (lines[cursor].trim().length === 0 ||
|
|
3049
|
-
/^ {4,}\S/u.test(lines[cursor])) {
|
|
3050
|
-
cursor += 1;
|
|
3051
|
-
continue;
|
|
3052
|
-
}
|
|
3053
|
-
break;
|
|
3054
|
-
}
|
|
3055
|
-
continue;
|
|
3056
|
-
}
|
|
3057
|
-
break;
|
|
3058
|
-
}
|
|
3059
|
-
if (sawQueuedPreview) {
|
|
3060
|
-
questionEvidenceStart = cursor;
|
|
3061
|
-
}
|
|
3062
|
-
}
|
|
3063
|
-
const mainComposerFooterIndex = mainComposerIndex < 0
|
|
3064
|
-
? -1
|
|
3065
|
-
: codexMainComposerFooterIndex(lines, mainComposerIndex);
|
|
3066
|
-
const summaryBeforeMainComposer = headerIndex < 0 || mainComposerIndex < 0
|
|
3067
|
-
? -1
|
|
3068
|
-
: normalized.findIndex((line, index) => index >= questionEvidenceStart && index < mainComposerIndex &&
|
|
3069
|
-
/^\?\s+\d+\s+questions?(?:\s+·\s+\d+s)?$/iu.test(line));
|
|
3070
|
-
if (mainComposerIndex >= 0 && mainComposerFooterIndex < 0) {
|
|
3071
|
-
// A statusline-disabled Composer is the strongest available ownership
|
|
3072
|
-
// anchor. Treat all following wrapped rows as its arbitrary draft instead
|
|
3073
|
-
// of letting question-like user text veto a human-priority Send.
|
|
3074
|
-
return summaryBeforeMainComposer >= 0 ? "collapsed" : "absent";
|
|
3075
|
-
}
|
|
3076
|
-
const evidenceFloor = Math.max(mainComposerFooterIndex, questionEvidenceStart - 1);
|
|
3077
|
-
const structuredFooter = codexStructuredAsyncFooterEvidence(lines, evidenceFloor);
|
|
3078
|
-
if (structuredFooter.labelCount >= 2) {
|
|
3079
|
-
return "expanded";
|
|
3080
|
-
}
|
|
3081
|
-
const clippedFooterIndex = normalized.findIndex((line, index) => index > evidenceFloor && /^ente(?:r)?…$/iu.test(line) &&
|
|
3082
|
-
/^ctrl…$/iu.test(normalized[index + 1] ?? ""));
|
|
3083
|
-
const clippedChoiceIndex = normalized.findIndex((line, index) => index > evidenceFloor &&
|
|
3084
|
-
line === "Expand terminal to read the entire option");
|
|
3085
|
-
if (clippedFooterIndex >= 0 || clippedChoiceIndex >= 0) {
|
|
3086
|
-
return "ambiguous";
|
|
3087
|
-
}
|
|
3088
|
-
if (headerIndex < 0) {
|
|
3089
|
-
const evidence = new Set();
|
|
3090
|
-
normalized.forEach((line, index) => {
|
|
3091
|
-
if (index <= evidenceFloor) {
|
|
3092
|
-
return;
|
|
3093
|
-
}
|
|
3094
|
-
if (/^\d+\s+of(?:\s+\d+)?$/iu.test(line)) {
|
|
3095
|
-
evidence.add("progress");
|
|
3096
|
-
}
|
|
3097
|
-
if (/^›\s+\S/u.test(line)) {
|
|
3098
|
-
evidence.add("selector");
|
|
3099
|
-
}
|
|
3100
|
-
if (/^Type your answer(?:\s*\(optional\))?$/iu.test(line)) {
|
|
3101
|
-
evidence.add("free_text");
|
|
3102
|
-
}
|
|
3103
|
-
});
|
|
3104
|
-
return evidence.size >= 2 ||
|
|
3105
|
-
(evidence.size >= 1 && structuredFooter.labelCount >= 1)
|
|
3106
|
-
? "ambiguous"
|
|
3107
|
-
: "absent";
|
|
3108
|
-
}
|
|
3109
|
-
const weakQuestionIndex = normalized.findIndex((line, index) => index > evidenceFloor && index >= questionEvidenceStart && (/^\d+\s+of(?:\s+\d+)?$/iu.test(line) ||
|
|
3110
|
-
/^›\s+\S/u.test(line) ||
|
|
3111
|
-
/^Type your answer(?:\s*\(optional\))?$/iu.test(line)));
|
|
3112
|
-
if (mainComposerIndex > headerIndex && weakQuestionIndex > evidenceFloor) {
|
|
3113
|
-
return "ambiguous";
|
|
3114
|
-
}
|
|
3115
|
-
const summaryIndex = normalized.findIndex((line, index) => index >= questionEvidenceStart &&
|
|
3116
|
-
(mainComposerIndex < 0 || index < mainComposerIndex) &&
|
|
3117
|
-
/^\?\s+\d+\s+questions?(?:\s+·\s+\d+s)?$/iu.test(line));
|
|
3118
|
-
if (summaryIndex >= 0) {
|
|
3119
|
-
return weakQuestionIndex >= 0 ? "ambiguous" : "collapsed";
|
|
3120
|
-
}
|
|
3121
|
-
if (mainComposerIndex > headerIndex) {
|
|
3122
|
-
return "absent";
|
|
3123
|
-
}
|
|
3124
|
-
const afterHeader = normalized.slice(questionEvidenceStart)
|
|
3125
|
-
.filter((line) => line.length > 0);
|
|
3126
|
-
const positiveAfterHeader = afterHeader.some((line) => /^\d+\s+of(?:\s+\d+)?$/iu.test(line) ||
|
|
3127
|
-
/^›\s+\d+\.(?:\s|$)/u.test(line) ||
|
|
3128
|
-
/^Type your answer(?:\s*\(optional\))?$/iu.test(line));
|
|
3129
|
-
if (positiveAfterHeader) {
|
|
3130
|
-
return "ambiguous";
|
|
3131
|
-
}
|
|
3132
|
-
// A bare heading (optionally followed by ↳ queued-message previews) is not
|
|
3133
|
-
// question authority. Preserve human-explicit Send in that ordinary state.
|
|
3134
|
-
if (afterHeader.length === 0) {
|
|
3135
|
-
return "absent";
|
|
3136
|
-
}
|
|
3137
|
-
// An expanded editor can temporarily replace its footer with a flash. If
|
|
3138
|
-
// there is question-like content under the shared heading and no proven main
|
|
3139
|
-
// Composer, do not guess which input surface will receive the next paste.
|
|
3140
|
-
return "ambiguous";
|
|
3141
|
-
}
|
|
3142
|
-
/**
|
|
3143
|
-
* Classify only the bottom live Codex composer region. A matching transcript
|
|
3144
|
-
* prompt elsewhere in scrollback is deliberately ignored.
|
|
3145
|
-
*/
|
|
3146
|
-
function currentCodexComposerCapture(styledScreen, expectedText, allowOpaqueLargePastePlaceholder = false, classifyOpaqueLargePasteAsDifferent = false) {
|
|
3147
|
-
const screen = stripTerminalEscapeSequences(styledScreen);
|
|
3148
|
-
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
3149
|
-
while (lines.length > 0 && lines.at(-1)?.trim().length === 0) {
|
|
3150
|
-
lines.pop();
|
|
3151
|
-
}
|
|
3152
|
-
let composerIndex = -1;
|
|
3153
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
3154
|
-
if (CODEX_COMPOSER_MARKER.test(lines[index])) {
|
|
3155
|
-
composerIndex = index;
|
|
3156
|
-
break;
|
|
3157
|
-
}
|
|
3158
|
-
}
|
|
3159
|
-
if (composerIndex < 0) {
|
|
3160
|
-
return undefined;
|
|
3161
|
-
}
|
|
3162
|
-
const footerIndex = lines.findIndex((line, index) => index > composerIndex &&
|
|
3163
|
-
CODEX_COMPLETE_COMPOSER_FOOTER.test(line.trim()));
|
|
3164
|
-
if (footerIndex >= 0 &&
|
|
3165
|
-
lines.slice(footerIndex + 1).some((line) => line.trim().length > 0)) {
|
|
3166
|
-
return undefined;
|
|
3167
|
-
}
|
|
3168
|
-
const regionEnd = footerIndex < 0 ? lines.length : footerIndex;
|
|
3169
|
-
const region = lines.slice(composerIndex, regionEnd);
|
|
3170
|
-
while (region.length > 1 && region.at(-1)?.trim().length === 0) {
|
|
3171
|
-
region.pop();
|
|
3172
|
-
}
|
|
3173
|
-
if (region.length === 0 ||
|
|
3174
|
-
region.slice(1).some((line) => line.length > 0 && !line.startsWith(" "))) {
|
|
3175
|
-
return undefined;
|
|
3176
|
-
}
|
|
3177
|
-
const bodyRows = [
|
|
3178
|
-
region[0].replace(/^[›»]\s?/u, ""),
|
|
3179
|
-
...region.slice(1).map((line) => line.startsWith(" ") ? line.slice(2) : line)
|
|
3180
|
-
];
|
|
3181
|
-
const digest = createHash("sha256").update(region.join("\n")).digest("hex");
|
|
3182
|
-
const positivelyEmpty = footerIndex >= 0 &&
|
|
3183
|
-
bodyRows.slice(1).every((row) => row.trim().length === 0) &&
|
|
3184
|
-
exactCodexReadyStyledComposerCapture(styledScreen) !== undefined;
|
|
3185
|
-
if (positivelyEmpty) {
|
|
3186
|
-
return { state: "exact_empty", digest };
|
|
3187
|
-
}
|
|
3188
|
-
if (footerIndex < 0) {
|
|
3189
|
-
return undefined;
|
|
3190
|
-
}
|
|
3191
|
-
const expectedComparable = composerComparableText(expectedText);
|
|
3192
|
-
const expectedCharacterCount = Array.from(expectedText).length;
|
|
3193
|
-
const comparable = composerComparableText(bodyRows.join("\n"));
|
|
3194
|
-
const opaqueLargePastePlaceholder = /^\[Pasted Content \d+ chars\]$/u.test(comparable);
|
|
3195
|
-
if (opaqueLargePastePlaceholder && !allowOpaqueLargePastePlaceholder) {
|
|
3196
|
-
return classifyOpaqueLargePasteAsDifferent
|
|
3197
|
-
? { state: "different_draft", digest }
|
|
3198
|
-
: undefined;
|
|
3199
|
-
}
|
|
3200
|
-
const exactVisibleDraft = terminalComposerRowsMatchExpected(bodyRows, expectedComparable);
|
|
3201
|
-
const exactLargePastePlaceholder = allowOpaqueLargePastePlaceholder &&
|
|
3202
|
-
expectedCharacterCount > CODEX_LARGE_PASTE_CHAR_THRESHOLD &&
|
|
3203
|
-
comparable === composerComparableText(`[Pasted Content ${expectedCharacterCount} chars]`);
|
|
3204
|
-
if (exactVisibleDraft || exactLargePastePlaceholder) {
|
|
3205
|
-
return { state: "exact_draft", digest };
|
|
3206
|
-
}
|
|
3207
|
-
return comparable.length > 0
|
|
3208
|
-
? { state: "different_draft", digest }
|
|
3209
|
-
: undefined;
|
|
3210
|
-
}
|
|
3211
|
-
function exactCodexComposerCapture(screen, expectedText) {
|
|
3212
|
-
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
3213
|
-
const expectedComparable = composerComparableText(expectedText);
|
|
3214
|
-
const expectedCharacterCount = Array.from(expectedText).length;
|
|
3215
|
-
const largePasteComparable = composerComparableText(`[Pasted Content ${expectedCharacterCount} chars]`);
|
|
3216
|
-
const matches = [];
|
|
3217
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
3218
|
-
if (!CODEX_COMPOSER_MARKER.test(lines[index])) {
|
|
3219
|
-
continue;
|
|
3220
|
-
}
|
|
3221
|
-
const footerIndex = lines.findIndex((line, candidateIndex) => candidateIndex > index && CODEX_COMPOSER_FOOTER.test(line.trim()));
|
|
3222
|
-
const region = lines.slice(index, footerIndex < 0 ? lines.length : footerIndex);
|
|
3223
|
-
while (region.length > 1 && region.at(-1)?.trim() === "") {
|
|
3224
|
-
region.pop();
|
|
3225
|
-
}
|
|
3226
|
-
const bodyRows = [
|
|
3227
|
-
region[0].replace(/^[›»]\s?/u, ""),
|
|
3228
|
-
...region.slice(1).map((line) => line.startsWith(" ") ? line.slice(2) : line)
|
|
3229
|
-
];
|
|
3230
|
-
const comparable = composerComparableText(bodyRows.join("\n"));
|
|
3231
|
-
const exactVisibleDraft = terminalComposerRowsMatchExpected(bodyRows, expectedComparable);
|
|
3232
|
-
const exactLargePastePlaceholder = expectedCharacterCount > CODEX_LARGE_PASTE_CHAR_THRESHOLD &&
|
|
3233
|
-
comparable === largePasteComparable;
|
|
3234
|
-
if (!exactVisibleDraft && !exactLargePastePlaceholder) {
|
|
3235
|
-
continue;
|
|
3236
|
-
}
|
|
3237
|
-
matches.push({
|
|
3238
|
-
digest: createHash("sha256")
|
|
3239
|
-
.update(region.join("\n"))
|
|
3240
|
-
.digest("hex")
|
|
3241
|
-
});
|
|
3242
|
-
}
|
|
3243
|
-
if (matches.length > 1) {
|
|
3244
|
-
throw new Error("multiple Codex composer regions matched the multiline terminal request");
|
|
3245
|
-
}
|
|
3246
|
-
return matches[0];
|
|
3247
|
-
}
|
|
3248
|
-
/**
|
|
3249
|
-
* Terminal UIs paint wrapped composer content as independent screen rows, so
|
|
3250
|
-
* a provider capture cannot distinguish a visual wrap from an authored
|
|
3251
|
-
* newline. Align the rows against the exact text AKK injected instead of
|
|
3252
|
-
* joining every row with `\n`.
|
|
3253
|
-
*
|
|
3254
|
-
* Only row boundaries are ambiguous: they may consume an authored newline, an
|
|
3255
|
-
* omitted run of ASCII spaces at a word wrap, or no character at a CJK/token
|
|
3256
|
-
* wrap. Every visible character remains character-for-character exact, and an
|
|
3257
|
-
* empty row can only advance through an authored newline (plus terminal-trimmed
|
|
3258
|
-
* spaces before it), so blank-line structure is preserved.
|
|
3259
|
-
*/
|
|
3260
|
-
function terminalComposerRowsMatchExpected(rows, expectedText) {
|
|
3261
|
-
const expected = composerComparableText(expectedText);
|
|
3262
|
-
if (rows.length === 0) {
|
|
3263
|
-
return expected.length === 0;
|
|
3264
|
-
}
|
|
3265
|
-
if (!expected.startsWith(rows[0])) {
|
|
3266
|
-
return false;
|
|
3267
|
-
}
|
|
3268
|
-
let offsets = new Set([rows[0].length]);
|
|
3269
|
-
for (let index = 1; index < rows.length && offsets.size > 0; index += 1) {
|
|
3270
|
-
const row = rows[index];
|
|
3271
|
-
const previousRow = rows[index - 1];
|
|
3272
|
-
const nextOffsets = new Set();
|
|
3273
|
-
for (const offset of offsets) {
|
|
3274
|
-
const candidateStarts = new Set();
|
|
3275
|
-
if (expected[offset] === "\n") {
|
|
3276
|
-
candidateStarts.add(offset + 1);
|
|
3277
|
-
}
|
|
3278
|
-
let whitespaceEnd = offset;
|
|
3279
|
-
while (expected[whitespaceEnd] === " ") {
|
|
3280
|
-
whitespaceEnd += 1;
|
|
3281
|
-
}
|
|
3282
|
-
if (previousRow.length > 0 && row.length > 0) {
|
|
3283
|
-
candidateStarts.add(offset);
|
|
3284
|
-
if (whitespaceEnd > offset) {
|
|
3285
|
-
candidateStarts.add(whitespaceEnd);
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
if (whitespaceEnd > offset &&
|
|
3289
|
-
expected[whitespaceEnd] === "\n") {
|
|
3290
|
-
candidateStarts.add(whitespaceEnd + 1);
|
|
3291
|
-
}
|
|
3292
|
-
for (const candidateStart of candidateStarts) {
|
|
3293
|
-
if (expected.startsWith(row, candidateStart)) {
|
|
3294
|
-
nextOffsets.add(candidateStart + row.length);
|
|
3295
|
-
}
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
offsets = nextOffsets;
|
|
3299
|
-
}
|
|
3300
|
-
return offsets.has(expected.length);
|
|
3301
|
-
}
|
|
3302
|
-
function exactTerminalComposerCapture(agent, screen, expectedText) {
|
|
3303
|
-
return agent === "codex"
|
|
3304
|
-
? exactCodexComposerCapture(screen, expectedText)
|
|
3305
|
-
: exactClaudeComposerCapture(screen, expectedText);
|
|
3306
|
-
}
|
|
3307
|
-
function exactClaudeComposerCapture(screen, expectedText) {
|
|
3308
|
-
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
3309
|
-
const dividerIndexes = lines
|
|
3310
|
-
.map((line, index) => /^\s*[─━]{8,}\s*$/u.test(line) ? index : -1)
|
|
3311
|
-
.filter((index) => index >= 0);
|
|
3312
|
-
if (dividerIndexes.length < 2) {
|
|
3313
|
-
return undefined;
|
|
3314
|
-
}
|
|
3315
|
-
const closeIndex = dividerIndexes.at(-1);
|
|
3316
|
-
const openIndex = dividerIndexes.at(-2);
|
|
3317
|
-
const trailing = lines.slice(closeIndex + 1)
|
|
3318
|
-
.filter((line) => line.trim().length > 0);
|
|
3319
|
-
if (trailing.length > 2 ||
|
|
3320
|
-
trailing.some((line) => !/^\s*(?:[⏵⏴⏸]{1,2}|\?)\s*.*(?:manual mode|shift\+tab|accept edits|bypass permissions|for shortcuts|← for agents)/iu
|
|
3321
|
-
.test(line))) {
|
|
3322
|
-
return undefined;
|
|
3323
|
-
}
|
|
3324
|
-
const region = lines.slice(openIndex + 1, closeIndex);
|
|
3325
|
-
while (region.length > 1 && region.at(-1)?.trim() === "") {
|
|
3326
|
-
region.pop();
|
|
3327
|
-
}
|
|
3328
|
-
if (region.length === 0 || !/^\s*❯(?:\s|$)/u.test(region[0])) {
|
|
3329
|
-
return undefined;
|
|
3330
|
-
}
|
|
3331
|
-
const bodyRows = [
|
|
3332
|
-
region[0].replace(/^\s*❯\s?/u, ""),
|
|
3333
|
-
...region.slice(1).map((line) => line.startsWith(" ") ? line.slice(2) : line)
|
|
3334
|
-
];
|
|
3335
|
-
if (!terminalComposerRowsMatchExpected(bodyRows, expectedText)) {
|
|
3336
|
-
return undefined;
|
|
3337
|
-
}
|
|
3338
|
-
return {
|
|
3339
|
-
digest: createHash("sha256")
|
|
3340
|
-
.update(lines.slice(openIndex, closeIndex + 1).join("\n"))
|
|
3341
|
-
.digest("hex")
|
|
3342
|
-
};
|
|
3343
|
-
}
|
|
3344
|
-
function exactClaudeInjectedPastePlaceholderCapture(screen, expectedText) {
|
|
3345
|
-
const expectedNewlineCount = (composerComparableText(expectedText).match(/\n/gu) ?? []).length;
|
|
3346
|
-
if (expectedNewlineCount === 0) {
|
|
3347
|
-
return undefined;
|
|
3348
|
-
}
|
|
3349
|
-
const frame = exactClaudeComposerFrame(screen);
|
|
3350
|
-
if (!frame ||
|
|
3351
|
-
frame.composerRows.length !== 1 ||
|
|
3352
|
-
!claudeInjectedPastePlaceholderTrailingMatches(frame.trailing)) {
|
|
3353
|
-
return undefined;
|
|
3354
|
-
}
|
|
3355
|
-
const placeholder = /^\s*❯\s*\[Pasted text #([1-9]\d*) \+([1-9]\d*) lines\]\s*$/u
|
|
3356
|
-
.exec(frame.composerRows[0]);
|
|
3357
|
-
if (!placeholder) {
|
|
3358
|
-
return undefined;
|
|
3359
|
-
}
|
|
3360
|
-
const pasteId = Number(placeholder[1]);
|
|
3361
|
-
const newlineCount = Number(placeholder[2]);
|
|
3362
|
-
if (!Number.isSafeInteger(pasteId) ||
|
|
3363
|
-
!Number.isSafeInteger(newlineCount) ||
|
|
3364
|
-
newlineCount !== expectedNewlineCount) {
|
|
3365
|
-
return undefined;
|
|
3366
|
-
}
|
|
3367
|
-
return {
|
|
3368
|
-
state: "exact_injected_paste_placeholder",
|
|
3369
|
-
digest: createHash("sha256")
|
|
3370
|
-
.update(frame.lines
|
|
3371
|
-
.slice(frame.openIndex, frame.closeIndex + 1)
|
|
3372
|
-
.concat(frame.trailing)
|
|
3373
|
-
.join("\n"))
|
|
3374
|
-
.digest("hex"),
|
|
3375
|
-
pasteId,
|
|
3376
|
-
newlineCount,
|
|
3377
|
-
frameProfile: CLAUDE_INJECTED_PASTE_FRAME_PROFILE
|
|
3378
|
-
};
|
|
3379
|
-
}
|
|
3380
|
-
function claudeInjectedPastePlaceholderTrailingMatches(lines) {
|
|
3381
|
-
if (lines.length === 0) {
|
|
3382
|
-
return false;
|
|
3383
|
-
}
|
|
3384
|
-
const hint = "paste again to expand";
|
|
3385
|
-
const first = lines[0].trimStart();
|
|
3386
|
-
if (!first.startsWith(hint)) {
|
|
3387
|
-
return false;
|
|
3388
|
-
}
|
|
3389
|
-
const sameRowStatus = first.slice(hint.length);
|
|
3390
|
-
if (sameRowStatus.length > 0 && !/^\s{2,}\S/u.test(sameRowStatus)) {
|
|
3391
|
-
return false;
|
|
3392
|
-
}
|
|
3393
|
-
const footerRows = [
|
|
3394
|
-
...(sameRowStatus.trim().length > 0 ? [sameRowStatus.trim()] : []),
|
|
3395
|
-
...lines.slice(1).map((line) => line.trim())
|
|
3396
|
-
];
|
|
3397
|
-
return footerRows.length === 0 ||
|
|
3398
|
-
claudeNativeInspectionTrailingIsFooter(footerRows) ||
|
|
3399
|
-
claudeInjectedPasteAuxiliaryFooterMatches(footerRows);
|
|
3400
|
-
}
|
|
3401
|
-
function claudeInjectedPasteAuxiliaryFooterMatches(lines) {
|
|
3402
|
-
if (lines.length === 0 || lines.length > 2) {
|
|
3403
|
-
return false;
|
|
3404
|
-
}
|
|
3405
|
-
const autoUpdate = lines.filter((line) => line === "✘ Auto-update failed · Run claude doctor").length;
|
|
3406
|
-
const effort = lines.filter((line) => /^[●○◐◉] (?:low|medium|high|xhigh|max|ultracode) · \/effort$/u
|
|
3407
|
-
.test(line)).length;
|
|
3408
|
-
return autoUpdate <= 1 && effort <= 1 &&
|
|
3409
|
-
autoUpdate + effort === lines.length;
|
|
3410
|
-
}
|
|
3411
|
-
function composerComparableText(value) {
|
|
3412
|
-
return value.replace(/\r\n?/gu, "\n");
|
|
3413
|
-
}
|
|
3414
|
-
async function terminalSettleDelay(milliseconds) {
|
|
3415
|
-
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
3416
|
-
}
|
|
3417
|
-
function sameTerminalControlIdentity(left, right) {
|
|
3418
|
-
return sameTerminalControlIncarnation(left, right);
|
|
3419
|
-
}
|
|
3420
|
-
export function terminalApprovalFingerprint(agent, terminalControl, inspection, options = {}) {
|
|
3421
|
-
if (!inspection.approval.approvable) {
|
|
3422
|
-
return undefined;
|
|
3423
|
-
}
|
|
3424
|
-
const decision = options.decision ?? "approve_once";
|
|
3425
|
-
const choices = terminalApprovalChoices(inspection.approval);
|
|
3426
|
-
const action = terminalApprovalActionForDecision(inspection.approval, decision);
|
|
3427
|
-
if (!action) {
|
|
3428
|
-
return undefined;
|
|
3429
|
-
}
|
|
3430
|
-
const decisionMode = action.mode ?? "keys";
|
|
3431
|
-
const promptEvidence = inspection.approval.promptEvidence;
|
|
3432
|
-
if (decisionMode === "keys" &&
|
|
3433
|
-
!isTerminalApprovalPromptEvidence(promptEvidence)) {
|
|
3434
|
-
return undefined;
|
|
1291
|
+
const decisionMode = action.mode ?? "keys";
|
|
1292
|
+
const promptEvidence = inspection.approval.promptEvidence;
|
|
1293
|
+
if (decisionMode === "keys" &&
|
|
1294
|
+
!isTerminalApprovalPromptEvidence(promptEvidence)) {
|
|
1295
|
+
return undefined;
|
|
3435
1296
|
}
|
|
3436
1297
|
const terminal = terminalEndpointFromControlRef(terminalControl);
|
|
3437
1298
|
// Legacy control records without canonical endpoint evidence bind v2 to
|
|
@@ -3527,414 +1388,19 @@ function isTerminalApprovalPromptEvidence(value) {
|
|
|
3527
1388
|
typeof evidence.sha256 === "string" &&
|
|
3528
1389
|
/^[0-9a-f]{64}$/u.test(evidence.sha256);
|
|
3529
1390
|
}
|
|
3530
|
-
function
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
const
|
|
3534
|
-
|
|
3535
|
-
TERMINAL_INTERACTION_IDENTIFIER_PATTERN.test(candidate)
|
|
3536
|
-
? candidate
|
|
3537
|
-
: undefined;
|
|
3538
|
-
}
|
|
3539
|
-
function terminalInteractionSubject(runtime) {
|
|
3540
|
-
if (runtime?.interactionSubject) {
|
|
3541
|
-
return runtime.interactionSubject;
|
|
3542
|
-
}
|
|
3543
|
-
const turnId = terminalInteractionTurnId(runtime);
|
|
3544
|
-
const messageId = runtime?.messageId;
|
|
3545
|
-
return turnId && typeof messageId === "string" &&
|
|
3546
|
-
TERMINAL_INTERACTION_IDENTIFIER_PATTERN.test(messageId)
|
|
3547
|
-
? { kind: "managed_turn", turn_id: turnId, message_id: messageId }
|
|
3548
|
-
: undefined;
|
|
3549
|
-
}
|
|
3550
|
-
function terminalInteractionExpiry(now) {
|
|
3551
|
-
const nowMs = now.getTime();
|
|
3552
|
-
if (!Number.isFinite(nowMs)) {
|
|
3553
|
-
return undefined;
|
|
3554
|
-
}
|
|
3555
|
-
const windowStart = Math.floor(nowMs / TERMINAL_INTERACTION_TTL_MS) *
|
|
3556
|
-
TERMINAL_INTERACTION_TTL_MS;
|
|
3557
|
-
return new Date(windowStart + TERMINAL_INTERACTION_TTL_MS).toISOString();
|
|
3558
|
-
}
|
|
3559
|
-
function terminalInteractionCanonicalIdentity(terminalControl) {
|
|
3560
|
-
if (!hasCanonicalTerminalEndpoint(terminalControl)) {
|
|
3561
|
-
return undefined;
|
|
3562
|
-
}
|
|
3563
|
-
const terminal = terminalEndpointFromControlRef(terminalControl);
|
|
3564
|
-
if (!Number.isInteger(terminal.processAnchorPid) ||
|
|
3565
|
-
Number(terminal.processAnchorPid) <= 0) {
|
|
3566
|
-
return undefined;
|
|
3567
|
-
}
|
|
3568
|
-
// Surface identity is shared by independent observers. Keep physical
|
|
3569
|
-
// terminal identity separate from runtime-native identity so an optional
|
|
3570
|
-
// observer-only fence (for example processStartedAt) cannot split one live
|
|
3571
|
-
// questionnaire into different Monitor and Watch surfaces. Dispatch still
|
|
3572
|
-
// revalidates the complete runtime immediately before any terminal input.
|
|
3573
|
-
return {
|
|
3574
|
-
terminal_identity: terminalEndpointIdentityKey(terminal),
|
|
3575
|
-
terminal_process_anchor_pid: terminal.processAnchorPid
|
|
3576
|
-
};
|
|
3577
|
-
}
|
|
3578
|
-
function terminalInteractionNativeTaskIdentity(runtime) {
|
|
3579
|
-
// Include only native-task fields that both managed Turn and exact Watch
|
|
3580
|
-
// observations can derive from their durable anchors. Optional runtime
|
|
3581
|
-
// verification fields remain on `runtime`; they are identity fences, not
|
|
3582
|
-
// cross-observer surface identity.
|
|
3583
|
-
return {
|
|
3584
|
-
agent_pid: runtime.pid,
|
|
3585
|
-
native_session_id: runtime.nativeSessionId,
|
|
3586
|
-
native_process_uuid: runtime.nativeProcessUuid,
|
|
3587
|
-
native_process_birth: runtime.nativeProcessBirth,
|
|
3588
|
-
native_rollout: runtime.nativeRollout,
|
|
3589
|
-
expected_native_session_id: runtime.expectedNativeSessionId,
|
|
3590
|
-
expected_empty_native_session: runtime.expectedEmptyNativeSession === true
|
|
3591
|
-
};
|
|
3592
|
-
}
|
|
3593
|
-
function legacyManagedInteractionProjection(projection) {
|
|
3594
|
-
if (projection.subject.kind !== "managed_turn") {
|
|
3595
|
-
throw new TypeError("only managed interaction subjects have a v1 projection");
|
|
3596
|
-
}
|
|
3597
|
-
return validateTerminalInteractionProjection({
|
|
3598
|
-
schema: TERMINAL_INTERACTION_SCHEMA,
|
|
3599
|
-
version: TERMINAL_INTERACTION_VERSION,
|
|
3600
|
-
interaction_id: projection.interaction_id,
|
|
3601
|
-
turn_id: projection.subject.turn_id,
|
|
3602
|
-
agent: projection.agent,
|
|
3603
|
-
kind: projection.kind,
|
|
3604
|
-
state: projection.state,
|
|
3605
|
-
step: projection.step,
|
|
3606
|
-
questions: projection.questions,
|
|
3607
|
-
expires_at: projection.expires_at,
|
|
3608
|
-
capabilities: projection.capabilities
|
|
3609
|
-
});
|
|
3610
|
-
}
|
|
3611
|
-
export function captureTerminalInteractionRuntimeOffer(input) {
|
|
3612
|
-
const subject = terminalInteractionSubject(input.runtime);
|
|
3613
|
-
const agentVersion = input.runtime?.agentVersion;
|
|
3614
|
-
const expiry = terminalInteractionExpiry(input.now);
|
|
3615
|
-
const canonicalIdentity = input.runtime
|
|
3616
|
-
? terminalInteractionCanonicalIdentity(input.terminalControl) ??
|
|
3617
|
-
terminalInteractionTrustedWatchIdentity(input.terminalControl, input.runtime, input.trustedTerminalEvidence)
|
|
3618
|
-
: undefined;
|
|
3619
|
-
if (!subject ||
|
|
3620
|
-
typeof agentVersion !== "string" ||
|
|
3621
|
-
agentVersion.length === 0 ||
|
|
3622
|
-
!expiry ||
|
|
3623
|
-
!canonicalIdentity ||
|
|
3624
|
-
input.approvalBlocked === true) {
|
|
3625
|
-
return undefined;
|
|
3626
|
-
}
|
|
3627
|
-
const coreOffer = captureTerminalInteraction({
|
|
3628
|
-
subject,
|
|
3629
|
-
agent: input.agent,
|
|
3630
|
-
agentVersion,
|
|
3631
|
-
canonicalTerminalIdentity: canonicalIdentity,
|
|
3632
|
-
nativeTaskIdentity: terminalInteractionNativeTaskIdentity(input.runtime),
|
|
3633
|
-
screen: input.screen,
|
|
3634
|
-
now: input.now,
|
|
3635
|
-
expiresAt: expiry,
|
|
3636
|
-
responseUncertain: input.runtime?.interactionDispatchState === "reserved" ||
|
|
3637
|
-
input.runtime?.interactionDispatchState === "uncertain",
|
|
3638
|
-
responseAuthority: input.runtime?.interactionResponseAuthority ?? "executable",
|
|
3639
|
-
includeLegacyTurnId: subject.kind === "managed_turn"
|
|
3640
|
-
});
|
|
3641
|
-
if (!coreOffer) {
|
|
3642
|
-
return undefined;
|
|
3643
|
-
}
|
|
3644
|
-
return {
|
|
3645
|
-
projection: subject.kind === "managed_turn"
|
|
3646
|
-
? legacyManagedInteractionProjection(coreOffer.projection)
|
|
3647
|
-
: coreOffer.projection,
|
|
3648
|
-
promptFingerprint: coreOffer.promptFingerprint,
|
|
3649
|
-
surfaceId: coreOffer.surfaceId,
|
|
3650
|
-
actionPlan: coreOffer.actionPlan,
|
|
3651
|
-
nativeInspection: coreOffer.nativeInspection
|
|
3652
|
-
};
|
|
3653
|
-
}
|
|
3654
|
-
function terminalInteractionTrustedWatchIdentity(terminalControl, runtime, evidence) {
|
|
3655
|
-
if (runtime.interactionSubject?.kind !== "terminal_watch" ||
|
|
3656
|
-
!evidence ||
|
|
3657
|
-
!terminalControlEvidenceMatches(evidence, terminalControl)) {
|
|
3658
|
-
return undefined;
|
|
3659
|
-
}
|
|
3660
|
-
const identity = terminalEndpointIdentityFromEvidence(evidence);
|
|
3661
|
-
if (!identity || !Number.isSafeInteger(evidence.process_anchor_pid) ||
|
|
3662
|
-
Number(evidence.process_anchor_pid) <= 0) {
|
|
3663
|
-
return undefined;
|
|
3664
|
-
}
|
|
3665
|
-
return {
|
|
3666
|
-
terminal_identity: terminalEndpointIdentityKey(identity),
|
|
3667
|
-
terminal_process_anchor_pid: evidence.process_anchor_pid
|
|
3668
|
-
};
|
|
3669
|
-
}
|
|
3670
|
-
function terminalInteractionOptionPreflightReason(options, runtime) {
|
|
3671
|
-
if (typeof options.agentVersion !== "string" ||
|
|
3672
|
-
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(options.agentVersion)) {
|
|
3673
|
-
return "an exact semantic agent version is required";
|
|
3674
|
-
}
|
|
3675
|
-
if (options.runtime?.agentVersion !== undefined &&
|
|
3676
|
-
options.runtime.agentVersion !== options.agentVersion) {
|
|
3677
|
-
return "runtime agent version does not match the requested interaction profile";
|
|
3678
|
-
}
|
|
3679
|
-
if (!terminalInteractionSubject(runtime)) {
|
|
3680
|
-
return "a protocol-safe exact interaction subject is required";
|
|
3681
|
-
}
|
|
3682
|
-
if (!TERMINAL_INTERACTION_FINGERPRINT_PATTERN.test(options.expectedFingerprint)) {
|
|
3683
|
-
return "a valid interaction prompt fingerprint is required";
|
|
3684
|
-
}
|
|
3685
|
-
if (!Number.isFinite(Date.parse(options.expectedExpiresAt))) {
|
|
3686
|
-
return "a valid interaction expiry is required";
|
|
3687
|
-
}
|
|
3688
|
-
return undefined;
|
|
3689
|
-
}
|
|
3690
|
-
function terminalInteractionOfferPreflightReason(offer, options, now) {
|
|
3691
|
-
if (!offer) {
|
|
3692
|
-
return "native questionnaire has no identity-fenced interaction offer";
|
|
3693
|
-
}
|
|
3694
|
-
if (offer.projection.state !== "pending" ||
|
|
3695
|
-
!offer.projection.capabilities.respond ||
|
|
3696
|
-
offer.actionPlan.kind === "manual_only") {
|
|
3697
|
-
return `native questionnaire requires manual response: ${offer.nativeInspection.status === "manual_required"
|
|
3698
|
-
? offer.nativeInspection.reason
|
|
3699
|
-
: "unsupported action plan"}`;
|
|
3700
|
-
}
|
|
3701
|
-
if (offer.promptFingerprint !== options.expectedFingerprint) {
|
|
3702
|
-
return "native questionnaire fingerprint changed before execution";
|
|
3703
|
-
}
|
|
3704
|
-
if (offer.projection.expires_at !== options.expectedExpiresAt &&
|
|
3705
|
-
Date.parse(options.expectedExpiresAt) > now.getTime()) {
|
|
3706
|
-
return "native questionnaire expiry changed before execution";
|
|
3707
|
-
}
|
|
3708
|
-
return undefined;
|
|
3709
|
-
}
|
|
3710
|
-
function terminalInteractionCapabilityReason(provider, terminalControl, plan) {
|
|
3711
|
-
const requiresText = plan.kind === "free_text";
|
|
3712
|
-
const missing = [
|
|
3713
|
-
!terminalControl.capabilities.includes("screen_status")
|
|
3714
|
-
? "terminal:screen_status"
|
|
3715
|
-
: undefined,
|
|
3716
|
-
!terminalControl.capabilities.includes("send_keys")
|
|
3717
|
-
? "terminal:send_keys"
|
|
3718
|
-
: undefined,
|
|
3719
|
-
!provider.providerCapabilities.includes("stable_resource_resolution")
|
|
3720
|
-
? "provider:stable_resource_resolution"
|
|
3721
|
-
: undefined,
|
|
3722
|
-
!provider.providerCapabilities.includes("screen_capture")
|
|
3723
|
-
? "provider:screen_capture"
|
|
3724
|
-
: undefined,
|
|
3725
|
-
!provider.providerCapabilities.includes("key_delivery")
|
|
3726
|
-
? "provider:key_delivery"
|
|
3727
|
-
: undefined,
|
|
3728
|
-
requiresText && !provider.providerCapabilities.includes("text_delivery")
|
|
3729
|
-
? "provider:text_delivery"
|
|
3730
|
-
: undefined
|
|
3731
|
-
].filter((value) => value !== undefined);
|
|
3732
|
-
return missing.length > 0
|
|
3733
|
-
? `terminal interaction capability preflight failed: ${missing.join(", ")}`
|
|
1391
|
+
function statusFromInspection(adapter, terminalControl, inspection, options = {}) {
|
|
1392
|
+
const exactModelSurface = options.screen !== undefined &&
|
|
1393
|
+
exactCurrentModelControlSurface(adapter.agent, options.runtime?.agentVersion, options.screen);
|
|
1394
|
+
const modelSurfaceReason = exactModelSurface
|
|
1395
|
+
? `exact ${adapter.displayName} native model-control surface is open`
|
|
3734
1396
|
: undefined;
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
}
|
|
3741
|
-
const textStage = plan.stages[0];
|
|
3742
|
-
if (textStage?.kind !== "answer_text" ||
|
|
3743
|
-
!Number.isSafeInteger(textStage.max_characters) ||
|
|
3744
|
-
textStage.max_characters < 1) {
|
|
3745
|
-
return "native free-text interaction has no verified input limit";
|
|
3746
|
-
}
|
|
3747
|
-
return answer.text.length <= textStage.max_characters
|
|
3748
|
-
? undefined
|
|
3749
|
-
: `free-text answer exceeds the verified native limit of ${textStage.max_characters} characters`;
|
|
3750
|
-
}
|
|
3751
|
-
function blockedTerminalInteractionResponse(response, reason, offer) {
|
|
3752
|
-
const answer = Array.isArray(response.answers) ? response.answers[0] : undefined;
|
|
3753
|
-
return {
|
|
3754
|
-
responded: false,
|
|
3755
|
-
blocked: true,
|
|
3756
|
-
reason,
|
|
3757
|
-
interactionId: response.interaction_id,
|
|
3758
|
-
questionId: answer?.question_id ?? offer?.projection.questions[0]?.question_id,
|
|
3759
|
-
responseKind: answer?.response_kind
|
|
3760
|
-
};
|
|
3761
|
-
}
|
|
3762
|
-
function interactionHookContext(agent, terminalControl, offer, response, runtime) {
|
|
3763
|
-
return {
|
|
3764
|
-
agent,
|
|
3765
|
-
terminalControl,
|
|
3766
|
-
fingerprint: offer.promptFingerprint,
|
|
3767
|
-
projection: offer.projection,
|
|
3768
|
-
response,
|
|
3769
|
-
runtime
|
|
3770
|
-
};
|
|
3771
|
-
}
|
|
3772
|
-
async function authorizeTerminalInteraction(authorize, context) {
|
|
3773
|
-
return authorize ? authorize(context) : { approved: true };
|
|
3774
|
-
}
|
|
3775
|
-
async function reserveTerminalInteractionDispatch(beforeDispatch, context) {
|
|
3776
|
-
if (!beforeDispatch) {
|
|
3777
|
-
return;
|
|
3778
|
-
}
|
|
3779
|
-
try {
|
|
3780
|
-
await beforeDispatch(context);
|
|
3781
|
-
}
|
|
3782
|
-
catch (error) {
|
|
3783
|
-
if (error instanceof TerminalInteractionInputNotStartedError) {
|
|
3784
|
-
throw error;
|
|
3785
|
-
}
|
|
3786
|
-
throw terminalInteractionReservedError("reservation_uncertain", error, "terminal interaction dispatch reservation failed");
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
function sameTerminalInteractionOffer(left, right, options = {}) {
|
|
3790
|
-
const leftProjection = options.ignoreExpiresAt && left.offer
|
|
3791
|
-
? terminalInteractionProjectionWithoutExpiry(left.offer.projection)
|
|
3792
|
-
: left.offer?.projection;
|
|
3793
|
-
const rightProjection = options.ignoreExpiresAt && right.offer
|
|
3794
|
-
? terminalInteractionProjectionWithoutExpiry(right.offer.projection)
|
|
3795
|
-
: right.offer?.projection;
|
|
3796
|
-
return Boolean(left.offer &&
|
|
3797
|
-
right.offer &&
|
|
3798
|
-
sameTerminalControlIdentity(left.terminalControl, right.terminalControl) &&
|
|
3799
|
-
left.offer.promptFingerprint === right.offer.promptFingerprint &&
|
|
3800
|
-
JSON.stringify(leftProjection) === JSON.stringify(rightProjection) &&
|
|
3801
|
-
JSON.stringify(left.offer.actionPlan) === JSON.stringify(right.offer.actionPlan));
|
|
3802
|
-
}
|
|
3803
|
-
function terminalInteractionProjectionWithoutExpiry(projection) {
|
|
3804
|
-
const { expires_at: _expiresAt, ...semanticProjection } = projection;
|
|
3805
|
-
return semanticProjection;
|
|
3806
|
-
}
|
|
3807
|
-
function terminalInteractionInputNotStartedError(error, message) {
|
|
3808
|
-
if (error instanceof TerminalInteractionInputNotStartedError) {
|
|
3809
|
-
return error;
|
|
3810
|
-
}
|
|
3811
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
3812
|
-
return new TerminalInteractionInputNotStartedError(`${message}: ${detail}`, { cause: error });
|
|
3813
|
-
}
|
|
3814
|
-
function terminalInteractionReservedError(stage, error, message) {
|
|
3815
|
-
if (error instanceof TerminalInteractionDispatchReservedError) {
|
|
3816
|
-
return error;
|
|
3817
|
-
}
|
|
3818
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
3819
|
-
return new TerminalInteractionDispatchReservedError(stage, `${message}: ${detail}`, { cause: error });
|
|
3820
|
-
}
|
|
3821
|
-
function terminalInteractionChoiceAction(agent, plan, answer) {
|
|
3822
|
-
const choice = plan.choices.find((candidate) => candidate.option_id === answer.selected_option_ids[0]);
|
|
3823
|
-
if (!choice) {
|
|
3824
|
-
throw new TerminalInteractionInputNotStartedError("single-select interaction has no closed adapter action");
|
|
3825
|
-
}
|
|
3826
|
-
const keys = choice.stages.flatMap((stage) => stage.kind === "key" ? [stage.key] : []);
|
|
3827
|
-
const directChoice = choice.outcome === "submit_or_advance" &&
|
|
3828
|
-
choice.stages.length === 1 &&
|
|
3829
|
-
keys.length === 1 &&
|
|
3830
|
-
/^[1-5]$/u.test(keys[0]);
|
|
3831
|
-
const directCustomTextChoice = agent === "claude" &&
|
|
3832
|
-
choice.outcome === "open_custom_text" &&
|
|
3833
|
-
choice.stages.length === 1 &&
|
|
3834
|
-
keys.length === 1 &&
|
|
3835
|
-
/^[1-5]$/u.test(keys[0]);
|
|
3836
|
-
const navigatedCustomTextChoice = agent === "codex" &&
|
|
3837
|
-
choice.outcome === "open_custom_text" &&
|
|
3838
|
-
choice.stages.length >= 1 &&
|
|
3839
|
-
choice.stages.length <= 4 &&
|
|
3840
|
-
keys.length === choice.stages.length &&
|
|
3841
|
-
keys.at(-1) === "C-m" &&
|
|
3842
|
-
keys.slice(0, -1).every((key) => key === "Down");
|
|
3843
|
-
if (!directChoice && !directCustomTextChoice && !navigatedCustomTextChoice) {
|
|
3844
|
-
throw new TerminalInteractionInputNotStartedError("single-select interaction has no closed adapter action");
|
|
3845
|
-
}
|
|
3846
|
-
return {
|
|
3847
|
-
keys,
|
|
3848
|
-
outcome: choice.outcome === "open_custom_text"
|
|
3849
|
-
? "custom_text_opened"
|
|
3850
|
-
: "submitted_or_advanced"
|
|
3851
|
-
};
|
|
3852
|
-
}
|
|
3853
|
-
function terminalInteractionConfirmKey(plan, answer) {
|
|
3854
|
-
const stages = answer.confirm ? plan.confirm_stages : plan.cancel_stages;
|
|
3855
|
-
const stage = stages[0];
|
|
3856
|
-
if (stages.length !== 1 ||
|
|
3857
|
-
stage?.kind !== "key" ||
|
|
3858
|
-
!["C-m", "Escape", "1", "2"].includes(stage.key) ||
|
|
3859
|
-
JSON.stringify(plan.confirm_stages) === JSON.stringify(plan.cancel_stages)) {
|
|
3860
|
-
throw new TerminalInteractionInputNotStartedError("confirmation interaction has no closed adapter action");
|
|
3861
|
-
}
|
|
3862
|
-
return stage.key;
|
|
3863
|
-
}
|
|
3864
|
-
async function sendTerminalInteractionKeys(provider, terminalControl, keys) {
|
|
3865
|
-
try {
|
|
3866
|
-
await provider.sendKeys(provider.endpoint(terminalControl), keys);
|
|
3867
|
-
}
|
|
3868
|
-
catch (error) {
|
|
3869
|
-
throw terminalInteractionReservedError("key_uncertain", error, "terminal interaction key dispatch is uncertain");
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
3872
|
-
async function dispatchTerminalInteractionFreeText(input) {
|
|
3873
|
-
const [textStage, enterStage] = input.plan.stages;
|
|
3874
|
-
if (input.plan.stages.length !== 2 ||
|
|
3875
|
-
textStage?.kind !== "answer_text" ||
|
|
3876
|
-
textStage.single_line !== true ||
|
|
3877
|
-
textStage.max_characters < input.answer.text.length ||
|
|
3878
|
-
enterStage?.kind !== "key" ||
|
|
3879
|
-
enterStage.key !== "C-m") {
|
|
3880
|
-
throw new TerminalInteractionInputNotStartedError("free-text interaction has no closed adapter action");
|
|
3881
|
-
}
|
|
3882
|
-
try {
|
|
3883
|
-
await input.provider.sendText(input.provider.endpoint(input.terminalControl), input.answer.text);
|
|
3884
|
-
}
|
|
3885
|
-
catch (error) {
|
|
3886
|
-
throw terminalInteractionReservedError("text_uncertain", error, "terminal interaction text dispatch is uncertain");
|
|
3887
|
-
}
|
|
3888
|
-
if (input.agent === "codex") {
|
|
3889
|
-
try {
|
|
3890
|
-
await input.sleep(CODEX_PASTE_ENTER_SETTLE_MS);
|
|
3891
|
-
}
|
|
3892
|
-
catch (error) {
|
|
3893
|
-
throw terminalInteractionReservedError("text_uncertain", error, "terminal interaction paste-settle outcome is uncertain");
|
|
3894
|
-
}
|
|
3895
|
-
}
|
|
3896
|
-
let enterControl;
|
|
3897
|
-
try {
|
|
3898
|
-
enterControl = await input.reverifyTerminalIdentity(input.terminalControl);
|
|
3899
|
-
if (!sameTerminalControlIdentity(input.terminalControl, enterControl)) {
|
|
3900
|
-
throw new Error("terminal identity changed between interaction text and Enter");
|
|
1397
|
+
const approval = exactModelSurface
|
|
1398
|
+
? {
|
|
1399
|
+
blocked: false,
|
|
1400
|
+
approvable: false,
|
|
1401
|
+
reason: modelSurfaceReason
|
|
3901
1402
|
}
|
|
3902
|
-
|
|
3903
|
-
catch (error) {
|
|
3904
|
-
throw terminalInteractionReservedError("text_uncertain", error, "terminal identity after interaction text dispatch is uncertain");
|
|
3905
|
-
}
|
|
3906
|
-
await sendTerminalInteractionKeys(input.provider, enterControl, [enterStage.key]);
|
|
3907
|
-
return "submitted_or_advanced";
|
|
3908
|
-
}
|
|
3909
|
-
async function dispatchTerminalInteractionAnswer(input) {
|
|
3910
|
-
if (input.actionPlan.kind === "single_select" &&
|
|
3911
|
-
input.answer.response_kind === "single_select") {
|
|
3912
|
-
const action = terminalInteractionChoiceAction(input.agent, input.actionPlan, input.answer);
|
|
3913
|
-
await sendTerminalInteractionKeys(input.provider, input.terminalControl, action.keys);
|
|
3914
|
-
return action.outcome;
|
|
3915
|
-
}
|
|
3916
|
-
if (input.actionPlan.kind === "confirm" &&
|
|
3917
|
-
input.answer.response_kind === "confirm") {
|
|
3918
|
-
const key = terminalInteractionConfirmKey(input.actionPlan, input.answer);
|
|
3919
|
-
await sendTerminalInteractionKeys(input.provider, input.terminalControl, [key]);
|
|
3920
|
-
return input.answer.confirm ? "confirmed" : "cancelled";
|
|
3921
|
-
}
|
|
3922
|
-
if (input.actionPlan.kind === "free_text" &&
|
|
3923
|
-
input.answer.response_kind === "free_text") {
|
|
3924
|
-
return dispatchTerminalInteractionFreeText({
|
|
3925
|
-
agent: input.agent,
|
|
3926
|
-
provider: input.provider,
|
|
3927
|
-
terminalControl: input.terminalControl,
|
|
3928
|
-
plan: input.actionPlan,
|
|
3929
|
-
answer: input.answer,
|
|
3930
|
-
reverifyTerminalIdentity: input.reverifyTerminalIdentity,
|
|
3931
|
-
sleep: input.sleep
|
|
3932
|
-
});
|
|
3933
|
-
}
|
|
3934
|
-
throw new TerminalInteractionInputNotStartedError("terminal interaction answer does not have a closed adapter action");
|
|
3935
|
-
}
|
|
3936
|
-
function statusFromInspection(adapter, terminalControl, inspection, options = {}) {
|
|
3937
|
-
const approval = dispatchableApprovalInspection(adapter, inspection.approval);
|
|
1403
|
+
: dispatchableApprovalInspection(adapter, inspection.approval);
|
|
3938
1404
|
const fingerprint = terminalApprovalFingerprint(adapter.agent, terminalControl, {
|
|
3939
1405
|
...inspection,
|
|
3940
1406
|
approval
|
|
@@ -3952,7 +1418,7 @@ function statusFromInspection(adapter, terminalControl, inspection, options = {}
|
|
|
3952
1418
|
}] : [];
|
|
3953
1419
|
})
|
|
3954
1420
|
: [];
|
|
3955
|
-
const interaction = options.screen === undefined
|
|
1421
|
+
const interaction = options.screen === undefined || exactModelSurface
|
|
3956
1422
|
? undefined
|
|
3957
1423
|
: captureTerminalInteractionRuntimeOffer({
|
|
3958
1424
|
agent: adapter.agent,
|
|
@@ -3968,10 +1434,10 @@ function statusFromInspection(adapter, terminalControl, inspection, options = {}
|
|
|
3968
1434
|
agent: adapter.agent,
|
|
3969
1435
|
reachable: true,
|
|
3970
1436
|
capabilities: adapter.capabilities,
|
|
3971
|
-
activity_state: inspection.activity.state,
|
|
3972
|
-
activity_reason: inspection.activity.reason,
|
|
3973
|
-
screen_state: inspection.activity.state,
|
|
3974
|
-
screen_reason: inspection.activity.reason,
|
|
1437
|
+
activity_state: exactModelSurface ? "unknown" : inspection.activity.state,
|
|
1438
|
+
activity_reason: modelSurfaceReason ?? inspection.activity.reason,
|
|
1439
|
+
screen_state: exactModelSurface ? "unknown" : inspection.activity.state,
|
|
1440
|
+
screen_reason: modelSurfaceReason ?? inspection.activity.reason,
|
|
3975
1441
|
approval_state: {
|
|
3976
1442
|
scanned: true,
|
|
3977
1443
|
blocked: approval.blocked,
|
|
@@ -4009,7 +1475,7 @@ function statusFromInspection(adapter, terminalControl, inspection, options = {}
|
|
|
4009
1475
|
approval: approvalOutput(approval)
|
|
4010
1476
|
},
|
|
4011
1477
|
...(interaction === undefined ? {} : {
|
|
4012
|
-
interaction_state: interaction.projection,
|
|
1478
|
+
interaction_state: terminalInteractionPublicCompatibilityProjection(interaction.projection),
|
|
4013
1479
|
interaction_prompt_fingerprint: interaction.promptFingerprint,
|
|
4014
1480
|
interaction_surface_id: interaction.surfaceId
|
|
4015
1481
|
})
|
|
@@ -4116,4 +1582,33 @@ function unsupportedScreenStatus(adapter, terminalControl) {
|
|
|
4116
1582
|
capability_limitation: reason
|
|
4117
1583
|
};
|
|
4118
1584
|
}
|
|
1585
|
+
function assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan) {
|
|
1586
|
+
if (!terminalControl.capabilities.includes("send_keys") ||
|
|
1587
|
+
!terminalControl.capabilities.includes("screen_status")) {
|
|
1588
|
+
throw new Error("terminal model control requires exact screen-status and key-delivery capabilities");
|
|
1589
|
+
}
|
|
1590
|
+
const capabilities = adapter.probeModelControl?.(agentVersion);
|
|
1591
|
+
if (capabilities?.status !== "supported" ||
|
|
1592
|
+
capabilities.modelSelection !== true ||
|
|
1593
|
+
capabilities.reasoningEffortSelection !== true ||
|
|
1594
|
+
capabilities.agentVersion !== agentVersion) {
|
|
1595
|
+
throw new Error(capabilities?.reason ??
|
|
1596
|
+
`${adapter.displayName} has no verified model-control profile`);
|
|
1597
|
+
}
|
|
1598
|
+
const expected = adapter.planModelControl?.(capabilities);
|
|
1599
|
+
if (!expected ||
|
|
1600
|
+
expected.command !== "/model" ||
|
|
1601
|
+
expected.requiresIdle !== true ||
|
|
1602
|
+
expected.requiresExactEmptyComposer !== true ||
|
|
1603
|
+
JSON.stringify(expected) !== JSON.stringify(plan)) {
|
|
1604
|
+
throw new Error("the terminal adapter did not produce the exact current model-control plan");
|
|
1605
|
+
}
|
|
1606
|
+
if (!terminalModelControlPlanConforms({
|
|
1607
|
+
agent: adapter.agent,
|
|
1608
|
+
agentVersion,
|
|
1609
|
+
plan
|
|
1610
|
+
})) {
|
|
1611
|
+
throw new Error("refusing an unprofiled terminal model-control plan");
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
4119
1614
|
//# sourceMappingURL=terminal-agent-bridge.js.map
|