@scotthuang/agent-knock-knock 0.13.2 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +27 -6
  3. package/dist/src/callback-outbox-policy.js +27 -6
  4. package/dist/src/callback-outbox-policy.js.map +1 -1
  5. package/dist/src/callback-outbox-service.d.ts +4 -30
  6. package/dist/src/callback-outbox-service.js +21 -154
  7. package/dist/src/callback-outbox-service.js.map +1 -1
  8. package/dist/src/callback-outbox-settlement.js +13 -8
  9. package/dist/src/callback-outbox-settlement.js.map +1 -1
  10. package/dist/src/callback-route-service.d.ts +46 -0
  11. package/dist/src/callback-route-service.js +155 -0
  12. package/dist/src/callback-route-service.js.map +1 -0
  13. package/dist/src/claude-terminal-agent-adapter.js +4 -0
  14. package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
  15. package/dist/src/cli-core.js +9 -1
  16. package/dist/src/cli-core.js.map +1 -1
  17. package/dist/src/codex-process-incarnation.d.ts +8 -0
  18. package/dist/src/codex-process-incarnation.js +36 -0
  19. package/dist/src/codex-process-incarnation.js.map +1 -0
  20. package/dist/src/codex-terminal-agent-adapter.js +4 -0
  21. package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
  22. package/dist/src/deferred-foreground-conflict-policy.d.ts +22 -0
  23. package/dist/src/deferred-foreground-conflict-policy.js +52 -0
  24. package/dist/src/deferred-foreground-conflict-policy.js.map +1 -0
  25. package/dist/src/durable-notification-kernel.d.ts +94 -0
  26. package/dist/src/durable-notification-kernel.js +68 -0
  27. package/dist/src/durable-notification-kernel.js.map +1 -0
  28. package/dist/src/herdr-socket-transport.d.ts +43 -0
  29. package/dist/src/herdr-socket-transport.js +170 -0
  30. package/dist/src/herdr-socket-transport.js.map +1 -0
  31. package/dist/src/herdr-terminal-control-provider.d.ts +2 -45
  32. package/dist/src/herdr-terminal-control-provider.js +8 -173
  33. package/dist/src/herdr-terminal-control-provider.js.map +1 -1
  34. package/dist/src/host-adapter-capabilities.d.ts +35 -0
  35. package/dist/src/host-adapter-capabilities.js +146 -0
  36. package/dist/src/host-adapter-capabilities.js.map +1 -0
  37. package/dist/src/host-adapter.d.ts +9 -3
  38. package/dist/src/host-adapter.js +33 -9
  39. package/dist/src/host-adapter.js.map +1 -1
  40. package/dist/src/host-bridge-tools.d.ts +3 -4
  41. package/dist/src/host-bridge-tools.js +49 -92
  42. package/dist/src/host-bridge-tools.js.map +1 -1
  43. package/dist/src/host-bridge.js +5 -5
  44. package/dist/src/host-bridge.js.map +1 -1
  45. package/dist/src/host-monitor-reconciliation.d.ts +6 -0
  46. package/dist/src/host-monitor-reconciliation.js +96 -0
  47. package/dist/src/host-monitor-reconciliation.js.map +1 -0
  48. package/dist/src/managed-session.js +2 -18
  49. package/dist/src/managed-session.js.map +1 -1
  50. package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +13 -0
  51. package/dist/src/native-thread-lifecycle-cli-adapter.js +635 -3
  52. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  53. package/dist/src/openclaw-list-projection.d.ts +2 -0
  54. package/dist/src/openclaw-list-projection.js +3 -0
  55. package/dist/src/openclaw-list-projection.js.map +1 -0
  56. package/dist/src/openclaw-model-facing-field-policy.d.ts +2 -0
  57. package/dist/src/openclaw-model-facing-field-policy.js +3 -0
  58. package/dist/src/openclaw-model-facing-field-policy.js.map +1 -0
  59. package/dist/src/openclaw-plugin-command-adapter.d.ts +6 -29
  60. package/dist/src/openclaw-plugin-command-adapter.js +7 -2370
  61. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  62. package/dist/src/openclaw-plugin-helpers.d.ts +2 -121
  63. package/dist/src/openclaw-plugin-helpers.js +2 -1164
  64. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  65. package/dist/src/openclaw-plugin-schemas.d.ts +2 -510
  66. package/dist/src/openclaw-plugin-schemas.js +2 -519
  67. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  68. package/dist/src/openclaw-plugin-supervisor.d.ts +4 -5
  69. package/dist/src/openclaw-plugin-supervisor.js +5 -94
  70. package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
  71. package/dist/src/openclaw-plugin-value-helpers.d.ts +2 -0
  72. package/dist/src/openclaw-plugin-value-helpers.js +3 -0
  73. package/dist/src/openclaw-plugin-value-helpers.js.map +1 -0
  74. package/dist/src/openclaw-private-authority-offers.d.ts +4 -39
  75. package/dist/src/openclaw-private-authority-offers.js +3 -258
  76. package/dist/src/openclaw-private-authority-offers.js.map +1 -1
  77. package/dist/src/semantic-private-authority-offers.d.ts +41 -0
  78. package/dist/src/semantic-private-authority-offers.js +261 -0
  79. package/dist/src/semantic-private-authority-offers.js.map +1 -0
  80. package/dist/src/semantic-tool-arguments.d.ts +9 -0
  81. package/dist/src/semantic-tool-arguments.js +64 -0
  82. package/dist/src/semantic-tool-arguments.js.map +1 -0
  83. package/dist/src/semantic-tool-catalog.d.ts +48 -0
  84. package/dist/src/semantic-tool-catalog.js +105 -0
  85. package/dist/src/semantic-tool-catalog.js.map +1 -0
  86. package/dist/src/semantic-tool-command-helpers.d.ts +145 -0
  87. package/dist/src/semantic-tool-command-helpers.js +1272 -0
  88. package/dist/src/semantic-tool-command-helpers.js.map +1 -0
  89. package/dist/src/semantic-tool-list-projection.d.ts +10 -0
  90. package/dist/src/semantic-tool-list-projection.js +497 -0
  91. package/dist/src/semantic-tool-list-projection.js.map +1 -0
  92. package/dist/src/semantic-tool-model-facing-field-policy.d.ts +3 -0
  93. package/dist/src/semantic-tool-model-facing-field-policy.js +66 -0
  94. package/dist/src/semantic-tool-model-facing-field-policy.js.map +1 -0
  95. package/dist/src/semantic-tool-presentation.d.ts +37 -0
  96. package/dist/src/semantic-tool-presentation.js +680 -0
  97. package/dist/src/semantic-tool-presentation.js.map +1 -0
  98. package/dist/src/semantic-tool-private-authority.d.ts +39 -0
  99. package/dist/src/semantic-tool-private-authority.js +602 -0
  100. package/dist/src/semantic-tool-private-authority.js.map +1 -0
  101. package/dist/src/semantic-tool-relay.d.ts +16 -0
  102. package/dist/src/semantic-tool-relay.js +175 -0
  103. package/dist/src/semantic-tool-relay.js.map +1 -0
  104. package/dist/src/semantic-tool-runtime.d.ts +10 -0
  105. package/dist/src/semantic-tool-runtime.js +1157 -0
  106. package/dist/src/semantic-tool-runtime.js.map +1 -0
  107. package/dist/src/semantic-tool-schemas.d.ts +563 -0
  108. package/dist/src/semantic-tool-schemas.js +573 -0
  109. package/dist/src/semantic-tool-schemas.js.map +1 -0
  110. package/dist/src/semantic-tool-value-helpers.d.ts +5 -0
  111. package/dist/src/semantic-tool-value-helpers.js +27 -0
  112. package/dist/src/semantic-tool-value-helpers.js.map +1 -0
  113. package/dist/src/store-event-presentation.d.ts +17 -0
  114. package/dist/src/store-event-presentation.js +33 -0
  115. package/dist/src/store-event-presentation.js.map +1 -0
  116. package/dist/src/store.d.ts +3 -16
  117. package/dist/src/store.js +2 -30
  118. package/dist/src/store.js.map +1 -1
  119. package/dist/src/terminal-action-projection.d.ts +3 -17
  120. package/dist/src/terminal-action-projection.js +13 -41
  121. package/dist/src/terminal-action-projection.js.map +1 -1
  122. package/dist/src/terminal-agent-adapter.d.ts +7 -0
  123. package/dist/src/terminal-agent-adapter.js +8 -0
  124. package/dist/src/terminal-agent-adapter.js.map +1 -1
  125. package/dist/src/terminal-agent-bridge.d.ts +35 -332
  126. package/dist/src/terminal-agent-bridge.js +293 -2798
  127. package/dist/src/terminal-agent-bridge.js.map +1 -1
  128. package/dist/src/terminal-approval-cli-adapter.d.ts +17 -0
  129. package/dist/src/terminal-approval-cli-adapter.js +921 -0
  130. package/dist/src/terminal-approval-cli-adapter.js.map +1 -0
  131. package/dist/src/terminal-command-cli-adapter.d.ts +2 -429
  132. package/dist/src/terminal-command-cli-adapter.js +344 -6061
  133. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  134. package/dist/src/terminal-command-cli-ports.d.ts +443 -0
  135. package/dist/src/terminal-command-cli-ports.js +2 -0
  136. package/dist/src/terminal-command-cli-ports.js.map +1 -0
  137. package/dist/src/terminal-command-dispatch-preparation.d.ts +81 -0
  138. package/dist/src/terminal-command-dispatch-preparation.js +209 -0
  139. package/dist/src/terminal-command-dispatch-preparation.js.map +1 -0
  140. package/dist/src/terminal-command-dispatch-transport.d.ts +35 -0
  141. package/dist/src/terminal-command-dispatch-transport.js +859 -0
  142. package/dist/src/terminal-command-dispatch-transport.js.map +1 -0
  143. package/dist/src/terminal-command-foreground-proof.d.ts +32 -0
  144. package/dist/src/terminal-command-foreground-proof.js +72 -0
  145. package/dist/src/terminal-command-foreground-proof.js.map +1 -0
  146. package/dist/src/terminal-command-send-preflight.d.ts +41 -0
  147. package/dist/src/terminal-command-send-preflight.js +86 -0
  148. package/dist/src/terminal-command-send-preflight.js.map +1 -0
  149. package/dist/src/terminal-composer-classifier.d.ts +55 -0
  150. package/dist/src/terminal-composer-classifier.js +638 -0
  151. package/dist/src/terminal-composer-classifier.js.map +1 -0
  152. package/dist/src/terminal-control-ref.d.ts +23 -0
  153. package/dist/src/terminal-control-ref.js +32 -0
  154. package/dist/src/terminal-control-ref.js.map +1 -1
  155. package/dist/src/terminal-dispatch-recovery-authority.d.ts +56 -0
  156. package/dist/src/terminal-dispatch-recovery-authority.js +251 -0
  157. package/dist/src/terminal-dispatch-recovery-authority.js.map +1 -0
  158. package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +1 -9
  159. package/dist/src/terminal-dispatch-recovery-cli-adapter.js +5 -240
  160. package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
  161. package/dist/src/terminal-handoff-cli-adapter.d.ts +1 -11
  162. package/dist/src/terminal-handoff-cli-adapter.js +2 -39
  163. package/dist/src/terminal-handoff-cli-adapter.js.map +1 -1
  164. package/dist/src/terminal-human-explicit-send-cli-adapter.d.ts +25 -0
  165. package/dist/src/terminal-human-explicit-send-cli-adapter.js +903 -0
  166. package/dist/src/terminal-human-explicit-send-cli-adapter.js.map +1 -0
  167. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -6
  168. package/dist/src/terminal-identity-authority-cli-adapter.js +3 -33
  169. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
  170. package/dist/src/terminal-interaction-cli-adapter.js +209 -210
  171. package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
  172. package/dist/src/terminal-interaction-core.d.ts +0 -2
  173. package/dist/src/terminal-interaction-core.js +4 -3
  174. package/dist/src/terminal-interaction-core.js.map +1 -1
  175. package/dist/src/terminal-interaction-protocol.d.ts +21 -0
  176. package/dist/src/terminal-interaction-protocol.js +74 -0
  177. package/dist/src/terminal-interaction-protocol.js.map +1 -1
  178. package/dist/src/terminal-interaction-response-bridge.d.ts +126 -0
  179. package/dist/src/terminal-interaction-response-bridge.js +579 -0
  180. package/dist/src/terminal-interaction-response-bridge.js.map +1 -0
  181. package/dist/src/terminal-interaction-response-transaction.d.ts +44 -0
  182. package/dist/src/terminal-interaction-response-transaction.js +56 -0
  183. package/dist/src/terminal-interaction-response-transaction.js.map +1 -0
  184. package/dist/src/terminal-list-action-policy.d.ts +43 -0
  185. package/dist/src/terminal-list-action-policy.js +164 -0
  186. package/dist/src/terminal-list-action-policy.js.map +1 -0
  187. package/dist/src/terminal-list-cli-adapter.d.ts +6 -36
  188. package/dist/src/terminal-list-cli-adapter.js +314 -1711
  189. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  190. package/dist/src/terminal-list-facts.d.ts +129 -0
  191. package/dist/src/terminal-list-facts.js +179 -0
  192. package/dist/src/terminal-list-facts.js.map +1 -0
  193. package/dist/src/terminal-list-inventory.d.ts +59 -0
  194. package/dist/src/terminal-list-inventory.js +194 -0
  195. package/dist/src/terminal-list-inventory.js.map +1 -0
  196. package/dist/src/terminal-list-ownership-service.d.ts +118 -0
  197. package/dist/src/terminal-list-ownership-service.js +1003 -0
  198. package/dist/src/terminal-list-ownership-service.js.map +1 -0
  199. package/dist/src/terminal-list-projection-service.d.ts +49 -0
  200. package/dist/src/terminal-list-projection-service.js +407 -0
  201. package/dist/src/terminal-list-projection-service.js.map +1 -0
  202. package/dist/src/terminal-list-renderer.d.ts +12 -11
  203. package/dist/src/terminal-list-renderer.js +237 -104
  204. package/dist/src/terminal-list-renderer.js.map +1 -1
  205. package/dist/src/terminal-managed-send-cli-adapter.d.ts +33 -0
  206. package/dist/src/terminal-managed-send-cli-adapter.js +994 -0
  207. package/dist/src/terminal-managed-send-cli-adapter.js.map +1 -0
  208. package/dist/src/terminal-managed-turn-list-action-policy.d.ts +47 -0
  209. package/dist/src/terminal-managed-turn-list-action-policy.js +64 -0
  210. package/dist/src/terminal-managed-turn-list-action-policy.js.map +1 -0
  211. package/dist/src/terminal-model-control-action-policy.d.ts +60 -0
  212. package/dist/src/terminal-model-control-action-policy.js +96 -0
  213. package/dist/src/terminal-model-control-action-policy.js.map +1 -0
  214. package/dist/src/terminal-model-control-availability.d.ts +68 -0
  215. package/dist/src/terminal-model-control-availability.js +157 -0
  216. package/dist/src/terminal-model-control-availability.js.map +1 -0
  217. package/dist/src/terminal-model-control-bridge.d.ts +48 -0
  218. package/dist/src/terminal-model-control-bridge.js +259 -0
  219. package/dist/src/terminal-model-control-bridge.js.map +1 -0
  220. package/dist/src/terminal-model-control-profile.d.ts +55 -0
  221. package/dist/src/terminal-model-control-profile.js +137 -0
  222. package/dist/src/terminal-model-control-profile.js.map +1 -0
  223. package/dist/src/terminal-model-control-subject.d.ts +34 -0
  224. package/dist/src/terminal-model-control-subject.js +58 -0
  225. package/dist/src/terminal-model-control-subject.js.map +1 -0
  226. package/dist/src/terminal-model-control-transaction.d.ts +19 -0
  227. package/dist/src/terminal-model-control-transaction.js +39 -0
  228. package/dist/src/terminal-model-control-transaction.js.map +1 -0
  229. package/dist/src/terminal-model-control.d.ts +331 -0
  230. package/dist/src/terminal-model-control.js +1686 -0
  231. package/dist/src/terminal-model-control.js.map +1 -0
  232. package/dist/src/terminal-monitor-application-service.js +50 -17
  233. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  234. package/dist/src/terminal-native-inspection-bridge.d.ts +211 -0
  235. package/dist/src/terminal-native-inspection-bridge.js +1127 -0
  236. package/dist/src/terminal-native-inspection-bridge.js.map +1 -0
  237. package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -0
  238. package/dist/src/terminal-runtime-cli-adapter.js +59 -17
  239. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
  240. package/dist/src/terminal-submission-retry-application.d.ts +24 -0
  241. package/dist/src/terminal-submission-retry-application.js +402 -0
  242. package/dist/src/terminal-submission-retry-application.js.map +1 -0
  243. package/dist/src/terminal-submission-retry-ports.d.ts +30 -0
  244. package/dist/src/terminal-submission-retry-ports.js +2 -0
  245. package/dist/src/terminal-submission-retry-ports.js.map +1 -0
  246. package/dist/src/terminal-submission-retry-reconciliation.d.ts +236 -0
  247. package/dist/src/terminal-submission-retry-reconciliation.js +1130 -0
  248. package/dist/src/terminal-submission-retry-reconciliation.js.map +1 -0
  249. package/dist/src/terminal-submission-retry-transport.d.ts +58 -0
  250. package/dist/src/terminal-submission-retry-transport.js +675 -0
  251. package/dist/src/terminal-submission-retry-transport.js.map +1 -0
  252. package/dist/src/terminal-text-submission-bridge.d.ts +200 -0
  253. package/dist/src/terminal-text-submission-bridge.js +632 -0
  254. package/dist/src/terminal-text-submission-bridge.js.map +1 -0
  255. package/dist/src/terminal-user-explicit-send-policy.d.ts +29 -0
  256. package/dist/src/terminal-user-explicit-send-policy.js +46 -0
  257. package/dist/src/terminal-user-explicit-send-policy.js.map +1 -0
  258. package/dist/src/terminal-watch-cli-adapter.js +91 -143
  259. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  260. package/dist/src/terminal-watch-codec.d.ts +9 -0
  261. package/dist/src/terminal-watch-codec.js +1051 -0
  262. package/dist/src/terminal-watch-codec.js.map +1 -0
  263. package/dist/src/terminal-watch-interaction-response-store.d.ts +19 -0
  264. package/dist/src/terminal-watch-interaction-response-store.js +87 -0
  265. package/dist/src/terminal-watch-interaction-response-store.js.map +1 -0
  266. package/dist/src/terminal-watch-record.d.ts +319 -0
  267. package/dist/src/terminal-watch-record.js +742 -0
  268. package/dist/src/terminal-watch-record.js.map +1 -0
  269. package/dist/src/terminal-watch-service.js +48 -27
  270. package/dist/src/terminal-watch-service.js.map +1 -1
  271. package/dist/src/terminal-watch-store.d.ts +3 -279
  272. package/dist/src/terminal-watch-store.js +5 -1779
  273. package/dist/src/terminal-watch-store.js.map +1 -1
  274. package/docs/quickstart-herdr.md +1 -1
  275. package/docs/quickstart-tmux.md +27 -2
  276. package/openclaw.plugin.json +13 -1
  277. package/package.json +7 -2
  278. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +54 -11
