@scotthuang/agent-knock-knock 0.12.13 → 0.12.15

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 (170) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +13 -13
  3. package/dist/src/callback-cli-adapter.d.ts +9 -6
  4. package/dist/src/callback-cli-adapter.js +119 -30
  5. package/dist/src/callback-cli-adapter.js.map +1 -1
  6. package/dist/src/callback-outbox-policy.d.ts +39 -0
  7. package/dist/src/callback-outbox-policy.js +163 -6
  8. package/dist/src/callback-outbox-policy.js.map +1 -1
  9. package/dist/src/callback-outbox-service.d.ts +28 -1
  10. package/dist/src/callback-outbox-service.js +413 -82
  11. package/dist/src/callback-outbox-service.js.map +1 -1
  12. package/dist/src/callback-outbox-settlement.d.ts +14 -1
  13. package/dist/src/callback-outbox-settlement.js +151 -30
  14. package/dist/src/callback-outbox-settlement.js.map +1 -1
  15. package/dist/src/callback-route-authority.d.ts +59 -0
  16. package/dist/src/callback-route-authority.js +123 -0
  17. package/dist/src/callback-route-authority.js.map +1 -0
  18. package/dist/src/callback-transport.d.ts +134 -0
  19. package/dist/src/callback-transport.js +256 -0
  20. package/dist/src/callback-transport.js.map +1 -0
  21. package/dist/src/cli-core.js +56 -18
  22. package/dist/src/cli-core.js.map +1 -1
  23. package/dist/src/cli-runtime-context.d.ts +2 -0
  24. package/dist/src/cli-runtime-context.js.map +1 -1
  25. package/dist/src/codex-lifecycle-compatibility.js +2 -1
  26. package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
  27. package/dist/src/deferred-foreground-application-service.d.ts +18 -0
  28. package/dist/src/deferred-foreground-application-service.js +72 -0
  29. package/dist/src/deferred-foreground-application-service.js.map +1 -1
  30. package/dist/src/deferred-foreground-authority-cli-adapter.js +10 -1
  31. package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -1
  32. package/dist/src/deferred-foreground-recovery-cli-adapter.d.ts +1 -1
  33. package/dist/src/deferred-foreground-recovery-cli-adapter.js +108 -37
  34. package/dist/src/deferred-foreground-recovery-cli-adapter.js.map +1 -1
  35. package/dist/src/deferred-foreground-recovery-service.d.ts +1 -1
  36. package/dist/src/deferred-foreground-recovery-service.js +10 -3
  37. package/dist/src/deferred-foreground-recovery-service.js.map +1 -1
  38. package/dist/src/deferred-foreground-transfer-policy.d.ts +1 -1
  39. package/dist/src/deferred-foreground-transfer-policy.js +3 -2
  40. package/dist/src/deferred-foreground-transfer-policy.js.map +1 -1
  41. package/dist/src/deferred-foreground-transfer.d.ts +17 -1
  42. package/dist/src/deferred-foreground-transfer.js +244 -35
  43. package/dist/src/deferred-foreground-transfer.js.map +1 -1
  44. package/dist/src/deferred-foreground-user-close.d.ts +18 -0
  45. package/dist/src/deferred-foreground-user-close.js +180 -0
  46. package/dist/src/deferred-foreground-user-close.js.map +1 -0
  47. package/dist/src/install-doctor-command-adapter.js +1 -1
  48. package/dist/src/install-doctor-command-adapter.js.map +1 -1
  49. package/dist/src/openclaw-callback-transport.d.ts +4 -1
  50. package/dist/src/openclaw-callback-transport.js +403 -10
  51. package/dist/src/openclaw-callback-transport.js.map +1 -1
  52. package/dist/src/openclaw-doctor.js +16 -2
  53. package/dist/src/openclaw-doctor.js.map +1 -1
  54. package/dist/src/openclaw-managed-callback-cli-adapter.d.ts +23 -0
  55. package/dist/src/openclaw-managed-callback-cli-adapter.js +77 -0
  56. package/dist/src/openclaw-managed-callback-cli-adapter.js.map +1 -0
  57. package/dist/src/openclaw-plugin-command-adapter.js +93 -39
  58. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  59. package/dist/src/openclaw-plugin-helpers.d.ts +1 -0
  60. package/dist/src/openclaw-plugin-helpers.js +9 -1
  61. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  62. package/dist/src/openclaw-plugin-schemas.d.ts +20 -1
  63. package/dist/src/openclaw-plugin-schemas.js +27 -3
  64. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  65. package/dist/src/protocol.d.ts +3 -0
  66. package/dist/src/protocol.js +4 -0
  67. package/dist/src/protocol.js.map +1 -1
  68. package/dist/src/store.d.ts +12 -1
  69. package/dist/src/store.js +81 -3
  70. package/dist/src/store.js.map +1 -1
  71. package/dist/src/terminal-acceptance-cli-adapter.d.ts +2 -0
  72. package/dist/src/terminal-acceptance-cli-adapter.js +62 -12
  73. package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -1
  74. package/dist/src/terminal-action-projection.d.ts +31 -3
  75. package/dist/src/terminal-action-projection.js +64 -9
  76. package/dist/src/terminal-action-projection.js.map +1 -1
  77. package/dist/src/terminal-agent-bridge.d.ts +82 -0
  78. package/dist/src/terminal-agent-bridge.js +553 -109
  79. package/dist/src/terminal-agent-bridge.js.map +1 -1
  80. package/dist/src/terminal-command-cli-adapter.d.ts +26 -2
  81. package/dist/src/terminal-command-cli-adapter.js +3162 -214
  82. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  83. package/dist/src/terminal-delegate-cli-adapter.d.ts +4 -22
  84. package/dist/src/terminal-delegate-cli-adapter.js +239 -192
  85. package/dist/src/terminal-delegate-cli-adapter.js.map +1 -1
  86. package/dist/src/terminal-delegate-send-binding.d.ts +63 -0
  87. package/dist/src/terminal-delegate-send-binding.js +360 -0
  88. package/dist/src/terminal-delegate-send-binding.js.map +1 -0
  89. package/dist/src/terminal-dispatch-application.d.ts +4 -1
  90. package/dist/src/terminal-dispatch-application.js +11 -2
  91. package/dist/src/terminal-dispatch-application.js.map +1 -1
  92. package/dist/src/terminal-dispatch-composition.d.ts +5 -0
  93. package/dist/src/terminal-dispatch-execution.js +3 -1
  94. package/dist/src/terminal-dispatch-execution.js.map +1 -1
  95. package/dist/src/terminal-dispatch-ledger-codec.d.ts +3 -0
  96. package/dist/src/terminal-dispatch-ledger-codec.js +46 -19
  97. package/dist/src/terminal-dispatch-ledger-codec.js.map +1 -1
  98. package/dist/src/terminal-dispatch-presenter.js +5 -3
  99. package/dist/src/terminal-dispatch-presenter.js.map +1 -1
  100. package/dist/src/terminal-dispatch-receipt.d.ts +3 -0
  101. package/dist/src/terminal-dispatch-receipt.js +15 -1
  102. package/dist/src/terminal-dispatch-receipt.js.map +1 -1
  103. package/dist/src/terminal-dispatch-recovery-cli-adapter.js +68 -7
  104. package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
  105. package/dist/src/terminal-dispatch-recovery-service.d.ts +2 -0
  106. package/dist/src/terminal-dispatch-recovery-service.js +9 -0
  107. package/dist/src/terminal-dispatch-recovery-service.js.map +1 -1
  108. package/dist/src/terminal-dispatch-repository-cli-adapter.d.ts +4 -2
  109. package/dist/src/terminal-dispatch-repository-cli-adapter.js +10 -3
  110. package/dist/src/terminal-dispatch-repository-cli-adapter.js.map +1 -1
  111. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +2 -0
  112. package/dist/src/terminal-identity-authority-cli-adapter.js +34 -27
  113. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
  114. package/dist/src/terminal-list-cli-adapter.d.ts +6 -0
  115. package/dist/src/terminal-list-cli-adapter.js +391 -177
  116. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  117. package/dist/src/terminal-list-renderer.d.ts +9 -1
  118. package/dist/src/terminal-list-renderer.js +226 -104
  119. package/dist/src/terminal-list-renderer.js.map +1 -1
  120. package/dist/src/terminal-maintenance-cli-adapter.js +253 -326
  121. package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -1
  122. package/dist/src/terminal-monitor-application-service.d.ts +5 -0
  123. package/dist/src/terminal-monitor-application-service.js +5 -0
  124. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  125. package/dist/src/terminal-monitor-cli-adapter.d.ts +25 -1
  126. package/dist/src/terminal-monitor-cli-adapter.js +170 -27
  127. package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
  128. package/dist/src/terminal-monitor-reconciliation-eligibility.d.ts +1 -1
  129. package/dist/src/terminal-monitor-reconciliation-eligibility.js +220 -32
  130. package/dist/src/terminal-monitor-reconciliation-eligibility.js.map +1 -1
  131. package/dist/src/terminal-monitor-state-cli-adapter.d.ts +1 -1
  132. package/dist/src/terminal-monitor-state-cli-adapter.js +808 -147
  133. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
  134. package/dist/src/terminal-monitor-state-reconciliation-service.d.ts +2 -1
  135. package/dist/src/terminal-monitor-state-reconciliation-service.js +12 -0
  136. package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -1
  137. package/dist/src/terminal-monitor-supervision-cli-adapter.d.ts +2 -0
  138. package/dist/src/terminal-monitor-supervision-cli-adapter.js +31 -11
  139. package/dist/src/terminal-monitor-supervision-cli-adapter.js.map +1 -1
  140. package/dist/src/terminal-process-source.d.ts +13 -0
  141. package/dist/src/terminal-process-source.js +37 -0
  142. package/dist/src/terminal-process-source.js.map +1 -1
  143. package/dist/src/terminal-runtime-cli-adapter.js +14 -2
  144. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
  145. package/dist/src/terminal-status-cli-adapter.d.ts +1 -0
  146. package/dist/src/terminal-status-cli-adapter.js +30 -28
  147. package/dist/src/terminal-status-cli-adapter.js.map +1 -1
  148. package/dist/src/terminal-submission-acceptance.js +78 -25
  149. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  150. package/dist/src/terminal-submission-retry-service.d.ts +114 -0
  151. package/dist/src/terminal-submission-retry-service.js +589 -0
  152. package/dist/src/terminal-submission-retry-service.js.map +1 -0
  153. package/dist/src/terminal-user-send-intent.d.ts +65 -0
  154. package/dist/src/terminal-user-send-intent.js +317 -0
  155. package/dist/src/terminal-user-send-intent.js.map +1 -0
  156. package/dist/src/terminal-watch-callback-cli-adapter.d.ts +10 -2
  157. package/dist/src/terminal-watch-callback-cli-adapter.js +76 -37
  158. package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -1
  159. package/dist/src/terminal-watch-cli-adapter.js +41 -17
  160. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  161. package/dist/src/terminal-watch-service.d.ts +11 -6
  162. package/dist/src/terminal-watch-service.js +210 -41
  163. package/dist/src/terminal-watch-service.js.map +1 -1
  164. package/dist/src/terminal-watch-store.d.ts +23 -0
  165. package/dist/src/terminal-watch-store.js +162 -4
  166. package/dist/src/terminal-watch-store.js.map +1 -1
  167. package/docs/quickstart-herdr.md +1 -1
  168. package/docs/quickstart-tmux.md +3 -3
  169. package/package.json +1 -1
  170. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +14 -11
