@scotthuang/agent-knock-knock 0.12.12 → 0.12.14

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 (164) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +40 -27
  3. package/dist/src/approval-policy.d.ts +11 -2
  4. package/dist/src/approval-policy.js +33 -7
  5. package/dist/src/approval-policy.js.map +1 -1
  6. package/dist/src/callback-cli-adapter.d.ts +9 -6
  7. package/dist/src/callback-cli-adapter.js +119 -30
  8. package/dist/src/callback-cli-adapter.js.map +1 -1
  9. package/dist/src/callback-outbox-policy.d.ts +39 -0
  10. package/dist/src/callback-outbox-policy.js +163 -6
  11. package/dist/src/callback-outbox-policy.js.map +1 -1
  12. package/dist/src/callback-outbox-service.d.ts +28 -1
  13. package/dist/src/callback-outbox-service.js +413 -82
  14. package/dist/src/callback-outbox-service.js.map +1 -1
  15. package/dist/src/callback-outbox-settlement.d.ts +14 -1
  16. package/dist/src/callback-outbox-settlement.js +151 -30
  17. package/dist/src/callback-outbox-settlement.js.map +1 -1
  18. package/dist/src/callback-route-authority.d.ts +59 -0
  19. package/dist/src/callback-route-authority.js +123 -0
  20. package/dist/src/callback-route-authority.js.map +1 -0
  21. package/dist/src/callback-transport.d.ts +134 -0
  22. package/dist/src/callback-transport.js +256 -0
  23. package/dist/src/callback-transport.js.map +1 -0
  24. package/dist/src/claude-local-transcript-provider.d.ts +81 -0
  25. package/dist/src/claude-local-transcript-provider.js +854 -18
  26. package/dist/src/claude-local-transcript-provider.js.map +1 -1
  27. package/dist/src/cli-core.js +102 -4
  28. package/dist/src/cli-core.js.map +1 -1
  29. package/dist/src/codex-lifecycle-compatibility.js +2 -1
  30. package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
  31. package/dist/src/deferred-foreground-application-service.d.ts +18 -0
  32. package/dist/src/deferred-foreground-application-service.js +72 -0
  33. package/dist/src/deferred-foreground-application-service.js.map +1 -1
  34. package/dist/src/deferred-foreground-authority-cli-adapter.js +92 -20
  35. package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -1
  36. package/dist/src/deferred-foreground-recovery-cli-adapter.d.ts +1 -1
  37. package/dist/src/deferred-foreground-recovery-cli-adapter.js +108 -37
  38. package/dist/src/deferred-foreground-recovery-cli-adapter.js.map +1 -1
  39. package/dist/src/deferred-foreground-recovery-service.d.ts +1 -1
  40. package/dist/src/deferred-foreground-recovery-service.js +10 -3
  41. package/dist/src/deferred-foreground-recovery-service.js.map +1 -1
  42. package/dist/src/deferred-foreground-transfer-policy.d.ts +1 -1
  43. package/dist/src/deferred-foreground-transfer-policy.js +3 -2
  44. package/dist/src/deferred-foreground-transfer-policy.js.map +1 -1
  45. package/dist/src/deferred-foreground-transfer.d.ts +17 -1
  46. package/dist/src/deferred-foreground-transfer.js +247 -39
  47. package/dist/src/deferred-foreground-transfer.js.map +1 -1
  48. package/dist/src/deferred-foreground-user-close.d.ts +18 -0
  49. package/dist/src/deferred-foreground-user-close.js +180 -0
  50. package/dist/src/deferred-foreground-user-close.js.map +1 -0
  51. package/dist/src/openclaw-callback-transport.d.ts +9 -1
  52. package/dist/src/openclaw-callback-transport.js +404 -11
  53. package/dist/src/openclaw-callback-transport.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-callback-adapter.js +96 -14
  58. package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -1
  59. package/dist/src/openclaw-plugin-command-adapter.js +547 -109
  60. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  61. package/dist/src/openclaw-plugin-helpers.d.ts +27 -4
  62. package/dist/src/openclaw-plugin-helpers.js +317 -44
  63. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  64. package/dist/src/openclaw-plugin-schemas.d.ts +61 -66
  65. package/dist/src/openclaw-plugin-schemas.js +87 -91
  66. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  67. package/dist/src/openclaw-plugin-supervisor.js +48 -10
  68. package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
  69. package/dist/src/openclaw-private-authority-offers.d.ts +22 -0
  70. package/dist/src/openclaw-private-authority-offers.js +177 -0
  71. package/dist/src/openclaw-private-authority-offers.js.map +1 -0
  72. package/dist/src/protocol.d.ts +3 -0
  73. package/dist/src/protocol.js +4 -0
  74. package/dist/src/protocol.js.map +1 -1
  75. package/dist/src/store.d.ts +10 -0
  76. package/dist/src/store.js +82 -2
  77. package/dist/src/store.js.map +1 -1
  78. package/dist/src/terminal-acceptance-cli-adapter.d.ts +2 -0
  79. package/dist/src/terminal-acceptance-cli-adapter.js +62 -12
  80. package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -1
  81. package/dist/src/terminal-action-projection.d.ts +19 -2
  82. package/dist/src/terminal-action-projection.js +27 -5
  83. package/dist/src/terminal-action-projection.js.map +1 -1
  84. package/dist/src/terminal-agent-bridge.d.ts +82 -0
  85. package/dist/src/terminal-agent-bridge.js +539 -111
  86. package/dist/src/terminal-agent-bridge.js.map +1 -1
  87. package/dist/src/terminal-command-cli-adapter.d.ts +14 -0
  88. package/dist/src/terminal-command-cli-adapter.js +2435 -150
  89. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  90. package/dist/src/terminal-dispatch-application.d.ts +4 -1
  91. package/dist/src/terminal-dispatch-application.js +11 -2
  92. package/dist/src/terminal-dispatch-application.js.map +1 -1
  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.js +305 -79
  109. package/dist/src/terminal-dispatch-repository-cli-adapter.js.map +1 -1
  110. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +2 -0
  111. package/dist/src/terminal-identity-authority-cli-adapter.js +30 -27
  112. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
  113. package/dist/src/terminal-list-cli-adapter.d.ts +32 -7
  114. package/dist/src/terminal-list-cli-adapter.js +335 -98
  115. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  116. package/dist/src/terminal-list-renderer.d.ts +11 -1
  117. package/dist/src/terminal-list-renderer.js +328 -132
  118. package/dist/src/terminal-list-renderer.js.map +1 -1
  119. package/dist/src/terminal-maintenance-cli-adapter.js +253 -326
  120. package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -1
  121. package/dist/src/terminal-monitor-application-service.d.ts +5 -0
  122. package/dist/src/terminal-monitor-application-service.js +5 -0
  123. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  124. package/dist/src/terminal-monitor-cli-adapter.d.ts +25 -1
  125. package/dist/src/terminal-monitor-cli-adapter.js +171 -28
  126. package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
  127. package/dist/src/terminal-monitor-reconciliation-eligibility.d.ts +1 -1
  128. package/dist/src/terminal-monitor-reconciliation-eligibility.js +220 -32
  129. package/dist/src/terminal-monitor-reconciliation-eligibility.js.map +1 -1
  130. package/dist/src/terminal-monitor-state-cli-adapter.d.ts +1 -1
  131. package/dist/src/terminal-monitor-state-cli-adapter.js +814 -162
  132. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
  133. package/dist/src/terminal-monitor-state-reconciliation-service.d.ts +2 -1
  134. package/dist/src/terminal-monitor-state-reconciliation-service.js +12 -0
  135. package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -1
  136. package/dist/src/terminal-monitor-supervision-cli-adapter.d.ts +2 -0
  137. package/dist/src/terminal-monitor-supervision-cli-adapter.js +31 -11
  138. package/dist/src/terminal-monitor-supervision-cli-adapter.js.map +1 -1
  139. package/dist/src/terminal-status-cli-adapter.d.ts +11 -0
  140. package/dist/src/terminal-status-cli-adapter.js +119 -35
  141. package/dist/src/terminal-status-cli-adapter.js.map +1 -1
  142. package/dist/src/terminal-submission-acceptance.d.ts +60 -1
  143. package/dist/src/terminal-submission-acceptance.js +613 -0
  144. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  145. package/dist/src/terminal-submission-retry-service.d.ts +114 -0
  146. package/dist/src/terminal-submission-retry-service.js +589 -0
  147. package/dist/src/terminal-submission-retry-service.js.map +1 -0
  148. package/dist/src/terminal-watch-callback-cli-adapter.d.ts +28 -0
  149. package/dist/src/terminal-watch-callback-cli-adapter.js +94 -0
  150. package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -0
  151. package/dist/src/terminal-watch-cli-adapter.d.ts +64 -0
  152. package/dist/src/terminal-watch-cli-adapter.js +532 -0
  153. package/dist/src/terminal-watch-cli-adapter.js.map +1 -0
  154. package/dist/src/terminal-watch-service.d.ts +102 -0
  155. package/dist/src/terminal-watch-service.js +883 -0
  156. package/dist/src/terminal-watch-service.js.map +1 -0
  157. package/dist/src/terminal-watch-store.d.ts +172 -0
  158. package/dist/src/terminal-watch-store.js +1019 -0
  159. package/dist/src/terminal-watch-store.js.map +1 -0
  160. package/docs/quickstart-herdr.md +8 -0
  161. package/docs/quickstart-tmux.md +33 -6
  162. package/openclaw.plugin.json +11 -3
  163. package/package.json +1 -1
  164. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +61 -40
