@scotthuang/agent-knock-knock 0.13.3 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +13 -5
  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/codex-process-incarnation.d.ts +8 -0
  14. package/dist/src/codex-process-incarnation.js +36 -0
  15. package/dist/src/codex-process-incarnation.js.map +1 -0
  16. package/dist/src/deferred-foreground-conflict-policy.d.ts +22 -0
  17. package/dist/src/deferred-foreground-conflict-policy.js +52 -0
  18. package/dist/src/deferred-foreground-conflict-policy.js.map +1 -0
  19. package/dist/src/durable-notification-kernel.d.ts +94 -0
  20. package/dist/src/durable-notification-kernel.js +68 -0
  21. package/dist/src/durable-notification-kernel.js.map +1 -0
  22. package/dist/src/herdr-socket-transport.d.ts +43 -0
  23. package/dist/src/herdr-socket-transport.js +170 -0
  24. package/dist/src/herdr-socket-transport.js.map +1 -0
  25. package/dist/src/herdr-terminal-control-provider.d.ts +2 -45
  26. package/dist/src/herdr-terminal-control-provider.js +2 -172
  27. package/dist/src/herdr-terminal-control-provider.js.map +1 -1
  28. package/dist/src/host-adapter-capabilities.d.ts +35 -0
  29. package/dist/src/host-adapter-capabilities.js +146 -0
  30. package/dist/src/host-adapter-capabilities.js.map +1 -0
  31. package/dist/src/host-adapter.d.ts +9 -3
  32. package/dist/src/host-adapter.js +33 -9
  33. package/dist/src/host-adapter.js.map +1 -1
  34. package/dist/src/host-bridge-tools.d.ts +3 -4
  35. package/dist/src/host-bridge-tools.js +49 -92
  36. package/dist/src/host-bridge-tools.js.map +1 -1
  37. package/dist/src/host-bridge.js +5 -5
  38. package/dist/src/host-bridge.js.map +1 -1
  39. package/dist/src/host-monitor-reconciliation.d.ts +6 -0
  40. package/dist/src/host-monitor-reconciliation.js +96 -0
  41. package/dist/src/host-monitor-reconciliation.js.map +1 -0
  42. package/dist/src/native-thread-lifecycle-cli-adapter.js +116 -60
  43. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  44. package/dist/src/openclaw-list-projection.d.ts +2 -0
  45. package/dist/src/openclaw-list-projection.js +3 -0
  46. package/dist/src/openclaw-list-projection.js.map +1 -0
  47. package/dist/src/openclaw-model-facing-field-policy.d.ts +2 -0
  48. package/dist/src/openclaw-model-facing-field-policy.js +3 -0
  49. package/dist/src/openclaw-model-facing-field-policy.js.map +1 -0
  50. package/dist/src/openclaw-plugin-command-adapter.d.ts +6 -29
  51. package/dist/src/openclaw-plugin-command-adapter.js +7 -2660
  52. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  53. package/dist/src/openclaw-plugin-helpers.d.ts +2 -148
  54. package/dist/src/openclaw-plugin-helpers.js +2 -1844
  55. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  56. package/dist/src/openclaw-plugin-schemas.d.ts +2 -563
  57. package/dist/src/openclaw-plugin-schemas.js +2 -572
  58. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  59. package/dist/src/openclaw-plugin-supervisor.d.ts +4 -5
  60. package/dist/src/openclaw-plugin-supervisor.js +5 -94
  61. package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
  62. package/dist/src/openclaw-plugin-value-helpers.d.ts +2 -0
  63. package/dist/src/openclaw-plugin-value-helpers.js +3 -0
  64. package/dist/src/openclaw-plugin-value-helpers.js.map +1 -0
  65. package/dist/src/openclaw-private-authority-offers.d.ts +4 -39
  66. package/dist/src/openclaw-private-authority-offers.js +3 -258
  67. package/dist/src/openclaw-private-authority-offers.js.map +1 -1
  68. package/dist/src/semantic-private-authority-offers.d.ts +41 -0
  69. package/dist/src/semantic-private-authority-offers.js +261 -0
  70. package/dist/src/semantic-private-authority-offers.js.map +1 -0
  71. package/dist/src/semantic-tool-arguments.d.ts +9 -0
  72. package/dist/src/semantic-tool-arguments.js +64 -0
  73. package/dist/src/semantic-tool-arguments.js.map +1 -0
  74. package/dist/src/semantic-tool-catalog.d.ts +48 -0
  75. package/dist/src/semantic-tool-catalog.js +105 -0
  76. package/dist/src/semantic-tool-catalog.js.map +1 -0
  77. package/dist/src/semantic-tool-command-helpers.d.ts +145 -0
  78. package/dist/src/semantic-tool-command-helpers.js +1272 -0
  79. package/dist/src/semantic-tool-command-helpers.js.map +1 -0
  80. package/dist/src/semantic-tool-list-projection.d.ts +10 -0
  81. package/dist/src/semantic-tool-list-projection.js +497 -0
  82. package/dist/src/semantic-tool-list-projection.js.map +1 -0
  83. package/dist/src/semantic-tool-model-facing-field-policy.d.ts +3 -0
  84. package/dist/src/semantic-tool-model-facing-field-policy.js +66 -0
  85. package/dist/src/semantic-tool-model-facing-field-policy.js.map +1 -0
  86. package/dist/src/semantic-tool-presentation.d.ts +37 -0
  87. package/dist/src/semantic-tool-presentation.js +680 -0
  88. package/dist/src/semantic-tool-presentation.js.map +1 -0
  89. package/dist/src/semantic-tool-private-authority.d.ts +39 -0
  90. package/dist/src/semantic-tool-private-authority.js +602 -0
  91. package/dist/src/semantic-tool-private-authority.js.map +1 -0
  92. package/dist/src/semantic-tool-relay.d.ts +16 -0
  93. package/dist/src/semantic-tool-relay.js +175 -0
  94. package/dist/src/semantic-tool-relay.js.map +1 -0
  95. package/dist/src/semantic-tool-runtime.d.ts +10 -0
  96. package/dist/src/semantic-tool-runtime.js +1157 -0
  97. package/dist/src/semantic-tool-runtime.js.map +1 -0
  98. package/dist/src/semantic-tool-schemas.d.ts +563 -0
  99. package/dist/src/semantic-tool-schemas.js +573 -0
  100. package/dist/src/semantic-tool-schemas.js.map +1 -0
  101. package/dist/src/semantic-tool-value-helpers.d.ts +5 -0
  102. package/dist/src/semantic-tool-value-helpers.js +27 -0
  103. package/dist/src/semantic-tool-value-helpers.js.map +1 -0
  104. package/dist/src/store-event-presentation.d.ts +17 -0
  105. package/dist/src/store-event-presentation.js +33 -0
  106. package/dist/src/store-event-presentation.js.map +1 -0
  107. package/dist/src/store.d.ts +3 -16
  108. package/dist/src/store.js +2 -30
  109. package/dist/src/store.js.map +1 -1
  110. package/dist/src/terminal-action-projection.d.ts +2 -98
  111. package/dist/src/terminal-action-projection.js +13 -215
  112. package/dist/src/terminal-action-projection.js.map +1 -1
  113. package/dist/src/terminal-agent-bridge.d.ts +14 -340
  114. package/dist/src/terminal-agent-bridge.js +155 -3226
  115. package/dist/src/terminal-agent-bridge.js.map +1 -1
  116. package/dist/src/terminal-approval-cli-adapter.d.ts +17 -0
  117. package/dist/src/terminal-approval-cli-adapter.js +921 -0
  118. package/dist/src/terminal-approval-cli-adapter.js.map +1 -0
  119. package/dist/src/terminal-command-cli-adapter.d.ts +2 -429
  120. package/dist/src/terminal-command-cli-adapter.js +344 -6061
  121. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  122. package/dist/src/terminal-command-cli-ports.d.ts +443 -0
  123. package/dist/src/terminal-command-cli-ports.js +2 -0
  124. package/dist/src/terminal-command-cli-ports.js.map +1 -0
  125. package/dist/src/terminal-command-dispatch-preparation.d.ts +81 -0
  126. package/dist/src/terminal-command-dispatch-preparation.js +209 -0
  127. package/dist/src/terminal-command-dispatch-preparation.js.map +1 -0
  128. package/dist/src/terminal-command-dispatch-transport.d.ts +35 -0
  129. package/dist/src/terminal-command-dispatch-transport.js +859 -0
  130. package/dist/src/terminal-command-dispatch-transport.js.map +1 -0
  131. package/dist/src/terminal-command-foreground-proof.d.ts +32 -0
  132. package/dist/src/terminal-command-foreground-proof.js +72 -0
  133. package/dist/src/terminal-command-foreground-proof.js.map +1 -0
  134. package/dist/src/terminal-command-send-preflight.d.ts +41 -0
  135. package/dist/src/terminal-command-send-preflight.js +86 -0
  136. package/dist/src/terminal-command-send-preflight.js.map +1 -0
  137. package/dist/src/terminal-composer-classifier.d.ts +55 -0
  138. package/dist/src/terminal-composer-classifier.js +638 -0
  139. package/dist/src/terminal-composer-classifier.js.map +1 -0
  140. package/dist/src/terminal-dispatch-recovery-authority.d.ts +56 -0
  141. package/dist/src/terminal-dispatch-recovery-authority.js +251 -0
  142. package/dist/src/terminal-dispatch-recovery-authority.js.map +1 -0
  143. package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +1 -9
  144. package/dist/src/terminal-dispatch-recovery-cli-adapter.js +5 -240
  145. package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
  146. package/dist/src/terminal-handoff-cli-adapter.d.ts +1 -11
  147. package/dist/src/terminal-handoff-cli-adapter.js +2 -39
  148. package/dist/src/terminal-handoff-cli-adapter.js.map +1 -1
  149. package/dist/src/terminal-human-explicit-send-cli-adapter.d.ts +25 -0
  150. package/dist/src/terminal-human-explicit-send-cli-adapter.js +903 -0
  151. package/dist/src/terminal-human-explicit-send-cli-adapter.js.map +1 -0
  152. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -6
  153. package/dist/src/terminal-identity-authority-cli-adapter.js +3 -33
  154. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
  155. package/dist/src/terminal-interaction-cli-adapter.js +209 -210
  156. package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
  157. package/dist/src/terminal-interaction-core.d.ts +0 -2
  158. package/dist/src/terminal-interaction-core.js +4 -3
  159. package/dist/src/terminal-interaction-core.js.map +1 -1
  160. package/dist/src/terminal-interaction-protocol.d.ts +21 -0
  161. package/dist/src/terminal-interaction-protocol.js +74 -0
  162. package/dist/src/terminal-interaction-protocol.js.map +1 -1
  163. package/dist/src/terminal-interaction-response-bridge.d.ts +126 -0
  164. package/dist/src/terminal-interaction-response-bridge.js +579 -0
  165. package/dist/src/terminal-interaction-response-bridge.js.map +1 -0
  166. package/dist/src/terminal-interaction-response-transaction.d.ts +44 -0
  167. package/dist/src/terminal-interaction-response-transaction.js +56 -0
  168. package/dist/src/terminal-interaction-response-transaction.js.map +1 -0
  169. package/dist/src/terminal-list-action-policy.d.ts +43 -0
  170. package/dist/src/terminal-list-action-policy.js +164 -0
  171. package/dist/src/terminal-list-action-policy.js.map +1 -0
  172. package/dist/src/terminal-list-cli-adapter.d.ts +6 -36
  173. package/dist/src/terminal-list-cli-adapter.js +241 -1946
  174. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  175. package/dist/src/terminal-list-facts.d.ts +129 -0
  176. package/dist/src/terminal-list-facts.js +179 -0
  177. package/dist/src/terminal-list-facts.js.map +1 -0
  178. package/dist/src/terminal-list-inventory.d.ts +59 -0
  179. package/dist/src/terminal-list-inventory.js +194 -0
  180. package/dist/src/terminal-list-inventory.js.map +1 -0
  181. package/dist/src/terminal-list-ownership-service.d.ts +118 -0
  182. package/dist/src/terminal-list-ownership-service.js +1003 -0
  183. package/dist/src/terminal-list-ownership-service.js.map +1 -0
  184. package/dist/src/terminal-list-projection-service.d.ts +49 -0
  185. package/dist/src/terminal-list-projection-service.js +407 -0
  186. package/dist/src/terminal-list-projection-service.js.map +1 -0
  187. package/dist/src/terminal-list-renderer.d.ts +11 -11
  188. package/dist/src/terminal-list-renderer.js +168 -119
  189. package/dist/src/terminal-list-renderer.js.map +1 -1
  190. package/dist/src/terminal-managed-send-cli-adapter.d.ts +33 -0
  191. package/dist/src/terminal-managed-send-cli-adapter.js +994 -0
  192. package/dist/src/terminal-managed-send-cli-adapter.js.map +1 -0
  193. package/dist/src/terminal-managed-turn-list-action-policy.d.ts +47 -0
  194. package/dist/src/terminal-managed-turn-list-action-policy.js +64 -0
  195. package/dist/src/terminal-managed-turn-list-action-policy.js.map +1 -0
  196. package/dist/src/terminal-model-control-action-policy.d.ts +60 -0
  197. package/dist/src/terminal-model-control-action-policy.js +96 -0
  198. package/dist/src/terminal-model-control-action-policy.js.map +1 -0
  199. package/dist/src/terminal-model-control-availability.d.ts +68 -0
  200. package/dist/src/terminal-model-control-availability.js +157 -0
  201. package/dist/src/terminal-model-control-availability.js.map +1 -0
  202. package/dist/src/terminal-model-control-bridge.d.ts +48 -0
  203. package/dist/src/terminal-model-control-bridge.js +259 -0
  204. package/dist/src/terminal-model-control-bridge.js.map +1 -0
  205. package/dist/src/terminal-model-control-profile.d.ts +55 -0
  206. package/dist/src/terminal-model-control-profile.js +137 -0
  207. package/dist/src/terminal-model-control-profile.js.map +1 -0
  208. package/dist/src/terminal-model-control-subject.d.ts +34 -0
  209. package/dist/src/terminal-model-control-subject.js +58 -0
  210. package/dist/src/terminal-model-control-subject.js.map +1 -0
  211. package/dist/src/terminal-model-control-transaction.d.ts +19 -0
  212. package/dist/src/terminal-model-control-transaction.js +39 -0
  213. package/dist/src/terminal-model-control-transaction.js.map +1 -0
  214. package/dist/src/terminal-model-control.d.ts +37 -18
  215. package/dist/src/terminal-model-control.js +285 -194
  216. package/dist/src/terminal-model-control.js.map +1 -1
  217. package/dist/src/terminal-monitor-application-service.js +50 -17
  218. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  219. package/dist/src/terminal-native-inspection-bridge.d.ts +211 -0
  220. package/dist/src/terminal-native-inspection-bridge.js +1127 -0
  221. package/dist/src/terminal-native-inspection-bridge.js.map +1 -0
  222. package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -2
  223. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
  224. package/dist/src/terminal-submission-retry-application.d.ts +24 -0
  225. package/dist/src/terminal-submission-retry-application.js +402 -0
  226. package/dist/src/terminal-submission-retry-application.js.map +1 -0
  227. package/dist/src/terminal-submission-retry-ports.d.ts +30 -0
  228. package/dist/src/terminal-submission-retry-ports.js +2 -0
  229. package/dist/src/terminal-submission-retry-ports.js.map +1 -0
  230. package/dist/src/terminal-submission-retry-reconciliation.d.ts +236 -0
  231. package/dist/src/terminal-submission-retry-reconciliation.js +1130 -0
  232. package/dist/src/terminal-submission-retry-reconciliation.js.map +1 -0
  233. package/dist/src/terminal-submission-retry-transport.d.ts +58 -0
  234. package/dist/src/terminal-submission-retry-transport.js +675 -0
  235. package/dist/src/terminal-submission-retry-transport.js.map +1 -0
  236. package/dist/src/terminal-text-submission-bridge.d.ts +200 -0
  237. package/dist/src/terminal-text-submission-bridge.js +632 -0
  238. package/dist/src/terminal-text-submission-bridge.js.map +1 -0
  239. package/dist/src/terminal-user-explicit-send-policy.d.ts +29 -0
  240. package/dist/src/terminal-user-explicit-send-policy.js +46 -0
  241. package/dist/src/terminal-user-explicit-send-policy.js.map +1 -0
  242. package/dist/src/terminal-watch-cli-adapter.js +91 -143
  243. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  244. package/dist/src/terminal-watch-codec.d.ts +9 -0
  245. package/dist/src/terminal-watch-codec.js +1051 -0
  246. package/dist/src/terminal-watch-codec.js.map +1 -0
  247. package/dist/src/terminal-watch-interaction-response-store.d.ts +19 -0
  248. package/dist/src/terminal-watch-interaction-response-store.js +87 -0
  249. package/dist/src/terminal-watch-interaction-response-store.js.map +1 -0
  250. package/dist/src/terminal-watch-record.d.ts +319 -0
  251. package/dist/src/terminal-watch-record.js +742 -0
  252. package/dist/src/terminal-watch-record.js.map +1 -0
  253. package/dist/src/terminal-watch-service.js +48 -27
  254. package/dist/src/terminal-watch-service.js.map +1 -1
  255. package/dist/src/terminal-watch-store.d.ts +3 -279
  256. package/dist/src/terminal-watch-store.js +5 -1779
  257. package/dist/src/terminal-watch-store.js.map +1 -1
  258. package/package.json +7 -2
  259. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +1 -1