@@ -9,16 +9,16 @@ import { executorForConversation, isSessionSendBlockingStatus, isTerminalDispatc
9
9
  import { resolveSessionSelector, sessionShortRef } from "./session-selector.js";
10
10
  import { projectSessionSelectorCandidate } from "./terminal-selector-projection-service.js";
11
11
  import { listManagedSessions, loadNativeThreadTransition, nativeThreadTransitionsDir, tryLoadManagedSession } from "./session-store.js";
12
- import { defaultStoreDir, ensureStoreWritable, inspectStoreCompatibility, listConversations, STORE_SESSION_AUTHORITY_PROTOCOL } from "./store.js";
12
+ import { defaultStoreDir, ensureStoreWritable, inspectStoreCompatibility, listConversations, storeManifestPath, STORE_SESSION_AUTHORITY_PROTOCOL } from "./store.js";
13
13
  import { exactCodexReadyStyledComposerCapture } from "./terminal-agent-bridge.js";
14
14
  import { captureCodexHumanStartedActiveTaskAnchor } from "./terminal-submission-acceptance.js";
15
- import { applySessionAuthorityToDispatch, authoritativeTerminalIdentity, compareManagedConversationRecency, decideManagedTerminalAssociation, decideLocalTerminalDispatchOwnership, decideTerminalSendAuthority, decideTerminalSessionAuthorityConflict, managedTurnNeedsAttention as terminalManagedTurnNeedsAttention, nonOwnerTerminalActions, projectBlockingTurn, projectHandoffDecision, projectHandoffPresentation, projectPublicManagementConflict, projectReconcileBindingAction, projectTerminalDispatchConflict, projectTerminalManagement, selectManagedTerminalHistory, selectTerminalAvailableActions } from "./terminal-action-projection.js";
15
+ import { applySessionAuthorityToDispatch, authoritativeTerminalIdentity, compareManagedConversationRecency, decideManagedTerminalAssociation, decideLocalTerminalDispatchOwnership, decideTerminalSendAuthority, decideTerminalSessionAuthorityConflict, decideTerminalUserExplicitSendAuthority, managedTurnNeedsAttention as terminalManagedTurnNeedsAttention, nonOwnerTerminalActions, projectBlockingTurn, projectHandoffDecision, projectHandoffPresentation, projectPublicManagementConflict, projectReconcileBindingAction, projectTerminalDispatchConflict, projectTerminalManagement, selectManagedTerminalHistory, selectTerminalAvailableActions } from "./terminal-action-projection.js";
16
16
  import { childProcessIdsForRoot, decideManagedBindingConflict, deferredCodexForegroundBindingToken, exactBoundCodexSendSource, isCompleteNativeRollout, nativeAgentIdentityMatchesTurn, processIncarnationRelationship, selectRootTerminalProcesses, terminalControlAliasMatches, terminalControlsShareIncarnation, verifiedEmptyCodexHandoffToken } from "./terminal-authority-policy.js";
17
17
  import { decideTerminalBindingMatch, terminalObservationFromListEntry } from "./terminal-binding-authority.js";
18
18
  import { decideTerminalScopedCodexApprovalAuthority, terminalScopedCodexApprovalPromptSnapshot } from "./terminal-scoped-approval-authority.js";
19
19
  import { sameCanonicalStatePath } from "./terminal-dispatch-ledger-codec.js";
20
20
  import * as dispatch from "./terminal-dispatch-policy.js";
21
- import { actionsForManagedSessionBinding, currentTerminalActions, listActionContracts, readOnlyListActions, readOnlyManagedTurn, renderAvailableListActions, renderCurrentManagedTurn, renderHistoricalManagedTurn, renderManagedTurnListEntry, safeUnavailableManagedTurnActions, sendActionForManagedSession, withoutGenericHandoffSourceClose } from "./terminal-list-renderer.js";
21
+ import { actionsForManagedSessionBinding, currentTerminalActions, listActionContracts, readOnlyListActions, readOnlyManagedTurn, userReleaseListActions, userReleasableManagedTurn, renderAvailableListActions, renderCurrentManagedTurn, renderHistoricalManagedTurn, renderManagedTurnListEntry, safeUnavailableManagedTurnActions, sendActionForManagedSession, withoutGenericHandoffSourceClose } from "./terminal-list-renderer.js";
22
22
  import { hasCanonicalTerminalEndpoint, terminalControlEvidenceMatches } from "./terminal-control-ref.js";
23
23
  import { validTerminalMonitorTimestampMs as validTimestampMs } from "./terminal-monitor-decision-policy.js";
24
24
  import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
@@ -65,13 +65,29 @@ function projectSelectorCandidate(entry, commandName, observedAtMs, options) {
65
65
  }
66
66
  async function runList(options) {
67
67
  const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(cliCwd()));
68
- const store = inspectStoreCompatibility(storeDir);
69
- const reconciliation = options.reconcile === true
70
- ? await reconcileStoreForList(storeDir, options)
71
- : {
68
+ const store = inspectStoreCompatibilityForTerminalList(storeDir);
69
+ let reconciliation;
70
+ if (options.reconcile === true) {
71
+ try {
72
+ reconciliation = await reconcileStoreForList(storeDir, options);
73
+ }
74
+ catch (error) {
75
+ reconciliation = {
76
+ status: "failed",
77
+ reason: error instanceof Error ? error.message : String(error)
78
+ };
79
+ runtimeLog("warn", "terminal_list_reconciliation_failed", {
80
+ store_dir: storeDir,
81
+ error: reconciliation.reason
82
+ });
83
+ }
84
+ }
85
+ else {
86
+ reconciliation = {
72
87
  status: "disabled",
73
88
  reason: "standalone list is read-only unless --reconcile is supplied"
74
89
  };
90
+ }
75
91
  const agentFilter = options.agent ? resolveExecutor({ kind: options.agent }).kind : undefined;
76
92
  const statusFilter = options.status;
77
93
  const terminalScan = await buildTerminalListGroup({ options, agentFilter, statusFilter });
@@ -107,14 +123,75 @@ async function runList(options) {
107
123
  reconciliation
108
124
  });
109
125
  }
