@scotthuang/agent-knock-knock 0.13.16 → 0.13.18

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.
Files changed (184) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +4 -4
  3. package/dist/src/claude-terminal-agent-adapter.js +1 -1
  4. package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
  5. package/dist/src/codex-app-server-interaction-client.js +68 -26
  6. package/dist/src/codex-app-server-interaction-client.js.map +1 -1
  7. package/dist/src/codex-app-server-read-client.js +52 -20
  8. package/dist/src/codex-app-server-read-client.js.map +1 -1
  9. package/dist/src/codex-astra-composer-proof.js +1 -1
  10. package/dist/src/codex-astra-composer-proof.js.map +1 -1
  11. package/dist/src/codex-async-question-adapter.js +14 -4
  12. package/dist/src/codex-async-question-adapter.js.map +1 -1
  13. package/dist/src/codex-lifecycle-compatibility.d.ts +1 -1
  14. package/dist/src/codex-lifecycle-compatibility.js +9 -3
  15. package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
  16. package/dist/src/codex-open-rollout-inventory.d.ts +51 -0
  17. package/dist/src/codex-open-rollout-inventory.js +365 -0
  18. package/dist/src/codex-open-rollout-inventory.js.map +1 -0
  19. package/dist/src/codex-paginated-thread-binding.js +1 -1
  20. package/dist/src/codex-paginated-thread-binding.js.map +1 -1
  21. package/dist/src/codex-permission-surface.d.ts +1 -1
  22. package/dist/src/codex-permission-surface.js +1 -1
  23. package/dist/src/codex-permission-surface.js.map +1 -1
  24. package/dist/src/codex-rollout-bound-completion.d.ts +44 -0
  25. package/dist/src/codex-rollout-bound-completion.js +357 -0
  26. package/dist/src/codex-rollout-bound-completion.js.map +1 -0
  27. package/dist/src/codex-rollout-file.d.ts +15 -0
  28. package/dist/src/codex-rollout-file.js +98 -0
  29. package/dist/src/codex-rollout-file.js.map +1 -0
  30. package/dist/src/codex-rollout-human-task.d.ts +61 -0
  31. package/dist/src/codex-rollout-human-task.js +555 -0
  32. package/dist/src/codex-rollout-human-task.js.map +1 -0
  33. package/dist/src/codex-rollout-interaction-evidence.d.ts +48 -0
  34. package/dist/src/codex-rollout-interaction-evidence.js +521 -0
  35. package/dist/src/codex-rollout-interaction-evidence.js.map +1 -0
  36. package/dist/src/codex-rollout-inventory.d.ts +5 -0
  37. package/dist/src/codex-rollout-inventory.js +81 -0
  38. package/dist/src/codex-rollout-inventory.js.map +1 -0
  39. package/dist/src/codex-rollout-records.d.ts +49 -0
  40. package/dist/src/codex-rollout-records.js +298 -0
  41. package/dist/src/codex-rollout-records.js.map +1 -0
  42. package/dist/src/codex-sqlite-thread-query.d.ts +85 -0
  43. package/dist/src/codex-sqlite-thread-query.js +440 -0
  44. package/dist/src/codex-sqlite-thread-query.js.map +1 -0
  45. package/dist/src/codex-status-history-navigation.d.ts +1 -1
  46. package/dist/src/codex-status-history-navigation.js +1 -1
  47. package/dist/src/codex-status-history-navigation.js.map +1 -1
  48. package/dist/src/codex-store-adapter.d.ts +3 -50
  49. package/dist/src/codex-store-adapter.js +5 -796
  50. package/dist/src/codex-store-adapter.js.map +1 -1
  51. package/dist/src/codex-terminal-agent-adapter.js +4 -2
  52. package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
  53. package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +4 -134
  54. package/dist/src/native-thread-lifecycle-cli-adapter.js +19 -633
  55. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  56. package/dist/src/native-thread-lifecycle-cli-contract.d.ts +138 -0
  57. package/dist/src/native-thread-lifecycle-cli-contract.js +2 -0
  58. package/dist/src/native-thread-lifecycle-cli-contract.js.map +1 -0
  59. package/dist/src/semantic-permission-control.js +33 -6
  60. package/dist/src/semantic-permission-control.js.map +1 -1
  61. package/dist/src/semantic-tool-descriptions.d.ts +26 -0
  62. package/dist/src/semantic-tool-descriptions.js +259 -0
  63. package/dist/src/semantic-tool-descriptions.js.map +1 -0
  64. package/dist/src/semantic-tool-runtime.js +45 -37
  65. package/dist/src/semantic-tool-runtime.js.map +1 -1
  66. package/dist/src/semantic-tool-schemas.js +134 -33
  67. package/dist/src/semantic-tool-schemas.js.map +1 -1
  68. package/dist/src/terminal-action-contracts.d.ts +4 -0
  69. package/dist/src/terminal-action-contracts.js +695 -0
  70. package/dist/src/terminal-action-contracts.js.map +1 -0
  71. package/dist/src/terminal-composer-classifier.d.ts +1 -1
  72. package/dist/src/terminal-composer-classifier.js +2 -2
  73. package/dist/src/terminal-composer-classifier.js.map +1 -1
  74. package/dist/src/terminal-interaction-response-bridge.js +1 -1
  75. package/dist/src/terminal-interaction-response-bridge.js.map +1 -1
  76. package/dist/src/terminal-list-renderer.d.ts +1 -2
  77. package/dist/src/terminal-list-renderer.js +8 -424
  78. package/dist/src/terminal-list-renderer.js.map +1 -1
  79. package/dist/src/terminal-model-control-action-policy.d.ts +1 -1
  80. package/dist/src/terminal-model-control-availability.d.ts +1 -1
  81. package/dist/src/terminal-model-control-availability.js +1 -1
  82. package/dist/src/terminal-model-control-availability.js.map +1 -1
  83. package/dist/src/terminal-model-control-cli-adapter.d.ts +34 -0
  84. package/dist/src/terminal-model-control-cli-adapter.js +669 -0
  85. package/dist/src/terminal-model-control-cli-adapter.js.map +1 -0
  86. package/dist/src/terminal-model-control-contract.d.ts +198 -0
  87. package/dist/src/terminal-model-control-contract.js +15 -0
  88. package/dist/src/terminal-model-control-contract.js.map +1 -0
  89. package/dist/src/terminal-model-control-input.d.ts +29 -0
  90. package/dist/src/terminal-model-control-input.js +86 -0
  91. package/dist/src/terminal-model-control-input.js.map +1 -0
  92. package/dist/src/terminal-model-control-profile.d.ts +5 -1
  93. package/dist/src/terminal-model-control-profile.js +21 -7
  94. package/dist/src/terminal-model-control-profile.js.map +1 -1
  95. package/dist/src/terminal-model-control-subject.d.ts +52 -0
  96. package/dist/src/terminal-model-control-subject.js +73 -0
  97. package/dist/src/terminal-model-control-subject.js.map +1 -1
  98. package/dist/src/terminal-model-control-surface.d.ts +82 -0
  99. package/dist/src/terminal-model-control-surface.js +688 -0
  100. package/dist/src/terminal-model-control-surface.js.map +1 -0
  101. package/dist/src/terminal-model-control.d.ts +6 -270
  102. package/dist/src/terminal-model-control.js +11 -988
  103. package/dist/src/terminal-model-control.js.map +1 -1
  104. package/dist/src/terminal-model-selection.d.ts +33 -0
  105. package/dist/src/terminal-model-selection.js +143 -0
  106. package/dist/src/terminal-model-selection.js.map +1 -0
  107. package/dist/src/terminal-monitor-application-service.js +38 -59
  108. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  109. package/dist/src/terminal-monitor-attention-store.d.ts +65 -0
  110. package/dist/src/terminal-monitor-attention-store.js +518 -0
  111. package/dist/src/terminal-monitor-attention-store.js.map +1 -0
  112. package/dist/src/terminal-monitor-collateral-recovery.d.ts +12 -0
  113. package/dist/src/terminal-monitor-collateral-recovery.js +415 -0
  114. package/dist/src/terminal-monitor-collateral-recovery.js.map +1 -0
  115. package/dist/src/terminal-monitor-interaction-projection.d.ts +19 -0
  116. package/dist/src/terminal-monitor-interaction-projection.js +48 -0
  117. package/dist/src/terminal-monitor-interaction-projection.js.map +1 -0
  118. package/dist/src/terminal-monitor-startup-recovery.d.ts +15 -0
  119. package/dist/src/terminal-monitor-startup-recovery.js +767 -0
  120. package/dist/src/terminal-monitor-startup-recovery.js.map +1 -0
  121. package/dist/src/terminal-monitor-state-cli-adapter.d.ts +2 -104
  122. package/dist/src/terminal-monitor-state-cli-adapter.js +28 -1640
  123. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
  124. package/dist/src/terminal-monitor-state-contract.d.ts +108 -0
  125. package/dist/src/terminal-monitor-state-contract.js +8 -0
  126. package/dist/src/terminal-monitor-state-contract.js.map +1 -0
  127. package/dist/src/terminal-native-inspection-bridge.d.ts +4 -153
  128. package/dist/src/terminal-native-inspection-bridge.js +7 -646
  129. package/dist/src/terminal-native-inspection-bridge.js.map +1 -1
  130. package/dist/src/terminal-native-inspection-contract.d.ts +128 -0
  131. package/dist/src/terminal-native-inspection-contract.js +38 -0
  132. package/dist/src/terminal-native-inspection-contract.js.map +1 -0
  133. package/dist/src/terminal-native-inspection-profile.d.ts +15 -0
  134. package/dist/src/terminal-native-inspection-profile.js +189 -0
  135. package/dist/src/terminal-native-inspection-profile.js.map +1 -0
  136. package/dist/src/terminal-native-inspection-proof.d.ts +62 -0
  137. package/dist/src/terminal-native-inspection-proof.js +362 -0
  138. package/dist/src/terminal-native-inspection-proof.js.map +1 -0
  139. package/dist/src/terminal-permission-control-bridge.js +1 -1
  140. package/dist/src/terminal-permission-control-bridge.js.map +1 -1
  141. package/dist/src/terminal-permission-control.js +5 -1
  142. package/dist/src/terminal-permission-control.js.map +1 -1
  143. package/dist/src/terminal-questionnaire-adapter.js +5 -2
  144. package/dist/src/terminal-questionnaire-adapter.js.map +1 -1
  145. package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -1
  146. package/dist/src/terminal-runtime-cli-adapter.js +1 -1
  147. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
  148. package/dist/src/terminal-submission-acceptance.d.ts +5 -148
  149. package/dist/src/terminal-submission-acceptance.js +21 -1845
  150. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  151. package/dist/src/terminal-watch-anchor-capture.d.ts +9 -0
  152. package/dist/src/terminal-watch-anchor-capture.js +139 -0
  153. package/dist/src/terminal-watch-anchor-capture.js.map +1 -0
  154. package/dist/src/terminal-watch-cli-adapter.d.ts +3 -117
  155. package/dist/src/terminal-watch-cli-adapter.js +10 -1992
  156. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  157. package/dist/src/terminal-watch-cli-contract.d.ts +117 -0
  158. package/dist/src/terminal-watch-cli-contract.js +2 -0
  159. package/dist/src/terminal-watch-cli-contract.js.map +1 -0
  160. package/dist/src/terminal-watch-fallback-observation.d.ts +15 -0
  161. package/dist/src/terminal-watch-fallback-observation.js +249 -0
  162. package/dist/src/terminal-watch-fallback-observation.js.map +1 -0
  163. package/dist/src/terminal-watch-interaction-observation.d.ts +26 -0
  164. package/dist/src/terminal-watch-interaction-observation.js +262 -0
  165. package/dist/src/terminal-watch-interaction-observation.js.map +1 -0
  166. package/dist/src/terminal-watch-observation-adapter.d.ts +4 -0
  167. package/dist/src/terminal-watch-observation-adapter.js +307 -0
  168. package/dist/src/terminal-watch-observation-adapter.js.map +1 -0
  169. package/dist/src/terminal-watch-observation-evidence.d.ts +25 -0
  170. package/dist/src/terminal-watch-observation-evidence.js +184 -0
  171. package/dist/src/terminal-watch-observation-evidence.js.map +1 -0
  172. package/dist/src/terminal-watch-response-authority.d.ts +6 -0
  173. package/dist/src/terminal-watch-response-authority.js +333 -0
  174. package/dist/src/terminal-watch-response-authority.js.map +1 -0
  175. package/dist/src/terminal-watch-response-cli-adapter.d.ts +3 -0
  176. package/dist/src/terminal-watch-response-cli-adapter.js +230 -0
  177. package/dist/src/terminal-watch-response-cli-adapter.js.map +1 -0
  178. package/dist/src/terminal-watch-terminal-identity.d.ts +39 -0
  179. package/dist/src/terminal-watch-terminal-identity.js +380 -0
  180. package/dist/src/terminal-watch-terminal-identity.js.map +1 -0
  181. package/docs/codex-0.160.0-compatibility.md +126 -0
  182. package/docs/codex-permissions.md +12 -1
  183. package/package.json +3 -2
  184. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +3 -3
@@ -1,208 +1,16 @@
1
- import { isCodexPaginatedVersion } from "./codex-lifecycle-compatibility.js";
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 { captureCodexFullscreenComposerFrame } from "./codex-fullscreen-composer-proof.js";
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 selected = await transitionModelControl(operation, control, picker, modelControlSelectModelKeys(picker, input.request.model), ["claude_model_picker"]);
712
- control = selected.capture.terminalControl;
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