@@ -11,13 +11,14 @@ import { projectSessionSelectorCandidate } from "./terminal-selector-projection-
11
11
  import { listManagedSessions, loadNativeThreadTransition, nativeThreadTransitionsDir, tryLoadManagedSession } from "./session-store.js";
12
12
  import { defaultStoreDir, ensureStoreWritable, inspectStoreCompatibility, listConversations, STORE_SESSION_AUTHORITY_PROTOCOL } from "./store.js";
13
13
  import { exactCodexReadyStyledComposerCapture } from "./terminal-agent-bridge.js";
14
+ import { captureCodexHumanStartedActiveTaskAnchor } from "./terminal-submission-acceptance.js";
14
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
16
  import { childProcessIdsForRoot, decideManagedBindingConflict, deferredCodexForegroundBindingToken, exactBoundCodexSendSource, isCompleteNativeRollout, nativeAgentIdentityMatchesTurn, processIncarnationRelationship, selectRootTerminalProcesses, terminalControlAliasMatches, terminalControlsShareIncarnation, verifiedEmptyCodexHandoffToken } from "./terminal-authority-policy.js";
16
17
  import { decideTerminalBindingMatch, terminalObservationFromListEntry } from "./terminal-binding-authority.js";
17
18
  import { decideTerminalScopedCodexApprovalAuthority, terminalScopedCodexApprovalPromptSnapshot } from "./terminal-scoped-approval-authority.js";