126
+ function inspectStoreCompatibilityForTerminalList(storeDir) {
127
+ try {
128
+ return inspectStoreCompatibility(storeDir);
129
+ }
130
+ catch (error) {
131
+ return {
132
+ status: "incompatible",
133
+ store_dir: storeDir,
134
+ manifest_path: storeManifestPath(storeDir),
135
+ readable: false,
136
+ writable: false,
137
+ reason: `AKK Store metadata is unavailable: ${error instanceof Error ? error.message : String(error)}`
138
+ };
139
+ }
140
+ }
141
+ function physicalOnlyTerminalProjection(terminals, reason) {
142
+ return {
143
+ terminals: terminals.map((terminal) => {
144
+ const { _automated_input_composer_ready: _composer, _codex_open_root_rollout_inventory: _inventory, _codex_latent_clear_resume: _resume, _terminal_user_explicit_send_action: terminalUserExplicitSendAction, _terminal_status_snapshot: _status, ...publicTerminal } = terminal;
145
+ const actions = isRecord(publicTerminal.available_actions)
146
+ ? publicTerminal.available_actions
147
+ : {};
148
+ const send = isRecord(terminalUserExplicitSendAction)
149
+ ? terminalUserExplicitSendAction
150
+ : undefined;
151
+ const status = isRecord(actions.status) ? actions.status : undefined;
152
+ return {
153
+ ...publicTerminal,
154
+ management_state: "unavailable",
155
+ management_unavailable: { reason },
156
+ managed: {
157
+ session_id: null,
158
+ session_short_ref: null,
159
+ current_turn: null,
160
+ recent_turn: null,
161
+ turn_count: 0,
162
+ hidden_turn_count: 0,
163
+ session_count: 0
164
+ },
165
+ available_actions: {
166
+ ...(status ? { status } : {}),
167
+ ...(send ? { send } : {})
168
+ }
169
+ };
170
+ }),
171
+ unavailableManagedTurns: []
172
+ };
173
+ }
110
174
  function projectTerminalListScan(input) {
111
175
  const { options, storeDir, store, terminalScan, agentFilter, statusFilter, tolerateInvalidWatchRecords } = input;
112
176
  const includeAll = Boolean(options.all);
113
- const allManagedConversations = listConversations(storeDir)
114
- .filter(terminalListRuntime().isDiscoverableTmuxConversation);
115
- const managedSessions = store.readable
116
- ? listManagedSessions(storeDir)
117
- : [];
177
+ const managementErrors = [];
178
+ let allManagedConversations = [];
179
+ try {
180
+ allManagedConversations = listConversations(storeDir)
181
+ .filter(terminalListRuntime().isDiscoverableTmuxConversation);
182
+ }
183
+ catch (error) {
184
+ managementErrors.push(`managed Turn inventory is unavailable: ${error instanceof Error ? error.message : String(error)}`);
185
+ }
186
+ let managedSessions = [];
187
+ if (store.readable) {
188
+ try {
189
+ managedSessions = listManagedSessions(storeDir);
190
+ }
191
+ catch (error) {
192
+ managementErrors.push(`managed Session inventory is unavailable: ${error instanceof Error ? error.message : String(error)}`);
193
+ }
194
+ }
118
195
  const displayedConversations = allManagedConversations
119
196
  .filter((conversation) => includeAll || terminalListRuntime().isActiveStatus(conversation.status))
120
197
  .filter((conversation) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, conversation.workspace))
@@ -122,26 +199,48 @@ function projectTerminalListScan(input) {
122
199
  .filter((conversation) => !statusFilter || conversation.status === statusFilter);
123
200
  const workspaceConversations = allManagedConversations.filter((conversation) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, conversation.workspace));
124
201
  const physicalTerminals = terminalScan.terminalControlled.filter((entry) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd));
