@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.
- package/CHANGELOG.md +15 -0
- package/README.md +4 -4
- 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-async-question-adapter.js +14 -4
- package/dist/src/codex-async-question-adapter.js.map +1 -1
- package/dist/src/codex-lifecycle-compatibility.d.ts +1 -1
- package/dist/src/codex-lifecycle-compatibility.js +9 -3
- package/dist/src/codex-lifecycle-compatibility.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-permission-surface.d.ts +1 -1
- package/dist/src/codex-permission-surface.js +1 -1
- package/dist/src/codex-permission-surface.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 +4 -2
- 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 +5 -1
- package/dist/src/terminal-model-control-profile.js +21 -7
- 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 +189 -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-permission-control.js +5 -1
- package/dist/src/terminal-permission-control.js.map +1 -1
- package/dist/src/terminal-questionnaire-adapter.js +5 -2
- package/dist/src/terminal-questionnaire-adapter.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/docs/codex-0.160.0-compatibility.md +126 -0
- package/docs/codex-permissions.md +12 -1
- package/package.json +3 -2
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +3 -3
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
export { probeTerminalModelControl, planTerminalModelControl } from "./terminal-model-control-profile.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { isCodexPaginatedVersion } from "./codex-lifecycle-compatibility.js";
|
|
4
|
+
import { captureCodexFullscreenComposerFrame } from "./codex-fullscreen-composer-proof.js";
|
|
5
|
+
import { isTerminalModelControlPlanForAgent, terminalModelControlProfileForPlan } from "./terminal-model-control-profile.js";
|
|
6
|
+
import { isTerminalModelReasoningEffort } from "./terminal-model-control-contract.js";
|
|
7
|
+
/** Pure native surfaces and semantic navigation; this module never performs input. */
|
|
8
|
+
export function parseCodexNativeModelCatalog(value) {
|
|
9
|
+
if (!isPlainRecord(value) || !Array.isArray(value.models)) {
|
|
10
|
+
throw new Error("the exact running Codex model catalog has an unknown shape");
|
|
11
|
+
}
|
|
12
|
+
const models = value.models.flatMap((candidate) => {
|
|
13
|
+
if (!isPlainRecord(candidate) || candidate.visibility !== "list")
|
|
14
|
+
return [];
|
|
15
|
+
const id = nonBlank(candidate.slug);
|
|
16
|
+
const label = nonBlank(candidate.display_name) ?? id;
|
|
17
|
+
if (!id || !label || !/^[a-z0-9][a-z0-9._:/+\-]{0,127}$/u.test(id) ||
|
|
18
|
+
!Array.isArray(candidate.supported_reasoning_levels)) {
|
|
19
|
+
throw new Error("the exact running Codex model catalog contains an invalid visible row");
|
|
20
|
+
}
|
|
21
|
+
const efforts = candidate.supported_reasoning_levels.flatMap((entry) => {
|
|
22
|
+
const effort = isPlainRecord(entry) ? entry.effort : undefined;
|
|
23
|
+
return isTerminalModelReasoningEffort(effort) ? [effort] : [];
|
|
24
|
+
});
|
|
25
|
+
if (efforts.length === 0 || new Set(efforts).size !== efforts.length) {
|
|
26
|
+
throw new Error(`the exact running Codex model catalog has no unique supported efforts for ${id}`);
|
|
27
|
+
}
|
|
28
|
+
return [{ id, label, reasoningEfforts: efforts }];
|
|
29
|
+
});
|
|
30
|
+
if (models.length === 0 ||
|
|
31
|
+
new Set(models.map((model) => model.id)).size !== models.length) {
|
|
32
|
+
throw new Error("the exact running Codex model catalog has no unique visible models");
|
|
33
|
+
}
|
|
34
|
+
return { models };
|
|
35
|
+
}
|
|
36
|
+
export function observeCodexIdleModel(screen, plan) {
|
|
37
|
+
const lines = stripAnsi(screen).replace(/\r\n?/gu, "\n").split("\n");
|
|
38
|
+
while (lines.length > 0 && lines.at(-1)?.trim() === "")
|
|
39
|
+
lines.pop();
|
|
40
|
+
const version = plan && terminalModelControlProfileForPlan(plan)?.agentVersion;
|
|
41
|
+
const modern = isCodexPaginatedVersion(version);
|
|
42
|
+
const frame = modern ? captureCodexFullscreenComposerFrame(screen, version) : undefined;
|
|
43
|
+
const footer = modern
|
|
44
|
+
? frame?.hasShortcutFooter ? frame.plainLines[frame.footerIndex] : undefined
|
|
45
|
+
: lines.at(-1);
|
|
46
|
+
const match = footer
|
|
47
|
+
? /^\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
|
|
48
|
+
.exec(footer)
|
|
49
|
+
: undefined;
|
|
50
|
+
if (!match || !isTerminalModelReasoningEffort(match[2]))
|
|
51
|
+
return undefined;
|
|
52
|
+
const model = codexSemanticModelLabel(match[1], modern);
|
|
53
|
+
if (!model)
|
|
54
|
+
return undefined;
|
|
55
|
+
return {
|
|
56
|
+
model,
|
|
57
|
+
reasoningEffort: match[2],
|
|
58
|
+
planMode: /\bPlan mode\b/u.test(footer ?? "")
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Observe only complete, current model-control modal frames. Historical or
|
|
63
|
+
* partial picker text is never sufficient to authorize a key dispatch.
|
|
64
|
+
*/
|
|
65
|
+
export function observeTerminalModelControl(plan, screen) {
|
|
66
|
+
const normalized = stripAnsi(screen).replace(/\r\n?/gu, "\n");
|
|
67
|
+
return isTerminalModelControlPlanForAgent(plan, "codex")
|
|
68
|
+
? observeCodexModelControl(normalized, plan)
|
|
69
|
+
: observeClaudeModelControl(normalized);
|
|
70
|
+
}
|
|
71
|
+
export function modelControlMoveKeys(currentIndex, targetIndex) {
|
|
72
|
+
if (!Number.isSafeInteger(currentIndex) ||
|
|
73
|
+
!Number.isSafeInteger(targetIndex) ||
|
|
74
|
+
currentIndex < 0 ||
|
|
75
|
+
targetIndex < 0) {
|
|
76
|
+
throw new Error("model-control selection index is invalid");
|
|
77
|
+
}
|
|
78
|
+
const key = targetIndex >= currentIndex ? "Down" : "Up";
|
|
79
|
+
return Array.from({ length: Math.abs(targetIndex - currentIndex) }, () => key);
|
|
80
|
+
}
|
|
81
|
+
/** Adapter-owned native navigation; never serialize these keys to a caller. */
|
|
82
|
+
export function modelControlSelectModelKeys(observation, model) {
|
|
83
|
+
const row = observation.rows.find((candidate) => candidate.id === model);
|
|
84
|
+
if (!row)
|
|
85
|
+
throw new Error("the requested model is absent from the current native picker");
|
|
86
|
+
return [
|
|
87
|
+
...modelControlMoveKeys(observation.selectedIndex, row.nativeIndex),
|
|
88
|
+
...(observation.state === "codex_model_picker" ? ["C-m"] : [])
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
export function selectedClaudeModelId(observation) {
|
|
92
|
+
return observation.rows.find((row) => row.nativeIndex === observation.selectedIndex)?.id;
|
|
93
|
+
}
|
|
94
|
+
export function codexModelControlSelectEffortKeys(observation, effort) {
|
|
95
|
+
const direct = observation.rows.find((row) => row.kind === "effort" && row.effort === effort);
|
|
96
|
+
const target = direct ?? (observation.state === "codex_reasoning_picker" &&
|
|
97
|
+
(effort === "max" || effort === "ultra")
|
|
98
|
+
? observation.rows.find((row) => row.kind === "advanced")
|
|
99
|
+
: undefined);
|
|
100
|
+
if (!target) {
|
|
101
|
+
throw new Error("the requested reasoning effort is absent from the current native picker");
|
|
102
|
+
}
|
|
103
|
+
const targetIndex = observation.rows.indexOf(target);
|
|
104
|
+
return [
|
|
105
|
+
...modelControlMoveKeys(observation.selectedIndex, targetIndex),
|
|
106
|
+
"C-m"
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
export function codexModelControlAllModesKeys(observation) {
|
|
110
|
+
const targetIndex = observation.rows.indexOf("Apply to global default and Plan mode override");
|
|
111
|
+
if (targetIndex < 0) {
|
|
112
|
+
throw new Error("Codex Plan-mode scope picker has no global-default choice");
|
|
113
|
+
}
|
|
114
|
+
return [
|
|
115
|
+
...modelControlMoveKeys(observation.selectedIndex, targetIndex),
|
|
116
|
+
"C-m"
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
export function claudeModelControlEffortKey(direction) {
|
|
120
|
+
return [direction === "lower" ? "Left" : "Right"];
|
|
121
|
+
}
|
|
122
|
+
export function claudeModelControlCommitKeys(plan) {
|
|
123
|
+
if (!isTerminalModelControlPlanForAgent(plan, "claude") ||
|
|
124
|
+
plan.scope !== "current_session") {
|
|
125
|
+
throw new Error("Claude model control cannot commit outside session-only scope");
|
|
126
|
+
}
|
|
127
|
+
return ["s"];
|
|
128
|
+
}
|
|
129
|
+
export function isExactTerminalModelControlObservation(observation) {
|
|
130
|
+
return observation.state !== "none" && observation.state !== "ambiguous";
|
|
131
|
+
}
|
|
132
|
+
/** Pure capture classifier. It never authorizes input by itself. */
|
|
133
|
+
export function classifyTerminalModelControlSurface(plan, capture) {
|
|
134
|
+
const observation = observeTerminalModelControl(plan, capture.screen);
|
|
135
|
+
// Approval and a positively identified non-model input owner remain hard
|
|
136
|
+
// safety boundaries even when stale transcript text resembles a picker.
|
|
137
|
+
const hardBlock = modelControlHardBlock(capture);
|
|
138
|
+
if (hardBlock)
|
|
139
|
+
return hardBlock;
|
|
140
|
+
// Exact profile-owned surfaces outrank generic lifecycle activity. Codex and
|
|
141
|
+
// Claude can report working/waiting while their native picker owns input.
|
|
142
|
+
if (isExactTerminalModelControlObservation(observation)) {
|
|
143
|
+
return {
|
|
144
|
+
state: "picker",
|
|
145
|
+
pickerKind: modelControlPickerKind(observation),
|
|
146
|
+
observation
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Only a complete picker owns the lifecycle override. Composer proofs do
|
|
150
|
+
// not supersede genuine working/approval lifecycle state.
|
|
151
|
+
const activityBlock = modelControlActivityBlock(capture);
|
|
152
|
+
if (activityBlock)
|
|
153
|
+
return activityBlock;
|
|
154
|
+
return classifyModelControlComposerSurface(capture, observation);
|
|
155
|
+
}
|
|
156
|
+
export function modelControlHardBlock(capture) {
|
|
157
|
+
if (capture.approvalBlocked) {
|
|
158
|
+
return { state: "blocked", owner: "approval" };
|
|
159
|
+
}
|
|
160
|
+
return capture.inputBlocked === true
|
|
161
|
+
? { state: "blocked", owner: "input" }
|
|
162
|
+
: undefined;
|
|
163
|
+
}
|
|
164
|
+
export function modelControlActivityBlock(capture) {
|
|
165
|
+
return capture.activityState === "working" ||
|
|
166
|
+
capture.activityState === "awaiting_approval"
|
|
167
|
+
? { state: "blocked", owner: "agent" }
|
|
168
|
+
: undefined;
|
|
169
|
+
}
|
|
170
|
+
export function modelControlPickerKind(observation) {
|
|
171
|
+
if (observation.state === "codex_reasoning_picker" ||
|
|
172
|
+
observation.state === "codex_advanced_reasoning_picker") {
|
|
173
|
+
return "effort";
|
|
174
|
+
}
|
|
175
|
+
return observation.state === "codex_plan_scope_picker" ? "scope" : "model";
|
|
176
|
+
}
|
|
177
|
+
export function classifyModelControlComposerSurface(capture, observation) {
|
|
178
|
+
const contradictoryComposerProof = capture.exactEmptyComposer && capture.exactCommandComposer ||
|
|
179
|
+
capture.exactCommandReady && capture.exactBareCommand === true ||
|
|
180
|
+
(capture.exactCommandReady || capture.exactBareCommand === true) &&
|
|
181
|
+
!capture.exactCommandComposer;
|
|
182
|
+
if (contradictoryComposerProof) {
|
|
183
|
+
return {
|
|
184
|
+
state: "unknown",
|
|
185
|
+
reason: "model-control Composer proofs conflict"
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (capture.exactCommandComposer &&
|
|
189
|
+
capture.exactCommandReady) {
|
|
190
|
+
return {
|
|
191
|
+
state: "command_popup",
|
|
192
|
+
...(capture.exactCommandFingerprint
|
|
193
|
+
? { fingerprint: capture.exactCommandFingerprint }
|
|
194
|
+
: {})
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (capture.exactCommandComposer &&
|
|
198
|
+
capture.exactBareCommand === true) {
|
|
199
|
+
return {
|
|
200
|
+
state: "bare_command",
|
|
201
|
+
...(capture.exactCommandFingerprint
|
|
202
|
+
? { fingerprint: capture.exactCommandFingerprint }
|
|
203
|
+
: {})
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (capture.exactCommandComposer) {
|
|
207
|
+
return {
|
|
208
|
+
state: "command_draft",
|
|
209
|
+
...(capture.exactCommandFingerprint
|
|
210
|
+
? { fingerprint: capture.exactCommandFingerprint }
|
|
211
|
+
: {})
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (capture.exactEmptyComposer && capture.activityState === "idle") {
|
|
215
|
+
return { state: "idle_empty" };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
state: "unknown",
|
|
219
|
+
reason: observation.state === "ambiguous"
|
|
220
|
+
? observation.reason
|
|
221
|
+
: "no exact current model-control surface is proven"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
export function modelControlCaptureBlocked(capture) {
|
|
225
|
+
return capture.approvalBlocked || capture.inputBlocked === true;
|
|
226
|
+
}
|
|
227
|
+
export function observeCodexModelControl(screen, plan) {
|
|
228
|
+
const lines = terminalTailLines(screen);
|
|
229
|
+
const fingerprint = screenFingerprint(lines);
|
|
230
|
+
const entryHeader = lastIndex(lines, (line) => line.trim() === "Select Model");
|
|
231
|
+
const modelHeader = lastIndex(lines, (line) => line.trim() === "Select Model and Effort");
|
|
232
|
+
const reasoningHeader = lastIndex(lines, (line) => /^\s*Select Reasoning Level for \S+\s*$/u.test(line));
|
|
233
|
+
const advancedHeader = lastIndex(lines, (line) => line.trim() === "Advanced Reasoning");
|
|
234
|
+
const scopeHeader = lastIndex(lines, (line) => line.trim() === "Apply reasoning change");
|
|
235
|
+
const latest = Math.max(entryHeader, modelHeader, reasoningHeader, advancedHeader, scopeHeader);
|
|
236
|
+
if (latest < 0) {
|
|
237
|
+
return { state: "none", fingerprint, reason: "no current Codex model picker is visible" };
|
|
238
|
+
}
|
|
239
|
+
const modern = isCodexPaginatedVersion(terminalModelControlProfileForPlan(plan)?.agentVersion);
|
|
240
|
+
const region = currentPickerRegion(lines, latest, modern);
|
|
241
|
+
if (!region) {
|
|
242
|
+
return {
|
|
243
|
+
state: "ambiguous",
|
|
244
|
+
fingerprint,
|
|
245
|
+
reason: "the current Codex model picker is incomplete or has trailing content"
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (region.some((line) => line.includes("OpenAI base URL is overridden"))) {
|
|
249
|
+
return {
|
|
250
|
+
state: "ambiguous",
|
|
251
|
+
fingerprint,
|
|
252
|
+
reason: "Codex model control is unavailable with an overridden OpenAI base URL"
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (latest === entryHeader) {
|
|
256
|
+
const parsedEntry = parseCodexEntryModelPicker(region);
|
|
257
|
+
if (!parsedEntry) {
|
|
258
|
+
return {
|
|
259
|
+
state: "ambiguous",
|
|
260
|
+
fingerprint,
|
|
261
|
+
reason: "the Codex entry model picker is not exact"
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return { state: "codex_entry_model_picker", fingerprint, ...parsedEntry };
|
|
265
|
+
}
|
|
266
|
+
if (latest === modelHeader) {
|
|
267
|
+
const parsedRows = parseCodexModelRows(region, modern);
|
|
268
|
+
if (!parsedRows || modern && ![
|
|
269
|
+
"enter select · esc back", "enter default · s session · esc back"
|
|
270
|
+
].includes(region.at(-1).trim())) {
|
|
271
|
+
return { state: "ambiguous", fingerprint, reason: "the Codex model catalog frame is not exact" };
|
|
272
|
+
}
|
|
273
|
+
return { state: "codex_model_picker", fingerprint, ...parsedRows };
|
|
274
|
+
}
|
|
275
|
+
if (latest === reasoningHeader) {
|
|
276
|
+
const modelLabel = /^\s*Select Reasoning Level for (\S+)\s*$/u.exec(region[0] ?? "")?.[1];
|
|
277
|
+
const model = modelLabel && codexSemanticModelLabel(modelLabel, modern);
|
|
278
|
+
const parsedRows = parseCodexEffortRows(region, false);
|
|
279
|
+
if (!model || !parsedRows || modern &&
|
|
280
|
+
!codexFullscreenEffortFooterMatches(region, parsedRows.rows[parsedRows.selectedIndex])) {
|
|
281
|
+
return { state: "ambiguous", fingerprint, reason: "the Codex reasoning frame is not exact" };
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
state: "codex_reasoning_picker",
|
|
285
|
+
fingerprint,
|
|
286
|
+
model,
|
|
287
|
+
...parsedRows
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (latest === advancedHeader) {
|
|
291
|
+
const parsedRows = parseCodexEffortRows(region, true);
|
|
292
|
+
if (!parsedRows || modern &&
|
|
293
|
+
!codexFullscreenEffortFooterMatches(region, parsedRows.rows[parsedRows.selectedIndex])) {
|
|
294
|
+
return { state: "ambiguous", fingerprint, reason: "the Codex advanced-reasoning frame is not exact" };
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
state: "codex_advanced_reasoning_picker",
|
|
298
|
+
fingerprint,
|
|
299
|
+
...parsedRows
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const rows = region.flatMap((line) => {
|
|
303
|
+
const match = (modern
|
|
304
|
+
? /^\s*(?:›\s*)?\d+\.\s+(Apply to (?:Plan mode override|global default and Plan mode override))(?:\s{2,}.+)?\s*$/u
|
|
305
|
+
: /^\s*(?:›\s*)?\d+\.\s+(Apply to (?:Plan mode override|global default and Plan mode override))\s*$/u)
|
|
306
|
+
.exec(line);
|
|
307
|
+
return match ? [match[1]] : [];
|
|
308
|
+
});
|
|
309
|
+
const numberedLines = region.filter((line) => /^\s*(?:›\s*)?\d+\./u.test(line));
|
|
310
|
+
const selectedLines = numberedLines.filter((line) => /^\s*›\s*\d+\./u.test(line));
|
|
311
|
+
if (rows.length !== 2 ||
|
|
312
|
+
numberedLines.length !== rows.length ||
|
|
313
|
+
selectedLines.length !== 1 ||
|
|
314
|
+
rows[0] !== "Apply to Plan mode override" ||
|
|
315
|
+
rows[1] !== "Apply to global default and Plan mode override" ||
|
|
316
|
+
modern && region.at(-1)?.trim() !== "enter select · esc back" ||
|
|
317
|
+
!numberedLines.every((line, index) => new RegExp(`^\\s*(?:›\\s*)?${index + 1}\\.`).test(line))) {
|
|
318
|
+
return { state: "ambiguous", fingerprint, reason: "the Codex Plan-mode scope frame is not exact" };
|
|
319
|
+
}
|
|
320
|
+
const selectedRow = numberedLines.findIndex((line) => /^\s*›/u.test(line));
|
|
321
|
+
return {
|
|
322
|
+
state: "codex_plan_scope_picker",
|
|
323
|
+
fingerprint,
|
|
324
|
+
rows,
|
|
325
|
+
selectedIndex: selectedRow
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
export function observeClaudeModelControl(screen) {
|
|
329
|
+
const lines = terminalTailLines(screen);
|
|
330
|
+
const fingerprint = screenFingerprint(lines);
|
|
331
|
+
const headerIndex = lastIndex(lines, (line) => line.trim() === "Select model");
|
|
332
|
+
if (headerIndex < 0) {
|
|
333
|
+
return { state: "none", fingerprint, reason: "no current Claude model picker is visible" };
|
|
334
|
+
}
|
|
335
|
+
if (headerIndex === 0 ||
|
|
336
|
+
!/^\s*[▔¯─━]{8,}\s*$/u.test(lines[headerIndex - 1])) {
|
|
337
|
+
return { state: "ambiguous", fingerprint, reason: "the Claude model picker is not anchored to its top border" };
|
|
338
|
+
}
|
|
339
|
+
const region = lines.slice(headerIndex);
|
|
340
|
+
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
|
|
341
|
+
.test(line));
|
|
342
|
+
if (footerIndex < 0 || region.slice(footerIndex + 1).some((line) => line.trim())) {
|
|
343
|
+
return { state: "ambiguous", fingerprint, reason: "the current Claude model picker footer is incomplete" };
|
|
344
|
+
}
|
|
345
|
+
const picker = region.slice(0, footerIndex + 1);
|
|
346
|
+
const rows = [];
|
|
347
|
+
const nativeRows = [];
|
|
348
|
+
for (const line of picker) {
|
|
349
|
+
const match = /^\s*(❯\s*)?(\d+)\.\s+(.+?)(?:\s{2,})(\S.*)$/u.exec(line);
|
|
350
|
+
if (!match)
|
|
351
|
+
continue;
|
|
352
|
+
const rawLabel = match[3].trim();
|
|
353
|
+
const label = rawLabel.replace(/\s+✔\s*$/u, "").trim();
|
|
354
|
+
const description = match[4].trim();
|
|
355
|
+
nativeRows.push({
|
|
356
|
+
nativeIndex: Number(match[2]) - 1,
|
|
357
|
+
selected: Boolean(match[1]),
|
|
358
|
+
label,
|
|
359
|
+
description,
|
|
360
|
+
current: rawLabel.endsWith("✔")
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
const numberedClaudeLines = picker.filter((line) => /^\s*(?:❯\s*)?\d+\./u.test(line));
|
|
364
|
+
if (numberedClaudeLines.length !== nativeRows.length ||
|
|
365
|
+
nativeRows.some((row, index) => row.nativeIndex !== index)) {
|
|
366
|
+
return { state: "ambiguous", fingerprint, reason: "the Claude model numbering is not exact" };
|
|
367
|
+
}
|
|
368
|
+
for (const row of nativeRows) {
|
|
369
|
+
if (/^Default(?:\s+\(recommended\))?$/iu.test(row.label) ||
|
|
370
|
+
/Opus Plan Mode/iu.test(row.label) ||
|
|
371
|
+
/Opus Plan Mode/iu.test(row.description) ||
|
|
372
|
+
/\bFable\b/iu.test(row.label) ||
|
|
373
|
+
/\bFable\b/iu.test(row.description))
|
|
374
|
+
continue;
|
|
375
|
+
const id = claudeSemanticModelId(`${row.label} ${row.description}`);
|
|
376
|
+
if (!id)
|
|
377
|
+
continue;
|
|
378
|
+
rows.push({
|
|
379
|
+
id,
|
|
380
|
+
label: row.label,
|
|
381
|
+
nativeIndex: row.nativeIndex,
|
|
382
|
+
selected: row.selected,
|
|
383
|
+
current: row.current,
|
|
384
|
+
presetDefault: false
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
const selectedRows = nativeRows.filter((row) => row.selected);
|
|
388
|
+
const selectedNativeIndex = selectedRows[0]?.nativeIndex ?? -1;
|
|
389
|
+
const currentNativeRows = nativeRows.filter((row) => row.current);
|
|
390
|
+
const currentModel = currentNativeRows.length === 1
|
|
391
|
+
? claudeSemanticModelId(`${currentNativeRows[0].label} ${currentNativeRows[0].description}`)
|
|
392
|
+
: undefined;
|
|
393
|
+
if (rows.length === 0 ||
|
|
394
|
+
selectedRows.length !== 1 ||
|
|
395
|
+
!currentModel ||
|
|
396
|
+
!rows.some((row) => row.id === currentModel) ||
|
|
397
|
+
new Set(rows.map((row) => row.id)).size !== rows.length) {
|
|
398
|
+
return { state: "ambiguous", fingerprint, reason: "the Claude model rows are not profiled" };
|
|
399
|
+
}
|
|
400
|
+
const displayedEfforts = picker.flatMap((line) => {
|
|
401
|
+
const match = /^\s*[●○◐◉◈✦]\s+(Low|Medium|High|Extra high|Xhigh|Max|Ultracode)\s+effort\b/iu
|
|
402
|
+
.exec(line);
|
|
403
|
+
const value = match ? claudeEffortFromNativeLabel(match[1]) : undefined;
|
|
404
|
+
return value ? [value] : [];
|
|
405
|
+
});
|
|
406
|
+
if (displayedEfforts.length > 1) {
|
|
407
|
+
return { state: "ambiguous", fingerprint, reason: "the Claude effort row is not exact" };
|
|
408
|
+
}
|
|
409
|
+
const displayedEffort = displayedEfforts[0];
|
|
410
|
+
return {
|
|
411
|
+
state: "claude_model_picker",
|
|
412
|
+
fingerprint,
|
|
413
|
+
rows,
|
|
414
|
+
selectedIndex: selectedNativeIndex,
|
|
415
|
+
currentNativeIndex: currentNativeRows[0].nativeIndex,
|
|
416
|
+
currentModel,
|
|
417
|
+
...(displayedEffort && displayedEffort !== "ultracode"
|
|
418
|
+
? { currentEffort: displayedEffort }
|
|
419
|
+
: {}),
|
|
420
|
+
...(displayedEffort ? { displayedEffort } : {})
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
export function codexSemanticModelLabel(label, fullscreen) {
|
|
424
|
+
if (/^[a-z0-9][a-z0-9._:/+\-]*$/u.test(label))
|
|
425
|
+
return label;
|
|
426
|
+
return fullscreen && /^GPT-[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(label)
|
|
427
|
+
? label.toLowerCase()
|
|
428
|
+
: undefined;
|
|
429
|
+
}
|
|
430
|
+
export function parseCodexModelRows(region, fullscreen = false) {
|
|
431
|
+
const rows = [];
|
|
432
|
+
for (const line of region.slice(1, -1)) {
|
|
433
|
+
const match = /^\s*(›\s*)?(\d+)\.\s+([A-Za-z0-9][A-Za-z0-9._:/+\-]*)(?:\s+((?:\((?:current|default)\)\s*){1,2}))?(?:\s{2,}.*)?$/u
|
|
434
|
+
.exec(line);
|
|
435
|
+
if (!match)
|
|
436
|
+
continue;
|
|
437
|
+
const id = codexSemanticModelLabel(match[3], fullscreen);
|
|
438
|
+
if (!id)
|
|
439
|
+
return undefined;
|
|
440
|
+
const flags = match[4] ?? "";
|
|
441
|
+
rows.push({
|
|
442
|
+
id,
|
|
443
|
+
label: match[3],
|
|
444
|
+
nativeIndex: Number(match[2]) - 1,
|
|
445
|
+
selected: Boolean(match[1]),
|
|
446
|
+
current: flags.includes("(current)"),
|
|
447
|
+
presetDefault: flags.includes("(default)")
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
const numberedLines = region.slice(1, -1).filter((line) => /^\s*(?:›\s*)?\d+\./u.test(line));
|
|
451
|
+
const selectedRows = rows.filter((row) => row.selected);
|
|
452
|
+
const selectedIndex = selectedRows[0]?.nativeIndex ?? -1;
|
|
453
|
+
const currentRows = rows.filter((row) => row.current);
|
|
454
|
+
if (rows.length === 0 ||
|
|
455
|
+
numberedLines.length !== rows.length ||
|
|
456
|
+
rows.some((row, index) => row.nativeIndex !== index) ||
|
|
457
|
+
selectedRows.length !== 1 ||
|
|
458
|
+
currentRows.length !== 1 ||
|
|
459
|
+
new Set(rows.map((row) => row.id)).size !== rows.length) {
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
rows,
|
|
464
|
+
selectedIndex,
|
|
465
|
+
currentNativeIndex: currentRows[0].nativeIndex,
|
|
466
|
+
currentModel: currentRows[0].id
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
export function parseCodexEntryModelPicker(region) {
|
|
470
|
+
const subtitle = region[1]?.trim();
|
|
471
|
+
const kind = subtitle === "Pick a quick auto mode or browse all models."
|
|
472
|
+
? "quick_auto"
|
|
473
|
+
: subtitle === "Other models return when ordinary usage is available again."
|
|
474
|
+
? "luna_reserve"
|
|
475
|
+
: undefined;
|
|
476
|
+
if (!kind)
|
|
477
|
+
return undefined;
|
|
478
|
+
const numbered = region.slice(2, -1).filter((line) => /^\s*(?:›\s*)?\d+\./u.test(line));
|
|
479
|
+
const rows = numbered.map((line) => {
|
|
480
|
+
const match = /^\s*(›\s*)?(\d+)\.\s+(.+)$/u.exec(line);
|
|
481
|
+
if (!match)
|
|
482
|
+
return undefined;
|
|
483
|
+
const current = /(?:^|\s)\(current\)(?:\s{2,}|\s*$)/u.test(match[3]);
|
|
484
|
+
const label = match[3]
|
|
485
|
+
.replace(/\s+\(current\)(?=\s{2,}|\s*$)/u, "")
|
|
486
|
+
.split(/\s{2,}/u, 1)[0]
|
|
487
|
+
?.trim();
|
|
488
|
+
return {
|
|
489
|
+
nativeIndex: Number(match[2]) - 1,
|
|
490
|
+
selected: Boolean(match[1]),
|
|
491
|
+
current,
|
|
492
|
+
label
|
|
493
|
+
};
|
|
494
|
+
});
|
|
495
|
+
if (rows.some((row) => !row) ||
|
|
496
|
+
rows.length === 0 ||
|
|
497
|
+
rows.some((row, index) => row?.nativeIndex !== index))
|
|
498
|
+
return undefined;
|
|
499
|
+
const exactRows = rows.filter((row) => Boolean(row));
|
|
500
|
+
const selected = exactRows.filter((row) => row.selected);
|
|
501
|
+
const current = exactRows.filter((row) => row.current);
|
|
502
|
+
if (selected.length !== 1 || current.length !== 1)
|
|
503
|
+
return undefined;
|
|
504
|
+
if (kind === "quick_auto") {
|
|
505
|
+
const allModels = exactRows.filter((row) => row.label === "All models");
|
|
506
|
+
if (allModels.length !== 1 ||
|
|
507
|
+
exactRows.at(-1)?.label !== "All models" ||
|
|
508
|
+
exactRows.slice(0, -1).some((row) => !/^codex-auto-[a-z0-9][a-z0-9._:/+\-]*$/u.test(row.label ?? "")))
|
|
509
|
+
return undefined;
|
|
510
|
+
return {
|
|
511
|
+
kind,
|
|
512
|
+
selectedIndex: selected[0].nativeIndex,
|
|
513
|
+
currentNativeIndex: current[0].nativeIndex,
|
|
514
|
+
allModelsNativeIndex: allModels[0].nativeIndex
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (exactRows.length !== 1 || !exactRows[0].label)
|
|
518
|
+
return undefined;
|
|
519
|
+
return {
|
|
520
|
+
kind,
|
|
521
|
+
selectedIndex: selected[0].nativeIndex,
|
|
522
|
+
currentNativeIndex: current[0].nativeIndex
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
export function parseCodexEffortRows(region, advanced) {
|
|
526
|
+
const rows = [];
|
|
527
|
+
for (const line of region.slice(1, -1)) {
|
|
528
|
+
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
|
|
529
|
+
.exec(line);
|
|
530
|
+
if (!match)
|
|
531
|
+
continue;
|
|
532
|
+
const effort = effortFromNativeLabel(match[2]);
|
|
533
|
+
const flags = match[3] ?? "";
|
|
534
|
+
rows.push({
|
|
535
|
+
...(effort ? { effort } : {}),
|
|
536
|
+
kind: match[2] === "More reasoning…"
|
|
537
|
+
? "advanced"
|
|
538
|
+
: match[2] === "Persistent"
|
|
539
|
+
? "unsupported"
|
|
540
|
+
: "effort",
|
|
541
|
+
selected: Boolean(match[1]),
|
|
542
|
+
current: flags.includes("(current)"),
|
|
543
|
+
presetDefault: flags.includes("(default)")
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
const numberedLines = region.slice(1, -1).filter((line) => /^\s*(?:›\s*)?\d+\./u.test(line));
|
|
547
|
+
const selectedRows = rows.filter((row) => row.selected);
|
|
548
|
+
const selectedIndex = rows.findIndex((row) => row.selected);
|
|
549
|
+
const current = rows.filter((row) => row.current && row.effort);
|
|
550
|
+
const defaults = rows.filter((row) => row.presetDefault && row.effort);
|
|
551
|
+
const allowed = advanced
|
|
552
|
+
? rows.every((row) => row.kind === "effort" && ["max", "ultra"].includes(row.effort ?? ""))
|
|
553
|
+
: rows.every((row) => row.kind === "advanced" || row.kind === "unsupported" ||
|
|
554
|
+
!["max", "ultra"].includes(row.effort ?? ""));
|
|
555
|
+
if (rows.length === 0 ||
|
|
556
|
+
numberedLines.length !== rows.length ||
|
|
557
|
+
!numberedLines.every((line, index) => new RegExp(`^\\s*(?:›\\s*)?${index + 1}\\.`).test(line)) ||
|
|
558
|
+
selectedRows.length !== 1 ||
|
|
559
|
+
selectedIndex < 0 ||
|
|
560
|
+
current.length > 1 ||
|
|
561
|
+
defaults.length > 1 ||
|
|
562
|
+
!allowed)
|
|
563
|
+
return undefined;
|
|
564
|
+
return {
|
|
565
|
+
rows,
|
|
566
|
+
selectedIndex,
|
|
567
|
+
...(current[0]?.effort ? { currentEffort: current[0].effort } : {}),
|
|
568
|
+
...(defaults[0]?.effort
|
|
569
|
+
? { presetDefaultEffort: defaults[0].effort }
|
|
570
|
+
: {})
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
export function currentPickerRegion(lines, headerIndex, fullscreen = false) {
|
|
574
|
+
const after = lines.slice(headerIndex);
|
|
575
|
+
const expectedFooters = fullscreen ? [
|
|
576
|
+
"enter select · esc back",
|
|
577
|
+
"enter default · s session · esc back",
|
|
578
|
+
"enter apply · s session · esc back"
|
|
579
|
+
] : ["Press enter to confirm or esc to go back"];
|
|
580
|
+
const footerIndex = after.findIndex((line) => expectedFooters.includes(line.trim()));
|
|
581
|
+
if (footerIndex < 0 || after.slice(footerIndex + 1).some((line) => line.trim())) {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
return after.slice(0, footerIndex + 1);
|
|
585
|
+
}
|
|
586
|
+
export function codexFullscreenEffortFooterMatches(region, selected) {
|
|
587
|
+
const expected = selected.kind !== "effort"
|
|
588
|
+
? "enter select · esc back"
|
|
589
|
+
: selected.effort === "ultra"
|
|
590
|
+
? "enter apply · s session · esc back"
|
|
591
|
+
: "enter default · s session · esc back";
|
|
592
|
+
return region.at(-1)?.trim() === expected;
|
|
593
|
+
}
|
|
594
|
+
export function effortFromNativeLabel(label) {
|
|
595
|
+
return {
|
|
596
|
+
Low: "low",
|
|
597
|
+
Medium: "medium",
|
|
598
|
+
High: "high",
|
|
599
|
+
"Extra high": "xhigh",
|
|
600
|
+
Max: "max",
|
|
601
|
+
Ultra: "ultra"
|
|
602
|
+
}[label];
|
|
603
|
+
}
|
|
604
|
+
export function claudeEffortFromNativeLabel(label) {
|
|
605
|
+
const normalized = label.trim().toLowerCase();
|
|
606
|
+
return {
|
|
607
|
+
low: "low",
|
|
608
|
+
medium: "medium",
|
|
609
|
+
high: "high",
|
|
610
|
+
"extra high": "xhigh",
|
|
611
|
+
xhigh: "xhigh",
|
|
612
|
+
max: "max",
|
|
613
|
+
ultracode: "ultracode"
|
|
614
|
+
}[normalized];
|
|
615
|
+
}
|
|
616
|
+
export function claudeSemanticModelId(label) {
|
|
617
|
+
const family = /\b(opus|sonnet|haiku)\b/iu.exec(label)?.[1].toLowerCase();
|
|
618
|
+
if (!family || !/^(?:opus|sonnet|haiku)$/u.test(family))
|
|
619
|
+
return undefined;
|
|
620
|
+
const version = new RegExp(`\\b${family}[-\\s]+(\\d+(?:\\.\\d+)?)\\b`, "iu").exec(label)?.[1] ?? /\b(\d+\.\d+)\b/u.exec(label)?.[1];
|
|
621
|
+
const longContext = /(?:\[?1m\]?|1\s*million)(?:\s+context)?\b/iu.test(label);
|
|
622
|
+
return [family, version, longContext ? "1m" : undefined]
|
|
623
|
+
.filter((part) => Boolean(part))
|
|
624
|
+
.join("-");
|
|
625
|
+
}
|
|
626
|
+
export function terminalTailLines(screen) {
|
|
627
|
+
const lines = screen.split("\n").slice(-160);
|
|
628
|
+
while (lines.length > 0 && lines.at(-1)?.trim() === "")
|
|
629
|
+
lines.pop();
|
|
630
|
+
return lines;
|
|
631
|
+
}
|
|
632
|
+
export function lastIndex(lines, predicate) {
|
|
633
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
634
|
+
if (predicate(lines[index]))
|
|
635
|
+
return index;
|
|
636
|
+
}
|
|
637
|
+
return -1;
|
|
638
|
+
}
|
|
639
|
+
export function screenFingerprint(lines) {
|
|
640
|
+
return createHash("sha256").update(lines.join("\n")).digest("hex");
|
|
641
|
+
}
|
|
642
|
+
export function stripAnsi(value) {
|
|
643
|
+
return value.replace(/\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/gu, "");
|
|
644
|
+
}
|
|
645
|
+
export function isPlainRecord(value) {
|
|
646
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
647
|
+
}
|
|
648
|
+
export function nonBlank(value) {
|
|
649
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
650
|
+
}
|
|
651
|
+
export function codexPersistencePostcondition(beforeScreen, afterScreen, request) {
|
|
652
|
+
const errorPatterns = [
|
|
653
|
+
/Failed to save default model:/giu,
|
|
654
|
+
/Failed to save Plan mode reasoning effort:/giu,
|
|
655
|
+
/Saved default model and reasoning effort, but a higher-priority configuration\s+layer overrides the saved value\./giu,
|
|
656
|
+
/Saved Plan mode reasoning effort, but a higher-priority configuration\s+layer overrides the saved value\./giu
|
|
657
|
+
];
|
|
658
|
+
if (errorPatterns.some((pattern) => matchCount(afterScreen, pattern) > matchCount(beforeScreen, pattern))) {
|
|
659
|
+
return {
|
|
660
|
+
proven: false,
|
|
661
|
+
final: true,
|
|
662
|
+
reason: "Codex reported that its persisted model default failed or is overridden by a higher-priority configuration layer"
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const model = escapeRegExp(request.model);
|
|
666
|
+
const effort = request.reasoningEffort === "xhigh"
|
|
667
|
+
? "(?:xhigh|extra high)"
|
|
668
|
+
: escapeRegExp(request.reasoningEffort);
|
|
669
|
+
const suffix = request.reasoningEffort === "ultra"
|
|
670
|
+
? "\\s+for this conversation"
|
|
671
|
+
: "";
|
|
672
|
+
const success = new RegExp(`Model changed to\\s+${model}\\s+${effort}${suffix}(?:\\s|$)`, "giu");
|
|
673
|
+
if (matchCount(afterScreen, success) <= matchCount(beforeScreen, success)) {
|
|
674
|
+
return {
|
|
675
|
+
proven: false,
|
|
676
|
+
final: false,
|
|
677
|
+
reason: "Codex did not expose a fresh exact model-default persistence success frame"
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return { proven: true };
|
|
681
|
+
}
|
|
682
|
+
export function matchCount(value, pattern) {
|
|
683
|
+
return [...value.matchAll(pattern)].length;
|
|
684
|
+
}
|
|
685
|
+
export function escapeRegExp(value) {
|
|
686
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
687
|
+
}
|
|
688
|
+
//# sourceMappingURL=terminal-model-control-surface.js.map
|