18
19
  import { sameCanonicalStatePath } from "./terminal-dispatch-ledger-codec.js";
19
20
  import * as dispatch from "./terminal-dispatch-policy.js";
20
- 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";
21
22
  import { hasCanonicalTerminalEndpoint, terminalControlEvidenceMatches } from "./terminal-control-ref.js";
22
23
  import { validTerminalMonitorTimestampMs as validTimestampMs } from "./terminal-monitor-decision-policy.js";
23
24
  import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
@@ -46,6 +47,7 @@ export function createTerminalListCliFacade(dependencies) {
46
47
  return {
47
48
  runList: (options) => call(() => runList(options)),
48
49
  buildTerminalListGroup: (request) => call(() => buildTerminalListGroup(request)),
50
+ observeExactTerminal: (request) => call(() => observeExactTerminal(request)),
49
51
  provisionalManagedBindingTurnCount: (storeDir, session) => call(() => provisionalManagedBindingTurnCount(storeDir, session)),
50
52
  managedSessionHasUnresolvedNativeTransition: (storeDir, session) => call(() => managedSessionHasUnresolvedNativeTransition(storeDir, session)),
51
53
  managedSessionHasAnyNativeTransition: (storeDir, session) => call(() => managedSessionHasAnyNativeTransition(storeDir, session)),
@@ -70,56 +72,149 @@ async function runList(options) {
70
72
  status: "disabled",
71
73
  reason: "standalone list is read-only unless --reconcile is supplied"
72
74
  };
73
- const includeAll = Boolean(options.all);
74
75
  const agentFilter = options.agent ? resolveExecutor({ kind: options.agent }).kind : undefined;
75
76
  const statusFilter = options.status;
76
- const allStoredConversations = listConversations(storeDir);
77
- const allManagedConversations = allStoredConversations
77
+ const terminalScan = await buildTerminalListGroup({ options, agentFilter, statusFilter });
78
+ const projected = projectTerminalListScan({
79
+ options,
80
+ storeDir,
81
+ store,
82
+ terminalScan,
83
+ agentFilter,
84
+ statusFilter
85
+ });
86
+ printJson({
87
+ store_dir: storeDir,
88
+ store,
89
+ reconciliation,
90
+ action_contracts: listActionContracts(),
91
+ terminals: projected.terminals,
92
+ terminal_watches: projected.terminalWatches,
93
+ unavailable_managed_turns: projected.unavailableManagedTurns,
94
+ terminal_scan: {
95
+ ...terminalScan.summary,
96
+ terminal_count: projected.terminals.length
97
+ }
98
+ });
99
+ runtimeLog("info", "terminals_listed", {
100
+ store_dir: storeDir,
101
+ terminal_count: projected.terminals.length,
102
+ unavailable_managed_turn_count: projected.unavailableManagedTurns.length,
103
+ terminal_scan_error: terminalScan.summary.error,
104
+ include_all: projected.includeAll,
105
+ agent_filter: agentFilter,
106
+ status_filter: statusFilter,
107
+ reconciliation
108
+ });
109
+ }
110
+ function projectTerminalListScan(input) {
111
+ const { options, storeDir, store, terminalScan, agentFilter, statusFilter, tolerateInvalidWatchRecords } = input;
112
+ const includeAll = Boolean(options.all);
113
+ const allManagedConversations = listConversations(storeDir)
78
114
  .filter(terminalListRuntime().isDiscoverableTmuxConversation);
79
115
  const managedSessions = store.readable
80
116
  ? listManagedSessions(storeDir)
81
117
  : [];
82
- const storedConversations = allManagedConversations
118
+ const displayedConversations = allManagedConversations
83
119
  .filter((conversation) => includeAll || terminalListRuntime().isActiveStatus(conversation.status))
84
120
  .filter((conversation) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, conversation.workspace))
85
121
  .filter((conversation) => !agentFilter || executorForConversation(conversation).kind === agentFilter)
86
122
  .filter((conversation) => !statusFilter || conversation.status === statusFilter);
87
- const terminalScan = await buildTerminalListGroup({ options, agentFilter, statusFilter });
123
+ const workspaceConversations = allManagedConversations.filter((conversation) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, conversation.workspace));
88
124
  const physicalTerminals = terminalScan.terminalControlled.filter((entry) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd));
89
125
  const projection = terminalFirstListProjection({
90
126
  storeDir,
91
127
  terminals: physicalTerminals,
92
128
  managedSessions,
93
129
  sessionAuthorityRequired: Number(store.writer_protocol) >= STORE_SESSION_AUTHORITY_PROTOCOL,
94
- allConversations: allManagedConversations.filter((conversation) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, conversation.workspace)),
95
- displayedConversations: storedConversations,
130
+ allConversations: workspaceConversations,
131
+ displayedConversations,
96
132
  includeAll,
97
133
  managedOnly: options.managedOnly === true,
98
134
  statusFilter,
99
135
  mutationsAllowed: store.writable === true
100
136
  });