125
- const projection = terminalFirstListProjection({
126
- storeDir,
127
- terminals: physicalTerminals,
128
- managedSessions,
129
- sessionAuthorityRequired: Number(store.writer_protocol) >= STORE_SESSION_AUTHORITY_PROTOCOL,
130
- allConversations: workspaceConversations,
131
- displayedConversations,
132
- includeAll,
133
- managedOnly: options.managedOnly === true,
134
- statusFilter,
135
- mutationsAllowed: store.writable === true
136
- });
202
+ let projection;
203
+ if (managementErrors.length === 0) {
204
+ try {
205
+ projection = terminalFirstListProjection({
206
+ storeDir,
207
+ terminals: physicalTerminals,
208
+ managedSessions,
209
+ sessionAuthorityRequired: Number(store.writer_protocol) >= STORE_SESSION_AUTHORITY_PROTOCOL,
210
+ allConversations: workspaceConversations,
211
+ displayedConversations,
212
+ includeAll,
213
+ managedOnly: options.managedOnly === true,
214
+ statusFilter,
215
+ mutationsAllowed: store.writable === true
216
+ });
217
+ }
218
+ catch (error) {
219
+ managementErrors.push(`managed terminal projection is unavailable: ${error instanceof Error ? error.message : String(error)}`);
220
+ projection = physicalOnlyTerminalProjection(physicalTerminals, managementErrors.join("; "));
221
+ }
222
+ }
223
+ else {
224
+ projection = physicalOnlyTerminalProjection(physicalTerminals, managementErrors.join("; "));
225
+ }
137
226
  const exactWatchScan = terminalListRuntime()
138
227
  .scanTerminalWatchesForExactObservation;
139
- const watchObservation = tolerateInvalidWatchRecords && exactWatchScan
140
- ? exactWatchScan(storeDir, { includeAll })
141
- : {
142
- watches: terminalListRuntime().listTerminalWatches?.(storeDir, { includeAll }) ?? [],
143
- activeOverlayTrusted: true
144
- };
228
+ let watchObservation;
229
+ try {
230
+ watchObservation = tolerateInvalidWatchRecords && exactWatchScan
231
+ ? exactWatchScan(storeDir, { includeAll })
232
+ : {
233
+ watches: terminalListRuntime().listTerminalWatches?.(storeDir, { includeAll }) ?? [],
234
+ activeOverlayTrusted: true
235
+ };
236
+ }
237
+ catch (error) {
238
+ runtimeLog("warn", "terminal_watch_inventory_unavailable", {
239
+ store_dir: storeDir,
240
+ error: error instanceof Error ? error.message : String(error)
241
+ });
242
+ watchObservation = { watches: [], activeOverlayTrusted: false };
243
+ }
145
244
  const observedTerminalWatches = watchObservation.watches;
146
245
  const terminalWatches = options.managedOnly
147
246
  ? []
@@ -167,7 +266,7 @@ function projectTerminalListScan(input) {
167
266
  async function observeExactTerminal(request) {
168
267
  const { options, terminalId } = request;
169
268
  const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(cliCwd()));
170
- const store = inspectStoreCompatibility(storeDir);
269
+ const store = inspectStoreCompatibilityForTerminalList(storeDir);
171
270
  const scan = await buildTerminalListGroup({ options, terminalId });
172
271
  const matches = scan.terminalControlled.filter((terminal) => stringValue(terminal.id) === terminalId);
173
272
  if (matches.length !== 1) {
@@ -340,9 +439,10 @@ function managedTurnListActionFacts(task, conversation) {
340
439
  const terminalBridgeReady = Boolean(conversation &&
341
440
  terminalListRuntime().terminalBridgeEnabled(conversation) &&
342
441
  terminalListRuntime().terminalControlFromTakeover(nativeTakeover) !== undefined);
442
+ const submission = terminalListRuntime().terminalBridgeSubmission(conversation);
343
443
  const renewEligible = Boolean(terminalBridgeReady &&
344
444
  task.status === "stalled" &&
345
- terminalListRuntime().terminalBridgeSubmission(conversation)?.status !== "uncertain" &&
445
+ submission?.status !== "uncertain" &&
346
446
  !terminalListRuntime().isVerifiedDeadTerminalAgentProcess(conversation ?? {}));
347
447
  const callbackDelivery = isRecord(conversation?.callback_delivery)
348
448
  ? conversation.callback_delivery
@@ -351,11 +451,18 @@ function managedTurnListActionFacts(task, conversation) {
351
451
  conversation.legacy_callback_status_error === undefined &&
352
452
  terminalListRuntime().callbackRetryDisposition(callbackDelivery).state ===
353
453
  "retryable");
454
+ const retrySubmissionCandidate = Boolean(conversation &&
455
+ executorForConversation(conversation).kind === "codex" &&
456
+ terminalBridgeReady &&
457
+ task.status === "stalled" &&
458
+ submission?.status === "uncertain" &&
459
+ stringValue(submission.last_proven_stage) === "text_injected");
354
460
  return {
355
461
  terminalBridgeReady,
356
462
  managedApprovalPending,
357
463
  renewEligible,
358
- retryCallbackEligible
464
+ retryCallbackEligible,
465
+ retrySubmissionCandidate
359
466
  };
360
467
  }
361
468
  async function terminalControlledListEntry(session, activeSessions, options, bridge = terminalListRuntime().createTerminalAgentBridge(options)) {
@@ -436,15 +543,35 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
436
543
  });
437
544
  const codexLifecycleIncarnationAvailable = session.agent !== "codex" ||
438
545
  Boolean(nativeProcessUuid && nativeProcessBirth);
439
- const terminalHasBlockingTurn = terminalIncarnationBlockingTurns(terminalListRuntime().storeDirFromOptions(options), terminalControl).length > 0;
546
+ let terminalHasBlockingTurn = true;
547
+ try {
548
+ terminalHasBlockingTurn = terminalIncarnationBlockingTurns(terminalListRuntime().storeDirFromOptions(options), terminalControl).length > 0;
549
+ }
550
+ catch (error) {
551
+ runtimeLog("warn", "terminal_managed_turn_inventory_unavailable", {
552
+ terminal_target: terminalControl.target,
553
+ error: error instanceof Error ? error.message : String(error)
554
+ });
555
+ }
440
556
  const automatedInputComposerReady = await observeAutomatedInputComposerReady({
441
557
  session,
442
558
  terminalControl,
443
559
  terminalState: effectiveTerminalState,
444
- nativeIdentityObservation,
445
- codexOpenRootRolloutInventory,
446
560
  options
447
561
  });
562
+ let physicalProcessIncarnation;
563
+ try {
564
+ physicalProcessIncarnation =
565
+ terminalListRuntime().processIncarnationForPid(session.pid);
566
+ }
567
+ catch (error) {
568
+ runtimeLog("warn", "terminal_physical_process_incarnation_unavailable", {
569
+ agent: session.agent,
570
+ terminal_target: terminalControl.target,
571
+ pid: session.pid,
572
+ error: error instanceof Error ? error.message : String(error)
573
+ });
574
+ }
448
575
  const entry = {
449
576
  id: bridge.terminalConversationId(session),
450
577
  short_ref: sessionShortRef(bridge.terminalConversationId(session)),
@@ -534,11 +661,38 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
534
661
  terminalHasBlockingTurn
535
662
  })