@@ -12,6 +12,8 @@ import { isSessionSendBlockingStatus, turnIdForConversation } from "./protocol.j
12
12
  import { listManagedSessions, loadNativeThreadTransition } from "./session-store.js";
13
13
  import { isExactClaudeNativeInspectionIdleComposer, NativeInspectionSubmissionError } from "./terminal-agent-bridge.js";
14
14
  import { terminalControlAliasMatches, selectRootTerminalProcesses, withCodexCompanionFences } from "./terminal-authority-policy.js";
15
+ import { decideModelControlAvailability } from "./terminal-model-control-availability.js";
16
+ import { CODEX_MODEL_CONTROL_AGENT_VERSION, isTerminalModelControlPlanForAgent, isTerminalModelReasoningEffort, terminalModelControlPlanConforms, terminalModelControlProfileForPlan, terminalUserExplicitModelControlBindingToken } from "./terminal-model-control.js";
15
17
  import { nonBlankString } from "./value-guards.js";
16
18
  export const CODEX_FOREGROUND_IDENTIFICATION_TTL_MS = 30_000;
17
19
  export const CODEX_FOREGROUND_IDENTIFICATION_SCROLLBACK_LINES = 240;
@@ -26,6 +28,9 @@ export function createNativeThreadLifecycleCliAdapter(ports) {
26
28
  agentAdapter: (options, agent) => app.agentAdapter(options, agent),
27
29
  runList: (options) => app.runList(options),
28
30
  runInspect: (options) => app.runInspect(options),
31
+ runModelOptions: (options) => app.runModelOptions(options),
32
+ runSetModel: (options) => app.runSetModel(options),
33
+ runRepairModelControl: (options) => app.runRepairModelControl(options),
29
34
  runIdentifyForeground: (options) => app.runIdentifyForeground(options),
30
35
  identifyCodexForegroundWhileLocked: (input) => app.identifyCodexForegroundWhileLocked(input),
31
36
  assertSameInspectionTerminal: (expected, actual, stage) => app.assertSameInspectionTerminal(expected, actual, stage),
@@ -216,6 +221,50 @@ class NativeThreadLifecycleCliApplication {
216
221
  bindingToken: bindingTokens[0], bindingTokens
217
222
  });
