@scotthuang/agent-knock-knock 0.13.3 → 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 +14 -0
- package/README.md +13 -5
- 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/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/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 +2 -172
- 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/native-thread-lifecycle-cli-adapter.js +116 -60
- 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 -2660
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +2 -148
- package/dist/src/openclaw-plugin-helpers.js +2 -1844
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +2 -563
- package/dist/src/openclaw-plugin-schemas.js +2 -572
- 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 +2 -98
- package/dist/src/terminal-action-projection.js +13 -215
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +14 -340
- package/dist/src/terminal-agent-bridge.js +155 -3226
- 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-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 +241 -1946
- 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 +11 -11
- package/dist/src/terminal-list-renderer.js +168 -119
- 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 +37 -18
- package/dist/src/terminal-model-control.js +285 -194
- package/dist/src/terminal-model-control.js.map +1 -1
- 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 -2
- 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/package.json +7 -2
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +1 -1
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { CODEX_MODEL_CONTROL_AGENT_VERSION, isTerminalModelControlPlanForAgent, planTerminalModelControlProfile, probeTerminalModelControlProfile, terminalModelControlProfileForPlan } from "./terminal-model-control-profile.js";
|
|
3
|
+
import { canonicalModelControlSubject } from "./terminal-model-control-subject.js";
|
|
4
|
+
import { decideTerminalModelControlFailure, reduceTerminalModelControlTransactionPhase } from "./terminal-model-control-transaction.js";
|
|
2
5
|
import { terminalPhysicalBindingToken } from "./terminal-control-ref.js";
|
|
6
|
+
export { CLAUDE_MODEL_CONTROL_AGENT_VERSION, CODEX_MODEL_CONTROL_AGENT_VERSION, TERMINAL_MODEL_CONTROL_PROFILE_IDS, isTerminalModelControlPlanForAgent, terminalModelControlPlanConforms, terminalModelControlProfileFor, terminalModelControlProfileForPlan, terminalModelControlProfiles, terminalModelControlSlashCompletionRows } from "./terminal-model-control-profile.js";
|
|
7
|
+
export { canonicalModelControlSubject } from "./terminal-model-control-subject.js";
|
|
3
8
|
/** Caller-visible reasoning values. Native labels and menu positions stay private. */
|
|
4
9
|
export const TERMINAL_MODEL_REASONING_EFFORTS = [
|
|
5
10
|
"low",
|
|
@@ -15,25 +20,36 @@ export const TERMINAL_MODEL_REASONING_EFFORTS = [
|
|
|
15
20
|
* as Send or native-lifecycle authority even though all bind the same pane.
|
|
16
21
|
*/
|
|
17
22
|
export function terminalUserExplicitModelControlBindingToken(value) {
|
|
23
|
+
const subject = requiredModelControlSubject({
|
|
24
|
+
...value,
|
|
25
|
+
agent: "codex"
|
|
26
|
+
});
|
|
18
27
|
const physicalTerminalToken = terminalPhysicalBindingToken({
|
|
19
|
-
terminalId:
|
|
20
|
-
terminalControl:
|
|
28
|
+
terminalId: subject.terminalId,
|
|
29
|
+
terminalControl: subject.terminalControl,
|
|
21
30
|
agent: "codex",
|
|
22
|
-
pid:
|
|
23
|
-
workspace:
|
|
24
|
-
processUuid:
|
|
25
|
-
processBirth:
|
|
31
|
+
pid: subject.pid,
|
|
32
|
+
workspace: subject.workspace,
|
|
33
|
+
processUuid: subject.processUuid,
|
|
34
|
+
processBirth: subject.processBirth
|
|
26
35
|
});
|
|
27
36
|
return createHash("sha256")
|
|
28
37
|
.update(JSON.stringify({
|
|
29
38
|
version: 1,
|
|
30
39
|
authority: "terminal_user_explicit_model_control",
|
|
31
40
|
physical_terminal_token: physicalTerminalToken,
|
|
32
|
-
agent_version:
|
|
33
|
-
behavior_profile:
|
|
41
|
+
agent_version: subject.agentVersion,
|
|
42
|
+
behavior_profile: subject.behaviorProfile
|
|
34
43
|
}))
|
|
35
44
|
.digest("hex");
|
|
36
45
|
}
|
|
46
|
+
function requiredModelControlSubject(input) {
|
|
47
|
+
const subject = canonicalModelControlSubject(input);
|
|
48
|
+
if (!subject) {
|
|
49
|
+
throw new Error("model-control authority requires one canonical supported terminal subject");
|
|
50
|
+
}
|
|
51
|
+
return subject;
|
|
52
|
+
}
|
|
37
53
|
/**
|
|
38
54
|
* Snapshot authority for adopting and cleaning one exact native `/model`
|
|
39
55
|
* residual. The residual kind and normalized surface digest prevent a token
|
|
@@ -162,63 +178,10 @@ export function isTerminalModelReasoningEffort(value) {
|
|
|
162
178
|
TERMINAL_MODEL_REASONING_EFFORTS.includes(value);
|
|
163
179
|
}
|
|
164
180
|
export function probeTerminalModelControl(agent, agentVersion) {
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
status: "unknown",
|
|
168
|
-
modelSelection: false,
|
|
169
|
-
reasoningEffortSelection: false,
|
|
170
|
-
reason: `the running ${agent === "codex" ? "Codex" : "Claude Code"} version could not be verified`
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
if (agent === "codex" && agentVersion === "0.154.0") {
|
|
174
|
-
return {
|
|
175
|
-
status: "supported",
|
|
176
|
-
agentVersion,
|
|
177
|
-
behaviorProfile: "codex-model-control-0.154.0",
|
|
178
|
-
scope: "current_and_new_sessions",
|
|
179
|
-
modelSelection: true,
|
|
180
|
-
reasoningEffortSelection: true,
|
|
181
|
-
reason: "Codex 0.154.0 /model control changes the current session and persisted defaults"
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
if (agent === "claude" && agentVersion === "2.1.266") {
|
|
185
|
-
return {
|
|
186
|
-
status: "supported",
|
|
187
|
-
agentVersion,
|
|
188
|
-
behaviorProfile: "claude-model-control-2.1.266",
|
|
189
|
-
scope: "current_session",
|
|
190
|
-
modelSelection: true,
|
|
191
|
-
reasoningEffortSelection: true,
|
|
192
|
-
reason: "Claude Code 2.1.266 /model supports the explicit session-only selection path"
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
return {
|
|
196
|
-
status: "unsupported",
|
|
197
|
-
agentVersion,
|
|
198
|
-
modelSelection: false,
|
|
199
|
-
reasoningEffortSelection: false,
|
|
200
|
-
reason: `${agent === "codex" ? "Codex" : "Claude Code"} ${agentVersion} has no verified model-control profile`
|
|
201
|
-
};
|
|
181
|
+
return probeTerminalModelControlProfile(agent, agentVersion);
|
|
202
182
|
}
|
|
203
183
|
export function planTerminalModelControl(capabilities) {
|
|
204
|
-
|
|
205
|
-
capabilities.modelSelection !== true ||
|
|
206
|
-
capabilities.reasoningEffortSelection !== true ||
|
|
207
|
-
!capabilities.behaviorProfile ||
|
|
208
|
-
!capabilities.scope) {
|
|
209
|
-
throw new Error(capabilities.reason);
|
|
210
|
-
}
|
|
211
|
-
if (capabilities.behaviorProfile !== "codex-model-control-0.154.0" &&
|
|
212
|
-
capabilities.behaviorProfile !== "claude-model-control-2.1.266") {
|
|
213
|
-
throw new Error("refusing an unprofiled terminal model-control plan");
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
behaviorProfile: capabilities.behaviorProfile,
|
|
217
|
-
command: "/model",
|
|
218
|
-
scope: capabilities.scope,
|
|
219
|
-
requiresIdle: true,
|
|
220
|
-
requiresExactEmptyComposer: true
|
|
221
|
-
};
|
|
184
|
+
return planTerminalModelControlProfile(capabilities);
|
|
222
185
|
}
|
|
223
186
|
/**
|
|
224
187
|
* Observe only complete, current model-control modal frames. Historical or
|
|
@@ -226,7 +189,7 @@ export function planTerminalModelControl(capabilities) {
|
|
|
226
189
|
*/
|
|
227
190
|
export function observeTerminalModelControl(plan, screen) {
|
|
228
191
|
const normalized = stripAnsi(screen).replace(/\r\n?/gu, "\n");
|
|
229
|
-
return plan
|
|
192
|
+
return isTerminalModelControlPlanForAgent(plan, "codex")
|
|
230
193
|
? observeCodexModelControl(normalized)
|
|
231
194
|
: observeClaudeModelControl(normalized);
|
|
232
195
|
}
|
|
@@ -302,44 +265,167 @@ export function claudeModelControlEffortKey(direction) {
|
|
|
302
265
|
return [direction === "lower" ? "Left" : "Right"];
|
|
303
266
|
}
|
|
304
267
|
export function claudeModelControlCommitKeys(plan) {
|
|
305
|
-
if (plan
|
|
268
|
+
if (!isTerminalModelControlPlanForAgent(plan, "claude") ||
|
|
306
269
|
plan.scope !== "current_session") {
|
|
307
270
|
throw new Error("Claude model control cannot commit outside session-only scope");
|
|
308
271
|
}
|
|
309
272
|
return ["s"];
|
|
310
273
|
}
|
|
274
|
+
function isExactTerminalModelControlObservation(observation) {
|
|
275
|
+
return observation.state !== "none" && observation.state !== "ambiguous";
|
|
276
|
+
}
|
|
277
|
+
/** Pure capture classifier. It never authorizes input by itself. */
|
|
278
|
+
export function classifyTerminalModelControlSurface(plan, capture) {
|
|
279
|
+
const observation = observeTerminalModelControl(plan, capture.screen);
|
|
280
|
+
// Approval and a positively identified non-model input owner remain hard
|
|
281
|
+
// safety boundaries even when stale transcript text resembles a picker.
|
|
282
|
+
const hardBlock = modelControlHardBlock(capture);
|
|
283
|
+
if (hardBlock)
|
|
284
|
+
return hardBlock;
|
|
285
|
+
// Exact profile-owned surfaces outrank generic lifecycle activity. Codex and
|
|
286
|
+
// Claude can report working/waiting while their native picker owns input.
|
|
287
|
+
if (isExactTerminalModelControlObservation(observation)) {
|
|
288
|
+
return {
|
|
289
|
+
state: "picker",
|
|
290
|
+
pickerKind: modelControlPickerKind(observation),
|
|
291
|
+
observation
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
// Only a complete picker owns the lifecycle override. Composer proofs do
|
|
295
|
+
// not supersede genuine working/approval lifecycle state.
|
|
296
|
+
const activityBlock = modelControlActivityBlock(capture);
|
|
297
|
+
if (activityBlock)
|
|
298
|
+
return activityBlock;
|
|
299
|
+
return classifyModelControlComposerSurface(capture, observation);
|
|
300
|
+
}
|
|
301
|
+
function modelControlHardBlock(capture) {
|
|
302
|
+
if (capture.approvalBlocked) {
|
|
303
|
+
return { state: "blocked", owner: "approval" };
|
|
304
|
+
}
|
|
305
|
+
return capture.inputBlocked === true
|
|
306
|
+
? { state: "blocked", owner: "input" }
|
|
307
|
+
: undefined;
|
|
308
|
+
}
|
|
309
|
+
function modelControlActivityBlock(capture) {
|
|
310
|
+
return capture.activityState === "working" ||
|
|
311
|
+
capture.activityState === "awaiting_approval"
|
|
312
|
+
? { state: "blocked", owner: "agent" }
|
|
313
|
+
: undefined;
|
|
314
|
+
}
|
|
315
|
+
function modelControlPickerKind(observation) {
|
|
316
|
+
if (observation.state === "codex_reasoning_picker" ||
|
|
317
|
+
observation.state === "codex_advanced_reasoning_picker") {
|
|
318
|
+
return "effort";
|
|
319
|
+
}
|
|
320
|
+
return observation.state === "codex_plan_scope_picker" ? "scope" : "model";
|
|
321
|
+
}
|
|
322
|
+
function classifyModelControlComposerSurface(capture, observation) {
|
|
323
|
+
const contradictoryComposerProof = capture.exactEmptyComposer && capture.exactCommandComposer ||
|
|
324
|
+
capture.exactCommandReady && capture.exactBareCommand === true ||
|
|
325
|
+
(capture.exactCommandReady || capture.exactBareCommand === true) &&
|
|
326
|
+
!capture.exactCommandComposer;
|
|
327
|
+
if (contradictoryComposerProof) {
|
|
328
|
+
return {
|
|
329
|
+
state: "unknown",
|
|
330
|
+
reason: "model-control Composer proofs conflict"
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (capture.exactCommandComposer &&
|
|
334
|
+
capture.exactCommandReady) {
|
|
335
|
+
return {
|
|
336
|
+
state: "command_popup",
|
|
337
|
+
...(capture.exactCommandFingerprint
|
|
338
|
+
? { fingerprint: capture.exactCommandFingerprint }
|
|
339
|
+
: {})
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (capture.exactCommandComposer &&
|
|
343
|
+
capture.exactBareCommand === true) {
|
|
344
|
+
return {
|
|
345
|
+
state: "bare_command",
|
|
346
|
+
...(capture.exactCommandFingerprint
|
|
347
|
+
? { fingerprint: capture.exactCommandFingerprint }
|
|
348
|
+
: {})
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (capture.exactCommandComposer) {
|
|
352
|
+
return {
|
|
353
|
+
state: "command_draft",
|
|
354
|
+
...(capture.exactCommandFingerprint
|
|
355
|
+
? { fingerprint: capture.exactCommandFingerprint }
|
|
356
|
+
: {})
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (capture.exactEmptyComposer && capture.activityState === "idle") {
|
|
360
|
+
return { state: "idle_empty" };
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
state: "unknown",
|
|
364
|
+
reason: observation.state === "ambiguous"
|
|
365
|
+
? observation.reason
|
|
366
|
+
: "no exact current model-control surface is proven"
|
|
367
|
+
};
|
|
368
|
+
}
|
|
311
369
|
const MODEL_CONTROL_SETTLE_TIMEOUT_MS = 5_000;
|
|
312
370
|
const MODEL_CONTROL_POLL_MS = 40;
|
|
371
|
+
function advanceTerminalModelControlTransaction(transaction, event) {
|
|
372
|
+
transaction.phase = reduceTerminalModelControlTransactionPhase(transaction.phase, event);
|
|
373
|
+
}
|
|
374
|
+
/** Track every nested native write even when its transport receipt rejects. */
|
|
375
|
+
function beginTerminalModelControlTransaction(input) {
|
|
376
|
+
const transaction = { phase: "no_input" };
|
|
377
|
+
return {
|
|
378
|
+
...input,
|
|
379
|
+
transaction,
|
|
380
|
+
ports: {
|
|
381
|
+
...input.ports,
|
|
382
|
+
sendText: async (terminalControl, text) => {
|
|
383
|
+
advanceTerminalModelControlTransaction(transaction, "reversible_input_attempted");
|
|
384
|
+
await input.ports.sendText(terminalControl, text);
|
|
385
|
+
},
|
|
386
|
+
sendKeys: async (terminalControl, keys) => {
|
|
387
|
+
advanceTerminalModelControlTransaction(transaction, "reversible_input_attempted");
|
|
388
|
+
await input.ports.sendKeys(terminalControl, keys);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function terminalModelControlInputAttempted(transaction) {
|
|
394
|
+
return transaction.phase === "reversible_input" ||
|
|
395
|
+
transaction.phase === "commit_attempted";
|
|
396
|
+
}
|
|
397
|
+
async function settleTerminalModelControlFailure(input, terminalControl) {
|
|
398
|
+
const pending = decideTerminalModelControlFailure(input.transaction.phase, "not_attempted");
|
|
399
|
+
if (!pending.unwind)
|
|
400
|
+
return { decision: pending };
|
|
401
|
+
const cleanupError = await tryUnwindModelControl(input, terminalControl);
|
|
402
|
+
return {
|
|
403
|
+
decision: decideTerminalModelControlFailure(input.transaction.phase, cleanupError ? "failed" : "proven"),
|
|
404
|
+
...(cleanupError ? { cleanupError } : {})
|
|
405
|
+
};
|
|
406
|
+
}
|
|
313
407
|
function modelControlCaptureBlocked(capture) {
|
|
314
408
|
return capture.approvalBlocked || capture.inputBlocked === true;
|
|
315
409
|
}
|
|
316
|
-
function modelCommandReadyForEnter(plan, capture) {
|
|
317
|
-
return capture.exactCommandReady ||
|
|
318
|
-
plan.behaviorProfile === "codex-model-control-0.154.0" &&
|
|
319
|
-
capture.exactBareCommand === true;
|
|
320
|
-
}
|
|
321
410
|
function residualFromCapture(plan, capture) {
|
|
322
|
-
|
|
411
|
+
const profile = terminalModelControlProfileForPlan(plan);
|
|
412
|
+
if (!profile?.supportsResidualRepair) {
|
|
323
413
|
return {
|
|
324
414
|
state: "unsafe",
|
|
325
|
-
reason: "model-control residual repair is profiled only for Codex
|
|
415
|
+
reason: "model-control residual repair is profiled only for Codex " +
|
|
416
|
+
CODEX_MODEL_CONTROL_AGENT_VERSION,
|
|
326
417
|
terminalControl: capture.terminalControl
|
|
327
418
|
};
|
|
328
419
|
}
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
modelObservation.state !== "ambiguous";
|
|
332
|
-
if (capture.approvalBlocked ||
|
|
333
|
-
capture.inputBlocked === true && !exactModelSurface ||
|
|
334
|
-
(capture.activityState === "working" ||
|
|
335
|
-
capture.activityState === "awaiting_approval") && !exactModelSurface) {
|
|
420
|
+
const surface = classifyTerminalModelControlSurface(plan, capture);
|
|
421
|
+
if (surface.state === "blocked") {
|
|
336
422
|
return {
|
|
337
423
|
state: "unsafe",
|
|
338
424
|
reason: "another prompt or active agent state owns terminal input",
|
|
339
425
|
terminalControl: capture.terminalControl
|
|
340
426
|
};
|
|
341
427
|
}
|
|
342
|
-
if (
|
|
428
|
+
if (surface.state === "picker") {
|
|
343
429
|
return {
|
|
344
430
|
state: "recoverable",
|
|
345
431
|
kind: "model_surface",
|
|
@@ -348,25 +434,31 @@ function residualFromCapture(plan, capture) {
|
|
|
348
434
|
version: 1,
|
|
349
435
|
behavior_profile: plan.behaviorProfile,
|
|
350
436
|
kind: "model_surface",
|
|
351
|
-
surface_state:
|
|
352
|
-
surface_fingerprint:
|
|
437
|
+
surface_state: surface.observation.state,
|
|
438
|
+
surface_fingerprint: surface.observation.fingerprint
|
|
353
439
|
}))
|
|
354
440
|
.digest("hex"),
|
|
355
441
|
terminalControl: capture.terminalControl
|
|
356
442
|
};
|
|
357
443
|
}
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
(!capture.exactCommandReady && capture.exactBareCommand !== true)) {
|
|
444
|
+
if (surface.state !== "command_popup" &&
|
|
445
|
+
surface.state !== "bare_command") {
|
|
361
446
|
return {
|
|
362
447
|
state: "absent",
|
|
363
|
-
reason:
|
|
448
|
+
reason: surface.state === "idle_empty"
|
|
364
449
|
? "the Composer is already empty"
|
|
365
450
|
: "the current Composer is not one exact profiled /model residual",
|
|
366
451
|
terminalControl: capture.terminalControl
|
|
367
452
|
};
|
|
368
453
|
}
|
|
369
|
-
|
|
454
|
+
if (!surface.fingerprint) {
|
|
455
|
+
return {
|
|
456
|
+
state: "absent",
|
|
457
|
+
reason: "the current Composer is not one exact profiled /model residual",
|
|
458
|
+
terminalControl: capture.terminalControl
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const kind = surface.state === "command_popup"
|
|
370
462
|
? "profiled_command_popup"
|
|
371
463
|
: "bare_command";
|
|
372
464
|
return {
|
|
@@ -377,7 +469,7 @@ function residualFromCapture(plan, capture) {
|
|
|
377
469
|
version: 1,
|
|
378
470
|
behavior_profile: plan.behaviorProfile,
|
|
379
471
|
kind,
|
|
380
|
-
exact_command_fingerprint:
|
|
472
|
+
exact_command_fingerprint: surface.fingerprint
|
|
381
473
|
}))
|
|
382
474
|
.digest("hex"),
|
|
383
475
|
terminalControl: capture.terminalControl
|
|
@@ -425,21 +517,15 @@ export async function repairTerminalModelControlResidual(input) {
|
|
|
425
517
|
if (observed.fingerprint !== input.expectedResidualFingerprint) {
|
|
426
518
|
throw new Error("the exact /model residual changed after authorization; refresh AKK list");
|
|
427
519
|
}
|
|
428
|
-
|
|
429
|
-
const ports = {
|
|
430
|
-
...input.ports,
|
|
431
|
-
sendKeys: async (terminalControl, keys) => {
|
|
432
|
-
terminalInputAttempted = true;
|
|
433
|
-
await input.ports.sendKeys(terminalControl, keys);
|
|
434
|
-
}
|
|
435
|
-
};
|
|
520
|
+
const operation = beginTerminalModelControlTransaction(input);
|
|
436
521
|
try {
|
|
437
|
-
const cleared = await unwindModelControl(
|
|
438
|
-
if (
|
|
439
|
-
|
|
440
|
-
!cleared.exactEmptyComposer) {
|
|
522
|
+
const cleared = await unwindModelControl(operation, observed.terminalControl);
|
|
523
|
+
if (classifyTerminalModelControlSurface(input.plan, cleared).state !==
|
|
524
|
+
"idle_empty") {
|
|
441
525
|
throw new Error("cleanup did not prove an exact idle empty Composer");
|
|
442
526
|
}
|
|
527
|
+
const terminalInputAttempted = terminalModelControlInputAttempted(operation.transaction);
|
|
528
|
+
advanceTerminalModelControlTransaction(operation.transaction, "postcondition_proven");
|
|
443
529
|
return {
|
|
444
530
|
outcome: "repaired",
|
|
445
531
|
terminalControl: cleared.terminalControl,
|
|
@@ -449,14 +535,15 @@ export async function repairTerminalModelControlResidual(input) {
|
|
|
449
535
|
};
|
|
450
536
|
}
|
|
451
537
|
catch (error) {
|
|
452
|
-
|
|
538
|
+
const decision = decideTerminalModelControlFailure(operation.transaction.phase, "failed");
|
|
539
|
+
if (decision.outcome === "throw")
|
|
453
540
|
throw error;
|
|
454
541
|
return {
|
|
455
542
|
outcome: "uncertain",
|
|
456
543
|
terminalControl: observed.terminalControl,
|
|
457
544
|
terminalInputAttempted: true,
|
|
458
545
|
composerPostcondition: "unproven",
|
|
459
|
-
doNotRetry:
|
|
546
|
+
doNotRetry: decision.doNotRetry,
|
|
460
547
|
reason: error instanceof Error ? error.message : String(error)
|
|
461
548
|
};
|
|
462
549
|
}
|
|
@@ -466,14 +553,15 @@ export async function repairTerminalModelControlResidual(input) {
|
|
|
466
553
|
* adapter-owned `/model` surface and always returns through exact Escape paths.
|
|
467
554
|
*/
|
|
468
555
|
export async function discoverTerminalModelOptions(input) {
|
|
469
|
-
const
|
|
470
|
-
|
|
556
|
+
const operation = beginTerminalModelControlTransaction(input);
|
|
557
|
+
const codexNativeCatalog = operation.agent === "codex"
|
|
558
|
+
? await operation.ports.loadCodexCatalog?.()
|
|
471
559
|
: undefined;
|
|
472
|
-
if (
|
|
560
|
+
if (operation.agent === "codex" && !codexNativeCatalog) {
|
|
473
561
|
throw new Error("Codex model discovery requires the exact running executable's validated model catalog");
|
|
474
562
|
}
|
|
475
|
-
let control =
|
|
476
|
-
const opened = await openModelPicker(
|
|
563
|
+
let control = operation.terminalControl;
|
|
564
|
+
const opened = await openModelPicker(operation, control);
|
|
477
565
|
control = opened.capture.terminalControl;
|
|
478
566
|
const picker = opened.observation;
|
|
479
567
|
try {
|
|
@@ -499,7 +587,7 @@ export async function discoverTerminalModelOptions(input) {
|
|
|
499
587
|
}]
|
|
500
588
|
: [];
|
|
501
589
|
});
|
|
502
|
-
const dismissed = await exactDismiss(
|
|
590
|
+
const dismissed = await exactDismiss(operation, control, picker);
|
|
503
591
|
control = dismissed.terminalControl;
|
|
504
592
|
// An adopted profiled slash popup temporarily replaces the ordinary
|
|
505
593
|
// footer. Once the read-only picker is dismissed, the exact idle footer
|
|
@@ -518,7 +606,7 @@ export async function discoverTerminalModelOptions(input) {
|
|
|
518
606
|
let livePicker = picker;
|
|
519
607
|
models = [];
|
|
520
608
|
for (const row of picker.rows) {
|
|
521
|
-
const inspected = await inspectClaudeModelEfforts(
|
|
609
|
+
const inspected = await inspectClaudeModelEfforts(operation, control, livePicker, row.id);
|
|
522
610
|
control = inspected.capture.terminalControl;
|
|
523
611
|
livePicker = inspected.observation;
|
|
524
612
|
if (inspected.reasoningEfforts.length > 0) {
|
|
@@ -529,7 +617,7 @@ export async function discoverTerminalModelOptions(input) {
|
|
|
529
617
|
});
|
|
530
618
|
}
|
|
531
619
|
}
|
|
532
|
-
control = (await exactDismiss(
|
|
620
|
+
control = (await exactDismiss(operation, control, livePicker)).terminalControl;
|
|
533
621
|
}
|
|
534
622
|
if (models.length === 0) {
|
|
535
623
|
throw new Error("the live native model picker had no verified model choices");
|
|
@@ -539,23 +627,25 @@ export async function discoverTerminalModelOptions(input) {
|
|
|
539
627
|
reasoningEffort: currentEffort
|
|
540
628
|
};
|
|
541
629
|
const fingerprintInput = {
|
|
542
|
-
agent:
|
|
543
|
-
agentVersion:
|
|
544
|
-
behaviorProfile:
|
|
545
|
-
scope:
|
|
630
|
+
agent: operation.agent,
|
|
631
|
+
agentVersion: operation.agentVersion,
|
|
632
|
+
behaviorProfile: operation.plan.behaviorProfile,
|
|
633
|
+
scope: operation.plan.scope,
|
|
546
634
|
current,
|
|
547
635
|
models
|
|
548
636
|
};
|
|
549
|
-
|
|
637
|
+
const result = {
|
|
550
638
|
terminalControl: control,
|
|
551
639
|
catalog: {
|
|
552
640
|
...fingerprintInput,
|
|
553
641
|
catalogFingerprint: terminalModelCatalogFingerprint(fingerprintInput)
|
|
554
642
|
}
|
|
555
643
|
};
|
|
644
|
+
advanceTerminalModelControlTransaction(operation.transaction, "postcondition_proven");
|
|
645
|
+
return result;
|
|
556
646
|
}
|
|
557
647
|
catch (error) {
|
|
558
|
-
const cleanupError = await
|
|
648
|
+
const { cleanupError } = await settleTerminalModelControlFailure(operation, control);
|
|
559
649
|
const detail = error instanceof Error ? error.message : String(error);
|
|
560
650
|
if (cleanupError) {
|
|
561
651
|
throw new Error(`${detail}; exact native model-control cleanup failed: ${cleanupError}`);
|
|
@@ -594,22 +684,22 @@ export async function switchTerminalModel(input) {
|
|
|
594
684
|
doNotRetry: false
|
|
595
685
|
};
|
|
596
686
|
}
|
|
597
|
-
|
|
687
|
+
const operation = beginTerminalModelControlTransaction(input);
|
|
598
688
|
let control = discovery.terminalControl;
|
|
599
689
|
let codexPersistenceBaseline;
|
|
600
690
|
let codexPersistenceScreen;
|
|
601
691
|
try {
|
|
602
|
-
const opened = await openModelPicker(
|
|
692
|
+
const opened = await openModelPicker(operation, control);
|
|
603
693
|
control = opened.capture.terminalControl;
|
|
604
694
|
const picker = opened.observation;
|
|
605
695
|
if (picker.state === "codex_model_picker") {
|
|
606
696
|
codexPersistenceBaseline = opened.idleCapture.screen;
|
|
607
|
-
const committed = await applyCodexModelSelection(
|
|
697
|
+
const committed = await applyCodexModelSelection(operation, control, picker, input.request, () => advanceTerminalModelControlTransaction(operation.transaction, "commit_attempted"));
|
|
608
698
|
control = committed.capture.terminalControl;
|
|
609
699
|
codexPersistenceScreen = committed.capture.screen;
|
|
610
700
|
}
|
|
611
701
|
else if (picker.state === "claude_model_picker") {
|
|
612
|
-
const selected = await transitionModelControl(
|
|
702
|
+
const selected = await transitionModelControl(operation, control, picker, modelControlSelectModelKeys(picker, input.request.model), ["claude_model_picker"]);
|
|
613
703
|
control = selected.capture.terminalControl;
|
|
614
704
|
if (selected.observation.state !== "claude_model_picker") {
|
|
615
705
|
throw new Error("Claude model selection did not remain in its picker");
|
|
@@ -629,7 +719,7 @@ export async function switchTerminalModel(input) {
|
|
|
629
719
|
throw new Error("the requested effort is absent from Claude's live effort ring");
|
|
630
720
|
}
|
|
631
721
|
seen.add(displayed);
|
|
632
|
-
const next = await transitionModelControl(
|
|
722
|
+
const next = await transitionModelControl(operation, control, effortPicker, claudeModelControlEffortKey("higher"), ["claude_model_picker"]);
|
|
633
723
|
control = next.capture.terminalControl;
|
|
634
724
|
if (next.observation.state !== "claude_model_picker") {
|
|
635
725
|
throw new Error("Claude effort adjustment left the model picker");
|
|
@@ -645,9 +735,9 @@ export async function switchTerminalModel(input) {
|
|
|
645
735
|
if (selectedClaudeModelId(effortPicker) !== input.request.model) {
|
|
646
736
|
throw new Error("Claude changed the highlighted model before session-only commit");
|
|
647
737
|
}
|
|
648
|
-
|
|
649
|
-
await exactDispatchKeys(
|
|
650
|
-
const closed = await waitForObservation(
|
|
738
|
+
advanceTerminalModelControlTransaction(operation.transaction, "commit_attempted");
|
|
739
|
+
await exactDispatchKeys(operation, control, effortPicker, claudeModelControlCommitKeys(input.plan));
|
|
740
|
+
const closed = await waitForObservation(operation, control, ["none"], {
|
|
651
741
|
allowImmediateNone: true
|
|
652
742
|
});
|
|
653
743
|
control = closed.capture.terminalControl;
|
|
@@ -656,7 +746,7 @@ export async function switchTerminalModel(input) {
|
|
|
656
746
|
throw new Error("the native model picker changed before selection");
|
|
657
747
|
}
|
|
658
748
|
let persistence = input.agent === "codex"
|
|
659
|
-
? await waitForCodexPersistencePostcondition(
|
|
749
|
+
? await waitForCodexPersistencePostcondition(operation, control, codexPersistenceBaseline ?? "", input.request, codexPersistenceScreen)
|
|
660
750
|
: { proven: true };
|
|
661
751
|
let post = await discoverTerminalModelOptions({
|
|
662
752
|
...input, terminalControl: control
|
|
@@ -670,14 +760,14 @@ export async function switchTerminalModel(input) {
|
|
|
670
760
|
effective.model === input.request.model &&
|
|
671
761
|
effective.reasoningEffort === discovery.catalog.current.reasoningEffort &&
|
|
672
762
|
effective.reasoningEffort !== requestedEffort) {
|
|
673
|
-
const reopened = await openModelPicker(
|
|
763
|
+
const reopened = await openModelPicker(operation, control);
|
|
674
764
|
if (reopened.observation.state !== "codex_model_picker") {
|
|
675
765
|
throw new Error("Codex Plan-mode reconciliation did not open its model picker");
|
|
676
766
|
}
|
|
677
767
|
const secondBaseline = reopened.idleCapture.screen;
|
|
678
|
-
const reconciled = await applyCodexModelSelection(
|
|
768
|
+
const reconciled = await applyCodexModelSelection(operation, reopened.capture.terminalControl, reopened.observation, input.request, () => advanceTerminalModelControlTransaction(operation.transaction, "commit_attempted"));
|
|
679
769
|
control = reconciled.capture.terminalControl;
|
|
680
|
-
persistence = await waitForCodexPersistencePostcondition(
|
|
770
|
+
persistence = await waitForCodexPersistencePostcondition(operation, control, secondBaseline, input.request, reconciled.capture.screen);
|
|
681
771
|
post = await discoverTerminalModelOptions({
|
|
682
772
|
...input, terminalControl: control
|
|
683
773
|
});
|
|
@@ -717,6 +807,7 @@ export async function switchTerminalModel(input) {
|
|
|
717
807
|
: { reasoningEffort: requestedEffort })
|
|
718
808
|
}
|
|
719
809
|
: undefined;
|
|
810
|
+
advanceTerminalModelControlTransaction(operation.transaction, "postcondition_proven");
|
|
720
811
|
return {
|
|
721
812
|
terminalControl: control,
|
|
722
813
|
outcome: "changed",
|
|
@@ -729,8 +820,8 @@ export async function switchTerminalModel(input) {
|
|
|
729
820
|
};
|
|
730
821
|
}
|
|
731
822
|
catch (error) {
|
|
732
|
-
const cleanupError = await
|
|
733
|
-
if (
|
|
823
|
+
const { decision, cleanupError } = await settleTerminalModelControlFailure(operation, control);
|
|
824
|
+
if (decision.outcome === "throw")
|
|
734
825
|
throw error;
|
|
735
826
|
const detail = error instanceof Error ? error.message : String(error);
|
|
736
827
|
return {
|
|
@@ -739,14 +830,14 @@ export async function switchTerminalModel(input) {
|
|
|
739
830
|
scope: input.plan.scope,
|
|
740
831
|
defaultsChanged: null,
|
|
741
832
|
requested: input.request,
|
|
742
|
-
doNotRetry:
|
|
833
|
+
doNotRetry: decision.doNotRetry,
|
|
743
834
|
reason: cleanupError
|
|
744
835
|
? `${detail}; exact native model-control cleanup failed: ${cleanupError}`
|
|
745
836
|
: detail
|
|
746
837
|
};
|
|
747
838
|
}
|
|
748
839
|
}
|
|
749
|
-
async function applyCodexModelSelection(input, terminalControl, picker, request,
|
|
840
|
+
async function applyCodexModelSelection(input, terminalControl, picker, request, beforeCommitAttempt) {
|
|
750
841
|
const target = picker.rows.find((row) => row.id === request.model);
|
|
751
842
|
if (!target) {
|
|
752
843
|
throw new Error("the requested Codex model left the current native picker");
|
|
@@ -761,7 +852,7 @@ async function applyCodexModelSelection(input, terminalControl, picker, request,
|
|
|
761
852
|
}
|
|
762
853
|
// A remotely refreshed model can become single-effort after discovery; in
|
|
763
854
|
// that case Enter may commit immediately instead of opening a submenu.
|
|
764
|
-
|
|
855
|
+
beforeCommitAttempt();
|
|
765
856
|
const reasoning = await transitionModelControl(input, moved.capture.terminalControl, moved.observation, ["C-m"], ["codex_reasoning_picker"]);
|
|
766
857
|
if (reasoning.observation.state !== "codex_reasoning_picker" ||
|
|
767
858
|
reasoning.observation.model !== request.model) {
|
|
@@ -772,7 +863,7 @@ async function applyCodexModelSelection(input, terminalControl, picker, request,
|
|
|
772
863
|
const advanced = request.reasoningEffort === "max" ||
|
|
773
864
|
request.reasoningEffort === "ultra";
|
|
774
865
|
if (!advanced)
|
|
775
|
-
|
|
866
|
+
beforeCommitAttempt();
|
|
776
867
|
let selected = await transitionModelControl(input, reasoning.capture.terminalControl, selection, keys, advanced
|
|
777
868
|
? ["codex_advanced_reasoning_picker"]
|
|
778
869
|
: ["none", "codex_plan_scope_picker"], advanced ? {} : { allowImmediateNone: true });
|
|
@@ -782,24 +873,25 @@ async function applyCodexModelSelection(input, terminalControl, picker, request,
|
|
|
782
873
|
}
|
|
783
874
|
selection = selected.observation;
|
|
784
875
|
keys = codexModelControlSelectEffortKeys(selection, request.reasoningEffort);
|
|
785
|
-
|
|
876
|
+
beforeCommitAttempt();
|
|
786
877
|
selected = await transitionModelControl(input, selected.capture.terminalControl, selection, keys, ["none", "codex_plan_scope_picker"], { allowImmediateNone: true });
|
|
787
878
|
}
|
|
788
879
|
if (selected.observation.state !== "codex_plan_scope_picker") {
|
|
789
880
|
return selected;
|
|
790
881
|
}
|
|
791
|
-
|
|
882
|
+
beforeCommitAttempt();
|
|
792
883
|
return transitionModelControl(input, selected.capture.terminalControl, selected.observation, codexModelControlAllModesKeys(selected.observation), ["none"], { allowImmediateNone: true });
|
|
793
884
|
}
|
|
794
885
|
async function openModelPicker(input, terminalControl) {
|
|
795
886
|
let cleanupControl = terminalControl;
|
|
796
|
-
let terminalInputAttempted = false;
|
|
797
887
|
try {
|
|
798
888
|
let idleCapture;
|
|
799
889
|
let revalidatedCommand;
|
|
800
890
|
if (input.initialResidual) {
|
|
801
|
-
|
|
802
|
-
|
|
891
|
+
const profile = terminalModelControlProfileForPlan(input.plan);
|
|
892
|
+
if (!profile?.supportsResidualContinuation) {
|
|
893
|
+
throw new Error("native model-control continuation is profiled only for Codex " +
|
|
894
|
+
CODEX_MODEL_CONTROL_AGENT_VERSION);
|
|
803
895
|
}
|
|
804
896
|
const observed = await inspectTerminalModelControlResidual({
|
|
805
897
|
plan: input.plan,
|
|
@@ -828,21 +920,18 @@ async function openModelPicker(input, terminalControl) {
|
|
|
828
920
|
else {
|
|
829
921
|
const before = await input.ports.capture({ terminalControl });
|
|
830
922
|
cleanupControl = before.terminalControl;
|
|
831
|
-
if (before.
|
|
832
|
-
|
|
833
|
-
!before.exactEmptyComposer) {
|
|
923
|
+
if (classifyTerminalModelControlSurface(input.plan, before).state !==
|
|
924
|
+
"idle_empty") {
|
|
834
925
|
throw new Error("model control requires an exact verified idle pane with an empty composer and no blocking interaction");
|
|
835
926
|
}
|
|
836
927
|
await input.ports.beforeInput();
|
|
837
928
|
const finalEmpty = await input.ports.capture({
|
|
838
929
|
terminalControl: before.terminalControl
|
|
839
930
|
});
|
|
840
|
-
if (finalEmpty.
|
|
841
|
-
|
|
842
|
-
!finalEmpty.exactEmptyComposer) {
|
|
931
|
+
if (classifyTerminalModelControlSurface(input.plan, finalEmpty).state !==
|
|
932
|
+
"idle_empty") {
|
|
843
933
|
throw new Error("the exact idle composer changed before /model input");
|
|
844
934
|
}
|
|
845
|
-
terminalInputAttempted = true;
|
|
846
935
|
await input.ports.sendText(finalEmpty.terminalControl, input.plan.command);
|
|
847
936
|
let commandCapture;
|
|
848
937
|
const startedAt = Date.now();
|
|
@@ -851,9 +940,10 @@ async function openModelPicker(input, terminalControl) {
|
|
|
851
940
|
terminalControl: before.terminalControl,
|
|
852
941
|
expectedComposer: input.plan.command
|
|
853
942
|
});
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
943
|
+
const surface = classifyTerminalModelControlSurface(input.plan, captured);
|
|
944
|
+
if (surface.state === "command_popup" ||
|
|
945
|
+
isTerminalModelControlPlanForAgent(input.plan, "codex") &&
|
|
946
|
+
surface.state === "bare_command") {
|
|
857
947
|
commandCapture = captured;
|
|
858
948
|
break;
|
|
859
949
|
}
|
|
@@ -867,15 +957,15 @@ async function openModelPicker(input, terminalControl) {
|
|
|
867
957
|
terminalControl: commandCapture.terminalControl,
|
|
868
958
|
expectedComposer: input.plan.command
|
|
869
959
|
});
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
!
|
|
960
|
+
const revalidatedSurface = classifyTerminalModelControlSurface(input.plan, revalidatedCommand);
|
|
961
|
+
if (revalidatedSurface.state !== "command_popup" &&
|
|
962
|
+
!(isTerminalModelControlPlanForAgent(input.plan, "codex") &&
|
|
963
|
+
revalidatedSurface.state === "bare_command")) {
|
|
873
964
|
throw new Error("the exact /model composer changed before Enter");
|
|
874
965
|
}
|
|
875
966
|
idleCapture = finalEmpty;
|
|
876
967
|
}
|
|
877
968
|
cleanupControl = revalidatedCommand.terminalControl;
|
|
878
|
-
terminalInputAttempted = true;
|
|
879
969
|
await input.ports.sendKeys(revalidatedCommand.terminalControl, ["C-m"]);
|
|
880
970
|
let opened = await waitForObservation(input, revalidatedCommand.terminalControl, ["codex_entry_model_picker", "codex_model_picker", "claude_model_picker"]);
|
|
881
971
|
if (opened.observation.state === "codex_entry_model_picker") {
|
|
@@ -902,9 +992,7 @@ async function openModelPicker(input, terminalControl) {
|
|
|
902
992
|
return { ...opened, idleCapture };
|
|
903
993
|
}
|
|
904
994
|
catch (error) {
|
|
905
|
-
|
|
906
|
-
throw error;
|
|
907
|
-
const cleanupError = await tryUnwindModelControl(input, cleanupControl);
|
|
995
|
+
const { cleanupError } = await settleTerminalModelControlFailure(input, cleanupControl);
|
|
908
996
|
if (!cleanupError)
|
|
909
997
|
throw error;
|
|
910
998
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -928,32 +1016,34 @@ async function unwindModelControl(input, terminalControl) {
|
|
|
928
1016
|
expectedComposer: input.plan.command
|
|
929
1017
|
});
|
|
930
1018
|
control = captured.terminalControl;
|
|
931
|
-
|
|
932
|
-
|
|
1019
|
+
const surface = classifyTerminalModelControlSurface(input.plan, captured);
|
|
1020
|
+
if (surface.state === "blocked") {
|
|
933
1021
|
throw new Error("the terminal became busy or blocked during cleanup");
|
|
934
1022
|
}
|
|
935
|
-
if (
|
|
1023
|
+
if (surface.state === "idle_empty") {
|
|
936
1024
|
return captured;
|
|
937
1025
|
}
|
|
938
|
-
if (
|
|
1026
|
+
if (surface.state === "command_popup" ||
|
|
1027
|
+
surface.state === "bare_command" ||
|
|
1028
|
+
surface.state === "command_draft") {
|
|
939
1029
|
await input.ports.beforeInput();
|
|
940
1030
|
const finalCommand = await input.ports.capture({
|
|
941
1031
|
terminalControl: control,
|
|
942
1032
|
expectedComposer: input.plan.command
|
|
943
1033
|
});
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1034
|
+
const finalCommandSurface = classifyTerminalModelControlSurface(input.plan, finalCommand);
|
|
1035
|
+
if (finalCommandSurface.state !== "command_popup" &&
|
|
1036
|
+
finalCommandSurface.state !== "bare_command" &&
|
|
1037
|
+
finalCommandSurface.state !== "command_draft") {
|
|
947
1038
|
throw new Error("the exact /model composer changed during cleanup");
|
|
948
1039
|
}
|
|
949
1040
|
control = finalCommand.terminalControl;
|
|
950
|
-
if (
|
|
951
|
-
input.plan
|
|
952
|
-
finalCommand.exactBareCommand !== true) {
|
|
1041
|
+
if (finalCommandSurface.state !== "bare_command" ||
|
|
1042
|
+
isTerminalModelControlPlanForAgent(input.plan, "claude")) {
|
|
953
1043
|
await input.ports.sendKeys(control, ["Escape"]);
|
|
954
1044
|
const afterEscape = await waitForModelCommandCleanupState(input, control);
|
|
955
1045
|
control = afterEscape.terminalControl;
|
|
956
|
-
if (afterEscape.
|
|
1046
|
+
if (classifyTerminalModelControlSurface(input.plan, afterEscape).state === "idle_empty")
|
|
957
1047
|
return afterEscape;
|
|
958
1048
|
}
|
|
959
1049
|
await input.ports.beforeInput();
|
|
@@ -961,10 +1051,7 @@ async function unwindModelControl(input, terminalControl) {
|
|
|
961
1051
|
terminalControl: control,
|
|
962
1052
|
expectedComposer: input.plan.command
|
|
963
1053
|
});
|
|
964
|
-
if (
|
|
965
|
-
finalBareCommand.activityState === "working" ||
|
|
966
|
-
!finalBareCommand.exactCommandComposer ||
|
|
967
|
-
finalBareCommand.exactCommandReady) {
|
|
1054
|
+
if (classifyTerminalModelControlSurface(input.plan, finalBareCommand).state !== "bare_command") {
|
|
968
1055
|
throw new Error(`the exact bare ${input.plan.behaviorProfile.startsWith("codex-")
|
|
969
1056
|
? "Codex"
|
|
970
1057
|
: "Claude"} /model composer changed during cleanup`);
|
|
@@ -977,10 +1064,10 @@ async function unwindModelControl(input, terminalControl) {
|
|
|
977
1064
|
control = cleared.capture.terminalControl;
|
|
978
1065
|
continue;
|
|
979
1066
|
}
|
|
980
|
-
|
|
981
|
-
if (observation.state === "none" || observation.state === "ambiguous") {
|
|
1067
|
+
if (surface.state !== "picker") {
|
|
982
1068
|
throw new Error("no exact current model-control frame was available for Escape");
|
|
983
1069
|
}
|
|
1070
|
+
const observation = surface.observation;
|
|
984
1071
|
control = await exactDispatchKeys(input, control, observation, ["Escape"]);
|
|
985
1072
|
const next = await waitForObservation(input, control, [
|
|
986
1073
|
"none",
|
|
@@ -1007,17 +1094,13 @@ async function waitForModelCommandCleanupState(input, terminalControl) {
|
|
|
1007
1094
|
terminalControl,
|
|
1008
1095
|
expectedComposer: input.plan.command
|
|
1009
1096
|
});
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
captured.exactBareCommand === true);
|
|
1014
|
-
if (!modelControlCaptureBlocked(captured) &&
|
|
1015
|
-
captured.activityState !== "working" && exactState) {
|
|
1016
|
-
const semanticState = captured.exactEmptyComposer
|
|
1097
|
+
const surface = classifyTerminalModelControlSurface(input.plan, captured);
|
|
1098
|
+
if (surface.state === "idle_empty" || surface.state === "bare_command") {
|
|
1099
|
+
const semanticState = surface.state === "idle_empty"
|
|
1017
1100
|
? "empty"
|
|
1018
|
-
:
|
|
1019
|
-
? `bare:${
|
|
1020
|
-
: input.plan
|
|
1101
|
+
: surface.fingerprint
|
|
1102
|
+
? `bare:${surface.fingerprint}`
|
|
1103
|
+
: isTerminalModelControlPlanForAgent(input.plan, "claude")
|
|
1021
1104
|
? "bare:claude-profiled-composer"
|
|
1022
1105
|
: undefined;
|
|
1023
1106
|
if (!semanticState) {
|
|
@@ -1073,9 +1156,11 @@ async function exactDispatchKeys(input, terminalControl, observation, keys) {
|
|
|
1073
1156
|
}
|
|
1074
1157
|
await input.ports.beforeInput();
|
|
1075
1158
|
const captured = await input.ports.capture({ terminalControl });
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1159
|
+
const surface = classifyTerminalModelControlSurface(input.plan, captured);
|
|
1160
|
+
const current = surface.state === "picker"
|
|
1161
|
+
? surface.observation
|
|
1162
|
+
: undefined;
|
|
1163
|
+
if (!current ||
|
|
1079
1164
|
current.state !== observation.state ||
|
|
1080
1165
|
current.fingerprint !== observation.fingerprint) {
|
|
1081
1166
|
throw new Error("the native model-control frame changed before key dispatch");
|
|
@@ -1089,14 +1174,18 @@ async function waitForObservation(input, terminalControl, expectedStates, option
|
|
|
1089
1174
|
let stableCaptures = 0;
|
|
1090
1175
|
while (Date.now() - startedAt <= MODEL_CONTROL_SETTLE_TIMEOUT_MS) {
|
|
1091
1176
|
const captured = await input.ports.capture({ terminalControl });
|
|
1092
|
-
|
|
1093
|
-
|
|
1177
|
+
const surface = classifyTerminalModelControlSurface(input.plan, captured);
|
|
1178
|
+
if (surface.state === "blocked") {
|
|
1094
1179
|
throw new Error("the terminal became busy or blocked during model control");
|
|
1095
1180
|
}
|
|
1096
|
-
const observation =
|
|
1097
|
-
|
|
1181
|
+
const observation = surface.state === "picker"
|
|
1182
|
+
? surface.observation
|
|
1183
|
+
: observeTerminalModelControl(input.plan, captured.screen);
|
|
1184
|
+
const expectedNone = observation.state === "none" && (options.allowImmediateNone === true && surface.state === "idle_empty" ||
|
|
1098
1185
|
options.allowExactCommandComposer === true &&
|
|
1099
|
-
|
|
1186
|
+
(surface.state === "command_popup" ||
|
|
1187
|
+
surface.state === "bare_command" ||
|
|
1188
|
+
surface.state === "command_draft"));
|
|
1100
1189
|
const expected = observation.fingerprint !== options.excludeFingerprint &&
|
|
1101
1190
|
expectedStates.includes(observation.state) &&
|
|
1102
1191
|
(observation.state !== "none" || expectedNone);
|
|
@@ -1125,11 +1214,13 @@ async function exactDismiss(input, terminalControl, observation) {
|
|
|
1125
1214
|
allowExactCommandComposer: true,
|
|
1126
1215
|
excludeFingerprint: observation.fingerprint
|
|
1127
1216
|
});
|
|
1128
|
-
|
|
1129
|
-
|
|
1217
|
+
const closedSurface = classifyTerminalModelControlSurface(input.plan, closed.capture);
|
|
1218
|
+
if (closedSurface.state === "idle_empty") {
|
|
1130
1219
|
return closed.capture;
|
|
1131
1220
|
}
|
|
1132
|
-
if (
|
|
1221
|
+
if (closedSurface.state === "command_popup" ||
|
|
1222
|
+
closedSurface.state === "bare_command" ||
|
|
1223
|
+
closedSurface.state === "command_draft") {
|
|
1133
1224
|
return unwindModelControl(input, closed.capture.terminalControl);
|
|
1134
1225
|
}
|
|
1135
1226
|
throw new Error("native model-control dismissal did not return to exact idle");
|