536
663
  };
537
- const availableActions = renderAvailableListActions(entry);
664
+ const renderedActions = renderAvailableListActions(entry);
665
+ const terminalUserExplicitSendAuthority = decideTerminalUserExplicitSendAuthority({
666
+ exactTerminalRow: true,
667
+ terminalId: entry.id,
668
+ processState: entry.process_state,
669
+ terminalControl,
670
+ agent: session.agent,
671
+ pid: session.pid,
672
+ processUuid: physicalProcessIncarnation?.processUuid,
673
+ processBirth: physicalProcessIncarnation?.processBirth,
674
+ approvalScanned: effectiveTerminalState.approval_state.scanned === true,
675
+ approvalBlocked: effectiveTerminalState.approval_state.blocked === true,
676
+ exactEmptyComposer: automatedInputComposerReady
677
+ });
678
+ const terminalUserExplicitSendAction = terminalUserExplicitSendAuthority.eligible
679
+ ? {
680
+ tool: "agent_knock_knock_send",
681
+ arguments: {
682
+ selector: terminalUserExplicitSendAuthority.terminalId,
683
+ expected_terminal_token: terminalUserExplicitSendAuthority.expectedTerminalToken
684
+ },
685
+ missing_required: ["request"],
686
+ scope: "terminal_user_explicit"
687
+ }
688
+ : undefined;
538
689
  const { commands: _commands, ...publicEntry } = entry;
539
690
  return {
540
691
  ...publicEntry,
541
- available_actions: availableActions
692
+ ...(terminalUserExplicitSendAction
693
+ ? { _terminal_user_explicit_send_action: terminalUserExplicitSendAction }
694
+ : {}),
695
+ available_actions: renderedActions
542
696
  };
543
697
  }
544
698
  function terminalListCommands(input) {
@@ -651,7 +805,17 @@ function terminalBridgeStatusWithActivity(status, activityState, activityReason)
651
805
  return Object.create(Object.getPrototypeOf(status), descriptors);
652
806
  }
653
807
  async function observeTerminalNativeListIdentity(session, terminalControl, options, bridge) {
654
- const orphanedDispatch = terminalListRuntime().orphanedTerminalDispatchForRecovery(terminalControl);
808
+ let orphanedDispatch;
809
+ try {
810
+ orphanedDispatch =
811
+ terminalListRuntime().orphanedTerminalDispatchForRecovery(terminalControl);
812
+ }
813
+ catch (error) {
814
+ runtimeLog("warn", "terminal_orphaned_dispatch_observation_unavailable", {
815
+ terminal_target: terminalControl.target,
816
+ error: error instanceof Error ? error.message : String(error)
817
+ });
818
+ }
655
819
  let codexIdentityContext;
656
820
  if (session.agent === "codex") {
657
821
  try {
@@ -677,15 +841,25 @@ async function observeTerminalNativeListIdentity(session, terminalControl, optio
677
841
  });
678
842
  }
679
843
  }
680
- const nativeIdentityObservation = await terminalListRuntime().observeCurrentNativeAgentSessionIdentity({
681
- options,
682
- agent: session.agent,
683
- pid: session.pid,
684
- cwd: session.cwd ?? terminalControl.currentPath,
685
- preferredSessionId: codexIdentityContext?.preferredSessionId,
686
- allowedCompanionIdentity: codexIdentityContext?.companions.primary,
687
- allowedAdditionalIdentities: codexIdentityContext?.companions.additional
688
- });
844
+ let nativeIdentityObservation;
845
+ try {
846
+ nativeIdentityObservation =
847
+ await terminalListRuntime().observeCurrentNativeAgentSessionIdentity({
848
+ options,
849
+ agent: session.agent,
850
+ pid: session.pid,
851
+ cwd: session.cwd ?? terminalControl.currentPath,
852
+ preferredSessionId: codexIdentityContext?.preferredSessionId,
853
+ allowedCompanionIdentity: codexIdentityContext?.companions.primary,
854
+ allowedAdditionalIdentities: codexIdentityContext?.companions.additional
855
+ });
856
+ }
857
+ catch (error) {
858
+ nativeIdentityObservation = {
859
+ status: "unavailable",
860
+ reason: error instanceof Error ? error.message : String(error)
861
+ };
862
+ }
689
863
  const nativeAgentIdentity = nativeIdentityObservation.status === "resolved"
690
864
  ? nativeIdentityObservation.identity
691
865
  : undefined;
@@ -752,13 +926,9 @@ async function observeTerminalNativeListIdentity(session, terminalControl, optio
752
926
  nativeProcessEvidence
753
927
  };
754
928
  }