218
223
  }
224
+ /**
225
+ * Model control on Codex is an explicit physical-pane operation. A freshly
226
+ * started TUI may legitimately have no rollout-backed Session yet. Keep the
227
+ * physical token separate from native lifecycle binding while retaining the
228
+ * resolver's fail-closed behavior for genuinely ambiguous live rollouts.
229
+ */
230
+ async modelControlSnapshot(options, terminal) {
231
+ if (terminal.agent !== "codex") {
232
+ return this.currentSnapshot(options, terminal);
233
+ }
234
+ const snapshot = await this.currentSnapshot(options, terminal);
235
+ const nativeThreadId = snapshot.identity?.sessionId ??
236
+ snapshot.session?.binding?.native_thread_id;
237
+ if (isExactNativeThreadId(nativeThreadId) ||
238
+ snapshot.identity || snapshot.session) {
239
+ return snapshot;
240
+ }
241
+ const adapter = this.agentAdapter(options, terminal.agent);
242
+ const { capability, plan, profile } = modelControlProfileObservation(adapter, snapshot.version);
243
+ if (!plan || profile?.agent !== "codex" ||
244
+ !profile.supportsZeroRolloutPhysicalAuthority) {
245
+ throw new Error(capability?.reason ?? "Codex has no verified physical model-control profile");
246
+ }
247
+ const bindingToken = this.physicalModelControlBindingToken(terminal, snapshot.version, plan.behaviorProfile);
248
+ return Object.freeze({
249
+ ...snapshot,
250
+ bindingToken,
251
+ bindingTokens: Object.freeze([bindingToken])
252
+ });
253
+ }
254
+ physicalModelControlBindingToken(terminal, agentVersion, behaviorProfile) {
255
+ const version = required(nonBlankString(agentVersion), "Codex physical model control requires an exact running version");
256
+ const incarnation = this.ports.identity.physicalProcessIncarnation(terminal.pid);
257
+ return terminalUserExplicitModelControlBindingToken({
258
+ terminalId: terminal.conversationId,
259
+ terminalControl: terminal.terminalControl,
260
+ pid: terminal.pid,
261
+ workspace: terminal.terminalControl.currentPath ?? this.ports.output.cwd(),
262
+ processUuid: incarnation.processUuid,
263
+ processBirth: incarnation.processBirth,
264
+ agentVersion: version,
265
+ behaviorProfile
266
+ });
267
+ }
219
268
  async runList(options) {
220
269
  const terminal = await this.resolveLifecycleTerminal(options);
221
270
  const snapshot = await this.currentSnapshot(options, terminal);
@@ -537,7 +586,7 @@ class NativeThreadLifecycleCliApplication {
537
586
  const terminal = await bridge.resolveStoredTerminal("codex", input.terminal.pid, input.terminal.terminalControl, physicalRuntime);
538
587
  this.assertSameInspectionTerminal(input.terminal, terminal, "while waiting for foreground-identification control");
539
588
  const realCwd = this.realForegroundWorkspace(terminal);
540
- const processIncarnation = this.ports.identity.processIncarnation(terminal.pid);
589
+ const processIncarnation = this.ports.identity.physicalProcessIncarnation(terminal.pid);
541
590
  this.assertForegroundPhysicalToken({
542
591
  terminal,
543
592
  processIncarnation,
@@ -659,7 +708,7 @@ class NativeThreadLifecycleCliApplication {
659
708
  if (this.realForegroundWorkspace(input.terminal) !== boundary.realCwd) {
660
709
  throw new Error(`terminal real cwd changed ${input.stage}; refresh AKK list`);
661
710
  }
662
- const processIncarnation = this.ports.identity.processIncarnation(input.terminal.pid);
711
+ const processIncarnation = this.ports.identity.physicalProcessIncarnation(input.terminal.pid);
663
712
  if (processIncarnation.processUuid !== boundary.processUuid ||
664
713
  processIncarnation.processBirth !== boundary.processBirth) {
665
714
  throw new Error(`Codex process incarnation changed ${input.stage}; refresh AKK list`);
@@ -735,6 +784,567 @@ class NativeThreadLifecycleCliApplication {
735
784
  "native-thread transition");
736
785
  }
737
786
  }
787
+ validateModelOptions(options) {
788
+ if (options.command !== undefined || options.message !== undefined ||
789
+ options.model !== undefined || options.reasoningEffort !== undefined ||
790
+ options.expectedCatalogFingerprint !== undefined) {
791
+ throw new Error("model-options accepts only an exact terminal and current binding authority");
792
+ }
793
+ return required(nonBlankString(options.expectedBindingToken), "--expected-binding-token is required");
794
+ }
795
+ validateSetModel(options) {
796
+ if (options.command !== undefined || options.message !== undefined ||
797
+ options.scope !== undefined || options.keys !== undefined ||
798
+ options.index !== undefined) {
799
+ throw new Error("set-model accepts no command, key, menu index, or caller-selected scope");
800
+ }
801
+ const expectedBindingToken = required(nonBlankString(options.expectedBindingToken), "--expected-binding-token is required");
802
+ const expectedCatalogFingerprint = required(nonBlankString(options.expectedCatalogFingerprint), "--expected-catalog-fingerprint is required");
803
+ if (!/^[0-9a-f]{64}$/u.test(expectedCatalogFingerprint)) {
804
+ throw new Error("--expected-catalog-fingerprint must be the exact sha256 from model-options");
805
+ }
806
+ const model = required(nonBlankString(options.model), "--model is required");
807
+ const reasoningEffort = options.reasoningEffort;
808
+ if (!isTerminalModelReasoningEffort(reasoningEffort)) {
809
+ throw new Error("--reasoning-effort must be one exact semantic value advertised by model-options");
810
+ }
811
+ return {
812
+ expectedBindingToken,
813
+ expectedCatalogFingerprint,
814
+ request: { model, reasoningEffort }
815
+ };
816
+ }
817
+ validateRepairModelControl(options) {
818
+ if (options.command !== undefined ||
819
+ options.message !== undefined ||
820
+ options.model !== undefined ||
821
+ options.reasoningEffort !== undefined ||
822
+ options.expectedCatalogFingerprint !== undefined ||
823
+ options.scope !== undefined ||
824
+ options.keys !== undefined ||
825
+ options.index !== undefined) {
826
+ throw new Error("repair-model-control accepts only an exact terminal and current private repair authority");
827
+ }
828
+ return required(nonBlankString(options.expectedBindingToken), "--expected-binding-token is required");
829
+ }
830
+ async runModelOptions(options) {
831
+ const expectedBindingToken = this.validateModelOptions(options);
832
+ const context = await this.withPreparedModelControl(options, expectedBindingToken, async (prepared) => {
833
+ const result = await prepared.bridge.modelOptions(prepared.terminal.agent, prepared.terminal.terminalControl, prepared.agentVersion, prepared.plan, {
834
+ runtime: prepared.runtime,
835
+ beforeInput: () => prepared.initialResidual
836
+ ? this.assertFreshModelControlResidualBoundary(prepared)
837
+ : this.assertFreshModelControlBoundary(prepared),
838
+ loadCodexCatalog: prepared.loadCodexCatalog,
839
+ initialResidual: prepared.initialResidual
840
+ });
841
+ if (prepared.initialResidual) {
842
+ await this.assertFreshModelControlBoundary({
843
+ ...prepared,
844
+ expectedBindingToken: prepared.ordinaryBindingToken,
845
+ initialResidual: undefined
846
+ });
847
+ }
848
+ return { prepared, catalog: result.catalog };
849
+ }, { allowResidualEntry: true });
850
+ this.ports.output.print(this.modelOptionsResult(context.prepared, context.catalog));
851
+ }
852
+ async runSetModel(options) {
853
+ const validated = this.validateSetModel(options);
854
+ const output = await this.withPreparedModelControl(options, validated.expectedBindingToken, async (prepared) => {
855
+ const result = await prepared.bridge.setModel(prepared.terminal.agent, prepared.terminal.terminalControl, prepared.agentVersion, prepared.plan, validated.expectedCatalogFingerprint, validated.request, {
856
+ runtime: prepared.runtime,
857
+ beforeInput: () => this.assertFreshModelControlBoundary(prepared),
858
+ loadCodexCatalog: prepared.loadCodexCatalog
859
+ });
860
+ return { prepared, result };
861
+ });
862
+ const { result, prepared } = output;
863
+ this.ports.output.print({
864
+ terminal_id: prepared.terminal.conversationId,
865
+ agent: prepared.terminal.agent,
866
+ agent_version: prepared.agentVersion,
867
+ behavior_profile: prepared.plan.behaviorProfile,
868
+ outcome: result.outcome,
869
+ requested: modelValueOutput(result.requested),
870
+ ...(result.effective
871
+ ? { effective: modelValueOutput(result.effective) }
872
+ : {}),
873
+ ...(result.newSessionDefaults
874
+ ? { new_session_defaults: modelValueOutput(result.newSessionDefaults) }
875
+ : {}),
876
+ scope: result.scope,
877
+ defaults_changed: result.defaultsChanged,
878
+ do_not_retry: result.doNotRetry,
879
+ ...(result.reason ? { reason: result.reason } : {})
880
+ });
881
+ }
882
+ async runRepairModelControl(options) {
883
+ const expectedBindingToken = this.validateRepairModelControl(options);
884
+ const output = await this.withPreparedModelControlRepair(options, expectedBindingToken, async (prepared) => {
885
+ const result = await prepared.bridge.repairModelControlResidual(prepared.terminal.agent, prepared.terminal.terminalControl, prepared.agentVersion, prepared.plan, prepared.residual.fingerprint, {
886
+ runtime: prepared.runtime,
887
+ beforeInput: () => this.assertFreshModelControlResidualBoundary(prepared)
888
+ });
889
+ return { prepared, result };
890
+ });
891
+ this.ports.output.print({
892
+ terminal_id: output.prepared.terminal.conversationId,
893
+ agent: output.prepared.terminal.agent,
894
+ agent_version: output.prepared.agentVersion,
895
+ behavior_profile: output.prepared.plan.behaviorProfile,
896
+ outcome: output.result.outcome,
897
+ terminal_input_attempted: output.result.terminalInputAttempted,
898
+ composer_postcondition: output.result.composerPostcondition,
899
+ do_not_retry: output.result.doNotRetry,
900
+ ...(output.result.reason ? { reason: output.result.reason } : {}),
901
+ ...(output.result.outcome === "repaired"
902
+ ? { next: "refresh_list_then_model_options" }
903
+ : {})
904
+ });
905
+ }
906
+ async withPreparedModelControlRepair(options, expectedBindingToken, operation) {
907
+ const storeDir = this.ports.state.storeDir(options);
908
+ if (this.ports.state.inspectStore(storeDir).writable !== true) {
909
+ throw new Error("model-control repair requires a compatible AKK Store for current-snapshot authority");
910
+ }
911
+ const initiallyResolved = await this.resolveLifecycleTerminal(options);
912
+ const release = this.ports.state.acquireTerminal(storeDir, initiallyResolved.terminalControl, { timeoutMs: 30_000 });
913
+ try {
914
+ const runtimeFacade = this.ports.runtime.forOptions(options);
915
+ const bridge = runtimeFacade.createBridge();
916
+ const terminal = await bridge.resolveStoredTerminal(initiallyResolved.agent, initiallyResolved.pid, initiallyResolved.terminalControl, { pid: initiallyResolved.pid });
917
+ this.assertSameInspectionTerminal(initiallyResolved, terminal, "while waiting for model-control repair ownership");
918
+ if (terminal.agent !== "codex") {
919
+ throw new Error("model-control residual repair currently supports only Codex " +
920
+ CODEX_MODEL_CONTROL_AGENT_VERSION);
921
+ }
922
+ const snapshot = await this.modelControlSnapshot(options, terminal);
923
+ const agentVersion = required(nonBlankString(snapshot.version), "model-control repair requires an exact running agent version");
924
+ const adapter = runtimeFacade.createAgentRegistry().require(terminal.agent);
925
+ const { capability, plan, profile } = modelControlProfileObservation(adapter, agentVersion);
926
+ if (!plan || profile?.agent !== "codex" ||
927
+ !profile.supportsResidualRepair) {
928
+ throw new Error(capability?.reason ??
929
+ "the running Codex version has no verified model-control repair profile");
930
+ }
931
+ await this.assertModelControlExclusive({ options, terminal, snapshot });
932
+ const runtime = this.inspectionRuntime(terminal, snapshot);
933
+ const status = await this.assertModelControlRepairReady({
934
+ options,
935
+ terminal,
936
+ snapshot,
937
+ bridge,
938
+ runtime
939
+ });
940
+ const residual = await bridge.inspectModelControlResidual(terminal.agent, terminal.terminalControl, agentVersion, plan, { runtime, beforeInput: () => undefined });
941
+ if (residual.state !== "recoverable") {
942
+ throw new Error(residual.reason);
943
+ }
944
+ const availability = this.modelControlAvailability({
945
+ options,
946
+ terminal,
947
+ snapshot,
948
+ agentVersion,
949
+ plan,
950
+ status,
951
+ surface: {
952
+ kind: "residual",
953
+ residualKind: residual.kind,
954
+ residualFingerprint: residual.fingerprint,
955
+ activityState: status.activity_state
956
+ }
957
+ });
958
+ const repairBindingToken = availability.availability === "residual_continuation"
959
+ ? availability.repairBindingToken
960
+ : availability.availability === "repair_only"
961
+ ? availability.expectedBindingToken
962
+ : undefined;
963
+ if (repairBindingToken !== expectedBindingToken) {
964
+ throw new Error("the exact model-control residual changed after it was listed; refresh AKK list");
965
+ }
966
+ return await operation({
967
+ options,
968
+ terminal,
969
+ snapshot,
970
+ bridge,
971
+ runtime,
972
+ adapter,
973
+ agentVersion,
974
+ plan,
975
+ expectedBindingToken,
976
+ residual
977
+ });
978
+ }
979
+ finally {
980
+ release();
981
+ }
982
+ }
983
+ async assertFreshModelControlResidualBoundary(context) {
984
+ const terminal = await this.resolveModelControlBoundaryTerminal(context);
985
+ this.assertSameInspectionTerminal(context.terminal, terminal, "immediately before model-control repair input");
986
+ const snapshot = await this.modelControlSnapshot(context.options, terminal);
987
+ if (snapshot.version !== context.agentVersion) {
988
+ throw new Error("terminal binding or coding-agent version changed during model-control repair");
989
+ }
990
+ const expectedNativeThreadId = context.snapshot.identity?.sessionId ??
991
+ context.snapshot.session?.binding?.native_thread_id;
992
+ const actualNativeThreadId = snapshot.identity?.sessionId ??
993
+ snapshot.session?.binding?.native_thread_id;
994
+ if (expectedNativeThreadId !== actualNativeThreadId) {
995
+ throw new Error("the current native Session changed during model-control repair");
996
+ }
997
+ const { plan } = modelControlProfileObservation(context.adapter, snapshot.version);
998
+ if (!plan || JSON.stringify(plan) !== JSON.stringify(context.plan)) {
999
+ throw new Error("the exact model-control repair profile changed during the operation");
1000
+ }
1001
+ await this.assertModelControlExclusive({
1002
+ options: context.options,
1003
+ terminal,
1004
+ snapshot
1005
+ });
1006
+ await this.assertModelControlRepairReady({
1007
+ options: context.options,
1008
+ terminal,
1009
+ snapshot,
1010
+ bridge: context.bridge,
1011
+ runtime: context.runtime
1012
+ });
1013
+ }
1014
+ async assertModelControlRepairReady(input) {
1015
+ const status = await input.bridge.status(input.terminal.agent, input.terminal.terminalControl, { runtime: input.runtime });
1016
+ if (status.reachable !== true ||
1017
+ status.approval_state.scanned !== true ||
1018
+ status.approval_state.blocked === true ||
1019
+ status.interaction_state !== undefined) {
1020
+ throw new Error("model-control repair requires a reachable prompt with no approval or questionnaire");
1021
+ }
1022
+ const storeDir = this.ports.state.storeDir(input.options);
1023
+ const blocker = this.ports.state.terminalBlockingTurns(storeDir, input.terminal.terminalControl)[0];
1024
+ if (blocker) {
1025
+ throw new Error(`terminal ${input.terminal.terminalControl.target} still has unresolved Turn ` +
1026
+ `${turnIdForConversation(blocker)} (${blocker.status})`);
1027
+ }
1028
+ if (input.snapshot.session &&
1029
+ this.ports.state.hasUnresolvedTransition(storeDir, input.snapshot.session)) {
1030
+ throw new Error(`managed Session ${input.snapshot.session.session_id} has an unresolved native-thread transition`);
1031
+ }
1032
+ if (this.ports.state.dispatchOwnership(input.terminal.terminalControl).state !==
1033
+ "none") {
1034
+ throw new Error("the terminal has unresolved dispatch ownership during model-control repair");
1035
+ }
1036
+ if (this.ports.state.orphanedForRecovery(input.terminal.terminalControl)) {
1037
+ throw new Error("the terminal has unresolved input ownership during model-control repair");
1038
+ }
1039
+ return status;
1040
+ }
1041
+ modelControlAvailability(input) {
1042
+ const nativeThreadId = input.snapshot.identity?.sessionId ??
1043
+ input.snapshot.session?.binding?.native_thread_id;
1044
+ const zeroRolloutVerified = input.terminal.agent === "codex" &&
1045
+ !input.snapshot.identity && !input.snapshot.session;
1046
+ const incarnation = this.ports.identity.physicalProcessIncarnation(input.terminal.pid);
1047
+ const storeDir = this.ports.state.storeDir(input.options);
1048
+ return decideModelControlAvailability({
1049
+ exactTerminalRow: true,
1050
+ terminalId: input.terminal.conversationId,
1051
+ processState: "active",
1052
+ terminalControl: input.terminal.terminalControl,
1053
+ agent: input.terminal.agent,
1054
+ pid: input.terminal.pid,
1055
+ processUuid: incarnation.processUuid,
1056
+ processBirth: incarnation.processBirth,
1057
+ agentVersion: input.agentVersion,
1058
+ behaviorProfile: input.plan.behaviorProfile,
1059
+ nativeAuthority: isExactNativeThreadId(nativeThreadId)
1060
+ ? {
1061
+ kind: "exact_session",
1062
+ ordinaryBindingToken: input.snapshot.bindingToken
1063
+ }
1064
+ : zeroRolloutVerified
1065
+ ? { kind: "verified_zero_rollout" }
1066
+ : { kind: "unavailable" },
1067
+ modelControlSupported: true,
1068
+ approvalScanned: input.status.approval_state.scanned === true,
1069
+ approvalBlocked: input.status.approval_state.blocked === true,
1070
+ terminalHasInteraction: input.status.interaction_state !== undefined,
1071
+ terminalHasBlockingTurn: this.ports.state.terminalBlockingTurns(storeDir, input.terminal.terminalControl).length > 0,
1072
+ hasOrphanedDispatch: this.ports.state.dispatchOwnership(input.terminal.terminalControl)
1073
+ .state !== "none" ||
1074
+ this.ports.state.orphanedForRecovery(input.terminal.terminalControl) !== undefined,
1075
+ surface: input.surface
1076
+ });
1077
+ }
1078
+ async withPreparedModelControl(options, expectedBindingToken, operation, behavior = {}) {
1079
+ const storeDir = this.ports.state.storeDir(options);
1080
+ if (this.ports.state.inspectStore(storeDir).writable !== true) {
1081
+ throw new Error("terminal model control requires a compatible AKK Store for current-snapshot authority");
1082
+ }
1083
+ const initiallyResolved = await this.resolveLifecycleTerminal(options);
1084
+ const release = this.ports.state.acquireTerminal(storeDir, initiallyResolved.terminalControl, { timeoutMs: 30_000 });
1085
+ try {
1086
+ const runtimeFacade = this.ports.runtime.forOptions(options);
1087
+ const bridge = runtimeFacade.createBridge();
1088
+ const terminal = await bridge.resolveStoredTerminal(initiallyResolved.agent, initiallyResolved.pid, initiallyResolved.terminalControl, { pid: initiallyResolved.pid });
1089
+ this.assertSameInspectionTerminal(initiallyResolved, terminal, "while waiting for model-control ownership");
1090
+ const snapshot = await this.modelControlSnapshot(options, terminal);
1091
+ const ordinaryBindingToken = required(nonBlankString(snapshot.bindingToken), "terminal model control requires current ordinary binding authority");
1092
+ const hasOrdinaryAuthority = snapshot.bindingTokens.includes(expectedBindingToken);
1093
+ if (!hasOrdinaryAuthority && !behavior.allowResidualEntry) {
1094
+ throw new Error("terminal binding changed after it was listed; refresh AKK list and retry");
1095
+ }
1096
+ const agentVersion = required(nonBlankString(snapshot.version), "model control requires an exact running agent version");
1097
+ const adapter = runtimeFacade.createAgentRegistry().require(terminal.agent);
1098
+ const { capability, plan } = modelControlProfileObservation(adapter, agentVersion);
1099
+ if (capability?.status !== "supported" ||
1100
+ capability.modelSelection !== true ||
1101
+ capability.reasoningEffortSelection !== true) {
1102
+ throw new Error(capability?.reason ??
1103
+ `${adapter.displayName} has no verified model-control profile`);
1104
+ }
1105
+ if (!plan) {
1106
+ throw new Error("the agent adapter did not produce a model-control plan");
1107
+ }
1108
+ await this.assertModelControlExclusive({ options, terminal, snapshot });
1109
+ let initialResidual;
1110
+ let runtime;
1111
+ if (hasOrdinaryAuthority) {
1112
+ const ready = await this.observeInitialInspectionReady(options, terminal, snapshot, bridge);
1113
+ runtime = ready.runtime;
1114
+ if (ready.status.approval_state.scanned !== true ||
1115
+ ready.status.approval_state.blocked === true ||
1116
+ ready.status.interaction_state !== undefined) {
1117
+ throw new Error("model control requires a freshly scanned terminal with no approval or questionnaire");
1118
+ }
1119
+ const availability = this.modelControlAvailability({
1120
+ options,
1121
+ terminal,
1122
+ snapshot,
1123
+ agentVersion,
1124
+ plan,
1125
+ status: ready.status,
1126
+ surface: {
1127
+ kind: "idle_empty",
1128
+ screenState: ready.status.screen_state
1129
+ }
1130
+ });
1131
+ if (availability.availability !== "open_from_empty" ||
1132
+ availability.expectedBindingToken !== ordinaryBindingToken) {
1133
+ throw new Error("model control requires an exact verified idle pane with an empty composer and no blocking interaction");
1134
+ }
1135
+ }
1136
+ else {
1137
+ if (terminal.agent !== "codex" ||
1138
+ terminalModelControlProfileForPlan(plan)
1139
+ ?.supportsResidualContinuation !== true) {
1140
+ throw new Error("terminal binding changed after it was listed; refresh AKK list and retry");
1141
+ }
1142
+ runtime = this.inspectionRuntime(terminal, snapshot);
1143
+ const status = await this.assertModelControlRepairReady({
1144
+ options,
1145
+ terminal,
1146
+ snapshot,
1147
+ bridge,
1148
+ runtime
1149
+ });
1150
+ const observed = await bridge.inspectModelControlResidual(terminal.agent, terminal.terminalControl, agentVersion, plan, { runtime, beforeInput: () => undefined });
1151
+ if (observed.state !== "recoverable") {
1152
+ throw new Error(observed.reason);
1153
+ }
1154
+ const availability = this.modelControlAvailability({
1155
+ options,
1156
+ terminal,
1157
+ snapshot,
1158
+ agentVersion,
1159
+ plan,
1160
+ status,
1161
+ surface: {
1162
+ kind: "residual",
1163
+ residualKind: observed.kind,
1164
+ residualFingerprint: observed.fingerprint,
1165
+ activityState: status.activity_state
1166
+ }
1167
+ });
1168
+ if (availability.availability !== "residual_continuation" ||
1169
+ availability.expectedBindingToken !== expectedBindingToken) {
1170
+ throw new Error("the exact model-control residual changed after it was listed; refresh AKK list");
1171
+ }
1172
+ initialResidual = observed;
1173
+ }
1174
+ return await operation({
1175
+ options,
1176
+ terminal,
1177
+ snapshot,
1178
+ bridge,
1179
+ runtime,
1180
+ adapter,
1181
+ agentVersion,
1182
+ plan,
1183
+ expectedBindingToken,
1184
+ ordinaryBindingToken,
1185
+ initialResidual,
1186
+ ...(terminal.agent === "codex" &&
1187
+ isTerminalModelControlPlanForAgent(plan, "codex")
1188
+ ? {
1189
+ loadCodexCatalog: async () => runtimeFacade.codexModelCatalogForRunningProcess(terminal.pid, CODEX_MODEL_CONTROL_AGENT_VERSION, terminal.terminalControl.currentPath)
1190
+ }
1191
+ : {})
1192
+ });
1193
+ }
1194
+ finally {
1195
+ release();
1196
+ }
1197
+ }
1198
+ async assertFreshModelControlBoundary(context) {
1199
+ const terminal = await this.resolveModelControlBoundaryTerminal(context);
1200
+ this.assertSameInspectionTerminal(context.terminal, terminal, "immediately before model-control input");
1201
+ const snapshot = await this.modelControlSnapshot(context.options, terminal);
1202
+ if (!snapshot.bindingTokens.includes(context.expectedBindingToken) ||
1203
+ snapshot.version !== context.agentVersion) {
1204
+ throw new Error("terminal binding or coding-agent version changed during model control; refresh AKK list");
1205
+ }
1206
+ const expectedNativeThreadId = context.snapshot.identity?.sessionId ??
1207
+ context.snapshot.session?.binding?.native_thread_id;
1208
+ const actualNativeThreadId = snapshot.identity?.sessionId ??
1209
+ snapshot.session?.binding?.native_thread_id;
1210
+ if (expectedNativeThreadId !== actualNativeThreadId) {
1211
+ throw new Error("the current native Session changed during model control; inspect the pane before retrying");
1212
+ }
1213
+ const { plan } = modelControlProfileObservation(context.adapter, snapshot.version);
1214
+ if (!plan || JSON.stringify(plan) !== JSON.stringify(context.plan)) {
1215
+ throw new Error("the exact model-control profile changed during the operation");
1216
+ }
1217
+ await this.assertModelControlExclusive({
1218
+ options: context.options,
1219
+ terminal,
1220
+ snapshot
1221
+ });
1222
+ this.assertInspectionReady({
1223
+ options: context.options,
1224
+ terminal,
1225
+ session: snapshot.session
1226
+ });
1227
+ this.assertModelControlAgentIdentity({
1228
+ options: context.options,
1229
+ terminal,
1230
+ snapshot
1231
+ });
1232
+ }
1233
+ async resolveModelControlBoundaryTerminal(context) {
1234
+ try {
1235
+ return await context.bridge.resolveStoredTerminal(context.terminal.agent, context.terminal.pid, context.terminal.terminalControl, context.runtime);
1236
+ }
1237
+ catch (idleError) {
1238
+ if (context.terminal.agent !== "claude" ||
1239
+ !terminalModelControlPlanConforms({
1240
+ agent: context.terminal.agent,
1241
+ agentVersion: context.agentVersion,
1242
+ plan: context.plan
1243
+ }) ||
1244
+ context.runtime.requireExactClaudeAgentRow !== true ||
1245
+ context.runtime.exactClaudeAgentState !== "idle") {
1246
+ throw idleError;
1247
+ }
1248
+ try {
1249
+ return await context.bridge.resolveStoredTerminal(context.terminal.agent, context.terminal.pid, context.terminal.terminalControl, { ...context.runtime, exactClaudeAgentState: "status_dialog" });
1250
+ }
1251
+ catch {
1252
+ // All immutable process, native Session, cwd, and terminal fences are
1253
+ // identical in both attempts. Only the exact Claude agents UI state
1254
+ // differs, and the bridge must still prove a fresh profiled model
1255
+ // surface after this read-only boundary before any key is dispatched.
1256
+ throw idleError;
1257
+ }
1258
+ }
1259
+ }
1260
+ assertModelControlAgentIdentity(input) {
1261
+ if (input.terminal.agent !== "claude")
1262
+ return;
1263
+ try {
1264
+ this.assertInspectionAgentIdentity({
1265
+ ...input,
1266
+ stage: "during model control",
1267
+ expectedClaudeState: "idle"
1268
+ });
1269
+ }
1270
+ catch (idleError) {
1271
+ try {
1272
+ this.assertInspectionAgentIdentity({
1273
+ ...input,
1274
+ stage: "during model control",
1275
+ expectedClaudeState: "status_dialog"
1276
+ });
1277
+ }
1278
+ catch {
1279
+ throw idleError;
1280
+ }
1281
+ }
1282
+ }
1283
+ async assertModelControlExclusive(input) {
1284
+ if (input.terminal.agent === "codex") {
1285
+ const nativeThreadId = input.snapshot.identity?.sessionId ??
1286
+ input.snapshot.session?.binding?.native_thread_id;
1287
+ if (!nativeThreadId && !input.snapshot.identity && !input.snapshot.session) {
1288
+ // A fresh, verified-empty Codex process has no durable native Session
1289
+ // to own yet. The physical pane/process/cwd fence remains exact.
1290
+ this.assertForegroundHasNoLifecycleTransition(input.options, input.terminal);
1291
+ return;
1292
+ }
1293
+ if (!isExactNativeThreadId(nativeThreadId)) {
1294
+ throw new Error("Codex model control requires either a fresh zero-rollout pane or one exact current native Session identity");
1295
+ }
1296
+ await this.assertExclusive({
1297
+ options: input.options,
1298
+ agent: input.terminal.agent,
1299
+ currentPid: input.terminal.pid,
1300
+ nativeThreadId,
1301
+ storeDir: this.ports.state.storeDir(input.options),
1302
+ terminalControl: input.terminal.terminalControl,
1303
+ excludedManagedSessionId: input.snapshot.session?.session_id
1304
+ });
1305
+ return;
1306
+ }
1307
+ const nativeThreadId = input.snapshot.identity?.sessionId ??
1308
+ input.snapshot.session?.binding?.native_thread_id;
1309
+ if (!isExactNativeThreadId(nativeThreadId)) {
1310
+ throw new Error("model control requires one exact current native Session identity");
1311
+ }
1312
+ await this.assertExclusive({
1313
+ options: input.options,
1314
+ agent: input.terminal.agent,
1315
+ currentPid: input.terminal.pid,
1316
+ nativeThreadId,
1317
+ storeDir: this.ports.state.storeDir(input.options),
1318
+ terminalControl: input.terminal.terminalControl,
1319
+ excludedManagedSessionId: input.snapshot.session?.session_id
1320
+ });
1321
+ }
1322
+ modelOptionsResult(context, catalog) {
1323
+ return {
1324
+ terminal_id: context.terminal.conversationId,
1325
+ agent: context.terminal.agent,
1326
+ agent_version: context.agentVersion,
1327
+ behavior_profile: catalog.behaviorProfile,
1328
+ scope: catalog.scope,
1329
+ current: modelValueOutput(catalog.current),
1330
+ models: catalog.models.map((model) => ({
1331
+ id: model.id,
1332
+ label: model.label,
1333
+ reasoning_efforts: [...model.reasoningEfforts]
1334
+ })),
1335
+ catalog_fingerprint: catalog.catalogFingerprint,
1336
+ available_actions: {
1337
+ set_model: {
1338
+ tool: "agent_knock_knock_set_model",
1339
+ arguments: {
1340
+ terminal_id: context.terminal.conversationId,
1341
+ expected_binding_token: context.ordinaryBindingToken,
1342
+ expected_catalog_fingerprint: catalog.catalogFingerprint
1343
+ }
1344
+ }
1345
+ }
1346
+ };
1347
+ }
738
1348
  validateInspectionOptions(options) {
739
1349
  const inspection = required(nonBlankString(options.inspection), "--inspection is required");
740
1350
  if (inspection !== "status") {
@@ -806,6 +1416,9 @@ class NativeThreadLifecycleCliApplication {
806
1416
  };
807
1417
  }
808
1418
  async assertInitialInspectionReady(options, terminal, snapshot, bridge) {
1419
+ return (await this.observeInitialInspectionReady(options, terminal, snapshot, bridge)).runtime;
1420
+ }
1421
+ async observeInitialInspectionReady(options, terminal, snapshot, bridge) {
809
1422
  this.assertInspectionAgentIdentity({
810
1423
  options, terminal, snapshot, stage: "before native status inspection"
811
1424
  });
@@ -816,7 +1429,7 @@ class NativeThreadLifecycleCliApplication {
816
1429
  options, terminal, terminalStatus: status, session: snapshot.session
817
1430
  });
818
1431
  await this.assertInspectionComposerReady({ options, terminal });
819
- return runtime;
1432
+ return { runtime, status };
820
1433
  }
821
1434
  async submitInspection(context) {
822
1435
  try {
@@ -1020,6 +1633,14 @@ class NativeThreadLifecycleCliApplication {
1020
1633
  });
1021
1634
  }
1022
1635
  }
1636
+ function modelValueOutput(value) {
1637
+ return {
1638
+ model: value.model,
1639
+ ...(value.reasoningEffort
1640
+ ? { reasoning_effort: value.reasoningEffort }
1641
+ : {})
1642
+ };
1643
+ }
1023
1644
  function bareNativeInspectionScreenDigest(fingerprint) {
1024
1645
  return /^sha256:([0-9a-f]{64})$/u.exec(fingerprint)?.[1];
1025
1646
  }
@@ -1093,6 +1714,17 @@ function nativeInspectionComposerEmpty(agent, screen) {
1093
1714
  ? codexComposerEmpty(screen)
1094
1715
  : isExactClaudeNativeInspectionIdleComposer(String(screen ?? ""));
1095
1716
  }
1717
+ function modelControlProfileObservation(adapter, agentVersion) {
1718
+ const capability = adapter.probeModelControl?.(agentVersion);
1719
+ const plan = capability?.status === "supported"
1720
+ ? adapter.planModelControl?.(capability)
1721
+ : undefined;
1722
+ return {
1723
+ capability,
1724
+ plan,
1725
+ profile: plan ? terminalModelControlProfileForPlan(plan) : undefined
1726
+ };
1727
+ }
1096
1728
  function required(value, message) {
1097
1729
  if (value === undefined || value === "")
1098
1730
  throw new Error(message);