@scotthuang/agent-knock-knock 0.13.16 → 0.13.17
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 +8 -0
- package/dist/src/claude-terminal-agent-adapter.js +1 -1
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/codex-app-server-interaction-client.js +68 -26
- package/dist/src/codex-app-server-interaction-client.js.map +1 -1
- package/dist/src/codex-app-server-read-client.js +52 -20
- package/dist/src/codex-app-server-read-client.js.map +1 -1
- package/dist/src/codex-astra-composer-proof.js +1 -1
- package/dist/src/codex-astra-composer-proof.js.map +1 -1
- package/dist/src/codex-open-rollout-inventory.d.ts +51 -0
- package/dist/src/codex-open-rollout-inventory.js +365 -0
- package/dist/src/codex-open-rollout-inventory.js.map +1 -0
- package/dist/src/codex-paginated-thread-binding.js +1 -1
- package/dist/src/codex-paginated-thread-binding.js.map +1 -1
- package/dist/src/codex-rollout-bound-completion.d.ts +44 -0
- package/dist/src/codex-rollout-bound-completion.js +357 -0
- package/dist/src/codex-rollout-bound-completion.js.map +1 -0
- package/dist/src/codex-rollout-file.d.ts +15 -0
- package/dist/src/codex-rollout-file.js +98 -0
- package/dist/src/codex-rollout-file.js.map +1 -0
- package/dist/src/codex-rollout-human-task.d.ts +61 -0
- package/dist/src/codex-rollout-human-task.js +555 -0
- package/dist/src/codex-rollout-human-task.js.map +1 -0
- package/dist/src/codex-rollout-interaction-evidence.d.ts +48 -0
- package/dist/src/codex-rollout-interaction-evidence.js +521 -0
- package/dist/src/codex-rollout-interaction-evidence.js.map +1 -0
- package/dist/src/codex-rollout-inventory.d.ts +5 -0
- package/dist/src/codex-rollout-inventory.js +81 -0
- package/dist/src/codex-rollout-inventory.js.map +1 -0
- package/dist/src/codex-rollout-records.d.ts +49 -0
- package/dist/src/codex-rollout-records.js +298 -0
- package/dist/src/codex-rollout-records.js.map +1 -0
- package/dist/src/codex-sqlite-thread-query.d.ts +85 -0
- package/dist/src/codex-sqlite-thread-query.js +440 -0
- package/dist/src/codex-sqlite-thread-query.js.map +1 -0
- package/dist/src/codex-status-history-navigation.d.ts +1 -1
- package/dist/src/codex-status-history-navigation.js +1 -1
- package/dist/src/codex-status-history-navigation.js.map +1 -1
- package/dist/src/codex-store-adapter.d.ts +3 -50
- package/dist/src/codex-store-adapter.js +5 -796
- package/dist/src/codex-store-adapter.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.js +1 -1
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +4 -134
- package/dist/src/native-thread-lifecycle-cli-adapter.js +19 -633
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-contract.d.ts +138 -0
- package/dist/src/native-thread-lifecycle-cli-contract.js +2 -0
- package/dist/src/native-thread-lifecycle-cli-contract.js.map +1 -0
- package/dist/src/semantic-permission-control.js +33 -6
- package/dist/src/semantic-permission-control.js.map +1 -1
- package/dist/src/semantic-tool-descriptions.d.ts +26 -0
- package/dist/src/semantic-tool-descriptions.js +259 -0
- package/dist/src/semantic-tool-descriptions.js.map +1 -0
- package/dist/src/semantic-tool-runtime.js +45 -37
- package/dist/src/semantic-tool-runtime.js.map +1 -1
- package/dist/src/semantic-tool-schemas.js +134 -33
- package/dist/src/semantic-tool-schemas.js.map +1 -1
- package/dist/src/terminal-action-contracts.d.ts +4 -0
- package/dist/src/terminal-action-contracts.js +695 -0
- package/dist/src/terminal-action-contracts.js.map +1 -0
- package/dist/src/terminal-composer-classifier.d.ts +1 -1
- package/dist/src/terminal-composer-classifier.js +2 -2
- package/dist/src/terminal-composer-classifier.js.map +1 -1
- package/dist/src/terminal-interaction-response-bridge.js +1 -1
- package/dist/src/terminal-interaction-response-bridge.js.map +1 -1
- package/dist/src/terminal-list-renderer.d.ts +1 -2
- package/dist/src/terminal-list-renderer.js +8 -424
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-model-control-action-policy.d.ts +1 -1
- package/dist/src/terminal-model-control-availability.d.ts +1 -1
- package/dist/src/terminal-model-control-availability.js +1 -1
- package/dist/src/terminal-model-control-availability.js.map +1 -1
- package/dist/src/terminal-model-control-cli-adapter.d.ts +34 -0
- package/dist/src/terminal-model-control-cli-adapter.js +669 -0
- package/dist/src/terminal-model-control-cli-adapter.js.map +1 -0
- package/dist/src/terminal-model-control-contract.d.ts +198 -0
- package/dist/src/terminal-model-control-contract.js +15 -0
- package/dist/src/terminal-model-control-contract.js.map +1 -0
- package/dist/src/terminal-model-control-input.d.ts +29 -0
- package/dist/src/terminal-model-control-input.js +86 -0
- package/dist/src/terminal-model-control-input.js.map +1 -0
- package/dist/src/terminal-model-control-profile.d.ts +2 -0
- package/dist/src/terminal-model-control-profile.js +6 -0
- package/dist/src/terminal-model-control-profile.js.map +1 -1
- package/dist/src/terminal-model-control-subject.d.ts +52 -0
- package/dist/src/terminal-model-control-subject.js +73 -0
- package/dist/src/terminal-model-control-subject.js.map +1 -1
- package/dist/src/terminal-model-control-surface.d.ts +82 -0
- package/dist/src/terminal-model-control-surface.js +688 -0
- package/dist/src/terminal-model-control-surface.js.map +1 -0
- package/dist/src/terminal-model-control.d.ts +6 -270
- package/dist/src/terminal-model-control.js +11 -988
- package/dist/src/terminal-model-control.js.map +1 -1
- package/dist/src/terminal-model-selection.d.ts +33 -0
- package/dist/src/terminal-model-selection.js +143 -0
- package/dist/src/terminal-model-selection.js.map +1 -0
- package/dist/src/terminal-monitor-application-service.js +38 -59
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-monitor-attention-store.d.ts +65 -0
- package/dist/src/terminal-monitor-attention-store.js +518 -0
- package/dist/src/terminal-monitor-attention-store.js.map +1 -0
- package/dist/src/terminal-monitor-collateral-recovery.d.ts +12 -0
- package/dist/src/terminal-monitor-collateral-recovery.js +415 -0
- package/dist/src/terminal-monitor-collateral-recovery.js.map +1 -0
- package/dist/src/terminal-monitor-interaction-projection.d.ts +19 -0
- package/dist/src/terminal-monitor-interaction-projection.js +48 -0
- package/dist/src/terminal-monitor-interaction-projection.js.map +1 -0
- package/dist/src/terminal-monitor-startup-recovery.d.ts +15 -0
- package/dist/src/terminal-monitor-startup-recovery.js +767 -0
- package/dist/src/terminal-monitor-startup-recovery.js.map +1 -0
- package/dist/src/terminal-monitor-state-cli-adapter.d.ts +2 -104
- package/dist/src/terminal-monitor-state-cli-adapter.js +28 -1640
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-state-contract.d.ts +108 -0
- package/dist/src/terminal-monitor-state-contract.js +8 -0
- package/dist/src/terminal-monitor-state-contract.js.map +1 -0
- package/dist/src/terminal-native-inspection-bridge.d.ts +4 -153
- package/dist/src/terminal-native-inspection-bridge.js +7 -646
- package/dist/src/terminal-native-inspection-bridge.js.map +1 -1
- package/dist/src/terminal-native-inspection-contract.d.ts +128 -0
- package/dist/src/terminal-native-inspection-contract.js +38 -0
- package/dist/src/terminal-native-inspection-contract.js.map +1 -0
- package/dist/src/terminal-native-inspection-profile.d.ts +15 -0
- package/dist/src/terminal-native-inspection-profile.js +185 -0
- package/dist/src/terminal-native-inspection-profile.js.map +1 -0
- package/dist/src/terminal-native-inspection-proof.d.ts +62 -0
- package/dist/src/terminal-native-inspection-proof.js +362 -0
- package/dist/src/terminal-native-inspection-proof.js.map +1 -0
- package/dist/src/terminal-permission-control-bridge.js +1 -1
- package/dist/src/terminal-permission-control-bridge.js.map +1 -1
- package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-runtime-cli-adapter.js +1 -1
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
- package/dist/src/terminal-submission-acceptance.d.ts +5 -148
- package/dist/src/terminal-submission-acceptance.js +21 -1845
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- package/dist/src/terminal-watch-anchor-capture.d.ts +9 -0
- package/dist/src/terminal-watch-anchor-capture.js +139 -0
- package/dist/src/terminal-watch-anchor-capture.js.map +1 -0
- package/dist/src/terminal-watch-cli-adapter.d.ts +3 -117
- package/dist/src/terminal-watch-cli-adapter.js +10 -1992
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-cli-contract.d.ts +117 -0
- package/dist/src/terminal-watch-cli-contract.js +2 -0
- package/dist/src/terminal-watch-cli-contract.js.map +1 -0
- package/dist/src/terminal-watch-fallback-observation.d.ts +15 -0
- package/dist/src/terminal-watch-fallback-observation.js +249 -0
- package/dist/src/terminal-watch-fallback-observation.js.map +1 -0
- package/dist/src/terminal-watch-interaction-observation.d.ts +26 -0
- package/dist/src/terminal-watch-interaction-observation.js +262 -0
- package/dist/src/terminal-watch-interaction-observation.js.map +1 -0
- package/dist/src/terminal-watch-observation-adapter.d.ts +4 -0
- package/dist/src/terminal-watch-observation-adapter.js +307 -0
- package/dist/src/terminal-watch-observation-adapter.js.map +1 -0
- package/dist/src/terminal-watch-observation-evidence.d.ts +25 -0
- package/dist/src/terminal-watch-observation-evidence.js +184 -0
- package/dist/src/terminal-watch-observation-evidence.js.map +1 -0
- package/dist/src/terminal-watch-response-authority.d.ts +6 -0
- package/dist/src/terminal-watch-response-authority.js +333 -0
- package/dist/src/terminal-watch-response-authority.js.map +1 -0
- package/dist/src/terminal-watch-response-cli-adapter.d.ts +3 -0
- package/dist/src/terminal-watch-response-cli-adapter.js +230 -0
- package/dist/src/terminal-watch-response-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-terminal-identity.d.ts +39 -0
- package/dist/src/terminal-watch-terminal-identity.js +380 -0
- package/dist/src/terminal-watch-terminal-identity.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,208 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
export { terminalUserExplicitModelControlBindingToken, terminalUserExplicitModelControlRepairBindingToken, terminalUserExplicitModelControlResidualEntryBindingToken } from "./terminal-model-control-subject.js";
|
|
2
|
+
export { probeTerminalModelControl, planTerminalModelControl } from "./terminal-model-control-profile.js";
|
|
3
|
+
import { isTerminalModelReasoningEffort } from "./terminal-model-control-contract.js";
|
|
4
|
+
import { observeCodexIdleModel, classifyTerminalModelControlSurface, modelControlCaptureBlocked, codexPersistencePostcondition } from "./terminal-model-control-surface.js";
|
|
5
|
+
import { MODEL_CONTROL_SETTLE_TIMEOUT_MS, MODEL_CONTROL_POLL_MS, transitionModelControl, exactDispatchKeys, waitForObservation } from "./terminal-model-control-input.js";
|
|
6
|
+
import { inspectClaudeModelEfforts, applyCodexModelSelection, applyClaudeModelSelection } from "./terminal-model-selection.js";
|
|
7
|
+
export { TERMINAL_MODEL_REASONING_EFFORTS, isTerminalModelReasoningEffort } from "./terminal-model-control-contract.js";
|
|
8
|
+
export { parseCodexNativeModelCatalog, observeTerminalModelControl, modelControlMoveKeys, modelControlSelectModelKeys, codexModelControlSelectEffortKeys, codexModelControlAllModesKeys, claudeModelControlEffortKey, claudeModelControlCommitKeys, classifyTerminalModelControlSurface } from "./terminal-model-control-surface.js";
|
|
2
9
|
import { createHash } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
-
import { isTerminalModelControlPlanForAgent, planTerminalModelControlProfile, probeTerminalModelControlProfile, terminalModelControlProfileForPlan } from "./terminal-model-control-profile.js";
|
|
5
|
-
import { canonicalModelControlSubject } from "./terminal-model-control-subject.js";
|
|
10
|
+
import { isTerminalModelControlPlanForAgent, terminalModelControlProfileForPlan } from "./terminal-model-control-profile.js";
|
|
6
11
|
import { decideTerminalModelControlFailure, reduceTerminalModelControlTransactionPhase } from "./terminal-model-control-transaction.js";
|
|
7
|
-
import { terminalPhysicalBindingToken } from "./terminal-control-ref.js";
|
|
8
12
|
export { CLAUDE_MODEL_CONTROL_AGENT_VERSION, CODEX_MODEL_CONTROL_AGENT_VERSION, CODEX_MODEL_CONTROL_AGENT_VERSIONS, TERMINAL_MODEL_CONTROL_PROFILE_IDS, isCodexModelControlAgentVersion, isTerminalModelControlPlanForAgent, terminalModelControlPlanConforms, terminalModelControlProfileFor, terminalModelControlProfileForPlan, terminalModelControlProfiles, terminalModelControlAllowsStyledSlashPopupWithoutViewportPaint, terminalModelControlSlashCompletionRows } from "./terminal-model-control-profile.js";
|
|
9
13
|
export { canonicalModelControlSubject } from "./terminal-model-control-subject.js";
|
|
10
|
-
/** Caller-visible reasoning values. Native labels and menu positions stay private. */
|
|
11
|
-
export const TERMINAL_MODEL_REASONING_EFFORTS = [
|
|
12
|
-
"low",
|
|
13
|
-
"medium",
|
|
14
|
-
"high",
|
|
15
|
-
"xhigh",
|
|
16
|
-
"max",
|
|
17
|
-
"ultra"
|
|
18
|
-
];
|
|
19
|
-
/**
|
|
20
|
-
* One action-specific physical authority token for fresh Codex model control.
|
|
21
|
-
* The domain separator and exact profile keep this authority from being reused
|
|
22
|
-
* as Send or native-lifecycle authority even though all bind the same pane.
|
|
23
|
-
*/
|
|
24
|
-
export function terminalUserExplicitModelControlBindingToken(value) {
|
|
25
|
-
const subject = requiredModelControlSubject({
|
|
26
|
-
...value,
|
|
27
|
-
agent: "codex"
|
|
28
|
-
});
|
|
29
|
-
const physicalTerminalToken = terminalPhysicalBindingToken({
|
|
30
|
-
terminalId: subject.terminalId,
|
|
31
|
-
terminalControl: subject.terminalControl,
|
|
32
|
-
agent: "codex",
|
|
33
|
-
pid: subject.pid,
|
|
34
|
-
workspace: subject.workspace,
|
|
35
|
-
processUuid: subject.processUuid,
|
|
36
|
-
processBirth: subject.processBirth
|
|
37
|
-
});
|
|
38
|
-
return createHash("sha256")
|
|
39
|
-
.update(JSON.stringify({
|
|
40
|
-
version: 1,
|
|
41
|
-
authority: "terminal_user_explicit_model_control",
|
|
42
|
-
physical_terminal_token: physicalTerminalToken,
|
|
43
|
-
agent_version: subject.agentVersion,
|
|
44
|
-
behavior_profile: subject.behaviorProfile
|
|
45
|
-
}))
|
|
46
|
-
.digest("hex");
|
|
47
|
-
}
|
|
48
|
-
function requiredModelControlSubject(input) {
|
|
49
|
-
const subject = canonicalModelControlSubject(input);
|
|
50
|
-
if (!subject) {
|
|
51
|
-
throw new Error("model-control authority requires one canonical supported terminal subject");
|
|
52
|
-
}
|
|
53
|
-
return subject;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Snapshot authority for adopting and cleaning one exact native `/model`
|
|
57
|
-
* residual. The residual kind and normalized surface digest prevent a token
|
|
58
|
-
* for one popup, pane generation, or bare Composer from authorizing another.
|
|
59
|
-
*/
|
|
60
|
-
export function terminalUserExplicitModelControlRepairBindingToken(value) {
|
|
61
|
-
const modelControlToken = terminalUserExplicitModelControlBindingToken(value);
|
|
62
|
-
return createHash("sha256")
|
|
63
|
-
.update(JSON.stringify({
|
|
64
|
-
version: 1,
|
|
65
|
-
authority: "terminal_user_explicit_model_control_repair",
|
|
66
|
-
model_control_token: modelControlToken,
|
|
67
|
-
residual_kind: value.residualKind,
|
|
68
|
-
residual_fingerprint: value.residualFingerprint
|
|
69
|
-
}))
|
|
70
|
-
.digest("hex");
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Snapshot authority for continuing one exact native `/model` residual into
|
|
74
|
-
* the read-only catalog picker. This is deliberately domain-separated from
|
|
75
|
-
* cleanup-only repair: a repair offer never authorizes Enter, while this
|
|
76
|
-
* offer authorizes exactly one profiled slash-command dispatch.
|
|
77
|
-
*/
|
|
78
|
-
export function terminalUserExplicitModelControlResidualEntryBindingToken(value) {
|
|
79
|
-
const modelControlToken = terminalUserExplicitModelControlBindingToken(value);
|
|
80
|
-
return createHash("sha256")
|
|
81
|
-
.update(JSON.stringify({
|
|
82
|
-
version: 1,
|
|
83
|
-
authority: "terminal_user_explicit_model_control_residual_entry",
|
|
84
|
-
model_control_token: modelControlToken,
|
|
85
|
-
residual_kind: value.residualKind,
|
|
86
|
-
residual_fingerprint: value.residualFingerprint
|
|
87
|
-
}))
|
|
88
|
-
.digest("hex");
|
|
89
|
-
}
|
|
90
|
-
export function parseCodexNativeModelCatalog(value) {
|
|
91
|
-
if (!isPlainRecord(value) || !Array.isArray(value.models)) {
|
|
92
|
-
throw new Error("the exact running Codex model catalog has an unknown shape");
|
|
93
|
-
}
|
|
94
|
-
const models = value.models.flatMap((candidate) => {
|
|
95
|
-
if (!isPlainRecord(candidate) || candidate.visibility !== "list")
|
|
96
|
-
return [];
|
|
97
|
-
const id = nonBlank(candidate.slug);
|
|
98
|
-
const label = nonBlank(candidate.display_name) ?? id;
|
|
99
|
-
if (!id || !label || !/^[a-z0-9][a-z0-9._:/+\-]{0,127}$/u.test(id) ||
|
|
100
|
-
!Array.isArray(candidate.supported_reasoning_levels)) {
|
|
101
|
-
throw new Error("the exact running Codex model catalog contains an invalid visible row");
|
|
102
|
-
}
|
|
103
|
-
const efforts = candidate.supported_reasoning_levels.flatMap((entry) => {
|
|
104
|
-
const effort = isPlainRecord(entry) ? entry.effort : undefined;
|
|
105
|
-
return isTerminalModelReasoningEffort(effort) ? [effort] : [];
|
|
106
|
-
});
|
|
107
|
-
if (efforts.length === 0 || new Set(efforts).size !== efforts.length) {
|
|
108
|
-
throw new Error(`the exact running Codex model catalog has no unique supported efforts for ${id}`);
|
|
109
|
-
}
|
|
110
|
-
return [{ id, label, reasoningEfforts: efforts }];
|
|
111
|
-
});
|
|
112
|
-
if (models.length === 0 ||
|
|
113
|
-
new Set(models.map((model) => model.id)).size !== models.length) {
|
|
114
|
-
throw new Error("the exact running Codex model catalog has no unique visible models");
|
|
115
|
-
}
|
|
116
|
-
return { models };
|
|
117
|
-
}
|
|
118
|
-
function observeCodexIdleModel(screen, plan) {
|
|
119
|
-
const lines = stripAnsi(screen).replace(/\r\n?/gu, "\n").split("\n");
|
|
120
|
-
while (lines.length > 0 && lines.at(-1)?.trim() === "")
|
|
121
|
-
lines.pop();
|
|
122
|
-
const version = plan && terminalModelControlProfileForPlan(plan)?.agentVersion;
|
|
123
|
-
const modern = isCodexPaginatedVersion(version);
|
|
124
|
-
const frame = modern ? captureCodexFullscreenComposerFrame(screen, version) : undefined;
|
|
125
|
-
const footer = modern
|
|
126
|
-
? frame?.hasShortcutFooter ? frame.plainLines[frame.footerIndex] : undefined
|
|
127
|
-
: lines.at(-1);
|
|
128
|
-
const match = footer
|
|
129
|
-
? /^\s{2,}([A-Za-z0-9][A-Za-z0-9._:/+\-]{0,127})\s+(low|medium|high|xhigh|max|ultra)(?:\s+fast)?(?:\s+·.*|\s+Plan mode(?:\s+\([^)]*\))?)?\s*$/u
|
|
130
|
-
.exec(footer)
|
|
131
|
-
: undefined;
|
|
132
|
-
if (!match || !isTerminalModelReasoningEffort(match[2]))
|
|
133
|
-
return undefined;
|
|
134
|
-
const model = codexSemanticModelLabel(match[1], modern);
|
|
135
|
-
if (!model)
|
|
136
|
-
return undefined;
|
|
137
|
-
return {
|
|
138
|
-
model,
|
|
139
|
-
reasoningEffort: match[2],
|
|
140
|
-
planMode: /\bPlan mode\b/u.test(footer ?? "")
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
async function inspectClaudeModelEfforts(input, terminalControl, picker, model) {
|
|
144
|
-
const selected = await transitionModelControl(input, terminalControl, picker, modelControlSelectModelKeys(picker, model), ["claude_model_picker"]);
|
|
145
|
-
if (selected.observation.state !== "claude_model_picker" ||
|
|
146
|
-
selectedClaudeModelId(selected.observation) !== model) {
|
|
147
|
-
throw new Error("Claude highlighted a different model during catalog inspection");
|
|
148
|
-
}
|
|
149
|
-
let control = selected.capture.terminalControl;
|
|
150
|
-
let live = selected.observation;
|
|
151
|
-
const initial = live.displayedEffort;
|
|
152
|
-
if (!initial) {
|
|
153
|
-
return {
|
|
154
|
-
capture: selected.capture,
|
|
155
|
-
observation: live,
|
|
156
|
-
reasoningEfforts: []
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
const seen = new Set([initial]);
|
|
160
|
-
for (let count = 0; count < 8; count += 1) {
|
|
161
|
-
const next = await transitionModelControl(input, control, live, claudeModelControlEffortKey("higher"), ["claude_model_picker"]);
|
|
162
|
-
if (next.observation.state !== "claude_model_picker" ||
|
|
163
|
-
selectedClaudeModelId(next.observation) !== model) {
|
|
164
|
-
throw new Error("Claude changed the highlighted model while probing its effort ring");
|
|
165
|
-
}
|
|
166
|
-
control = next.capture.terminalControl;
|
|
167
|
-
live = next.observation;
|
|
168
|
-
const effort = live.displayedEffort;
|
|
169
|
-
if (!effort) {
|
|
170
|
-
throw new Error("Claude removed its effort row while it was being inspected");
|
|
171
|
-
}
|
|
172
|
-
if (effort === initial) {
|
|
173
|
-
return {
|
|
174
|
-
capture: next.capture,
|
|
175
|
-
observation: live,
|
|
176
|
-
reasoningEfforts: TERMINAL_MODEL_REASONING_EFFORTS.filter((effort) => seen.has(effort))
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
if (seen.has(effort)) {
|
|
180
|
-
throw new Error("Claude's native effort ring did not return to its initial value");
|
|
181
|
-
}
|
|
182
|
-
seen.add(effort);
|
|
183
|
-
}
|
|
184
|
-
throw new Error("Claude's native effort ring exceeded the verified bounded size");
|
|
185
|
-
}
|
|
186
|
-
export function isTerminalModelReasoningEffort(value) {
|
|
187
|
-
return typeof value === "string" &&
|
|
188
|
-
TERMINAL_MODEL_REASONING_EFFORTS.includes(value);
|
|
189
|
-
}
|
|
190
|
-
export function probeTerminalModelControl(agent, agentVersion) {
|
|
191
|
-
return probeTerminalModelControlProfile(agent, agentVersion);
|
|
192
|
-
}
|
|
193
|
-
export function planTerminalModelControl(capabilities) {
|
|
194
|
-
return planTerminalModelControlProfile(capabilities);
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Observe only complete, current model-control modal frames. Historical or
|
|
198
|
-
* partial picker text is never sufficient to authorize a key dispatch.
|
|
199
|
-
*/
|
|
200
|
-
export function observeTerminalModelControl(plan, screen) {
|
|
201
|
-
const normalized = stripAnsi(screen).replace(/\r\n?/gu, "\n");
|
|
202
|
-
return isTerminalModelControlPlanForAgent(plan, "codex")
|
|
203
|
-
? observeCodexModelControl(normalized, plan)
|
|
204
|
-
: observeClaudeModelControl(normalized);
|
|
205
|
-
}
|
|
206
14
|
export function terminalModelCatalogFingerprint(input) {
|
|
207
15
|
const canonical = {
|
|
208
16
|
agent: input.agent,
|
|
@@ -223,161 +31,6 @@ export function terminalModelCatalogFingerprint(input) {
|
|
|
223
31
|
.update(JSON.stringify(canonical))
|
|
224
32
|
.digest("hex");
|
|
225
33
|
}
|
|
226
|
-
export function modelControlMoveKeys(currentIndex, targetIndex) {
|
|
227
|
-
if (!Number.isSafeInteger(currentIndex) ||
|
|
228
|
-
!Number.isSafeInteger(targetIndex) ||
|
|
229
|
-
currentIndex < 0 ||
|
|
230
|
-
targetIndex < 0) {
|
|
231
|
-
throw new Error("model-control selection index is invalid");
|
|
232
|
-
}
|
|
233
|
-
const key = targetIndex >= currentIndex ? "Down" : "Up";
|
|
234
|
-
return Array.from({ length: Math.abs(targetIndex - currentIndex) }, () => key);
|
|
235
|
-
}
|
|
236
|
-
/** Adapter-owned native navigation; never serialize these keys to a caller. */
|
|
237
|
-
export function modelControlSelectModelKeys(observation, model) {
|
|
238
|
-
const row = observation.rows.find((candidate) => candidate.id === model);
|
|
239
|
-
if (!row)
|
|
240
|
-
throw new Error("the requested model is absent from the current native picker");
|
|
241
|
-
return [
|
|
242
|
-
...modelControlMoveKeys(observation.selectedIndex, row.nativeIndex),
|
|
243
|
-
...(observation.state === "codex_model_picker" ? ["C-m"] : [])
|
|
244
|
-
];
|
|
245
|
-
}
|
|
246
|
-
function selectedClaudeModelId(observation) {
|
|
247
|
-
return observation.rows.find((row) => row.nativeIndex === observation.selectedIndex)?.id;
|
|
248
|
-
}
|
|
249
|
-
export function codexModelControlSelectEffortKeys(observation, effort) {
|
|
250
|
-
const direct = observation.rows.find((row) => row.kind === "effort" && row.effort === effort);
|
|
251
|
-
const target = direct ?? (observation.state === "codex_reasoning_picker" &&
|
|
252
|
-
(effort === "max" || effort === "ultra")
|
|
253
|
-
? observation.rows.find((row) => row.kind === "advanced")
|
|
254
|
-
: undefined);
|
|
255
|
-
if (!target) {
|
|
256
|
-
throw new Error("the requested reasoning effort is absent from the current native picker");
|
|
257
|
-
}
|
|
258
|
-
const targetIndex = observation.rows.indexOf(target);
|
|
259
|
-
return [
|
|
260
|
-
...modelControlMoveKeys(observation.selectedIndex, targetIndex),
|
|
261
|
-
"C-m"
|
|
262
|
-
];
|
|
263
|
-
}
|
|
264
|
-
export function codexModelControlAllModesKeys(observation) {
|
|
265
|
-
const targetIndex = observation.rows.indexOf("Apply to global default and Plan mode override");
|
|
266
|
-
if (targetIndex < 0) {
|
|
267
|
-
throw new Error("Codex Plan-mode scope picker has no global-default choice");
|
|
268
|
-
}
|
|
269
|
-
return [
|
|
270
|
-
...modelControlMoveKeys(observation.selectedIndex, targetIndex),
|
|
271
|
-
"C-m"
|
|
272
|
-
];
|
|
273
|
-
}
|
|
274
|
-
export function claudeModelControlEffortKey(direction) {
|
|
275
|
-
return [direction === "lower" ? "Left" : "Right"];
|
|
276
|
-
}
|
|
277
|
-
export function claudeModelControlCommitKeys(plan) {
|
|
278
|
-
if (!isTerminalModelControlPlanForAgent(plan, "claude") ||
|
|
279
|
-
plan.scope !== "current_session") {
|
|
280
|
-
throw new Error("Claude model control cannot commit outside session-only scope");
|
|
281
|
-
}
|
|
282
|
-
return ["s"];
|
|
283
|
-
}
|
|
284
|
-
function isExactTerminalModelControlObservation(observation) {
|
|
285
|
-
return observation.state !== "none" && observation.state !== "ambiguous";
|
|
286
|
-
}
|
|
287
|
-
/** Pure capture classifier. It never authorizes input by itself. */
|
|
288
|
-
export function classifyTerminalModelControlSurface(plan, capture) {
|
|
289
|
-
const observation = observeTerminalModelControl(plan, capture.screen);
|
|
290
|
-
// Approval and a positively identified non-model input owner remain hard
|
|
291
|
-
// safety boundaries even when stale transcript text resembles a picker.
|
|
292
|
-
const hardBlock = modelControlHardBlock(capture);
|
|
293
|
-
if (hardBlock)
|
|
294
|
-
return hardBlock;
|
|
295
|
-
// Exact profile-owned surfaces outrank generic lifecycle activity. Codex and
|
|
296
|
-
// Claude can report working/waiting while their native picker owns input.
|
|
297
|
-
if (isExactTerminalModelControlObservation(observation)) {
|
|
298
|
-
return {
|
|
299
|
-
state: "picker",
|
|
300
|
-
pickerKind: modelControlPickerKind(observation),
|
|
301
|
-
observation
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
// Only a complete picker owns the lifecycle override. Composer proofs do
|
|
305
|
-
// not supersede genuine working/approval lifecycle state.
|
|
306
|
-
const activityBlock = modelControlActivityBlock(capture);
|
|
307
|
-
if (activityBlock)
|
|
308
|
-
return activityBlock;
|
|
309
|
-
return classifyModelControlComposerSurface(capture, observation);
|
|
310
|
-
}
|
|
311
|
-
function modelControlHardBlock(capture) {
|
|
312
|
-
if (capture.approvalBlocked) {
|
|
313
|
-
return { state: "blocked", owner: "approval" };
|
|
314
|
-
}
|
|
315
|
-
return capture.inputBlocked === true
|
|
316
|
-
? { state: "blocked", owner: "input" }
|
|
317
|
-
: undefined;
|
|
318
|
-
}
|
|
319
|
-
function modelControlActivityBlock(capture) {
|
|
320
|
-
return capture.activityState === "working" ||
|
|
321
|
-
capture.activityState === "awaiting_approval"
|
|
322
|
-
? { state: "blocked", owner: "agent" }
|
|
323
|
-
: undefined;
|
|
324
|
-
}
|
|
325
|
-
function modelControlPickerKind(observation) {
|
|
326
|
-
if (observation.state === "codex_reasoning_picker" ||
|
|
327
|
-
observation.state === "codex_advanced_reasoning_picker") {
|
|
328
|
-
return "effort";
|
|
329
|
-
}
|
|
330
|
-
return observation.state === "codex_plan_scope_picker" ? "scope" : "model";
|
|
331
|
-
}
|
|
332
|
-
function classifyModelControlComposerSurface(capture, observation) {
|
|
333
|
-
const contradictoryComposerProof = capture.exactEmptyComposer && capture.exactCommandComposer ||
|
|
334
|
-
capture.exactCommandReady && capture.exactBareCommand === true ||
|
|
335
|
-
(capture.exactCommandReady || capture.exactBareCommand === true) &&
|
|
336
|
-
!capture.exactCommandComposer;
|
|
337
|
-
if (contradictoryComposerProof) {
|
|
338
|
-
return {
|
|
339
|
-
state: "unknown",
|
|
340
|
-
reason: "model-control Composer proofs conflict"
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
if (capture.exactCommandComposer &&
|
|
344
|
-
capture.exactCommandReady) {
|
|
345
|
-
return {
|
|
346
|
-
state: "command_popup",
|
|
347
|
-
...(capture.exactCommandFingerprint
|
|
348
|
-
? { fingerprint: capture.exactCommandFingerprint }
|
|
349
|
-
: {})
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
if (capture.exactCommandComposer &&
|
|
353
|
-
capture.exactBareCommand === true) {
|
|
354
|
-
return {
|
|
355
|
-
state: "bare_command",
|
|
356
|
-
...(capture.exactCommandFingerprint
|
|
357
|
-
? { fingerprint: capture.exactCommandFingerprint }
|
|
358
|
-
: {})
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
if (capture.exactCommandComposer) {
|
|
362
|
-
return {
|
|
363
|
-
state: "command_draft",
|
|
364
|
-
...(capture.exactCommandFingerprint
|
|
365
|
-
? { fingerprint: capture.exactCommandFingerprint }
|
|
366
|
-
: {})
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
if (capture.exactEmptyComposer && capture.activityState === "idle") {
|
|
370
|
-
return { state: "idle_empty" };
|
|
371
|
-
}
|
|
372
|
-
return {
|
|
373
|
-
state: "unknown",
|
|
374
|
-
reason: observation.state === "ambiguous"
|
|
375
|
-
? observation.reason
|
|
376
|
-
: "no exact current model-control surface is proven"
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
const MODEL_CONTROL_SETTLE_TIMEOUT_MS = 5_000;
|
|
380
|
-
const MODEL_CONTROL_POLL_MS = 40;
|
|
381
34
|
function advanceTerminalModelControlTransaction(transaction, event) {
|
|
382
35
|
transaction.phase = reduceTerminalModelControlTransactionPhase(transaction.phase, event);
|
|
383
36
|
}
|
|
@@ -414,9 +67,6 @@ async function settleTerminalModelControlFailure(input, terminalControl) {
|
|
|
414
67
|
...(cleanupError ? { cleanupError } : {})
|
|
415
68
|
};
|
|
416
69
|
}
|
|
417
|
-
function modelControlCaptureBlocked(capture) {
|
|
418
|
-
return capture.approvalBlocked || capture.inputBlocked === true;
|
|
419
|
-
}
|
|
420
70
|
function residualFromCapture(plan, capture) {
|
|
421
71
|
const profile = terminalModelControlProfileForPlan(plan);
|
|
422
72
|
if (!profile?.supportsResidualRepair) {
|
|
@@ -708,48 +358,8 @@ export async function switchTerminalModel(input) {
|
|
|
708
358
|
codexPersistenceScreen = committed.capture.screen;
|
|
709
359
|
}
|
|
710
360
|
else if (picker.state === "claude_model_picker") {
|
|
711
|
-
const
|
|
712
|
-
control =
|
|
713
|
-
if (selected.observation.state !== "claude_model_picker") {
|
|
714
|
-
throw new Error("Claude model selection did not remain in its picker");
|
|
715
|
-
}
|
|
716
|
-
if (selectedClaudeModelId(selected.observation) !== input.request.model) {
|
|
717
|
-
throw new Error("Claude highlighted a different model before effort selection");
|
|
718
|
-
}
|
|
719
|
-
let effortPicker = selected.observation;
|
|
720
|
-
const seen = new Set();
|
|
721
|
-
for (let count = 0; count < 7; count += 1) {
|
|
722
|
-
const displayed = effortPicker.displayedEffort;
|
|
723
|
-
if (!displayed)
|
|
724
|
-
throw new Error("Claude model picker has no exact effort value");
|
|
725
|
-
if (displayed === requestedEffort)
|
|
726
|
-
break;
|
|
727
|
-
if (seen.has(displayed)) {
|
|
728
|
-
throw new Error("the requested effort is absent from Claude's live effort ring");
|
|
729
|
-
}
|
|
730
|
-
seen.add(displayed);
|
|
731
|
-
const next = await transitionModelControl(operation, control, effortPicker, claudeModelControlEffortKey("higher"), ["claude_model_picker"]);
|
|
732
|
-
control = next.capture.terminalControl;
|
|
733
|
-
if (next.observation.state !== "claude_model_picker") {
|
|
734
|
-
throw new Error("Claude effort adjustment left the model picker");
|
|
735
|
-
}
|
|
736
|
-
if (selectedClaudeModelId(next.observation) !== input.request.model) {
|
|
737
|
-
throw new Error("Claude changed the highlighted model during effort selection");
|
|
738
|
-
}
|
|
739
|
-
effortPicker = next.observation;
|
|
740
|
-
}
|
|
741
|
-
if (effortPicker.displayedEffort !== requestedEffort) {
|
|
742
|
-
throw new Error("the requested effort is absent from Claude's live effort ring");
|
|
743
|
-
}
|
|
744
|
-
if (selectedClaudeModelId(effortPicker) !== input.request.model) {
|
|
745
|
-
throw new Error("Claude changed the highlighted model before session-only commit");
|
|
746
|
-
}
|
|
747
|
-
advanceTerminalModelControlTransaction(operation.transaction, "commit_attempted");
|
|
748
|
-
await exactDispatchKeys(operation, control, effortPicker, claudeModelControlCommitKeys(input.plan));
|
|
749
|
-
const closed = await waitForObservation(operation, control, ["none"], {
|
|
750
|
-
allowImmediateNone: true
|
|
751
|
-
});
|
|
752
|
-
control = closed.capture.terminalControl;
|
|
361
|
+
const committed = await applyClaudeModelSelection(operation, control, picker, input.request, () => advanceTerminalModelControlTransaction(operation.transaction, "commit_attempted"), (observedControl) => { control = observedControl; });
|
|
362
|
+
control = committed.capture.terminalControl;
|
|
753
363
|
}
|
|
754
364
|
else {
|
|
755
365
|
throw new Error("the native model picker changed before selection");
|
|
@@ -846,51 +456,6 @@ export async function switchTerminalModel(input) {
|
|
|
846
456
|
};
|
|
847
457
|
}
|
|
848
458
|
}
|
|
849
|
-
async function applyCodexModelSelection(input, terminalControl, picker, request, beforeCommitAttempt) {
|
|
850
|
-
const target = picker.rows.find((row) => row.id === request.model);
|
|
851
|
-
if (!target) {
|
|
852
|
-
throw new Error("the requested Codex model left the current native picker");
|
|
853
|
-
}
|
|
854
|
-
const moved = await transitionModelControl(input, terminalControl, picker, modelControlMoveKeys(picker.selectedIndex, target.nativeIndex), ["codex_model_picker"]);
|
|
855
|
-
if (moved.observation.state !== "codex_model_picker") {
|
|
856
|
-
throw new Error("Codex model navigation left its native picker");
|
|
857
|
-
}
|
|
858
|
-
const movedSelection = moved.observation.selectedIndex;
|
|
859
|
-
if (moved.observation.rows.find((row) => row.nativeIndex === movedSelection)?.id !== request.model) {
|
|
860
|
-
throw new Error("Codex highlighted a different model before selection");
|
|
861
|
-
}
|
|
862
|
-
// A remotely refreshed model can become single-effort after discovery; in
|
|
863
|
-
// that case Enter may commit immediately instead of opening a submenu.
|
|
864
|
-
beforeCommitAttempt();
|
|
865
|
-
const reasoning = await transitionModelControl(input, moved.capture.terminalControl, moved.observation, ["C-m"], ["codex_reasoning_picker"]);
|
|
866
|
-
if (reasoning.observation.state !== "codex_reasoning_picker" ||
|
|
867
|
-
reasoning.observation.model !== request.model) {
|
|
868
|
-
throw new Error("Codex opened a different reasoning picker");
|
|
869
|
-
}
|
|
870
|
-
let selection = reasoning.observation;
|
|
871
|
-
let keys = codexModelControlSelectEffortKeys(selection, request.reasoningEffort);
|
|
872
|
-
const advanced = request.reasoningEffort === "max" ||
|
|
873
|
-
request.reasoningEffort === "ultra";
|
|
874
|
-
if (!advanced)
|
|
875
|
-
beforeCommitAttempt();
|
|
876
|
-
let selected = await transitionModelControl(input, reasoning.capture.terminalControl, selection, keys, advanced
|
|
877
|
-
? ["codex_advanced_reasoning_picker"]
|
|
878
|
-
: ["none", "codex_plan_scope_picker"], advanced ? {} : { allowImmediateNone: true });
|
|
879
|
-
if (advanced) {
|
|
880
|
-
if (selected.observation.state !== "codex_advanced_reasoning_picker") {
|
|
881
|
-
throw new Error("Codex advanced-reasoning picker did not materialize");
|
|
882
|
-
}
|
|
883
|
-
selection = selected.observation;
|
|
884
|
-
keys = codexModelControlSelectEffortKeys(selection, request.reasoningEffort);
|
|
885
|
-
beforeCommitAttempt();
|
|
886
|
-
selected = await transitionModelControl(input, selected.capture.terminalControl, selection, keys, ["none", "codex_plan_scope_picker"], { allowImmediateNone: true });
|
|
887
|
-
}
|
|
888
|
-
if (selected.observation.state !== "codex_plan_scope_picker") {
|
|
889
|
-
return selected;
|
|
890
|
-
}
|
|
891
|
-
beforeCommitAttempt();
|
|
892
|
-
return transitionModelControl(input, selected.capture.terminalControl, selected.observation, codexModelControlAllModesKeys(selected.observation), ["none"], { allowImmediateNone: true });
|
|
893
|
-
}
|
|
894
459
|
async function openModelPicker(input, terminalControl) {
|
|
895
460
|
let cleanupControl = terminalControl;
|
|
896
461
|
try {
|
|
@@ -1134,87 +699,6 @@ async function waitForModelCommandCleanupState(input, terminalControl) {
|
|
|
1134
699
|
}
|
|
1135
700
|
throw new Error("/model suggestions did not close to an exact bare or empty composer");
|
|
1136
701
|
}
|
|
1137
|
-
async function transitionModelControl(input, terminalControl, observation, keys, expectedStates, options = {}) {
|
|
1138
|
-
if (keys.length === 0) {
|
|
1139
|
-
return waitForObservation(input, terminalControl, expectedStates, options);
|
|
1140
|
-
}
|
|
1141
|
-
let control = terminalControl;
|
|
1142
|
-
let current = observation;
|
|
1143
|
-
for (const [index, key] of keys.entries()) {
|
|
1144
|
-
control = await exactDispatchKeys(input, control, current, [key]);
|
|
1145
|
-
const isLast = index === keys.length - 1;
|
|
1146
|
-
const next = await waitForObservation(input, control, isLast ? expectedStates : [current.state], {
|
|
1147
|
-
...(isLast ? options : {}),
|
|
1148
|
-
...(key === "Up" || key === "Down" || key === "Left" || key === "Right"
|
|
1149
|
-
? { excludeFingerprint: current.fingerprint }
|
|
1150
|
-
: {})
|
|
1151
|
-
});
|
|
1152
|
-
current = next.observation;
|
|
1153
|
-
control = next.capture.terminalControl;
|
|
1154
|
-
if (isLast)
|
|
1155
|
-
return next;
|
|
1156
|
-
}
|
|
1157
|
-
throw new Error("model-control transition dispatched no key");
|
|
1158
|
-
}
|
|
1159
|
-
async function exactDispatchKeys(input, terminalControl, observation, keys) {
|
|
1160
|
-
if (keys.length === 0)
|
|
1161
|
-
return terminalControl;
|
|
1162
|
-
if (keys.length !== 1) {
|
|
1163
|
-
throw new Error("model-control transport accepts exactly one native key per proof");
|
|
1164
|
-
}
|
|
1165
|
-
await input.ports.beforeInput();
|
|
1166
|
-
const captured = await input.ports.capture({ terminalControl });
|
|
1167
|
-
const surface = classifyTerminalModelControlSurface(input.plan, captured);
|
|
1168
|
-
const current = surface.state === "picker"
|
|
1169
|
-
? surface.observation
|
|
1170
|
-
: undefined;
|
|
1171
|
-
if (!current ||
|
|
1172
|
-
current.state !== observation.state ||
|
|
1173
|
-
current.fingerprint !== observation.fingerprint) {
|
|
1174
|
-
throw new Error("the native model-control frame changed before key dispatch");
|
|
1175
|
-
}
|
|
1176
|
-
await input.ports.sendKeys(captured.terminalControl, keys);
|
|
1177
|
-
return captured.terminalControl;
|
|
1178
|
-
}
|
|
1179
|
-
async function waitForObservation(input, terminalControl, expectedStates, options = {}) {
|
|
1180
|
-
const startedAt = Date.now();
|
|
1181
|
-
let stableFingerprint;
|
|
1182
|
-
let stableCaptures = 0;
|
|
1183
|
-
while (Date.now() - startedAt <= MODEL_CONTROL_SETTLE_TIMEOUT_MS) {
|
|
1184
|
-
const captured = await input.ports.capture({ terminalControl });
|
|
1185
|
-
const surface = classifyTerminalModelControlSurface(input.plan, captured);
|
|
1186
|
-
if (surface.state === "blocked") {
|
|
1187
|
-
throw new Error("the terminal became busy or blocked during model control");
|
|
1188
|
-
}
|
|
1189
|
-
const observation = surface.state === "picker"
|
|
1190
|
-
? surface.observation
|
|
1191
|
-
: observeTerminalModelControl(input.plan, captured.screen);
|
|
1192
|
-
const expectedNone = observation.state === "none" && (options.allowImmediateNone === true && surface.state === "idle_empty" ||
|
|
1193
|
-
options.allowExactCommandComposer === true &&
|
|
1194
|
-
(surface.state === "command_popup" ||
|
|
1195
|
-
surface.state === "bare_command" ||
|
|
1196
|
-
surface.state === "command_draft"));
|
|
1197
|
-
const expected = observation.fingerprint !== options.excludeFingerprint &&
|
|
1198
|
-
expectedStates.includes(observation.state) &&
|
|
1199
|
-
(observation.state !== "none" || expectedNone);
|
|
1200
|
-
if (expected) {
|
|
1201
|
-
if (observation.fingerprint === stableFingerprint)
|
|
1202
|
-
stableCaptures += 1;
|
|
1203
|
-
else {
|
|
1204
|
-
stableFingerprint = observation.fingerprint;
|
|
1205
|
-
stableCaptures = 1;
|
|
1206
|
-
}
|
|
1207
|
-
if (stableCaptures >= 2)
|
|
1208
|
-
return { capture: captured, observation };
|
|
1209
|
-
}
|
|
1210
|
-
else {
|
|
1211
|
-
stableFingerprint = undefined;
|
|
1212
|
-
stableCaptures = 0;
|
|
1213
|
-
}
|
|
1214
|
-
await input.ports.sleep(MODEL_CONTROL_POLL_MS);
|
|
1215
|
-
}
|
|
1216
|
-
throw new Error("the expected native model-control frame did not become stable");
|
|
1217
|
-
}
|
|
1218
702
|
async function exactDismiss(input, terminalControl, observation) {
|
|
1219
703
|
const control = await exactDispatchKeys(input, terminalControl, observation, ["Escape"]);
|
|
1220
704
|
const closed = await waitForObservation(input, control, ["none"], {
|
|
@@ -1233,461 +717,6 @@ async function exactDismiss(input, terminalControl, observation) {
|
|
|
1233
717
|
}
|
|
1234
718
|
throw new Error("native model-control dismissal did not return to exact idle");
|
|
1235
719
|
}
|
|
1236
|
-
function observeCodexModelControl(screen, plan) {
|
|
1237
|
-
const lines = terminalTailLines(screen);
|
|
1238
|
-
const fingerprint = screenFingerprint(lines);
|
|
1239
|
-
const entryHeader = lastIndex(lines, (line) => line.trim() === "Select Model");
|
|
1240
|
-
const modelHeader = lastIndex(lines, (line) => line.trim() === "Select Model and Effort");
|
|
1241
|
-
const reasoningHeader = lastIndex(lines, (line) => /^\s*Select Reasoning Level for \S+\s*$/u.test(line));
|
|
1242
|
-
const advancedHeader = lastIndex(lines, (line) => line.trim() === "Advanced Reasoning");
|
|
1243
|
-
const scopeHeader = lastIndex(lines, (line) => line.trim() === "Apply reasoning change");
|
|
1244
|
-
const latest = Math.max(entryHeader, modelHeader, reasoningHeader, advancedHeader, scopeHeader);
|
|
1245
|
-
if (latest < 0) {
|
|
1246
|
-
return { state: "none", fingerprint, reason: "no current Codex model picker is visible" };
|
|
1247
|
-
}
|
|
1248
|
-
const modern = isCodexPaginatedVersion(terminalModelControlProfileForPlan(plan)?.agentVersion);
|
|
1249
|
-
const region = currentPickerRegion(lines, latest, modern);
|
|
1250
|
-
if (!region) {
|
|
1251
|
-
return {
|
|
1252
|
-
state: "ambiguous",
|
|
1253
|
-
fingerprint,
|
|
1254
|
-
reason: "the current Codex model picker is incomplete or has trailing content"
|
|
1255
|
-
};
|
|
1256
|
-
}
|
|
1257
|
-
if (region.some((line) => line.includes("OpenAI base URL is overridden"))) {
|
|
1258
|
-
return {
|
|
1259
|
-
state: "ambiguous",
|
|
1260
|
-
fingerprint,
|
|
1261
|
-
reason: "Codex model control is unavailable with an overridden OpenAI base URL"
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
if (latest === entryHeader) {
|
|
1265
|
-
const parsedEntry = parseCodexEntryModelPicker(region);
|
|
1266
|
-
if (!parsedEntry) {
|
|
1267
|
-
return {
|
|
1268
|
-
state: "ambiguous",
|
|
1269
|
-
fingerprint,
|
|
1270
|
-
reason: "the Codex entry model picker is not exact"
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
|
-
return { state: "codex_entry_model_picker", fingerprint, ...parsedEntry };
|
|
1274
|
-
}
|
|
1275
|
-
if (latest === modelHeader) {
|
|
1276
|
-
const parsedRows = parseCodexModelRows(region, modern);
|
|
1277
|
-
if (!parsedRows || modern && ![
|
|
1278
|
-
"enter select · esc back", "enter default · s session · esc back"
|
|
1279
|
-
].includes(region.at(-1).trim())) {
|
|
1280
|
-
return { state: "ambiguous", fingerprint, reason: "the Codex model catalog frame is not exact" };
|
|
1281
|
-
}
|
|
1282
|
-
return { state: "codex_model_picker", fingerprint, ...parsedRows };
|
|
1283
|
-
}
|
|
1284
|
-
if (latest === reasoningHeader) {
|
|
1285
|
-
const modelLabel = /^\s*Select Reasoning Level for (\S+)\s*$/u.exec(region[0] ?? "")?.[1];
|
|
1286
|
-
const model = modelLabel && codexSemanticModelLabel(modelLabel, modern);
|
|
1287
|
-
const parsedRows = parseCodexEffortRows(region, false);
|
|
1288
|
-
if (!model || !parsedRows || modern &&
|
|
1289
|
-
!codexFullscreenEffortFooterMatches(region, parsedRows.rows[parsedRows.selectedIndex])) {
|
|
1290
|
-
return { state: "ambiguous", fingerprint, reason: "the Codex reasoning frame is not exact" };
|
|
1291
|
-
}
|
|
1292
|
-
return {
|
|
1293
|
-
state: "codex_reasoning_picker",
|
|
1294
|
-
fingerprint,
|
|
1295
|
-
model,
|
|
1296
|
-
...parsedRows
|
|
1297
|
-
};
|
|
1298
|
-
}
|
|
1299
|
-
if (latest === advancedHeader) {
|
|
1300
|
-
const parsedRows = parseCodexEffortRows(region, true);
|
|
1301
|
-
if (!parsedRows || modern &&
|
|
1302
|
-
!codexFullscreenEffortFooterMatches(region, parsedRows.rows[parsedRows.selectedIndex])) {
|
|
1303
|
-
return { state: "ambiguous", fingerprint, reason: "the Codex advanced-reasoning frame is not exact" };
|
|
1304
|
-
}
|
|
1305
|
-
return {
|
|
1306
|
-
state: "codex_advanced_reasoning_picker",
|
|
1307
|
-
fingerprint,
|
|
1308
|
-
...parsedRows
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
const rows = region.flatMap((line) => {
|
|
1312
|
-
const match = (modern
|
|
1313
|
-
? /^\s*(?:›\s*)?\d+\.\s+(Apply to (?:Plan mode override|global default and Plan mode override))(?:\s{2,}.+)?\s*$/u
|
|
1314
|
-
: /^\s*(?:›\s*)?\d+\.\s+(Apply to (?:Plan mode override|global default and Plan mode override))\s*$/u)
|
|
1315
|
-
.exec(line);
|
|
1316
|
-
return match ? [match[1]] : [];
|
|
1317
|
-
});
|
|
1318
|
-
const numberedLines = region.filter((line) => /^\s*(?:›\s*)?\d+\./u.test(line));
|
|
1319
|
-
const selectedLines = numberedLines.filter((line) => /^\s*›\s*\d+\./u.test(line));
|
|
1320
|
-
if (rows.length !== 2 ||
|
|
1321
|
-
numberedLines.length !== rows.length ||
|
|
1322
|
-
selectedLines.length !== 1 ||
|
|
1323
|
-
rows[0] !== "Apply to Plan mode override" ||
|
|
1324
|
-
rows[1] !== "Apply to global default and Plan mode override" ||
|
|
1325
|
-
modern && region.at(-1)?.trim() !== "enter select · esc back" ||
|
|
1326
|
-
!numberedLines.every((line, index) => new RegExp(`^\\s*(?:›\\s*)?${index + 1}\\.`).test(line))) {
|
|
1327
|
-
return { state: "ambiguous", fingerprint, reason: "the Codex Plan-mode scope frame is not exact" };
|
|
1328
|
-
}
|
|
1329
|
-
const selectedRow = numberedLines.findIndex((line) => /^\s*›/u.test(line));
|
|
1330
|
-
return {
|
|
1331
|
-
state: "codex_plan_scope_picker",
|
|
1332
|
-
fingerprint,
|
|
1333
|
-
rows,
|
|
1334
|
-
selectedIndex: selectedRow
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
|
-
function observeClaudeModelControl(screen) {
|
|
1338
|
-
const lines = terminalTailLines(screen);
|
|
1339
|
-
const fingerprint = screenFingerprint(lines);
|
|
1340
|
-
const headerIndex = lastIndex(lines, (line) => line.trim() === "Select model");
|
|
1341
|
-
if (headerIndex < 0) {
|
|
1342
|
-
return { state: "none", fingerprint, reason: "no current Claude model picker is visible" };
|
|
1343
|
-
}
|
|
1344
|
-
if (headerIndex === 0 ||
|
|
1345
|
-
!/^\s*[▔¯─━]{8,}\s*$/u.test(lines[headerIndex - 1])) {
|
|
1346
|
-
return { state: "ambiguous", fingerprint, reason: "the Claude model picker is not anchored to its top border" };
|
|
1347
|
-
}
|
|
1348
|
-
const region = lines.slice(headerIndex);
|
|
1349
|
-
const footerIndex = region.findIndex((line) => /^\s*Enter to set as default\s+·\s+s to use this session only\s+·\s+Esc to cancel\s*$/u
|
|
1350
|
-
.test(line));
|
|
1351
|
-
if (footerIndex < 0 || region.slice(footerIndex + 1).some((line) => line.trim())) {
|
|
1352
|
-
return { state: "ambiguous", fingerprint, reason: "the current Claude model picker footer is incomplete" };
|
|
1353
|
-
}
|
|
1354
|
-
const picker = region.slice(0, footerIndex + 1);
|
|
1355
|
-
const rows = [];
|
|
1356
|
-
const nativeRows = [];
|
|
1357
|
-
for (const line of picker) {
|
|
1358
|
-
const match = /^\s*(❯\s*)?(\d+)\.\s+(.+?)(?:\s{2,})(\S.*)$/u.exec(line);
|
|
1359
|
-
if (!match)
|
|
1360
|
-
continue;
|
|
1361
|
-
const rawLabel = match[3].trim();
|
|
1362
|
-
const label = rawLabel.replace(/\s+✔\s*$/u, "").trim();
|
|
1363
|
-
const description = match[4].trim();
|
|
1364
|
-
nativeRows.push({
|
|
1365
|
-
nativeIndex: Number(match[2]) - 1,
|
|
1366
|
-
selected: Boolean(match[1]),
|
|
1367
|
-
label,
|
|
1368
|
-
description,
|
|
1369
|
-
current: rawLabel.endsWith("✔")
|
|
1370
|
-
});
|
|
1371
|
-
}
|
|
1372
|
-
const numberedClaudeLines = picker.filter((line) => /^\s*(?:❯\s*)?\d+\./u.test(line));
|
|
1373
|
-
if (numberedClaudeLines.length !== nativeRows.length ||
|
|
1374
|
-
nativeRows.some((row, index) => row.nativeIndex !== index)) {
|
|
1375
|
-
return { state: "ambiguous", fingerprint, reason: "the Claude model numbering is not exact" };
|
|
1376
|
-
}
|
|
1377
|
-
for (const row of nativeRows) {
|
|
1378
|
-
if (/^Default(?:\s+\(recommended\))?$/iu.test(row.label) ||
|
|
1379
|
-
/Opus Plan Mode/iu.test(row.label) ||
|
|
1380
|
-
/Opus Plan Mode/iu.test(row.description) ||
|
|
1381
|
-
/\bFable\b/iu.test(row.label) ||
|
|
1382
|
-
/\bFable\b/iu.test(row.description))
|
|
1383
|
-
continue;
|
|
1384
|
-
const id = claudeSemanticModelId(`${row.label} ${row.description}`);
|
|
1385
|
-
if (!id)
|
|
1386
|
-
continue;
|
|
1387
|
-
rows.push({
|
|
1388
|
-
id,
|
|
1389
|
-
label: row.label,
|
|
1390
|
-
nativeIndex: row.nativeIndex,
|
|
1391
|
-
selected: row.selected,
|
|
1392
|
-
current: row.current,
|
|
1393
|
-
presetDefault: false
|
|
1394
|
-
});
|
|
1395
|
-
}
|
|
1396
|
-
const selectedRows = nativeRows.filter((row) => row.selected);
|
|
1397
|
-
const selectedNativeIndex = selectedRows[0]?.nativeIndex ?? -1;
|
|
1398
|
-
const currentNativeRows = nativeRows.filter((row) => row.current);
|
|
1399
|
-
const currentModel = currentNativeRows.length === 1
|
|
1400
|
-
? claudeSemanticModelId(`${currentNativeRows[0].label} ${currentNativeRows[0].description}`)
|
|
1401
|
-
: undefined;
|
|
1402
|
-
if (rows.length === 0 ||
|
|
1403
|
-
selectedRows.length !== 1 ||
|
|
1404
|
-
!currentModel ||
|
|
1405
|
-
!rows.some((row) => row.id === currentModel) ||
|
|
1406
|
-
new Set(rows.map((row) => row.id)).size !== rows.length) {
|
|
1407
|
-
return { state: "ambiguous", fingerprint, reason: "the Claude model rows are not profiled" };
|
|
1408
|
-
}
|
|
1409
|
-
const displayedEfforts = picker.flatMap((line) => {
|
|
1410
|
-
const match = /^\s*[●○◐◉◈✦]\s+(Low|Medium|High|Extra high|Xhigh|Max|Ultracode)\s+effort\b/iu
|
|
1411
|
-
.exec(line);
|
|
1412
|
-
const value = match ? claudeEffortFromNativeLabel(match[1]) : undefined;
|
|
1413
|
-
return value ? [value] : [];
|
|
1414
|
-
});
|
|
1415
|
-
if (displayedEfforts.length > 1) {
|
|
1416
|
-
return { state: "ambiguous", fingerprint, reason: "the Claude effort row is not exact" };
|
|
1417
|
-
}
|
|
1418
|
-
const displayedEffort = displayedEfforts[0];
|
|
1419
|
-
return {
|
|
1420
|
-
state: "claude_model_picker",
|
|
1421
|
-
fingerprint,
|
|
1422
|
-
rows,
|
|
1423
|
-
selectedIndex: selectedNativeIndex,
|
|
1424
|
-
currentNativeIndex: currentNativeRows[0].nativeIndex,
|
|
1425
|
-
currentModel,
|
|
1426
|
-
...(displayedEffort && displayedEffort !== "ultracode"
|
|
1427
|
-
? { currentEffort: displayedEffort }
|
|
1428
|
-
: {}),
|
|
1429
|
-
...(displayedEffort ? { displayedEffort } : {})
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
function codexSemanticModelLabel(label, fullscreen) {
|
|
1433
|
-
if (/^[a-z0-9][a-z0-9._:/+\-]*$/u.test(label))
|
|
1434
|
-
return label;
|
|
1435
|
-
return fullscreen && /^GPT-[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(label)
|
|
1436
|
-
? label.toLowerCase()
|
|
1437
|
-
: undefined;
|
|
1438
|
-
}
|
|
1439
|
-
function parseCodexModelRows(region, fullscreen = false) {
|
|
1440
|
-
const rows = [];
|
|
1441
|
-
for (const line of region.slice(1, -1)) {
|
|
1442
|
-
const match = /^\s*(›\s*)?(\d+)\.\s+([A-Za-z0-9][A-Za-z0-9._:/+\-]*)(?:\s+((?:\((?:current|default)\)\s*){1,2}))?(?:\s{2,}.*)?$/u
|
|
1443
|
-
.exec(line);
|
|
1444
|
-
if (!match)
|
|
1445
|
-
continue;
|
|
1446
|
-
const id = codexSemanticModelLabel(match[3], fullscreen);
|
|
1447
|
-
if (!id)
|
|
1448
|
-
return undefined;
|
|
1449
|
-
const flags = match[4] ?? "";
|
|
1450
|
-
rows.push({
|
|
1451
|
-
id,
|
|
1452
|
-
label: match[3],
|
|
1453
|
-
nativeIndex: Number(match[2]) - 1,
|
|
1454
|
-
selected: Boolean(match[1]),
|
|
1455
|
-
current: flags.includes("(current)"),
|
|
1456
|
-
presetDefault: flags.includes("(default)")
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
const numberedLines = region.slice(1, -1).filter((line) => /^\s*(?:›\s*)?\d+\./u.test(line));
|
|
1460
|
-
const selectedRows = rows.filter((row) => row.selected);
|
|
1461
|
-
const selectedIndex = selectedRows[0]?.nativeIndex ?? -1;
|
|
1462
|
-
const currentRows = rows.filter((row) => row.current);
|
|
1463
|
-
if (rows.length === 0 ||
|
|
1464
|
-
numberedLines.length !== rows.length ||
|
|
1465
|
-
rows.some((row, index) => row.nativeIndex !== index) ||
|
|
1466
|
-
selectedRows.length !== 1 ||
|
|
1467
|
-
currentRows.length !== 1 ||
|
|
1468
|
-
new Set(rows.map((row) => row.id)).size !== rows.length) {
|
|
1469
|
-
return undefined;
|
|
1470
|
-
}
|
|
1471
|
-
return {
|
|
1472
|
-
rows,
|
|
1473
|
-
selectedIndex,
|
|
1474
|
-
currentNativeIndex: currentRows[0].nativeIndex,
|
|
1475
|
-
currentModel: currentRows[0].id
|
|
1476
|
-
};
|
|
1477
|
-
}
|
|
1478
|
-
function parseCodexEntryModelPicker(region) {
|
|
1479
|
-
const subtitle = region[1]?.trim();
|
|
1480
|
-
const kind = subtitle === "Pick a quick auto mode or browse all models."
|
|
1481
|
-
? "quick_auto"
|
|
1482
|
-
: subtitle === "Other models return when ordinary usage is available again."
|
|
1483
|
-
? "luna_reserve"
|
|
1484
|
-
: undefined;
|
|
1485
|
-
if (!kind)
|
|
1486
|
-
return undefined;
|
|
1487
|
-
const numbered = region.slice(2, -1).filter((line) => /^\s*(?:›\s*)?\d+\./u.test(line));
|
|
1488
|
-
const rows = numbered.map((line) => {
|
|
1489
|
-
const match = /^\s*(›\s*)?(\d+)\.\s+(.+)$/u.exec(line);
|
|
1490
|
-
if (!match)
|
|
1491
|
-
return undefined;
|
|
1492
|
-
const current = /(?:^|\s)\(current\)(?:\s{2,}|\s*$)/u.test(match[3]);
|
|
1493
|
-
const label = match[3]
|
|
1494
|
-
.replace(/\s+\(current\)(?=\s{2,}|\s*$)/u, "")
|
|
1495
|
-
.split(/\s{2,}/u, 1)[0]
|
|
1496
|
-
?.trim();
|
|
1497
|
-
return {
|
|
1498
|
-
nativeIndex: Number(match[2]) - 1,
|
|
1499
|
-
selected: Boolean(match[1]),
|
|
1500
|
-
current,
|
|
1501
|
-
label
|
|
1502
|
-
};
|
|
1503
|
-
});
|
|
1504
|
-
if (rows.some((row) => !row) ||
|
|
1505
|
-
rows.length === 0 ||
|
|
1506
|
-
rows.some((row, index) => row?.nativeIndex !== index))
|
|
1507
|
-
return undefined;
|
|
1508
|
-
const exactRows = rows.filter((row) => Boolean(row));
|
|
1509
|
-
const selected = exactRows.filter((row) => row.selected);
|
|
1510
|
-
const current = exactRows.filter((row) => row.current);
|
|
1511
|
-
if (selected.length !== 1 || current.length !== 1)
|
|
1512
|
-
return undefined;
|
|
1513
|
-
if (kind === "quick_auto") {
|
|
1514
|
-
const allModels = exactRows.filter((row) => row.label === "All models");
|
|
1515
|
-
if (allModels.length !== 1 ||
|
|
1516
|
-
exactRows.at(-1)?.label !== "All models" ||
|
|
1517
|
-
exactRows.slice(0, -1).some((row) => !/^codex-auto-[a-z0-9][a-z0-9._:/+\-]*$/u.test(row.label ?? "")))
|
|
1518
|
-
return undefined;
|
|
1519
|
-
return {
|
|
1520
|
-
kind,
|
|
1521
|
-
selectedIndex: selected[0].nativeIndex,
|
|
1522
|
-
currentNativeIndex: current[0].nativeIndex,
|
|
1523
|
-
allModelsNativeIndex: allModels[0].nativeIndex
|
|
1524
|
-
};
|
|
1525
|
-
}
|
|
1526
|
-
if (exactRows.length !== 1 || !exactRows[0].label)
|
|
1527
|
-
return undefined;
|
|
1528
|
-
return {
|
|
1529
|
-
kind,
|
|
1530
|
-
selectedIndex: selected[0].nativeIndex,
|
|
1531
|
-
currentNativeIndex: current[0].nativeIndex
|
|
1532
|
-
};
|
|
1533
|
-
}
|
|
1534
|
-
function parseCodexEffortRows(region, advanced) {
|
|
1535
|
-
const rows = [];
|
|
1536
|
-
for (const line of region.slice(1, -1)) {
|
|
1537
|
-
const match = /^\s*(›\s*)?\d+\.\s+(Low|Medium|High|Extra high|Max|Ultra|Persistent|More reasoning…)(?:\s+((?:\((?:current|default)\)\s*){1,2}))?(?:\s{2,}.*)?$/u
|
|
1538
|
-
.exec(line);
|
|
1539
|
-
if (!match)
|
|
1540
|
-
continue;
|
|
1541
|
-
const effort = effortFromNativeLabel(match[2]);
|
|
1542
|
-
const flags = match[3] ?? "";
|
|
1543
|
-
rows.push({
|
|
1544
|
-
...(effort ? { effort } : {}),
|
|
1545
|
-
kind: match[2] === "More reasoning…"
|
|
1546
|
-
? "advanced"
|
|
1547
|
-
: match[2] === "Persistent"
|
|
1548
|
-
? "unsupported"
|
|
1549
|
-
: "effort",
|
|
1550
|
-
selected: Boolean(match[1]),
|
|
1551
|
-
current: flags.includes("(current)"),
|
|
1552
|
-
presetDefault: flags.includes("(default)")
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
const numberedLines = region.slice(1, -1).filter((line) => /^\s*(?:›\s*)?\d+\./u.test(line));
|
|
1556
|
-
const selectedRows = rows.filter((row) => row.selected);
|
|
1557
|
-
const selectedIndex = rows.findIndex((row) => row.selected);
|
|
1558
|
-
const current = rows.filter((row) => row.current && row.effort);
|
|
1559
|
-
const defaults = rows.filter((row) => row.presetDefault && row.effort);
|
|
1560
|
-
const allowed = advanced
|
|
1561
|
-
? rows.every((row) => row.kind === "effort" && ["max", "ultra"].includes(row.effort ?? ""))
|
|
1562
|
-
: rows.every((row) => row.kind === "advanced" || row.kind === "unsupported" ||
|
|
1563
|
-
!["max", "ultra"].includes(row.effort ?? ""));
|
|
1564
|
-
if (rows.length === 0 ||
|
|
1565
|
-
numberedLines.length !== rows.length ||
|
|
1566
|
-
!numberedLines.every((line, index) => new RegExp(`^\\s*(?:›\\s*)?${index + 1}\\.`).test(line)) ||
|
|
1567
|
-
selectedRows.length !== 1 ||
|
|
1568
|
-
selectedIndex < 0 ||
|
|
1569
|
-
current.length > 1 ||
|
|
1570
|
-
defaults.length > 1 ||
|
|
1571
|
-
!allowed)
|
|
1572
|
-
return undefined;
|
|
1573
|
-
return {
|
|
1574
|
-
rows,
|
|
1575
|
-
selectedIndex,
|
|
1576
|
-
...(current[0]?.effort ? { currentEffort: current[0].effort } : {}),
|
|
1577
|
-
...(defaults[0]?.effort
|
|
1578
|
-
? { presetDefaultEffort: defaults[0].effort }
|
|
1579
|
-
: {})
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
function currentPickerRegion(lines, headerIndex, fullscreen = false) {
|
|
1583
|
-
const after = lines.slice(headerIndex);
|
|
1584
|
-
const expectedFooters = fullscreen ? [
|
|
1585
|
-
"enter select · esc back",
|
|
1586
|
-
"enter default · s session · esc back",
|
|
1587
|
-
"enter apply · s session · esc back"
|
|
1588
|
-
] : ["Press enter to confirm or esc to go back"];
|
|
1589
|
-
const footerIndex = after.findIndex((line) => expectedFooters.includes(line.trim()));
|
|
1590
|
-
if (footerIndex < 0 || after.slice(footerIndex + 1).some((line) => line.trim())) {
|
|
1591
|
-
return undefined;
|
|
1592
|
-
}
|
|
1593
|
-
return after.slice(0, footerIndex + 1);
|
|
1594
|
-
}
|
|
1595
|
-
function codexFullscreenEffortFooterMatches(region, selected) {
|
|
1596
|
-
const expected = selected.kind !== "effort"
|
|
1597
|
-
? "enter select · esc back"
|
|
1598
|
-
: selected.effort === "ultra"
|
|
1599
|
-
? "enter apply · s session · esc back"
|
|
1600
|
-
: "enter default · s session · esc back";
|
|
1601
|
-
return region.at(-1)?.trim() === expected;
|
|
1602
|
-
}
|
|
1603
|
-
function effortFromNativeLabel(label) {
|
|
1604
|
-
return {
|
|
1605
|
-
Low: "low",
|
|
1606
|
-
Medium: "medium",
|
|
1607
|
-
High: "high",
|
|
1608
|
-
"Extra high": "xhigh",
|
|
1609
|
-
Max: "max",
|
|
1610
|
-
Ultra: "ultra"
|
|
1611
|
-
}[label];
|
|
1612
|
-
}
|
|
1613
|
-
function claudeEffortFromNativeLabel(label) {
|
|
1614
|
-
const normalized = label.trim().toLowerCase();
|
|
1615
|
-
return {
|
|
1616
|
-
low: "low",
|
|
1617
|
-
medium: "medium",
|
|
1618
|
-
high: "high",
|
|
1619
|
-
"extra high": "xhigh",
|
|
1620
|
-
xhigh: "xhigh",
|
|
1621
|
-
max: "max",
|
|
1622
|
-
ultracode: "ultracode"
|
|
1623
|
-
}[normalized];
|
|
1624
|
-
}
|
|
1625
|
-
function claudeSemanticModelId(label) {
|
|
1626
|
-
const family = /\b(opus|sonnet|haiku)\b/iu.exec(label)?.[1].toLowerCase();
|
|
1627
|
-
if (!family || !/^(?:opus|sonnet|haiku)$/u.test(family))
|
|
1628
|
-
return undefined;
|
|
1629
|
-
const version = new RegExp(`\\b${family}[-\\s]+(\\d+(?:\\.\\d+)?)\\b`, "iu").exec(label)?.[1] ?? /\b(\d+\.\d+)\b/u.exec(label)?.[1];
|
|
1630
|
-
const longContext = /(?:\[?1m\]?|1\s*million)(?:\s+context)?\b/iu.test(label);
|
|
1631
|
-
return [family, version, longContext ? "1m" : undefined]
|
|
1632
|
-
.filter((part) => Boolean(part))
|
|
1633
|
-
.join("-");
|
|
1634
|
-
}
|
|
1635
|
-
function terminalTailLines(screen) {
|
|
1636
|
-
const lines = screen.split("\n").slice(-160);
|
|
1637
|
-
while (lines.length > 0 && lines.at(-1)?.trim() === "")
|
|
1638
|
-
lines.pop();
|
|
1639
|
-
return lines;
|
|
1640
|
-
}
|
|
1641
|
-
function lastIndex(lines, predicate) {
|
|
1642
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
1643
|
-
if (predicate(lines[index]))
|
|
1644
|
-
return index;
|
|
1645
|
-
}
|
|
1646
|
-
return -1;
|
|
1647
|
-
}
|
|
1648
|
-
function screenFingerprint(lines) {
|
|
1649
|
-
return createHash("sha256").update(lines.join("\n")).digest("hex");
|
|
1650
|
-
}
|
|
1651
|
-
function stripAnsi(value) {
|
|
1652
|
-
return value.replace(/\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/gu, "");
|
|
1653
|
-
}
|
|
1654
|
-
function isPlainRecord(value) {
|
|
1655
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1656
|
-
}
|
|
1657
|
-
function nonBlank(value) {
|
|
1658
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1659
|
-
}
|
|
1660
|
-
function codexPersistencePostcondition(beforeScreen, afterScreen, request) {
|
|
1661
|
-
const errorPatterns = [
|
|
1662
|
-
/Failed to save default model:/giu,
|
|
1663
|
-
/Failed to save Plan mode reasoning effort:/giu,
|
|
1664
|
-
/Saved default model and reasoning effort, but a higher-priority configuration\s+layer overrides the saved value\./giu,
|
|
1665
|
-
/Saved Plan mode reasoning effort, but a higher-priority configuration\s+layer overrides the saved value\./giu
|
|
1666
|
-
];
|
|
1667
|
-
if (errorPatterns.some((pattern) => matchCount(afterScreen, pattern) > matchCount(beforeScreen, pattern))) {
|
|
1668
|
-
return {
|
|
1669
|
-
proven: false,
|
|
1670
|
-
final: true,
|
|
1671
|
-
reason: "Codex reported that its persisted model default failed or is overridden by a higher-priority configuration layer"
|
|
1672
|
-
};
|
|
1673
|
-
}
|
|
1674
|
-
const model = escapeRegExp(request.model);
|
|
1675
|
-
const effort = request.reasoningEffort === "xhigh"
|
|
1676
|
-
? "(?:xhigh|extra high)"
|
|
1677
|
-
: escapeRegExp(request.reasoningEffort);
|
|
1678
|
-
const suffix = request.reasoningEffort === "ultra"
|
|
1679
|
-
? "\\s+for this conversation"
|
|
1680
|
-
: "";
|
|
1681
|
-
const success = new RegExp(`Model changed to\\s+${model}\\s+${effort}${suffix}(?:\\s|$)`, "giu");
|
|
1682
|
-
if (matchCount(afterScreen, success) <= matchCount(beforeScreen, success)) {
|
|
1683
|
-
return {
|
|
1684
|
-
proven: false,
|
|
1685
|
-
final: false,
|
|
1686
|
-
reason: "Codex did not expose a fresh exact model-default persistence success frame"
|
|
1687
|
-
};
|
|
1688
|
-
}
|
|
1689
|
-
return { proven: true };
|
|
1690
|
-
}
|
|
1691
720
|
async function waitForCodexPersistencePostcondition(input, terminalControl, baselineScreen, request, initialScreen) {
|
|
1692
721
|
const startedAt = Date.now();
|
|
1693
722
|
let screen = initialScreen;
|
|
@@ -1717,10 +746,4 @@ async function waitForCodexPersistencePostcondition(input, terminalControl, base
|
|
|
1717
746
|
reason: "Codex model-default persistence evidence did not materialize in time"
|
|
1718
747
|
};
|
|
1719
748
|
}
|
|
1720
|
-
function matchCount(value, pattern) {
|
|
1721
|
-
return [...value.matchAll(pattern)].length;
|
|
1722
|
-
}
|
|
1723
|
-
function escapeRegExp(value) {
|
|
1724
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
1725
|
-
}
|
|
1726
749
|
//# sourceMappingURL=terminal-model-control.js.map
|