755
- async function observeAutomatedInputComposerReady({ session, terminalControl, terminalState, nativeIdentityObservation, codexOpenRootRolloutInventory, options }) {
929
+ async function observeAutomatedInputComposerReady({ session, terminalControl, terminalState, options }) {
756
930
  let ready = terminalListRuntime().nativeInspectionComposerEmpty(session.agent, terminalState.screen_excerpt);
757
931
  if (session.agent !== "codex" ||
758
- (terminalState.activity_state !== "idle" &&
759
- (terminalState.activity_state !== "unknown" ||
760
- (nativeIdentityObservation.status !== "verified_absent" &&
761
- codexOpenRootRolloutInventory === undefined))) ||
762
932
  terminalState.approval_state.blocked === true ||
763
933
  !terminalControl.capabilities.includes("send_keys") ||
764
934
  !terminalControl.capabilities.includes("screen_status")) {
@@ -779,7 +949,7 @@ async function observeAutomatedInputComposerReady({ session, terminalControl, te
779
949
  }
780
950
  function observeTerminalListBindingAuthority(terminal, context) {
781
951
  const { storeDir, managedSessions, allConversations, displayedConversations, mutationsAllowed, nonterminalDeferredTransfers } = context;
782
- const { _automated_input_composer_ready: automatedInputComposerReady, _codex_open_root_rollout_inventory: codexOpenRootRolloutInventoryValue, _codex_latent_clear_resume: codexLatentClearResumeValue, _terminal_status_snapshot: _terminalStatusSnapshot, ...publicTerminal } = terminal;
952
+ const { _automated_input_composer_ready: automatedInputComposerReady, _codex_open_root_rollout_inventory: codexOpenRootRolloutInventoryValue, _codex_latent_clear_resume: codexLatentClearResumeValue, _terminal_user_explicit_send_action: terminalUserExplicitSendAction, _terminal_status_snapshot: _terminalStatusSnapshot, ...publicTerminal } = terminal;
783
953
  const codexOpenRootRolloutInventory = isRecord(codexOpenRootRolloutInventoryValue)
784
954
  ? codexOpenRootRolloutInventoryValue
785
955
  : undefined;
@@ -938,6 +1108,7 @@ function observeTerminalListBindingAuthority(terminal, context) {
938
1108
  automatedInputComposerReady,
939
1109
  codexOpenRootRolloutInventory,
940
1110
  codexLatentClearResumeValue,
1111
+ terminalUserExplicitSendAction,
941
1112
  publicTerminal,
942
1113
  terminalControl,
943
1114
  terminalHasNonterminalDeferredTransfer,
@@ -1235,14 +1406,9 @@ function observeTerminalHandoffAuthority(terminal, context, observation) {
1235
1406
  })
1236
1407
  : undefined;
1237
1408
  const blockingHandoffTurnIds = new Set(handoffSourceBlockingTurns.map((turn) => turn.conversation_id));
1238
- // An active source Turn may be superseded only through the snapshot-bound
1239
- // handoff decision above. Never expose the generic Store-only close action
1240
- // for that Turn, because doing so would bypass expected_handoff_token after
1241
- // the live native thread or Turn generation changed. `blocking_turns` is
1242
- // reserved for other same-incarnation blockers such as legacy collateral
1243
- // stalls; clear those first, refresh, and only then decide the handoff.
1244
- const terminalRecoveryBlockingTurns = terminalBlockingTurns.filter((turn) => !blockingHandoffTurnIds.has(turn.conversation_id) &&
1245
- !conversationHasNonterminalDeferredTransfer(turn));
1409
+ // A blocking managed Turn always remains explicitly closable. Close releases
1410
+ // AKK management only; it does not send input or stop the coding agent.
1411
+ const terminalRecoveryBlockingTurns = terminalBlockingTurns;
1246
1412
  return {
1247
1413
  ...observation,
1248
1414
  reconcileBindingAction,
@@ -1329,7 +1495,7 @@ function observeTerminalListActionAuthority(terminal, context, binding) {
1329
1495
  }
1330
1496
  function renderTerminalFirstListEntry(terminal, context, observation) {
1331
1497
  const { sessionAuthorityRequired, includeAll, mutationsAllowed, conversationHasNonterminalDeferredTransfer } = context;
1332
- const { automatedInputComposerReady, publicTerminal, allRelated, displayedRelated, relatedSessions, authoritativeSession, ownership, rawSendAction, sessionAwareRawActions, externalHandoffDetected, externalHandoffSnapshotToken, handoffSourceBlockingTurns, verifiedEmptyRawSendAction, verifiedEmptyCodexSnapshotToken, deferredCodexSourceRolloutAuthority, deferredCodexForegroundToken, reconcileBindingAction, externalHandoffAdoptable, handoffDecision, blockingHandoffTurnIds, terminalRecoveryBlockingTurns, terminalScopedCodexApprovalAction, rolloutBackedCodexSession } = observation;
1498
+ const { automatedInputComposerReady, terminalUserExplicitSendAction, publicTerminal, terminalControl, allRelated, displayedRelated, relatedSessions, authoritativeSession, ownership, rawSendAction, sessionAwareRawActions, externalHandoffDetected, externalHandoffSnapshotToken, handoffSourceBlockingTurns, verifiedEmptyRawSendAction, verifiedEmptyCodexSnapshotToken, deferredCodexSourceRolloutAuthority, deferredCodexForegroundToken, reconcileBindingAction, externalHandoffAdoptable, handoffDecision, blockingHandoffTurnIds, terminalRecoveryBlockingTurns, terminalScopedCodexApprovalAction, rolloutBackedCodexSession } = observation;
1333
1499
  const association = decideManagedTerminalAssociation({
1334
1500
  allRelated,
1335
1501
  displayedRelated,
@@ -1351,11 +1517,14 @@ function renderTerminalFirstListEntry(terminal, context, observation) {
1351
1517
  const currentTurnValue = ownership.state === "current"
1352
1518
  ? currentManagedTurnForTerminal(ownership.conversation, terminal, sessionAwareRawActions)
1353
1519
  : undefined;
1354
- const currentTurnProjection = currentTurnValue && (!mutationsAllowed ||
1355
- (ownership.state === "current" &&
1356
- conversationHasNonterminalDeferredTransfer(ownership.conversation)))
1357
- ? readOnlyManagedTurn(currentTurnValue)
1358
- : currentTurnValue;
1520
+ const currentTurnProjection = currentTurnValue
1521
+ ? !mutationsAllowed
1522
+ ? readOnlyManagedTurn(currentTurnValue)
1523
+ : ownership.state === "current" &&
1524
+ conversationHasNonterminalDeferredTransfer(ownership.conversation)
1525
+ ? userReleasableManagedTurn(currentTurnValue)
1526
+ : currentTurnValue
1527
+ : undefined;
1359
1528
  const currentTurn = currentTurnProjection
1360
1529
  ? withoutGenericHandoffSourceClose(currentTurnProjection, blockingHandoffTurnIds)
1361
1530
  : undefined;
@@ -1372,20 +1541,24 @@ function renderTerminalFirstListEntry(terminal, context, observation) {
1372
1541
  const recentTurnValue = recentConversation
1373
1542
  ? historicalManagedTurnForTerminal(recentConversation)
1374
1543
  : undefined;
1375
- const recentTurnProjection = recentTurnValue && (!mutationsAllowed ||
1376
- Boolean(recentConversation &&
1377
- conversationHasNonterminalDeferredTransfer(recentConversation)))
1378
- ? readOnlyManagedTurn(recentTurnValue)
1379
- : recentTurnValue;
1544
+ const recentTurnProjection = recentTurnValue
1545
+ ? !mutationsAllowed
1546
+ ? readOnlyManagedTurn(recentTurnValue)
1547
+ : recentConversation &&
1548
+ conversationHasNonterminalDeferredTransfer(recentConversation)
1549
+ ? userReleasableManagedTurn(recentTurnValue)
1550
+ : recentTurnValue
1551
+ : undefined;
1380
1552
  const recentTurn = recentTurnProjection
1381
1553
  ? withoutGenericHandoffSourceClose(recentTurnProjection, blockingHandoffTurnIds)
1382
1554
  : undefined;
1383
1555
  const history = historyConversations.map((conversation) => {
1384
1556
  const turn = historicalManagedTurnForTerminal(conversation);
1385
- return withoutGenericHandoffSourceClose(mutationsAllowed &&
1386
- !conversationHasNonterminalDeferredTransfer(conversation)
1387
- ? turn
1388
- : readOnlyManagedTurn(turn), blockingHandoffTurnIds);
1557
+ return withoutGenericHandoffSourceClose(!mutationsAllowed
1558
+ ? readOnlyManagedTurn(turn)
1559
+ : conversationHasNonterminalDeferredTransfer(conversation)
1560
+ ? userReleasableManagedTurn(turn)
1561
+ : turn, blockingHandoffTurnIds);
1389
1562
  });
1390
1563
  const visibleTurnIds = new Set([currentTurn, recentTurn, ...history]
1391
1564
  .map((turn) => stringValue(turn?.conversation_id))
@@ -1446,6 +1619,20 @@ function renderTerminalFirstListEntry(terminal, context, observation) {
1446
1619
  }
1447
1620
  }
1448
1621
  : undefined;
1622
+ const managedFastPathToken = "token" in sendAuthority
1623
+ ? sendAuthority.token
1624
+ : undefined;
1625
+ const prioritizedTerminalUserExplicitSendAction = terminalUserExplicitSendAction && managedFastPathToken
1626
+ ? {
1627
+ ...terminalUserExplicitSendAction,
1628
+ arguments: {
1629
+ ...(isRecord(terminalUserExplicitSendAction.arguments)
1630
+ ? terminalUserExplicitSendAction.arguments
1631
+ : {}),
1632
+ expected_managed_terminal_token: managedFastPathToken
1633
+ }
1634
+ }
1635
+ : terminalUserExplicitSendAction;
1449
1636
  const availableActions = selectTerminalAvailableActions({
1450
1637
  ownership: ownership.state,
1451
1638
  currentActions: ownership.state === "current"
@@ -1454,6 +1641,7 @@ function renderTerminalFirstListEntry(terminal, context, observation) {
1454
1641
  sessionAwareRawActions: sessionAwareRawActions,
1455
1642
  nonOwnerRawActions,
1456
1643
  authoritativeSendAction,
1644
+ terminalUserExplicitSendAction: prioritizedTerminalUserExplicitSendAction,
1457
1645
  reconcileBindingAction,
1458
1646
  terminalScopedApprovalAction: terminalScopedCodexApprovalAction,
1459
1647
  isAction: isRecord
@@ -1562,14 +1750,17 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
1562
1750
  });
1563
1751
  return {
1564
1752
  ...managedTurn,
1565
- available_actions: mutationsAllowed &&
1566
- !conversationHasNonterminalDeferredTransfer(conversation)
1567
- ? safeUnavailableManagedTurnActions(isRecord(managedTurn.available_actions)
1753
+ available_actions: !mutationsAllowed
1754
+ ? readOnlyListActions(isRecord(managedTurn.available_actions)
1568
1755
  ? managedTurn.available_actions
1569
1756
  : {})
1570
- : readOnlyListActions(isRecord(managedTurn.available_actions)
1571
- ? managedTurn.available_actions
1572
- : {}),
1757
+ : conversationHasNonterminalDeferredTransfer(conversation)
1758
+ ? userReleaseListActions(isRecord(managedTurn.available_actions)
1759
+ ? managedTurn.available_actions
1760
+ : {}, turnIdForConversation(conversation))
1761
+ : safeUnavailableManagedTurnActions(isRecord(managedTurn.available_actions)
1762
+ ? managedTurn.available_actions
1763
+ : {}),
1573
1764
  terminal_availability: {
1574
1765
  available: false,
1575
1766
  reason: managedOnly
@@ -2266,6 +2457,12 @@ function managedListApprovalState(conversation) {
2266
2457
  }
2267
2458
  async function resolveConversationSelectorOption(commandName, options) {
2268
2459
  const sendOperation = commandName === "send";
2460
+ // Submission retry is already bound to one authoritative managed Turn.
2461
+ // Keep it out of ordinary-send selector discovery so an omitted Session
2462
+ // target cannot be inferred and mixed into the exact `send --turn` form.
2463
+ if (sendOperation && stringValue(options.turn)) {
2464
+ return;
2465
+ }
2269
2466
  if (stringValue(options.expectedTerminalToken)) {
2270
2467
  // Selector resolution may replace an alias (or an omitted selector) with a
2271
2468
  // discovered full terminal id. Preserve the caller's actual authority so
@@ -2282,11 +2479,54 @@ async function resolveConversationSelectorOption(commandName, options) {
2282
2479
  const supplied = stringValue(sendOperation
2283
2480
  ? options.session ?? options.conversation ?? options.conversationId
2284
2481
  : options.turn ?? options.conversation ?? options.conversationId)?.trim();
2285
- if (supplied && !isSessionSelectorSyntax(supplied)) {
2482
+ if (sendOperation &&
2483
+ options.managedOnly === true &&
2484
+ supplied?.startsWith("terminal:v") &&
2485
+ !stringValue(options.expectedTerminalToken)) {
2486
+ // `--managed-only` is an explicit diagnostic/test request for the legacy
2487
+ // managed state machine. Keep an exact physical id on that path without
2488
+ // upgrading it to the default user-priority Send authority. Ordinary user
2489
+ // Send never sets this option and continues to discover and bind fresh
2490
+ // physical authority below.
2491
+ options.session = supplied;
2492
+ return;
2493
+ }
2494
+ if (supplied &&
2495
+ !isSessionSelectorSyntax(supplied) &&
2496
+ !(sendOperation &&
2497
+ supplied.startsWith("terminal:v") &&
2498
+ !stringValue(options.expectedTerminalToken))) {
2286
2499
  // Full authoritative IDs keep their existing command-specific validation
2287
2500
  // path. This avoids a discovery scan before option validation and preserves
2288
2501
  // precise downstream errors for closed or currently non-actionable state.
2289
2502
  if (sendOperation) {
2503
+ if (supplied.startsWith("terminal:v") &&
2504
+ stringValue(options.expectedTerminalToken)) {
2505
+ try {
2506
+ const observed = await observeExactTerminal({
2507
+ options,
2508
+ terminalId: supplied
2509
+ });
2510
+ const actions = observed.state === "available" &&
2511
+ isRecord(observed.terminal.available_actions)
2512
+ ? observed.terminal.available_actions
2513
+ : {};
2514
+ const send = isRecord(actions.send) ? actions.send : {};
2515
+ const argumentsValue = isRecord(send.arguments)
2516
+ ? send.arguments
2517
+ : {};
2518
+ options.expectedManagedTerminalToken =
2519
+ stringValue(argumentsValue.expected_terminal_token) ===
2520
+ stringValue(options.expectedTerminalToken)
2521
+ ? stringValue(argumentsValue.expected_managed_terminal_token)
2522
+ : undefined;
2523
+ }
2524
+ catch {
2525
+ // Managed fast-path discovery is optional. The physical Send token
2526
+ // is still revalidated by execution and may fall back without Store.
2527
+ options.expectedManagedTerminalToken = undefined;
2528
+ }
2529
+ }
2290
2530
  options.session = supplied;
2291
2531
  }
2292
2532
  else {
@@ -2299,12 +2539,24 @@ async function resolveConversationSelectorOption(commandName, options) {
2299
2539
  operation: commandName
2300
2540
  });
2301
2541
  if (sendOperation) {
2302
- options.session = resolution.id;
2542
+ const physical = resolution.candidate.userExplicitTerminalSend;
2543
+ if (!physical) {
2544
+ throw new Error("the selected terminal no longer advertises user-priority Send");
2545
+ }
2546
+ // Preserve a token-bearing caller's original selector for the exact fence.
2547
+ if (!stringValue(options.expectedTerminalToken)) {
2548
+ terminalListRuntime().rememberOriginalExpectedTerminalSelector(options, physical.terminalId);
2549
+ }
2550
+ options.expectedTerminalToken = physical.expectedTerminalToken;
2551
+ options.expectedManagedTerminalToken =
2552
+ physical.expectedManagedTerminalToken;
2553
+ options.session = physical.terminalId;
2554
+ options.conversation = physical.terminalId;
2303
2555
  }
2304
2556
  else {
2305
2557
  options.turn = resolution.id;
2558
+ options.conversation = resolution.id;
2306
2559
  }
2307
- options.conversation = resolution.id;
2308
2560
  delete options.conversationId;
2309
2561
  }
2310
2562
  function isSessionSelectorSyntax(value) {
@@ -2312,6 +2564,59 @@ function isSessionSelectorSyntax(value) {
2312
2564
  /^@[0-9a-f]+$/iu.test(value));
2313
2565
  }
2314
2566
  async function sessionSelectorCandidates(commandName, options) {
2567
+ const terminalScan = await buildTerminalListGroup({
2568
+ options: {
2569
+ ...options,
2570
+ noApprovalScan: commandName === "send"
2571
+ ? false
2572
+ : ["approve", "cancel"].includes(commandName)
2573
+ ? options.noApprovalScan
2574
+ : true
2575
+ },
2576
+ agentFilter: undefined,
2577
+ statusFilter: undefined
2578
+ });
2579
+ const observedAtMs = cliNowMs();
2580
+ if (commandName === "send") {
2581
+ return terminalScan.terminalControlled
2582
+ .filter((entry) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd))
2583
+ .flatMap((entry) => {
2584
+ const action = isRecord(entry._terminal_user_explicit_send_action)
2585
+ ? entry._terminal_user_explicit_send_action
2586
+ : undefined;
2587
+ const argumentsValue = isRecord(action?.arguments)
2588
+ ? action.arguments
2589
+ : undefined;
2590
+ const terminalId = stringValue(entry.id);
2591
+ const expectedTerminalToken = stringValue(argumentsValue?.expected_terminal_token);
2592
+ if (action?.scope !== "terminal_user_explicit" ||
2593
+ !terminalId ||
2594
+ stringValue(argumentsValue?.selector ?? argumentsValue?.terminal_id) !== terminalId ||
2595
+ !expectedTerminalToken) {
2596
+ return [];
2597
+ }
2598
+ const candidate = projectSelectorCandidate({
2599
+ ...entry,
2600
+ available_actions: { send: action }
2601
+ }, commandName, observedAtMs, {
2602
+ defaultActionable: true,
2603
+ // Physical Send authority is independent of Store writability.
2604
+ mutationsAllowed: true
2605
+ });
2606
+ return [{
2607
+ ...candidate,
2608
+ userExplicitTerminalSend: {
2609
+ terminalId,
2610
+ expectedTerminalToken,
2611
+ ...(stringValue(argumentsValue?.expected_managed_terminal_token) === undefined
2612
+ ? {}
2613
+ : {
2614
+ expectedManagedTerminalToken: stringValue(argumentsValue?.expected_managed_terminal_token)
2615
+ })
2616
+ }
2617
+ }];
2618
+ });
2619
+ }
2315
2620
  const storeDir = terminalListRuntime().storeDirFromOptions(options);
2316
2621
  const mutationsAllowed = inspectStoreCompatibility(storeDir).writable === true;
2317
2622
  const selectorStore = inspectStoreCompatibility(storeDir);
@@ -2325,16 +2630,6 @@ async function sessionSelectorCandidates(commandName, options) {
2325
2630
  approvalState: managedListApprovalState(conversation),
2326
2631
  conversation
2327
2632
  }));
2328
- const terminalScan = await buildTerminalListGroup({
2329
- options: {
2330
- ...options,
2331
- noApprovalScan: ["send", "approve", "cancel"].includes(commandName)
2332
- ? options.noApprovalScan
2333
- : true
2334
- },
2335
- agentFilter: undefined,
2336
- statusFilter: undefined
2337
- });
2338
2633
  const terminalProjection = terminalFirstListProjection({
2339
2634
  storeDir,
2340
2635
  terminals: terminalScan.terminalControlled.filter((entry) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd)),
@@ -2349,87 +2644,6 @@ async function sessionSelectorCandidates(commandName, options) {
2349
2644
  statusFilter: undefined,
2350
2645
  mutationsAllowed
2351
2646
  });
2352
- const observedAtMs = cliNowMs();
2353
- if (commandName === "send") {
2354
- const sessionEntries = terminalProjection.terminals.flatMap((entry) => {
2355
- const managedState = isRecord(entry.managed) ? entry.managed : undefined;
2356
- const recentTurn = isRecord(managedState?.recent_turn)
2357
- ? managedState.recent_turn
2358
- : undefined;
2359
- const currentTurn = isRecord(managedState?.current_turn)
2360
- ? managedState.current_turn
2361
- : undefined;
2362
- const sessionId = stringValue(managedState?.session_id);
2363
- const actions = isRecord(entry.available_actions)
2364
- ? entry.available_actions
2365
- : undefined;
2366
- const sendAction = isRecord(actions?.send)
2367
- ? actions.send
2368
- : undefined;
2369
- const sendArguments = isRecord(sendAction?.arguments)
2370
- ? sendAction.arguments
2371
- : undefined;
2372
- if (!sessionId ||
2373
- !sendAction ||
2374
- stringValue(sendArguments?.session_id) !== sessionId) {
2375
- return [];
2376
- }
2377
- const commonEntry = {
2378
- agent: entry.agent,
2379
- status: "idle",
2380
- workspace: entry.workspace ?? entry.cwd,
2381
- updated_at: recentTurn?.updated_at ??
2382
- currentTurn?.updated_at,
2383
- available_actions: {
2384
- send: sendAction
2385
- }
2386
- };
2387
- return [
2388
- {
2389
- ...commonEntry,
2390
- id: sessionId,
2391
- short_ref: sessionShortRef(sessionId),
2392
- source: "managed_session"
2393
- },
2394
- {
2395
- ...commonEntry,
2396
- id: String(entry.id),
2397
- short_ref: stringValue(entry.short_ref) ??
2398
- sessionShortRef(String(entry.id)),
2399
- source: "managed_session_terminal_alias"
2400
- }
2401
- ];
2402
- });
2403
- const rawTerminalEntries = terminalProjection.terminals.filter((entry) => {
2404
- const managedState = isRecord(entry.managed) ? entry.managed : undefined;
2405
- const actions = isRecord(entry.available_actions)
2406
- ? entry.available_actions
2407
- : undefined;
2408
- const sendAction = isRecord(actions?.send) ? actions.send : undefined;
2409
- const sendArguments = isRecord(sendAction?.arguments)
2410
- ? sendAction.arguments
2411
- : undefined;
2412
- return (!stringValue(managedState?.session_id) ||
2413
- !stringValue(sendArguments?.session_id));
2414
- });
2415
- return [
2416
- ...managed.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2417
- defaultActionable: false,
2418
- mutationsAllowed
2419
- })),
2420
- ...sessionEntries.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2421
- // A managed terminal's full id/@short-ref is an explicit alias for
2422
- // its Session send target. It must not duplicate the Session in
2423
- // omitted, only, latest, or agent-name selection.
2424
- defaultActionable: entry.source !== "managed_session_terminal_alias",
2425
- mutationsAllowed
2426
- })),
2427
- ...rawTerminalEntries.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2428
- defaultActionable: true,
2429
- mutationsAllowed
2430
- }))
2431
- ];
2432
- }
2433
2647
  return [
2434
2648
  ...managed.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2435
2649
  defaultActionable: options.managedOnly === true,