@@ -0,0 +1,1003 @@
1
+ import fs from "node:fs";
2
+ import { listDeferredForegroundTransfers } from "./deferred-foreground-transfer.js";
3
+ import { isFinalDeferredForegroundTransferStatus } from "./deferred-foreground-transfer-policy.js";
4
+ import { humanObservedHandoffBindingToken, isExactNativeThreadId, managedSessionBindingToken, managedSessionRevision, unmanagedTerminalBindingToken } from "./managed-session.js";
5
+ import { executorForConversation, isSessionSendBlockingStatus, isTerminalDispatchOwnerReleasedStatus, sessionIdForConversation, turnIdForConversation } from "./protocol.js";
6
+ import { listManagedSessions, loadNativeThreadTransition, nativeThreadTransitionsDir } from "./session-store.js";
7
+ import { listConversations } from "./store.js";
8
+ import { applySessionAuthorityToDispatch, compareManagedConversationRecency, decideLocalTerminalDispatchOwnership, decideTerminalSessionAuthorityConflict, managedTurnNeedsAttention as terminalManagedTurnNeedsAttention, projectHandoffDecision, projectReconcileBindingAction, projectTerminalDispatchConflict } from "./terminal-action-projection.js";
9
+ import { decideManagedBindingConflict, deferredCodexForegroundBindingToken, exactBoundCodexSendSource, isCompleteNativeRollout, nativeAgentIdentityMatchesTurn, processIncarnationRelationship, terminalControlAliasMatches, terminalControlsShareIncarnation, verifiedEmptyCodexHandoffToken } from "./terminal-authority-policy.js";
10
+ import { decideTerminalBindingMatch, terminalObservationFromListEntry } from "./terminal-binding-authority.js";
11
+ import { decideTerminalScopedCodexApprovalAuthority, terminalScopedCodexApprovalPromptSnapshot } from "./terminal-scoped-approval-authority.js";
12
+ import { sameCanonicalStatePath } from "./terminal-dispatch-ledger-codec.js";
13
+ import * as dispatch from "./terminal-dispatch-policy.js";
14
+ import { actionsForManagedSessionBinding, readOnlyListActions, withoutInspectionActionsDuringNativeTransition } from "./terminal-list-renderer.js";
15
+ import { hasCanonicalTerminalEndpoint, terminalControlEvidenceMatches } from "./terminal-control-ref.js";
16
+ import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
17
+ function observeBindingAuthority(ports, listedTerminal, context) {
18
+ const { storeDir, managedSessions, allConversations, displayedConversations, mutationsAllowed, nonterminalDeferredTransfers } = context;
19
+ const { _automated_input_composer_ready: automatedInputComposerReady, _user_explicit_composer_ready: _userExplicitComposerReady, _codex_open_root_rollout_inventory: codexOpenRootRolloutInventoryValue, _codex_latent_clear_resume: codexLatentClearResumeValue, _terminal_user_explicit_send_action: terminalUserExplicitSendAction, _terminal_status_snapshot: _terminalStatusSnapshot, _native_identity_authority: nativeIdentityAuthorityValue, ...publicTerminal } = listedTerminal;
20
+ const terminal = terminalIdentityAuthorityView(listedTerminal, nativeIdentityAuthorityValue);
21
+ const codexOpenRootRolloutInventory = isRecord(codexOpenRootRolloutInventoryValue)
22
+ ? codexOpenRootRolloutInventoryValue
23
+ : undefined;
24
+ const terminalControl = isRecord(terminal.terminal_control)
25
+ ? terminal.terminal_control
26
+ : undefined;
27
+ const terminalHasNonterminalDeferredTransfer = Boolean(terminalControl && nonterminalDeferredTransfers.some((transfer) => transfer.terminal_id === String(terminal.id) &&
28
+ transfer.process_pid === Number(terminal.pid) &&
29
+ terminalControlEvidenceMatches(transfer.terminal_endpoint, terminalControl)));
30
+ const allRelated = terminalControl
31
+ ? allConversations.filter((conversation) => terminalControlsShareIncarnation(terminalControlForManagedConversation(ports, conversation), terminalControl))
32
+ : [];
33
+ const displayedRelated = terminalControl
34
+ ? displayedConversations.filter((conversation) => terminalControlsShareIncarnation(terminalControlForManagedConversation(ports, conversation), terminalControl))
35
+ : [];
36
+ const relatedSessions = terminalControl
37
+ ? managedSessions.filter((session) => terminalControlsShareIncarnation(session.binding?.terminal_control, terminalControl))
38
+ : [];
39
+ const matchingSessions = relatedSessions.filter((session) => managedSessionMatchesLiveTerminalEntry(ports, session, terminal, storeDir));
40
+ const conflictingBoundSessionClaims = relatedSessions.flatMap((session) => {
41
+ const kind = managedBindingConflictKindForLiveTerminalEntry(ports, {
42
+ storeDir,
43
+ session,
44
+ terminal
45
+ });
46
+ return kind && kind !== "stale_process_incarnation"
47
+ ? [{ session, kind }]
48
+ : [];
49
+ });
50
+ const unresolvedSessionClaims = relatedSessions.filter((session) => ["transitioning", "quarantined"].includes(session.status) &&
51
+ managedSessionClaimsLiveTerminalEntry(session, terminal));
52
+ const sessionAuthorityConflict = decideTerminalSessionAuthorityConflict({
53
+ unresolvedSessionClaims,
54
+ conflictingBoundSessionClaims,
55
+ matchingSessions
56
+ });
57
+ const authoritativeSession = matchingSessions[0];
58
+ const discoveredOwnership = terminalControl
59
+ ? terminalDispatchOwnership(ports, terminalControl)
60
+ : { state: "none" };
61
+ const localOwnership = discoveredOwnership.state === "current"
62
+ ? localTerminalDispatchOwnership(ports, discoveredOwnership.conversation, allRelated, terminal)
63
+ : discoveredOwnership;
64
+ const dispatchOwnerMismatch = !sessionAuthorityConflict &&
65
+ localOwnership.state === "current" &&
66
+ authoritativeSession &&
67
+ sessionIdForConversation(localOwnership.conversation) !==
68
+ authoritativeSession.session_id
69
+ ? {
70
+ ownerSessionId: sessionIdForConversation(discoveredOwnership.state === "current"
71
+ ? discoveredOwnership.conversation
72
+ : localOwnership.conversation)
73
+ }
74
+ : undefined;
75
+ const ownership = applySessionAuthorityToDispatch({
76
+ localOwnership,
77
+ sessionAuthorityConflict,
78
+ authoritativeSession,
79
+ dispatchOwnerMismatch
80
+ });
81
+ const discoveredRawActions = isRecord(terminal.available_actions)
82
+ ? terminal.available_actions
83
+ : {};
84
+ const rawActions = mutationsAllowed
85
+ ? discoveredRawActions
86
+ : readOnlyListActions(discoveredRawActions);
87
+ const rawSendAction = isRecord(rawActions.send)
88
+ ? rawActions.send
89
+ : {};
90
+ const bindingAwareRawActions = authoritativeSession
91
+ ? actionsForManagedSessionBinding(rawActions, authoritativeSession)
92
+ : rawActions;
93
+ const sessionAwareRawActionsBase = authoritativeSession &&
94
+ managedSessionHasUnresolvedNativeTransition(storeDir, authoritativeSession)
95
+ ? withoutInspectionActionsDuringNativeTransition(bindingAwareRawActions)
96
+ : bindingAwareRawActions;
97
+ const sessionAwareRawActions = terminalHasNonterminalDeferredTransfer
98
+ ? readOnlyListActions(sessionAwareRawActionsBase)
99
+ : sessionAwareRawActionsBase;
100
+ const soleBindingConflict = conflictingBoundSessionClaims.length === 1
101
+ ? conflictingBoundSessionClaims[0]
102
+ : undefined;
103
+ const externalHandoffDetected = conflictingBoundSessionClaims.some(({ kind }) => kind === "live_external_thread_change");
104
+ const conflictingSessionRevision = Number(soleBindingConflict?.session.revision);
105
+ const conflictingSessionTurns = soleBindingConflict
106
+ ? ports.managedTurnsForSession(storeDir, soleBindingConflict.session.session_id)
107
+ : [];
108
+ const expectedTerminalToken = stringValue(terminal.lifecycle_binding_token);
109
+ const externalHandoffNativeThreadId = stringValue(terminal.native_agent_status_card_session_id) ?? stringValue(terminal.native_agent_session_id);
110
+ const resolvedNativeThreadId = stringValue(terminal.native_agent_session_id);
111
+ const externalHandoffTerminalToken = terminalControl &&
112
+ externalHandoffNativeThreadId &&
113
+ isExactNativeThreadId(externalHandoffNativeThreadId)
114
+ ? unmanagedTerminalBindingToken({
115
+ terminalId: stringValue(terminal.id),
116
+ terminalControl,
117
+ agent: terminal.agent,
118
+ pid: Number(terminal.pid),
119
+ workspace: terminal.workspace ?? terminal.cwd ??
120
+ ports.currentWorkingDirectory(),
121
+ nativeThreadId: externalHandoffNativeThreadId,
122
+ processUuid: stringValue(terminal.native_agent_process_uuid),
123
+ processBirth: stringValue(terminal.native_agent_process_birth),
124
+ rollout: resolvedNativeThreadId === externalHandoffNativeThreadId &&
125
+ isRecord(terminal.native_agent_rollout)
126
+ ? terminal.native_agent_rollout
127
+ : undefined
128
+ })
129
+ : undefined;
130
+ const externalHandoffTarget = soleBindingConflict?.kind === "live_external_thread_change" &&
131
+ externalHandoffNativeThreadId &&
132
+ isExactNativeThreadId(externalHandoffNativeThreadId)
133
+ ? ports.observedHandoffTargetResolution({
134
+ storeDir,
135
+ agent: terminal.agent,
136
+ workspace: terminal.workspace ?? terminal.cwd ??
137
+ ports.currentWorkingDirectory(),
138
+ nativeThreadId: externalHandoffNativeThreadId.toLowerCase(),
139
+ sourceSessionId: soleBindingConflict.session.session_id
140
+ })
141
+ : undefined;
142
+ const externalHandoffSnapshotToken = externalHandoffTerminalToken &&
143
+ soleBindingConflict?.kind === "live_external_thread_change" &&
144
+ externalHandoffTarget?.status === "eligible"
145
+ ? humanObservedHandoffBindingToken({
146
+ terminal_token: externalHandoffTerminalToken,
147
+ source_session_id: soleBindingConflict.session.session_id,
148
+ source_revision: managedSessionRevision(soleBindingConflict.session),
149
+ source_binding_token: managedSessionBindingToken(soleBindingConflict.session),
150
+ target: externalHandoffTarget.snapshot
151
+ })
152
+ : undefined;
153
+ const blockingHandoffTurns = conflictingSessionTurns.filter((turn) => isSessionSendBlockingStatus(turn.status));
154
+ const terminalBlockingTurns = terminalControl
155
+ ? terminalIncarnationBlockingTurns(ports, storeDir, terminalControl, allRelated)
156
+ : [];
157
+ const externalHandoffSourceSessionIds = new Set(conflictingBoundSessionClaims
158
+ .filter(({ kind }) => kind === "live_external_thread_change")
159
+ .map(({ session }) => session.session_id));
160
+ const handoffSourceBlockingTurns = terminalBlockingTurns.filter((turn) => externalHandoffSourceSessionIds.has(sessionIdForConversation(turn)));
161
+ const nativeIdentityObservation = isRecord(terminal.native_agent_identity_observation)
162
+ ? terminal.native_agent_identity_observation
163
+ : undefined;
164
+ const codexProcessUuid = stringValue(terminal.native_agent_process_uuid);
165
+ const codexProcessBirth = stringValue(terminal.native_agent_process_birth);
166
+ const codexWorkspace = stringValue(terminal.workspace ?? terminal.cwd);
167
+ const codexSendAuthorityContext = terminalControl
168
+ ? {
169
+ terminalId: String(terminal.id),
170
+ terminalControl,
171
+ pid: Number(terminal.pid),
172
+ workspace: codexWorkspace,
173
+ liveProcessUuid: codexProcessUuid,
174
+ liveProcessBirth: codexProcessBirth
175
+ }
176
+ : undefined;
177
+ return {
178
+ authorityTerminal: terminal,
179
+ automatedInputComposerReady,
180
+ codexOpenRootRolloutInventory,
181
+ codexLatentClearResumeValue,
182
+ terminalUserExplicitSendAction,
183
+ publicTerminal,
184
+ terminalControl,
185
+ terminalHasNonterminalDeferredTransfer,
186
+ allRelated,
187
+ displayedRelated,
188
+ relatedSessions,
189
+ matchingSessions,
190
+ conflictingBoundSessionClaims,
191
+ unresolvedSessionClaims,
192
+ sessionAuthorityConflict,
193
+ authoritativeSession,
194
+ discoveredOwnership,
195
+ ownership,
196
+ rawActions,
197
+ rawSendAction,
198
+ sessionAwareRawActions,
199
+ soleBindingConflict,
200
+ externalHandoffDetected,
201
+ conflictingSessionRevision,
202
+ conflictingSessionTurns,
203
+ expectedTerminalToken,
204
+ externalHandoffNativeThreadId,
205
+ externalHandoffTarget,
206
+ externalHandoffSnapshotToken,
207
+ blockingHandoffTurns,
208
+ terminalBlockingTurns,
209
+ handoffSourceBlockingTurns,
210
+ nativeIdentityObservation,
211
+ codexProcessUuid,
212
+ codexProcessBirth,
213
+ codexWorkspace,
214
+ codexSendAuthorityContext
215
+ };
216
+ }
217
+ function terminalIdentityAuthorityView(terminal, authorityValue) {
218
+ if (!isRecord(authorityValue)) {
219
+ return terminal;
220
+ }
221
+ const observation = isRecord(authorityValue.observation)
222
+ ? authorityValue.observation
223
+ : { status: "not_observed" };
224
+ const identity = isRecord(authorityValue.identity)
225
+ ? authorityValue.identity
226
+ : undefined;
227
+ return {
228
+ ...terminal,
229
+ native_agent_session_id: stringValue(identity?.sessionId),
230
+ native_agent_rollout: isRecord(identity?.rollout)
231
+ ? identity.rollout
232
+ : undefined,
233
+ native_agent_identity_observation: observation,
234
+ lifecycle_binding_token: stringValue(authorityValue.lifecycle_binding_token) ??
235
+ terminal.lifecycle_binding_token
236
+ };
237
+ }
238
+ function observeVerifiedEmptyTerminalAuthority(ports, terminal, context, binding) {
239
+ const { storeDir, terminals, mutationsAllowed } = context;
240
+ const { automatedInputComposerReady, terminalControl, matchingSessions, unresolvedSessionClaims, discoveredOwnership, rawActions, soleBindingConflict, blockingHandoffTurns, terminalBlockingTurns, nativeIdentityObservation, codexProcessUuid, codexProcessBirth, codexWorkspace, codexSendAuthorityContext } = binding;
241
+ const verifiedEmptySourceNativeThreadId = stringValue(soleBindingConflict?.session.binding?.native_thread_id)?.toLowerCase();
242
+ const verifiedEmptySourceActiveElsewhere = Boolean(verifiedEmptySourceNativeThreadId &&
243
+ terminals.some((candidate) => stringValue(candidate.id) !== stringValue(terminal.id) &&
244
+ candidate.agent === "codex" &&
245
+ stringValue(candidate.native_agent_session_id)?.toLowerCase() ===
246
+ verifiedEmptySourceNativeThreadId));
247
+ const verifiedEmptyRawSendAction = isRecord(rawActions.send)
248
+ ? rawActions.send
249
+ : {
250
+ tool: "agent_knock_knock_send",
251
+ arguments: { selector: stringValue(terminal.id) },
252
+ missing_required: ["request"]
253
+ };
254
+ const verifiedEmptyCodexHandoffEligible = Boolean(mutationsAllowed &&
255
+ terminal.agent === "codex" &&
256
+ terminalControl &&
257
+ discoveredOwnership.state === "none" &&
258
+ unresolvedSessionClaims.length === 0 &&
259
+ matchingSessions.length === 0 &&
260
+ soleBindingConflict?.kind === "unverifiable" &&
261
+ nativeIdentityObservation?.status === "verified_absent" &&
262
+ codexSendAuthorityContext &&
263
+ codexProcessUuid &&
264
+ codexProcessBirth &&
265
+ codexWorkspace &&
266
+ exactBoundCodexSendSource({
267
+ kind: "verified_empty",
268
+ sourceSession: soleBindingConflict.session,
269
+ context: codexSendAuthorityContext
270
+ }) &&
271
+ ["idle", "unknown"].includes(String(terminal.activity_state)) &&
272
+ automatedInputComposerReady === true &&
273
+ !(isRecord(terminal.approval_state) &&
274
+ terminal.approval_state.blocked === true) &&
275
+ blockingHandoffTurns.length === 0 &&
276
+ terminalBlockingTurns.length === 0 &&
277
+ !managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session) &&
278
+ !verifiedEmptySourceActiveElsewhere &&
279
+ terminal.orphaned_terminal_dispatch === undefined &&
280
+ terminalControl.capabilities.includes("send_keys") &&
281
+ terminalControl.capabilities.includes("screen_status"));
282
+ const verifiedEmptyCodexSnapshotToken = verifiedEmptyCodexHandoffEligible &&
283
+ codexSendAuthorityContext &&
284
+ soleBindingConflict &&
285
+ codexProcessUuid &&
286
+ codexProcessBirth &&
287
+ codexWorkspace
288
+ ? verifiedEmptyCodexHandoffToken({
289
+ terminalId: String(terminal.id),
290
+ terminalControl: codexSendAuthorityContext.terminalControl,
291
+ pid: Number(terminal.pid),
292
+ workspace: codexWorkspace,
293
+ processUuid: codexProcessUuid,
294
+ processBirth: codexProcessBirth,
295
+ sourceSession: soleBindingConflict.session
296
+ })
297
+ : undefined;
298
+ return {
299
+ ...binding,
300
+ verifiedEmptyRawSendAction,
301
+ verifiedEmptyCodexSnapshotToken
302
+ };
303
+ }
304
+ function observeDeferredSourceAuthority(ports, terminal, context, observation) {
305
+ const { terminals } = context;
306
+ const { codexLatentClearResumeValue, codexOpenRootRolloutInventory, authoritativeSession, matchingSessions, conflictingBoundSessionClaims, unresolvedSessionClaims, soleBindingConflict, nativeIdentityObservation } = observation;
307
+ const deferredCodexCandidateInventory = codexOpenRootRolloutInventory &&
308
+ codexOpenRootRolloutInventory.roots.length > 0
309
+ ? codexOpenRootRolloutInventory
310
+ : undefined;
311
+ const abandonedConflictSource = !authoritativeSession &&
312
+ soleBindingConflict?.kind === "unverifiable" &&
313
+ matchingSessions.length === 0 &&
314
+ conflictingBoundSessionClaims.length === 1 &&
315
+ unresolvedSessionClaims.length === 0 &&
316
+ terminal.agent === "codex"
317
+ ? soleBindingConflict.session
318
+ : undefined;
319
+ const deferredCodexSource = (authoritativeSession ??
320
+ abandonedConflictSource) &&
321
+ terminal.agent === "codex" &&
322
+ (nativeIdentityObservation?.status === "verified_absent" ||
323
+ deferredCodexCandidateInventory !== undefined)
324
+ ? authoritativeSession ?? abandonedConflictSource
325
+ : undefined;
326
+ const deferredCodexSourceNativeThreadId = stringValue(deferredCodexSource?.binding?.native_thread_id)?.toLowerCase();
327
+ const deferredCodexLatentClearResumeFingerprint = isRecord(codexLatentClearResumeValue) &&
328
+ stringValue(codexLatentClearResumeValue.source_native_thread_id)?.toLowerCase() === deferredCodexSourceNativeThreadId
329
+ ? stringValue(codexLatentClearResumeValue.fingerprint)
330
+ : undefined;
331
+ if (deferredCodexLatentClearResumeFingerprint) {
332
+ ports.runtimeLog("info", "terminal_codex_latent_clear_hint_observed", {
333
+ terminal_id: String(terminal.id),
334
+ source_session_id: deferredCodexSource?.session_id,
335
+ source_native_thread_id: deferredCodexSourceNativeThreadId
336
+ });
337
+ }
338
+ const deferredCodexSourceActiveElsewhere = Boolean(deferredCodexSourceNativeThreadId &&
339
+ terminals.some((candidate) => stringValue(candidate.id) !== stringValue(terminal.id) &&
340
+ candidate.agent === "codex" &&
341
+ stringValue(candidate.native_agent_session_id)?.toLowerCase() ===
342
+ deferredCodexSourceNativeThreadId));
343
+ return {
344
+ ...observation,
345
+ deferredCodexCandidateInventory,
346
+ abandonedConflictSource,
347
+ deferredCodexSource,
348
+ deferredCodexSourceActiveElsewhere
349
+ };
350
+ }
351
+ function observeDeferredTerminalAuthority(ports, terminal, context, observation) {
352
+ const { storeDir, mutationsAllowed } = context;
353
+ const { automatedInputComposerReady, terminalControl, terminalHasNonterminalDeferredTransfer, matchingSessions, conflictingBoundSessionClaims, unresolvedSessionClaims, discoveredOwnership, terminalBlockingTurns, codexProcessUuid, codexProcessBirth, codexWorkspace, codexSendAuthorityContext, deferredCodexCandidateInventory, abandonedConflictSource, deferredCodexSource, deferredCodexSourceActiveElsewhere } = observation;
354
+ const deferredCodexAuthority = codexSendAuthorityContext
355
+ ? ports.observeDeferredCodexAuthority({
356
+ mode: "list",
357
+ storeDir,
358
+ context: codexSendAuthorityContext,
359
+ sourceSession: deferredCodexSource,
360
+ candidateInventory: deferredCodexCandidateInventory,
361
+ abandonment: abandonedConflictSource
362
+ ? "missing_rollout"
363
+ : "never",
364
+ requireUnclaimedCandidate: true
365
+ })
366
+ : undefined;
367
+ const deferredCodexSourceRolloutAuthority = deferredCodexAuthority?.sourceRolloutAuthority ?? "present";
368
+ const deferredCodexDispatchSnapshot = deferredCodexAuthority?.dispatchSnapshot;
369
+ const deferredCodexForegroundEligible = Boolean(mutationsAllowed &&
370
+ !terminalHasNonterminalDeferredTransfer &&
371
+ deferredCodexSource &&
372
+ terminalControl &&
373
+ hasCanonicalTerminalEndpoint(terminalControl) &&
374
+ discoveredOwnership.state === "none" &&
375
+ unresolvedSessionClaims.length === 0 &&
376
+ ((matchingSessions.length === 1 &&
377
+ conflictingBoundSessionClaims.length === 0) ||
378
+ (deferredCodexSourceRolloutAuthority ===
379
+ "explicitly_abandoned_predecessor" &&
380
+ matchingSessions.length === 0 &&
381
+ conflictingBoundSessionClaims.length === 1)) &&
382
+ codexProcessUuid &&
383
+ codexProcessBirth &&
384
+ codexWorkspace &&
385
+ deferredCodexAuthority?.exactSource &&
386
+ terminalBlockingTurns.length === 0 &&
387
+ terminal.orphaned_terminal_dispatch === undefined &&
388
+ deferredCodexDispatchSnapshot &&
389
+ ["idle", "unknown"].includes(String(terminal.activity_state)) &&
390
+ automatedInputComposerReady === true &&
391
+ !(isRecord(terminal.approval_state) &&
392
+ terminal.approval_state.blocked === true) &&
393
+ !deferredCodexSourceActiveElsewhere &&
394
+ terminalControl.capabilities.includes("send_keys") &&
395
+ terminalControl.capabilities.includes("screen_status"));
396
+ const deferredCodexForegroundToken = deferredCodexForegroundEligible &&
397
+ deferredCodexSource &&
398
+ terminalControl &&
399
+ codexProcessUuid &&
400
+ codexProcessBirth &&
401
+ codexWorkspace &&
402
+ deferredCodexDispatchSnapshot
403
+ ? deferredCodexForegroundBindingToken({
404
+ terminalId: String(terminal.id),
405
+ terminalControl,
406
+ pid: Number(terminal.pid),
407
+ workspace: codexWorkspace,
408
+ processUuid: codexProcessUuid,
409
+ processBirth: codexProcessBirth,
410
+ sourceSession: deferredCodexSource,
411
+ dispatchSnapshot: deferredCodexDispatchSnapshot,
412
+ sourceTurnHistory: deferredCodexAuthority?.sourceTurnHistory,
413
+ sourceRolloutAuthority: deferredCodexSourceRolloutAuthority,
414
+ sourceAbandonmentFingerprint: deferredCodexAuthority?.sourceAbandonmentFingerprint,
415
+ ...(deferredCodexCandidateInventory
416
+ ? { candidateInventory: deferredCodexCandidateInventory }
417
+ : {})
418
+ })
419
+ : undefined;
420
+ return {
421
+ ...observation,
422
+ deferredCodexSourceRolloutAuthority,
423
+ deferredCodexForegroundToken
424
+ };
425
+ }
426
+ function observeTerminalHandoffAuthority(ports, terminal, context, observation) {
427
+ const { storeDir, mutationsAllowed } = context;
428
+ const { automatedInputComposerReady, terminalControl, matchingSessions, unresolvedSessionClaims, discoveredOwnership, rawActions, soleBindingConflict, conflictingSessionRevision, conflictingSessionTurns, expectedTerminalToken, externalHandoffNativeThreadId, externalHandoffTarget, externalHandoffSnapshotToken, blockingHandoffTurns, terminalBlockingTurns, handoffSourceBlockingTurns } = observation;
429
+ const reconcileBindingAction = mutationsAllowed &&
430
+ discoveredOwnership.state === "none" &&
431
+ unresolvedSessionClaims.length === 0 &&
432
+ matchingSessions.length === 0 &&
433
+ soleBindingConflict &&
434
+ soleBindingConflict.kind !== "unverifiable" &&
435
+ Number.isSafeInteger(conflictingSessionRevision) &&
436
+ conflictingSessionRevision > 0 &&
437
+ expectedTerminalToken &&
438
+ terminal.activity_state === "idle" &&
439
+ !(isRecord(terminal.approval_state) &&
440
+ terminal.approval_state.blocked === true) &&
441
+ !conflictingSessionTurns.some((turn) => isSessionSendBlockingStatus(turn.status)) &&
442
+ terminalBlockingTurns.length === 0 &&
443
+ !managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session)
444
+ ? projectReconcileBindingAction({
445
+ terminalId: stringValue(terminal.id),
446
+ conflictingSession: soleBindingConflict.session,
447
+ conflictingSessionRevision,
448
+ expectedTerminalToken
449
+ })
450
+ : undefined;
451
+ const externalHandoffAdoptable = Boolean(mutationsAllowed &&
452
+ discoveredOwnership.state === "none" &&
453
+ unresolvedSessionClaims.length === 0 &&
454
+ soleBindingConflict?.kind === "live_external_thread_change" &&
455
+ terminal.activity_state === "idle" &&
456
+ automatedInputComposerReady === true &&
457
+ !(isRecord(terminal.approval_state) &&
458
+ terminal.approval_state.blocked === true) &&
459
+ !conflictingSessionTurns.some((turn) => isSessionSendBlockingStatus(turn.status)) &&
460
+ terminalBlockingTurns.length === 0 &&
461
+ !managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session) &&
462
+ externalHandoffTarget?.status === "eligible" &&
463
+ isRecord(rawActions.send) &&
464
+ Boolean(externalHandoffSnapshotToken));
465
+ const handoffDecisionTurn = mutationsAllowed &&
466
+ soleBindingConflict?.kind === "live_external_thread_change" &&
467
+ externalHandoffTarget?.status === "eligible" &&
468
+ terminal.activity_state === "idle" &&
469
+ !(isRecord(terminal.approval_state) &&
470
+ terminal.approval_state.blocked === true) &&
471
+ !managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session) &&
472
+ blockingHandoffTurns.length === 1 &&
473
+ terminalBlockingTurns.every((turn) => turn.conversation_id === blockingHandoffTurns[0].conversation_id)
474
+ ? blockingHandoffTurns[0]
475
+ : undefined;
476
+ const handoffDecisionToken = handoffDecisionTurn &&
477
+ externalHandoffSnapshotToken &&
478
+ terminalControl
479
+ ? ports.activeTurnHandoffDecisionToken({
480
+ handoffToken: externalHandoffSnapshotToken,
481
+ turn: handoffDecisionTurn,
482
+ ledger: ports.loadTerminalBridgeDispatchLedger(terminalControl)
483
+ })
484
+ : undefined;
485
+ const handoffDecision = handoffDecisionTurn &&
486
+ handoffDecisionToken &&
487
+ externalHandoffNativeThreadId
488
+ ? projectHandoffDecision({
489
+ sourceSessionId: soleBindingConflict?.session.session_id,
490
+ sourceTurnId: turnIdForConversation(handoffDecisionTurn),
491
+ liveNativeThreadId: externalHandoffNativeThreadId,
492
+ handoffDecisionToken,
493
+ actionTurnId: turnIdForConversation(handoffDecisionTurn)
494
+ })
495
+ : undefined;
496
+ const blockingHandoffTurnIds = new Set(handoffSourceBlockingTurns.map((turn) => turn.conversation_id));
497
+ const terminalRecoveryBlockingTurns = terminalBlockingTurns;
498
+ return {
499
+ ...observation,
500
+ reconcileBindingAction,
501
+ externalHandoffAdoptable,
502
+ handoffDecision,
503
+ blockingHandoffTurnIds,
504
+ terminalRecoveryBlockingTurns
505
+ };
506
+ }
507
+ function observeTerminalScopedApprovalAuthority(ports, terminal, context, observation) {
508
+ const { storeDir, mutationsAllowed } = context;
509
+ const { terminalControl, terminalHasNonterminalDeferredTransfer, conflictingBoundSessionClaims, unresolvedSessionClaims, sessionAuthorityConflict, authoritativeSession, discoveredOwnership, ownership, rawActions } = observation;
510
+ let terminalScopedCodexApprovalAction;
511
+ const terminalScopedCodexApprovalPrompt = terminalScopedCodexApprovalPromptSnapshot(terminal.approval_state);
512
+ if (mutationsAllowed &&
513
+ terminal.agent === "codex" &&
514
+ terminalControl &&
515
+ hasCanonicalTerminalEndpoint(terminalControl) &&
516
+ authoritativeSession &&
517
+ !sessionAuthorityConflict &&
518
+ !terminalHasNonterminalDeferredTransfer &&
519
+ unresolvedSessionClaims.length === 0 &&
520
+ conflictingBoundSessionClaims.length === 0 &&
521
+ terminalScopedCodexApprovalPrompt &&
522
+ isRecord(rawActions.approve)) {
523
+ try {
524
+ const ledger = ports.loadTerminalBridgeDispatchLedger(terminalControl);
525
+ const boundary = ownership.state === "conflict" &&
526
+ discoveredOwnership.state === "current" &&
527
+ ledger
528
+ ? terminalScopedCodexApprovalBoundary(ports, {
529
+ storeDir,
530
+ terminal,
531
+ owner: discoveredOwnership.conversation,
532
+ session: authoritativeSession,
533
+ ledger,
534
+ approval: terminalScopedCodexApprovalPrompt
535
+ })
536
+ : ownership.state === "none" &&
537
+ discoveredOwnership.state === "none"
538
+ ? terminalScopedCodexApprovalBoundary(ports, {
539
+ storeDir,
540
+ terminal,
541
+ session: authoritativeSession,
542
+ ledger,
543
+ approval: terminalScopedCodexApprovalPrompt
544
+ })
545
+ : undefined;
546
+ if (!boundary) {
547
+ throw new Error("terminal-scoped Codex approval has no eligible managed authority");
548
+ }
549
+ terminalScopedCodexApprovalAction = {
550
+ ...rawActions.approve,
551
+ arguments: {
552
+ conversation_id: String(terminal.id),
553
+ expected_terminal_token: boundary.token
554
+ },
555
+ scope: "terminal_current_prompt",
556
+ authority: boundary.authority.kind,
557
+ managed_state_unchanged: true,
558
+ automatic_approval_eligible: false,
559
+ durable_dispatch_receipt: false,
560
+ uncertain_outcome_recovery: "refresh status and inspect the live prompt; do not retry blindly"
561
+ };
562
+ }
563
+ catch (error) {
564
+ ports.runtimeLog("info", "terminal_scoped_codex_approval_not_advertised", {
565
+ terminal_id: String(terminal.id),
566
+ terminal_target: terminalControl.target,
567
+ reason: error instanceof Error ? error.message : String(error)
568
+ });
569
+ }
570
+ }
571
+ const rolloutBackedCodexSession = Boolean(authoritativeSession?.agent === "codex" &&
572
+ isCompleteNativeRollout(authoritativeSession.binding?.native_process.rollout));
573
+ return {
574
+ ...observation,
575
+ terminalScopedCodexApprovalAction,
576
+ rolloutBackedCodexSession
577
+ };
578
+ }
579
+ function observeActionAuthority(ports, terminal, context, binding) {
580
+ return observeTerminalScopedApprovalAuthority(ports, terminal, context, observeTerminalHandoffAuthority(ports, terminal, context, observeDeferredTerminalAuthority(ports, terminal, context, observeDeferredSourceAuthority(ports, terminal, context, observeVerifiedEmptyTerminalAuthority(ports, terminal, context, binding)))));
581
+ }
582
+ function managedSessionMatchesLiveTerminalEntry(ports, session, terminal, storeDir) {
583
+ const binding = session.binding;
584
+ const liveControl = isRecord(terminal.terminal_control)
585
+ ? terminal.terminal_control
586
+ : undefined;
587
+ if (session.status !== "bound" ||
588
+ !binding ||
589
+ session.agent !== terminal.agent ||
590
+ binding.native_process.pid !== Number(terminal.pid)) {
591
+ return false;
592
+ }
593
+ const terminalAliasMatches = terminalControlAliasMatches(binding.terminal_id, binding.terminal_control, terminal.id, liveControl);
594
+ if (!terminalAliasMatches) {
595
+ return false;
596
+ }
597
+ const workspaceMatches = ports.matchesConfiguredWorkspace(session.workspace, terminal.workspace ?? terminal.cwd);
598
+ if (!workspaceMatches) {
599
+ return false;
600
+ }
601
+ const observation = terminalObservationFromListEntry(terminal, session.agent);
602
+ const evidence = { terminalAliasMatches, workspaceMatches };
603
+ let decision = decideTerminalBindingMatch(session, observation, evidence);
604
+ if (decision.state === "not_exact" &&
605
+ decision.reason === "native_identity_mismatch" &&
606
+ session.agent === "codex" &&
607
+ observation.nativeIdentity.status === "resolved") {
608
+ decision = decideTerminalBindingMatch(session, observation, {
609
+ ...evidence,
610
+ codexLingeringBeforeMatches: ports.codexLingeringBeforeIdentityMatchesSession({
611
+ storeDir,
612
+ session,
613
+ identity: observation.nativeIdentity.identity
614
+ })
615
+ });
616
+ }
617
+ return decision.state === "exact";
618
+ }
619
+ function managedSessionClaimsLiveTerminalEntry(session, terminal) {
620
+ const binding = session.binding;
621
+ const liveControl = isRecord(terminal.terminal_control)
622
+ ? terminal.terminal_control
623
+ : undefined;
624
+ return Boolean(binding &&
625
+ session.agent === terminal.agent &&
626
+ binding.native_process.pid === Number(terminal.pid) &&
627
+ terminalControlsShareIncarnation(binding.terminal_control, liveControl));
628
+ }
629
+ function listedTerminalProcessIncarnation(ports, terminal) {
630
+ const processUuid = stringValue(terminal.native_agent_process_uuid);
631
+ const processBirth = stringValue(terminal.native_agent_process_birth);
632
+ if (terminal.agent !== "codex" || (processUuid && processBirth)) {
633
+ return { processUuid, processBirth };
634
+ }
635
+ const pid = Number(terminal.pid);
636
+ if (!Number.isSafeInteger(pid) || pid <= 1) {
637
+ return { processUuid, processBirth };
638
+ }
639
+ try {
640
+ const incarnation = ports.codexProcessIncarnationForPid(pid);
641
+ return {
642
+ processUuid: processUuid ?? incarnation.processUuid,
643
+ processBirth: processBirth ?? incarnation.processBirth
644
+ };
645
+ }
646
+ catch {
647
+ return { processUuid, processBirth };
648
+ }
649
+ }
650
+ function managedBindingConflictKindForLiveTerminalEntry(ports, { storeDir, session, terminal }) {
651
+ const binding = session.binding;
652
+ if (session.status !== "bound" ||
653
+ !binding ||
654
+ !managedSessionClaimsLiveTerminalEntry(session, terminal) ||
655
+ managedSessionMatchesLiveTerminalEntry(ports, session, terminal, storeDir)) {
656
+ return undefined;
657
+ }
658
+ const livePid = Number(terminal.pid);
659
+ const incarnation = listedTerminalProcessIncarnation(ports, terminal);
660
+ const relationship = processIncarnationRelationship({
661
+ binding,
662
+ livePid,
663
+ liveProcessUuid: incarnation.processUuid,
664
+ liveProcessBirth: incarnation.processBirth
665
+ });
666
+ if (relationship === "different") {
667
+ return "stale_process_incarnation";
668
+ }
669
+ if (!terminalControlAliasMatches(binding.terminal_id, binding.terminal_control, terminal.id, isRecord(terminal.terminal_control)
670
+ ? terminal.terminal_control
671
+ : undefined) ||
672
+ !ports.matchesConfiguredWorkspace(session.workspace, terminal.workspace ?? terminal.cwd)) {
673
+ return "unverifiable";
674
+ }
675
+ return decideManagedBindingConflict({
676
+ session,
677
+ claimsTerminal: true,
678
+ exactBinding: false,
679
+ ownerConclusivelyInactive: false,
680
+ processRelationship: relationship,
681
+ liveNativeThreadId: stringValue(terminal.native_agent_session_id),
682
+ statusCardNativeThreadId: stringValue(terminal.native_agent_status_card_session_id),
683
+ managedTurnCount: provisionalManagedBindingTurnCount(ports, storeDir, session)
684
+ });
685
+ }
686
+ function provisionalManagedBindingTurnCount(ports, storeDir, session) {
687
+ const binding = session.binding;
688
+ return binding && session.lineage.created_by === "attach" &&
689
+ !session.last_transition_id &&
690
+ !binding.native_thread_id &&
691
+ !binding.native_process.rollout
692
+ ? ports.managedTurnsForSession(storeDir, session.session_id).length
693
+ : undefined;
694
+ }
695
+ function managedSessionHasUnresolvedNativeTransition(storeDir, session) {
696
+ const root = nativeThreadTransitionsDir(storeDir);
697
+ if (!fs.existsSync(root)) {
698
+ return false;
699
+ }
700
+ let entries;
701
+ try {
702
+ entries = fs.readdirSync(root, { withFileTypes: true });
703
+ }
704
+ catch {
705
+ return true;
706
+ }
707
+ for (const entry of entries) {
708
+ if (!entry.isDirectory()) {
709
+ continue;
710
+ }
711
+ let transition;
712
+ try {
713
+ transition = loadNativeThreadTransition(storeDir, entry.name);
714
+ }
715
+ catch {
716
+ return true;
717
+ }
718
+ if (transition.source_session_id !== session.session_id &&
719
+ transition.target_session_id !== session.session_id) {
720
+ continue;
721
+ }
722
+ if (!["committed", "aborted"].includes(transition.status)) {
723
+ return true;
724
+ }
725
+ }
726
+ return false;
727
+ }
728
+ function managedSessionHasAnyNativeTransition(storeDir, session) {
729
+ const root = nativeThreadTransitionsDir(storeDir);
730
+ if (!fs.existsSync(root)) {
731
+ return false;
732
+ }
733
+ let entries;
734
+ try {
735
+ entries = fs.readdirSync(root, { withFileTypes: true });
736
+ }
737
+ catch {
738
+ return true;
739
+ }
740
+ for (const entry of entries) {
741
+ if (!entry.isDirectory()) {
742
+ continue;
743
+ }
744
+ let transition;
745
+ try {
746
+ transition = loadNativeThreadTransition(storeDir, entry.name);
747
+ }
748
+ catch {
749
+ return true;
750
+ }
751
+ if (transition.source_session_id === session.session_id ||
752
+ transition.target_session_id === session.session_id) {
753
+ return true;
754
+ }
755
+ }
756
+ return false;
757
+ }
758
+ function terminalControlForManagedConversation(ports, conversation) {
759
+ return ports.terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
760
+ ? conversation.native_session_takeover
761
+ : undefined);
762
+ }
763
+ function terminalIncarnationBlockingTurns(ports, storeDir, terminalControl, conversations = listConversations(storeDir)) {
764
+ return conversations
765
+ .filter(ports.isDiscoverableTmuxConversation)
766
+ .filter((turn) => terminalControlsShareIncarnation(terminalControlForManagedConversation(ports, turn), terminalControl) && isSessionSendBlockingStatus(turn.status))
767
+ .sort(compareManagedConversationRecency);
768
+ }
769
+ function managedTurnNeedsAttention(conversation) {
770
+ return terminalManagedTurnNeedsAttention({
771
+ status: conversation.status,
772
+ get callbackDeliveryStatus() {
773
+ const delivery = isRecord(conversation.callback_delivery)
774
+ ? conversation.callback_delivery
775
+ : undefined;
776
+ return String(delivery?.status ?? "");
777
+ }
778
+ });
779
+ }
780
+ function assertTerminalIncarnationCanStartTurn(ports, storeDir, terminalControl) {
781
+ const blocker = terminalIncarnationBlockingTurns(ports, storeDir, terminalControl)[0];
782
+ if (!blocker) {
783
+ return;
784
+ }
785
+ throw new Error(`terminal ${terminalControl.target} still has unresolved Turn ` +
786
+ `${turnIdForConversation(blocker)} (${blocker.status})`);
787
+ }
788
+ function terminalDispatchOwnership(ports, terminalControl) {
789
+ let ledger;
790
+ try {
791
+ ledger = ports.loadTerminalBridgeDispatchLedger(terminalControl);
792
+ }
793
+ catch (error) {
794
+ const decision = dispatch.decideTerminalDispatchOwnership("unreadable");
795
+ return {
796
+ state: "conflict",
797
+ conflict: {
798
+ reason: decision.code === "ledger_unreadable"
799
+ ? error instanceof Error ? error.message : String(error)
800
+ : "terminal dispatch ledger is unreadable",
801
+ recovery: "inspect the shared terminal pane before performing a side effect"
802
+ }
803
+ };
804
+ }
805
+ const ledgerAuthority = !ledger
806
+ ? "absent"
807
+ : ledger.status === "resolved"
808
+ ? "resolved"
809
+ : ports.terminalDispatchRecordMatchesControl(ledger, terminalControl, { requireProcessAnchor: false }) && !ports.terminalDispatchRecordMatchesControl(ledger, terminalControl)
810
+ ? "stale_process_incarnation"
811
+ : dispatch.isActiveTerminalDispatchStatus(String(ledger.status))
812
+ ? "active"
813
+ : "inactive_status";
814
+ let decision = dispatch.decideTerminalDispatchOwnership(ledgerAuthority);
815
+ if (decision.state === "none" || !ledger) {
816
+ return { state: "none" };
817
+ }
818
+ const owner = ports.loadTerminalDispatchLedgerOwner(ledger);
819
+ const ledgerMessageId = stringValue(ledger.message_id);
820
+ const ownerAuthority = !owner
821
+ ? "unavailable"
822
+ : isTerminalDispatchOwnerReleasedStatus(owner.status)
823
+ ? "released"
824
+ : !terminalControlsShareIncarnation(terminalControlForManagedConversation(ports, owner), terminalControl)
825
+ ? "terminal_mismatch"
826
+ : ledgerMessageId && stringValue(isRecord(owner.native_session_takeover)
827
+ ? owner.native_session_takeover.terminal_bridge_message_id
828
+ : undefined) !== ledgerMessageId
829
+ ? "generation_mismatch"
830
+ : "current";
831
+ decision = dispatch.decideTerminalDispatchOwnership(ledgerAuthority, ownerAuthority);
832
+ if (decision.state === "none") {
833
+ return { state: "none" };
834
+ }
835
+ if (decision.state === "current" && owner) {
836
+ return { state: "current", conversation: owner };
837
+ }
838
+ const reason = decision.state === "conflict" &&
839
+ decision.code === "owner_terminal_mismatch"
840
+ ? "dispatch owner does not reference this terminal pane incarnation"
841
+ : decision.state === "conflict" &&
842
+ decision.code === "owner_generation_mismatch"
843
+ ? "dispatch generation does not match the owner state"
844
+ : "dispatch owner state is unavailable";
845
+ return {
846
+ state: "conflict",
847
+ conflict: projectTerminalDispatchConflict({
848
+ reason,
849
+ dispatchStatus: stringValue(ledger.status),
850
+ ownerConversationId: stringValue(ledger.conversation_id),
851
+ messageId: stringValue(ledger.message_id)
852
+ })
853
+ };
854
+ }
855
+ function localTerminalDispatchOwnership(ports, ledgerOwner, localConversations, terminal) {
856
+ const localOwner = localConversations.find((conversation) => conversation.conversation_id === ledgerOwner.conversation_id &&
857
+ sameCanonicalStatePath(conversation.state_path, ledgerOwner.state_path));
858
+ return decideLocalTerminalDispatchOwnership({
859
+ ledgerOwnerId: ledgerOwner.conversation_id,
860
+ localOwner,
861
+ localOwnerMatchesLiveTerminal: localOwner
862
+ ? managedTurnMatchesLiveTerminal(ports, localOwner, terminal)
863
+ : false
864
+ });
865
+ }
866
+ function terminalScopedCodexApprovalBoundary(ports, { storeDir, terminal, session, ledger, approval, owner }) {
867
+ const terminalRecord = isRecord(terminal) ? terminal : {};
868
+ const terminalControl = isRecord(terminalRecord.terminal_control)
869
+ ? terminalRecord.terminal_control
870
+ : undefined;
871
+ const control = terminalControl;
872
+ const terminalId = String(terminalRecord.id);
873
+ const relatedBoundSessionIds = () => listManagedSessions(storeDir)
874
+ .filter((candidate) => candidate.status === "bound" &&
875
+ candidate.agent === "codex" &&
876
+ candidate.binding?.native_process.pid === Number(terminalRecord.pid) &&
877
+ terminalControlsShareIncarnation(candidate.binding?.terminal_control, terminalControl))
878
+ .map((candidate) => candidate.session_id);
879
+ const blockingTurnIds = () => terminalIncarnationBlockingTurns(ports, storeDir, control)
880
+ .map((turn) => turnIdForConversation(turn));
881
+ const hasDeferredRecovery = () => listDeferredForegroundTransfers(storeDir).some((transfer) => !isFinalDeferredForegroundTransferStatus(transfer.status) &&
882
+ (transfer.source_session_id === session.session_id ||
883
+ transfer.target_session_id === session.session_id ||
884
+ (transfer.terminal_id === terminalId &&
885
+ terminalControlEvidenceMatches(transfer.terminal_endpoint, control))));
886
+ const commonChecks = {
887
+ relatedBoundSessionIds,
888
+ blockingTurnIds,
889
+ hasNativeTransition: () => managedSessionHasAnyNativeTransition(storeDir, session),
890
+ hasDeferredRecovery,
891
+ ledgerMatchesTerminal: () => ports.terminalDispatchRecordMatchesControl(ledger, control)
892
+ };
893
+ if (owner && ledger) {
894
+ return decideTerminalScopedCodexApprovalAuthority({
895
+ kind: "current_dispatch_owner",
896
+ storeDir,
897
+ terminal,
898
+ owner,
899
+ session,
900
+ ledger,
901
+ approval,
902
+ checks: {
903
+ ...commonChecks,
904
+ assertDispatchOwner: () => ports.assertManagedTerminalDispatchOwner({
905
+ storeDir,
906
+ conversation: owner,
907
+ terminalControl: control,
908
+ action: "approve"
909
+ }),
910
+ ownerMatchesNativeIdentity: (identity) => nativeAgentIdentityMatchesTurn(owner, identity
911
+ ? { ...identity, evidence: "terminal_scoped_approval" }
912
+ : undefined)
913
+ }
914
+ });
915
+ }
916
+ return decideTerminalScopedCodexApprovalAuthority({
917
+ kind: "managed_session_no_dispatch_owner",
918
+ storeDir,
919
+ terminal,
920
+ session,
921
+ ledger,
922
+ approval,
923
+ checks: {
924
+ ...commonChecks,
925
+ dispatchOwnershipIsNone: () => terminalDispatchOwnership(ports, control).state === "none",
926
+ hasOrphanedDispatch: () => Boolean(ports.orphanedTerminalDispatchForRecovery(control))
927
+ }
928
+ });
929
+ }
930
+ function managedTurnMatchesLiveTerminal(ports, conversation, terminal) {
931
+ const takeover = isRecord(conversation.native_session_takeover)
932
+ ? conversation.native_session_takeover
933
+ : undefined;
934
+ const liveControl = isRecord(terminal.terminal_control)
935
+ ? terminal.terminal_control
936
+ : undefined;
937
+ const storedControl = ports.terminalControlFromTakeover(takeover);
938
+ const livePid = Number(terminal.pid);
939
+ const storedPid = Number(takeover?.terminal_agent_pid);
940
+ if (executorForConversation(conversation).kind !== terminal.agent ||
941
+ !Number.isSafeInteger(livePid) ||
942
+ livePid <= 1 ||
943
+ storedPid !== livePid ||
944
+ !terminalControlAliasMatches(stringValue(takeover?.native_session_id), storedControl, stringValue(terminal.id), liveControl) ||
945
+ !terminalControlsShareIncarnation(storedControl, liveControl)) {
946
+ return false;
947
+ }
948
+ const liveSessionId = stringValue(terminal.native_agent_session_id);
949
+ const liveProcessUuid = stringValue(terminal.native_agent_process_uuid);
950
+ const liveProcessBirth = stringValue(terminal.native_agent_process_birth);
951
+ const liveRollout = isRecord(terminal.native_agent_rollout)
952
+ ? terminal.native_agent_rollout
953
+ : undefined;
954
+ const liveNativeIdentity = liveSessionId
955
+ ? {
956
+ sessionId: liveSessionId,
957
+ ...(liveProcessUuid ? { processUuid: liveProcessUuid } : {}),
958
+ ...(liveProcessBirth ? { processBirth: liveProcessBirth } : {}),
959
+ ...(liveRollout
960
+ ? {
961
+ rollout: {
962
+ fd: String(liveRollout.fd ?? ""),
963
+ device: String(liveRollout.device ?? ""),
964
+ inode: String(liveRollout.inode ?? ""),
965
+ path: String(liveRollout.path ?? "")
966
+ }
967
+ }
968
+ : {}),
969
+ evidence: "live_terminal"
970
+ }
971
+ : undefined;
972
+ if (!nativeAgentIdentityMatchesTurn(conversation, liveNativeIdentity)) {
973
+ return false;
974
+ }
975
+ const liveWorkspace = terminal.workspace ?? terminal.cwd;
976
+ if (!ports.matchesConfiguredWorkspace(conversation.workspace, liveWorkspace)) {
977
+ return false;
978
+ }
979
+ const livePanePath = liveControl?.currentPath;
980
+ if (livePanePath !== undefined &&
981
+ !ports.matchesConfiguredWorkspace(conversation.workspace, livePanePath)) {
982
+ return false;
983
+ }
984
+ return true;
985
+ }
986
+ export function createTerminalListOwnershipService(ports) {
987
+ return Object.freeze({
988
+ assertTerminalIncarnationCanStartTurn: (storeDir, terminalControl) => assertTerminalIncarnationCanStartTurn(ports, storeDir, terminalControl),
989
+ managedSessionHasAnyNativeTransition,
990
+ managedSessionHasUnresolvedNativeTransition,
991
+ managedSessionMatchesLiveTerminalEntry: (session, terminal, storeDir) => managedSessionMatchesLiveTerminalEntry(ports, session, terminal, storeDir),
992
+ managedTurnMatchesLiveTerminal: (conversation, terminal) => managedTurnMatchesLiveTerminal(ports, conversation, terminal),
993
+ managedTurnNeedsAttention,
994
+ observeActionAuthority: (terminal, context, binding) => observeActionAuthority(ports, terminal, context, binding),
995
+ observeBindingAuthority: (terminal, context) => observeBindingAuthority(ports, terminal, context),
996
+ provisionalManagedBindingTurnCount: (storeDir, session) => provisionalManagedBindingTurnCount(ports, storeDir, session),
997
+ terminalControlForManagedConversation: (conversation) => terminalControlForManagedConversation(ports, conversation),
998
+ terminalDispatchOwnership: (terminalControl) => terminalDispatchOwnership(ports, terminalControl),
999
+ terminalIncarnationBlockingTurns: (storeDir, terminalControl, conversations) => terminalIncarnationBlockingTurns(ports, storeDir, terminalControl, conversations),
1000
+ terminalScopedCodexApprovalBoundary: (request) => terminalScopedCodexApprovalBoundary(ports, request)
1001
+ });
1002
+ }
1003
+ //# sourceMappingURL=terminal-list-ownership-service.js.map