101
- printJson({
102
- store_dir: storeDir,
137
+ const exactWatchScan = terminalListRuntime()
138
+ .scanTerminalWatchesForExactObservation;
139
+ const watchObservation = tolerateInvalidWatchRecords && exactWatchScan
140
+ ? exactWatchScan(storeDir, { includeAll })
141
+ : {
142
+ watches: terminalListRuntime().listTerminalWatches?.(storeDir, { includeAll }) ?? [],
143
+ activeOverlayTrusted: true
144
+ };
145
+ const observedTerminalWatches = watchObservation.watches;
146
+ const terminalWatches = options.managedOnly
147
+ ? []
148
+ : observedTerminalWatches
149
+ .filter((watch) => !agentFilter || watch.agent === agentFilter)
150
+ .filter((watch) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, watch.workspace))
151
+ .filter((watch) => !statusFilter || watch.status === statusFilter);
152
+ const activeWatchedTerminals = new Set(observedTerminalWatches
153
+ .filter((watch) => watch.status === "active")
154
+ .map((watch) => stringValue(watch.terminal_id))
155
+ .filter((terminalId) => terminalId !== undefined));
156
+ const terminals = projection.terminals.map((terminal) => !watchObservation.activeOverlayTrusted ||
157
+ activeWatchedTerminals.has(stringValue(terminal.id) ?? "")
158
+ ? withoutTerminalWatchAuthority(terminal)
159
+ : terminal);
160
+ return {
161
+ includeAll,
162
+ terminals,
163
+ terminalWatches,
164
+ unavailableManagedTurns: projection.unavailableManagedTurns
165
+ };
166
+ }
167
+ async function observeExactTerminal(request) {
168
+ const { options, terminalId } = request;
169
+ const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(cliCwd()));
170
+ const store = inspectStoreCompatibility(storeDir);
171
+ const scan = await buildTerminalListGroup({ options, terminalId });
172
+ const matches = scan.terminalControlled.filter((terminal) => stringValue(terminal.id) === terminalId);
173
+ if (matches.length !== 1) {
174
+ return scan.summary.error
175
+ ? {
176
+ state: "unavailable",
177
+ reason: scan.summary.error,
178
+ summary: scan.summary
179
+ }
180
+ : { state: "absent", summary: scan.summary };
181
+ }
182
+ const projected = projectTerminalListScan({
183
+ options,
184
+ storeDir,
103
185
  store,
104
- reconciliation,
105
- action_contracts: listActionContracts(),
106
- terminals: projection.terminals,
107
- unavailable_managed_turns: projection.unavailableManagedTurns,
108
- terminal_scan: {
109
- ...terminalScan.summary,
110
- terminal_count: projection.terminals.length
111
- }
112
- });
113
- runtimeLog("info", "terminals_listed", {
114
- store_dir: storeDir,
115
- terminal_count: projection.terminals.length,
116
- unavailable_managed_turn_count: projection.unavailableManagedTurns.length,
117
- terminal_scan_error: terminalScan.summary.error,
118
- include_all: includeAll,
119
- agent_filter: agentFilter,
120
- status_filter: statusFilter,
121
- reconciliation
186
+ terminalScan: scan,
187
+ tolerateInvalidWatchRecords: true
122
188
  });
189
+ const projectedMatches = projected.terminals.filter((terminal) => stringValue(terminal.id) === terminalId);
190
+ if (projectedMatches.length !== 1) {
191
+ return {
192
+ state: "unavailable",
193
+ reason: "the exact terminal could not be projected authoritatively",
194
+ summary: scan.summary
195
+ };
196
+ }
197
+ return {
198
+ state: "available",
199
+ rawTerminal: matches[0],
200
+ terminal: projectedMatches[0],
201
+ summary: scan.summary
202
+ };
203
+ }
204
+ function withoutAvailableAction(terminal, action) {
205
+ if (!isRecord(terminal.available_actions))
206
+ return terminal;
207
+ const actions = { ...terminal.available_actions };
208
+ delete actions[action];
209
+ return { ...terminal, available_actions: actions };
210
+ }
211
+ function withoutTerminalWatchAuthority(terminal) {
212
+ const projected = withoutAvailableAction(terminal, "watch");
213
+ if (!Object.hasOwn(projected, "terminal_watch_hint"))
214
+ return projected;
215
+ const safe = { ...projected };
216
+ delete safe.terminal_watch_hint;
217
+ return safe;
123
218
  }
124
219
  async function reconcileStoreForList(storeDir, options) {
125
220
  try {
@@ -157,7 +252,7 @@ async function reconcileStoreForList(storeDir, options) {
157
252
  idle_timeout_minutes: idle.idle_timeout_minutes
158
253
  };
159
254
  }
160
- async function buildTerminalListGroup({ options, agentFilter, statusFilter }) {
255
+ async function buildTerminalListGroup({ options, agentFilter, statusFilter, terminalId }) {
161
256
  const empty = {
162
257
  terminalControlled: [],
163
258
  summary: {
@@ -198,8 +293,17 @@ async function buildTerminalListGroup({ options, agentFilter, statusFilter }) {
198
293
  const rootSessions = selectRootTerminalProcesses(activeSessions);
199
294
  const controlledSessions = rootSessions.filter((session) => session.terminalControl !== undefined);
200
295
  activeCount = controlledSessions.length;
201
- for (const session of controlledSessions) {
202
- terminalControlled.push(await terminalControlledListEntry(session, activeSessions, options, bridge));
296
+ const selectedSessions = terminalId
297
+ ? controlledSessions.filter((session) => bridge.terminalConversationId(session) === terminalId)
298
+ : controlledSessions;
299
+ for (const session of selectedSessions) {
300
+ try {
301
+ terminalControlled.push(await terminalControlledListEntry(session, activeSessions, options, bridge));
302
+ }
303
+ catch (error) {
304
+ errors.push(`terminal process ${session.pid}: ` +
305
+ (error instanceof Error ? error.message : String(error)));
306
+ }
203
307
  }
204
308
  }
205
309
  catch (error) {
@@ -236,9 +340,10 @@ function managedTurnListActionFacts(task, conversation) {
236
340
  const terminalBridgeReady = Boolean(conversation &&
237
341
  terminalListRuntime().terminalBridgeEnabled(conversation) &&
238
342
  terminalListRuntime().terminalControlFromTakeover(nativeTakeover) !== undefined);
343
+ const submission = terminalListRuntime().terminalBridgeSubmission(conversation);
239
344
  const renewEligible = Boolean(terminalBridgeReady &&
240
345
  task.status === "stalled" &&
241
- terminalListRuntime().terminalBridgeSubmission(conversation)?.status !== "uncertain" &&
346
+ submission?.status !== "uncertain" &&
242
347
  !terminalListRuntime().isVerifiedDeadTerminalAgentProcess(conversation ?? {}));
243
348
  const callbackDelivery = isRecord(conversation?.callback_delivery)
244
349
  ? conversation.callback_delivery
@@ -247,11 +352,18 @@ function managedTurnListActionFacts(task, conversation) {
247
352
  conversation.legacy_callback_status_error === undefined &&
248
353
  terminalListRuntime().callbackRetryDisposition(callbackDelivery).state ===
249
354
  "retryable");
355
+ const retrySubmissionCandidate = Boolean(conversation &&
356
+ executorForConversation(conversation).kind === "codex" &&
357
+ terminalBridgeReady &&
358
+ task.status === "stalled" &&
359
+ submission?.status === "uncertain" &&
360
+ stringValue(submission.last_proven_stage) === "text_injected");
250
361
  return {
251
362
  terminalBridgeReady,
252
363
  managedApprovalPending,
253
364
  renewEligible,
254
- retryCallbackEligible
365
+ retryCallbackEligible,
366
+ retrySubmissionCandidate
255
367
  };
256
368
  }
257
369
  async function terminalControlledListEntry(session, activeSessions, options, bridge = terminalListRuntime().createTerminalAgentBridge(options)) {
@@ -272,17 +384,28 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
272
384
  terminalTarget: terminalControl.target
273
385
  });
274
386
  const { orphanedDispatch, nativeIdentityObservation, nativeAgentIdentity, codexOpenRootRolloutInventory, nativeProcessUuid, nativeProcessBirth, nativeProcessEvidence } = await observeTerminalNativeListIdentity(session, terminalControl, options, bridge);
387
+ const effectiveTerminalState = effectiveTerminalListState({
388
+ session,
389
+ terminalState,
390
+ nativeIdentityObservation,
391
+ nativeAgentIdentity,
392
+ nativeProcessUuid,
393
+ nativeProcessBirth
394
+ });
395
+ const terminalStatusSnapshot = effectiveTerminalState._terminal_status_snapshot
396
+ ? terminalBridgeStatusWithActivity(effectiveTerminalState._terminal_status_snapshot, effectiveTerminalState.activity_state, effectiveTerminalState.activity_reason)
397
+ : undefined;
275
398
  const statusCardObservation = session.agent === "codex" &&
276
- typeof terminalState.screen_excerpt === "string"
399
+ typeof effectiveTerminalState.screen_excerpt === "string"
277
400
  ? bridge.registry.require("codex").observeThreadLifecycle?.({
278
401
  operation: { kind: "new_thread" },
279
402
  phase: "before",
280
- screen: terminalState.screen_excerpt
403
+ screen: effectiveTerminalState.screen_excerpt
281
404
  })
282
405
  : undefined;
283
406
  const statusCardNativeThreadId = statusCardObservation?.status === "observed" &&
284
- terminalState.activity_state === "idle" &&
285
- terminalState.approval_state.blocked !== true &&
407
+ effectiveTerminalState.activity_state === "idle" &&
408
+ effectiveTerminalState.approval_state.blocked !== true &&
286
409
  isExactNativeThreadId(statusCardObservation.nativeThreadId)
287
410
  ? statusCardObservation.nativeThreadId
288
411
  : undefined;
@@ -304,7 +427,7 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
304
427
  };
305
428
  const codexLatentClearResumeObservationValue = session.agent === "codex"
306
429
  ? terminalListRuntime().codexLatentClearResumeObservation({
307
- screen: terminalState.screen_excerpt,
430
+ screen: effectiveTerminalState.screen_excerpt,
308
431
  agentVersion
309
432
  })
310
433
  : undefined;
@@ -325,7 +448,7 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
325
448
  const automatedInputComposerReady = await observeAutomatedInputComposerReady({
326
449
  session,
327
450
  terminalControl,
328
- terminalState,
451
+ terminalState: effectiveTerminalState,
329
452
  nativeIdentityObservation,
330
453
  codexOpenRootRolloutInventory,
331
454
  options
@@ -363,9 +486,15 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
363
486
  confidence: session.confidence,
364
487
  reason: session.reason,
365
488
  terminal_control: terminalControl,
366
- approval_state: terminalState.approval_state,
367
- activity_state: terminalState.activity_state,
368
- activity_reason: terminalState.activity_reason,
489
+ approval_state: effectiveTerminalState.approval_state,
490
+ activity_state: effectiveTerminalState.activity_state,
491
+ activity_reason: effectiveTerminalState.activity_reason,
492
+ // Internal exact-observation evidence. The public projection strips this
493
+ // object; raw terminal status reuses it so screen, approval, and activity
494
+ // all describe the same capture.
495
+ ...(terminalStatusSnapshot
496
+ ? { _terminal_status_snapshot: terminalStatusSnapshot }
497
+ : {}),
369
498
  // Internal action-projection evidence. terminalFirstListProjection strips
370
499
  // this field after gating every automated-input action that can follow a
371
500
  // human native-thread switch.
@@ -398,33 +527,20 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
398
527
  }
399
528
  }
400
529
  : {}),
401
- commands: {
402
- send: !terminalHasBlockingTurn,
403
- approve: terminalControl.capabilities.includes("terminal_approval") &&
404
- terminalState.approval_state.approvable === true,
405
- status: true,
406
- cancel: terminalControl.capabilities.includes("terminal_cancel"),
407
- close: orphanedDispatch !== undefined,
408
- new_thread: lifecycleCapability.status === "supported" &&
409
- lifecycleCapability.newThread === true &&
410
- codexLifecycleIncarnationAvailable &&
411
- !terminalHasBlockingTurn,
412
- list_resumable_threads: lifecycleCapability.status === "supported" &&
413
- lifecycleCapability.resumeExact === true &&
414
- codexLifecycleIncarnationAvailable,
415
- native_inspect: nativeInspectionCapability.status === "supported" &&
416
- nativeInspectionCapability.statusInspection === true &&
417
- terminalState.activity_state === "idle" &&
418
- automatedInputComposerReady &&
419
- terminalControl.capabilities.includes("send_keys") &&
420
- terminalControl.capabilities.includes("screen_status") &&
421
- (session.agent === "codex"
422
- ? codexLifecycleIncarnationAvailable
423
- : Boolean(nativeAgentIdentity?.sessionId &&
424
- nativeAgentIdentity.processUuid)) &&
425
- orphanedDispatch === undefined &&
426
- !terminalHasBlockingTurn
427
- }
530
+ commands: terminalListCommands({
531
+ agent: session.agent,
532
+ terminalControl,
533
+ terminalState: effectiveTerminalState,
534
+ lifecycleCapability,
535
+ nativeInspectionCapability,
536
+ nativeAgentIdentity,
537
+ nativeProcessUuid,
538
+ nativeProcessBirth,
539
+ codexLifecycleIncarnationAvailable,
540
+ automatedInputComposerReady,
541
+ hasOrphanedDispatch: orphanedDispatch !== undefined,
542
+ terminalHasBlockingTurn
543
+ })
428
544
  };
429
545
  const availableActions = renderAvailableListActions(entry);
430
546
  const { commands: _commands, ...publicEntry } = entry;
@@ -433,6 +549,115 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
433
549
  available_actions: availableActions
434
550
  };
435
551
  }
552
+ function terminalListCommands(input) {
553
+ const { agent, terminalControl, terminalState, lifecycleCapability, nativeInspectionCapability, nativeAgentIdentity, nativeProcessUuid, nativeProcessBirth, codexLifecycleIncarnationAvailable, automatedInputComposerReady, hasOrphanedDispatch, terminalHasBlockingTurn } = input;
554
+ return {
555
+ send: !terminalHasBlockingTurn,
556
+ approve: terminalControl.capabilities.includes("terminal_approval") &&
557
+ terminalState.approval_state.approvable === true,
558
+ status: true,
559
+ cancel: terminalControl.capabilities.includes("terminal_cancel"),
560
+ close: hasOrphanedDispatch,
561
+ new_thread: lifecycleCapability.status === "supported" &&
562
+ lifecycleCapability.newThread === true &&
563
+ codexLifecycleIncarnationAvailable &&
564
+ !terminalHasBlockingTurn,
565
+ list_resumable_threads: lifecycleCapability.status === "supported" &&
566
+ lifecycleCapability.resumeExact === true &&
567
+ codexLifecycleIncarnationAvailable,
568
+ native_inspect: nativeInspectionCapability.status === "supported" &&
569
+ nativeInspectionCapability.statusInspection === true &&
570
+ terminalState.activity_state === "idle" &&
571
+ automatedInputComposerReady &&
572
+ terminalControl.capabilities.includes("send_keys") &&
573
+ terminalControl.capabilities.includes("screen_status") &&
574
+ (agent === "codex"
575
+ ? codexLifecycleIncarnationAvailable
576
+ : Boolean(nativeAgentIdentity?.sessionId &&
577
+ nativeAgentIdentity.processUuid)) &&
578
+ !hasOrphanedDispatch &&
579
+ !terminalHasBlockingTurn,
580
+ watch: lifecycleCapability.status === "supported" &&
581
+ (terminalState.activity_state === "working" ||
582
+ terminalState.activity_state === "awaiting_approval") &&
583
+ Boolean(nativeAgentIdentity?.sessionId) &&
584
+ (agent !== "codex" || Boolean(nativeAgentIdentity?.rollout)) &&
585
+ Boolean(nativeProcessUuid) &&
586
+ Boolean(nativeProcessBirth) &&
587
+ !hasOrphanedDispatch &&
588
+ !terminalHasBlockingTurn
589
+ };
590
+ }
591
+ function effectiveTerminalListState(input) {
592
+ const { session, terminalState, nativeIdentityObservation, nativeAgentIdentity, nativeProcessUuid, nativeProcessBirth } = input;
593
+ if (session.agent !== "codex" ||
594
+ terminalState.activity_state === "working" ||
595
+ terminalState.activity_state === "awaiting_approval" ||
596
+ terminalState.approval_state.blocked === true) {
597
+ return terminalState;
598
+ }
599
+ if (nativeIdentityObservation.status === "verified_absent") {
600
+ return terminalState;
601
+ }
602
+ if (nativeIdentityObservation.status !== "resolved" ||
603
+ !nativeAgentIdentity?.rollout ||
604
+ !nativeAgentIdentity.sessionId ||
605
+ !nativeProcessUuid ||
606
+ !nativeProcessBirth) {
607
+ return terminalListStateWithUnavailableDurableActivity(terminalState, nativeIdentityObservation.status === "unavailable"
608
+ ? nativeIdentityObservation.reason ??
609
+ "exact Codex identity observation failed"
610
+ : "exact Codex rollout/process identity is incomplete");
611
+ }
612
+ const currentIdentity = {
613
+ sessionId: nativeAgentIdentity.sessionId,
614
+ processUuid: nativeProcessUuid,
615
+ processBirth: nativeProcessBirth,
616
+ rollout: nativeAgentIdentity.rollout
617
+ };
618
+ try {
619
+ if (!captureCodexHumanStartedActiveTaskAnchor({ currentIdentity })) {
620
+ return terminalState;
621
+ }
622
+ return {
623
+ ...terminalState,
624
+ activity_state: "working",
625
+ activity_reason: "Codex rollout contains an exact unfinished human-started task"
626
+ };
627
+ }
628
+ catch (error) {
629
+ const reason = error instanceof Error ? error.message : String(error);
630
+ runtimeLog("warn", "terminal_durable_activity_unavailable", {
631
+ agent: session.agent,
632
+ pid: session.pid,
633
+ reason
634
+ });
635
+ return terminalListStateWithUnavailableDurableActivity(terminalState, reason);
636
+ }
637
+ }
638
+ function terminalListStateWithUnavailableDurableActivity(terminalState, reason) {
639
+ return {
640
+ ...terminalState,
641
+ activity_state: "unknown",
642
+ activity_reason: `durable Codex activity evidence is unavailable: ${reason}`
643
+ };
644
+ }
645
+ function terminalBridgeStatusWithActivity(status, activityState, activityReason) {
646
+ const descriptors = Object.getOwnPropertyDescriptors(status);
647
+ descriptors.activity_state = {
648
+ configurable: true,
649
+ enumerable: true,
650
+ value: activityState,
651
+ writable: true
652
+ };
653
+ descriptors.activity_reason = {
654
+ configurable: true,
655
+ enumerable: true,
656
+ value: activityReason,
657
+ writable: true
658
+ };
659
+ return Object.create(Object.getPrototypeOf(status), descriptors);
660
+ }
436
661
  async function observeTerminalNativeListIdentity(session, terminalControl, options, bridge) {
437
662
  const orphanedDispatch = terminalListRuntime().orphanedTerminalDispatchForRecovery(terminalControl);
438
663
  let codexIdentityContext;
@@ -562,7 +787,7 @@ async function observeAutomatedInputComposerReady({ session, terminalControl, te
562
787
  }
563
788
  function observeTerminalListBindingAuthority(terminal, context) {
564
789
  const { storeDir, managedSessions, allConversations, displayedConversations, mutationsAllowed, nonterminalDeferredTransfers } = context;
565
- const { _automated_input_composer_ready: automatedInputComposerReady, _codex_open_root_rollout_inventory: codexOpenRootRolloutInventoryValue, _codex_latent_clear_resume: codexLatentClearResumeValue, ...publicTerminal } = terminal;
790
+ const { _automated_input_composer_ready: automatedInputComposerReady, _codex_open_root_rollout_inventory: codexOpenRootRolloutInventoryValue, _codex_latent_clear_resume: codexLatentClearResumeValue, _terminal_status_snapshot: _terminalStatusSnapshot, ...publicTerminal } = terminal;
566
791
  const codexOpenRootRolloutInventory = isRecord(codexOpenRootRolloutInventoryValue)
567
792
  ? codexOpenRootRolloutInventoryValue
568
793
  : undefined;
@@ -1018,14 +1243,9 @@ function observeTerminalHandoffAuthority(terminal, context, observation) {
1018
1243
  })
1019
1244
  : undefined;
1020
1245
  const blockingHandoffTurnIds = new Set(handoffSourceBlockingTurns.map((turn) => turn.conversation_id));
1021
- // An active source Turn may be superseded only through the snapshot-bound
1022
- // handoff decision above. Never expose the generic Store-only close action
1023
- // for that Turn, because doing so would bypass expected_handoff_token after
1024
- // the live native thread or Turn generation changed. `blocking_turns` is
1025
- // reserved for other same-incarnation blockers such as legacy collateral
1026
- // stalls; clear those first, refresh, and only then decide the handoff.
1027
- const terminalRecoveryBlockingTurns = terminalBlockingTurns.filter((turn) => !blockingHandoffTurnIds.has(turn.conversation_id) &&
1028
- !conversationHasNonterminalDeferredTransfer(turn));
1246
+ // A blocking managed Turn always remains explicitly closable. Close releases
1247
+ // AKK management only; it does not send input or stop the coding agent.
1248
+ const terminalRecoveryBlockingTurns = terminalBlockingTurns;
1029
1249
  return {
1030
1250
  ...observation,
1031
1251
  reconcileBindingAction,
@@ -1134,11 +1354,14 @@ function renderTerminalFirstListEntry(terminal, context, observation) {
1134
1354
  const currentTurnValue = ownership.state === "current"
1135
1355
  ? currentManagedTurnForTerminal(ownership.conversation, terminal, sessionAwareRawActions)
1136
1356
  : undefined;
1137
- const currentTurnProjection = currentTurnValue && (!mutationsAllowed ||
1138
- (ownership.state === "current" &&
1139
- conversationHasNonterminalDeferredTransfer(ownership.conversation)))
1140
- ? readOnlyManagedTurn(currentTurnValue)
1141
- : currentTurnValue;
1357
+ const currentTurnProjection = currentTurnValue
1358
+ ? !mutationsAllowed
1359
+ ? readOnlyManagedTurn(currentTurnValue)
1360
+ : ownership.state === "current" &&
1361
+ conversationHasNonterminalDeferredTransfer(ownership.conversation)
1362
+ ? userReleasableManagedTurn(currentTurnValue)
1363
+ : currentTurnValue
1364
+ : undefined;
1142
1365
  const currentTurn = currentTurnProjection
1143
1366
  ? withoutGenericHandoffSourceClose(currentTurnProjection, blockingHandoffTurnIds)
1144
1367
  : undefined;
@@ -1155,20 +1378,24 @@ function renderTerminalFirstListEntry(terminal, context, observation) {
1155
1378
  const recentTurnValue = recentConversation
1156
1379
  ? historicalManagedTurnForTerminal(recentConversation)
1157
1380
  : undefined;
1158
- const recentTurnProjection = recentTurnValue && (!mutationsAllowed ||
1159
- Boolean(recentConversation &&
1160
- conversationHasNonterminalDeferredTransfer(recentConversation)))
1161
- ? readOnlyManagedTurn(recentTurnValue)
1162
- : recentTurnValue;
1381
+ const recentTurnProjection = recentTurnValue
1382
+ ? !mutationsAllowed
1383
+ ? readOnlyManagedTurn(recentTurnValue)
1384
+ : recentConversation &&
1385
+ conversationHasNonterminalDeferredTransfer(recentConversation)
1386
+ ? userReleasableManagedTurn(recentTurnValue)
1387
+ : recentTurnValue
1388
+ : undefined;
1163
1389
  const recentTurn = recentTurnProjection
1164
1390
  ? withoutGenericHandoffSourceClose(recentTurnProjection, blockingHandoffTurnIds)
1165
1391
  : undefined;
1166
1392
  const history = historyConversations.map((conversation) => {
1167
1393
  const turn = historicalManagedTurnForTerminal(conversation);
1168
- return withoutGenericHandoffSourceClose(mutationsAllowed &&
1169
- !conversationHasNonterminalDeferredTransfer(conversation)
1170
- ? turn
1171
- : readOnlyManagedTurn(turn), blockingHandoffTurnIds);
1394
+ return withoutGenericHandoffSourceClose(!mutationsAllowed
1395
+ ? readOnlyManagedTurn(turn)
1396
+ : conversationHasNonterminalDeferredTransfer(conversation)
1397
+ ? userReleasableManagedTurn(turn)
1398
+ : turn, blockingHandoffTurnIds);
1172
1399
  });
1173
1400
  const visibleTurnIds = new Set([currentTurn, recentTurn, ...history]
1174
1401
  .map((turn) => stringValue(turn?.conversation_id))
@@ -1345,14 +1572,17 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
1345
1572
  });
1346
1573
  return {
1347
1574
  ...managedTurn,
1348
- available_actions: mutationsAllowed &&
1349
- !conversationHasNonterminalDeferredTransfer(conversation)
1350
- ? safeUnavailableManagedTurnActions(isRecord(managedTurn.available_actions)
1575
+ available_actions: !mutationsAllowed
1576
+ ? readOnlyListActions(isRecord(managedTurn.available_actions)
1351
1577
  ? managedTurn.available_actions
1352
1578
  : {})
1353
- : readOnlyListActions(isRecord(managedTurn.available_actions)
1354
- ? managedTurn.available_actions
1355
- : {}),
1579
+ : conversationHasNonterminalDeferredTransfer(conversation)
1580
+ ? userReleaseListActions(isRecord(managedTurn.available_actions)
1581
+ ? managedTurn.available_actions
1582
+ : {}, turnIdForConversation(conversation))
1583
+ : safeUnavailableManagedTurnActions(isRecord(managedTurn.available_actions)
1584
+ ? managedTurn.available_actions
1585
+ : {}),
1356
1586
  terminal_availability: {
1357
1587
  available: false,
1358
1588
  reason: managedOnly
@@ -1995,6 +2225,7 @@ async function listStateForTerminal(agent, terminalControl, options, bridge = te
1995
2225
  activity_state: status.activity_state,
1996
2226
  activity_reason: status.activity_reason,
1997
2227
  capability_limitation: status.capability_limitation,
2228
+ _terminal_status_snapshot: status,
1998
2229
  // Internal projection evidence; terminalControlledListEntry selects all
1999
2230
  // public fields explicitly and never exposes the pane excerpt itself.
2000
2231
  screen_excerpt: status.screen.excerpt
@@ -2048,6 +2279,12 @@ function managedListApprovalState(conversation) {
2048
2279
  }
2049
2280
  async function resolveConversationSelectorOption(commandName, options) {
2050
2281
  const sendOperation = commandName === "send";
2282
+ // Submission retry is already bound to one authoritative managed Turn.
2283
+ // Keep it out of ordinary-send selector discovery so an omitted Session
2284
+ // target cannot be inferred and mixed into the exact `send --turn` form.
2285
+ if (sendOperation && stringValue(options.turn)) {
2286
+ return;
2287
+ }
2051
2288
  if (stringValue(options.expectedTerminalToken)) {
2052
2289
  // Selector resolution may replace an alias (or an omitted selector) with a
2053
2290
  // discovered full terminal id. Preserve the caller's actual authority so