commandmate 0.40.0 → 0.41.0

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 (207) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +189 -189
  3. package/.next/app-path-routes-manifest.json +34 -34
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +15 -15
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
  9. package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
  10. package/.next/server/app/api/app/release-notes/route_client-reference-manifest.js +1 -1
  11. package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
  12. package/.next/server/app/api/app/update-check/route.js +1 -1
  13. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/capabilities/route.js +1 -1
  18. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/hooks/permission-request/route.js +1 -1
  29. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  46. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  58. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  64. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  69. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  70. package/.next/server/app/api/worktrees/[id]/direct-input/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
  72. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  76. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  82. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  96. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  135. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  141. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/worktrees/route.js +1 -1
  147. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  149. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  151. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  152. package/.next/server/app/page_client-reference-manifest.js +1 -1
  153. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  155. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  156. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  157. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  158. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  159. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  160. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  161. package/.next/server/app/worktrees/[id]/page.js +1 -1
  162. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  163. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  164. package/.next/server/app-paths-manifest.json +34 -34
  165. package/.next/server/chunks/4443.js +1 -1
  166. package/.next/server/chunks/5164.js +1 -1
  167. package/.next/server/chunks/6130.js +1 -1
  168. package/.next/server/chunks/650.js +1 -1
  169. package/.next/server/chunks/6661.js +1 -1
  170. package/.next/server/chunks/8581.js +2 -2
  171. package/.next/server/chunks/8728.js +1 -1
  172. package/.next/server/chunks/9627.js +30 -24
  173. package/.next/server/chunks/9882.js +3 -3
  174. package/.next/server/middleware-manifest.json +5 -5
  175. package/.next/server/pages/500.html +1 -1
  176. package/.next/server/server-reference-manifest.json +1 -1
  177. package/.next/static/chunks/{1642-767aed8e7c61ef7f.js → 1642-fb2e176aaadbd137.js} +1 -1
  178. package/.next/static/chunks/{2407-e259744922e0834c.js → 2407-99799e3c2ff03bae.js} +1 -1
  179. package/.next/static/chunks/{2619-a305ad4810f1393a.js → 2619-04bc32f026a0d946.js} +1 -1
  180. package/.next/static/chunks/{4768-979c8a03bed218e7.js → 4768-fc0aa1d0891cac08.js} +1 -1
  181. package/.next/static/chunks/{8165-8944f74c53f3021f.js → 8165-d3b7c8a7aa776bc7.js} +1 -1
  182. package/.next/static/chunks/{8606-9a381b996f9b6834.js → 8606-79c36d4c204bbd38.js} +1 -1
  183. package/.next/static/chunks/app/{layout-b90cce6a51c91f4c.js → layout-1fb647d6df55c498.js} +1 -1
  184. package/.next/static/chunks/app/worktrees/[id]/{page-e9ebdcd317b62e4a.js → page-f24da37453224716.js} +1 -1
  185. package/.next/static/chunks/app/worktrees/[id]/terminal/{page-c601f3b3ece6557e.js → page-373ea37b16d930c7.js} +1 -1
  186. package/.next/trace +13 -13
  187. package/dist/server/src/lib/auto-yes-poller.js +72 -1
  188. package/dist/server/src/lib/db/chat-db.js +38 -0
  189. package/dist/server/src/lib/detection/cli-patterns.js +11 -0
  190. package/dist/server/src/lib/detection/model-info-extractor.js +49 -10
  191. package/dist/server/src/lib/detection/selection-shape.js +76 -0
  192. package/dist/server/src/lib/detection/tools/antigravity/detect.js +64 -0
  193. package/dist/server/src/lib/detection/tools/claude/detect.js +28 -1
  194. package/dist/server/src/lib/detection/tools/codex/cli-patterns.js +51 -0
  195. package/dist/server/src/lib/detection/tools/codex/detect.js +21 -2
  196. package/dist/server/src/lib/detection/tools/command-code/detect.js +14 -3
  197. package/dist/server/src/lib/polling/antigravity-permission-receipts.js +113 -0
  198. package/dist/server/src/lib/polling/response-checker.js +73 -4
  199. package/dist/server/src/lib/session/agent-event-state.js +8 -1
  200. package/dist/server/src/lib/session/current-output-builder.js +25 -0
  201. package/dist/server/src/lib/session/layer-disagreement.js +108 -0
  202. package/dist/server/src/types/sidebar.js +1 -0
  203. package/package.json +1 -1
  204. package/release-notes/0.40.1.json +20 -0
  205. package/release-notes/0.41.0.json +29 -0
  206. /package/.next/static/{o3mZdwNhCks6IlcAOPinc → UoGuvKnR_kGftU9RHtebq}/_buildManifest.js +0 -0
  207. /package/.next/static/{o3mZdwNhCks6IlcAOPinc → UoGuvKnR_kGftU9RHtebq}/_ssgManifest.js +0 -0
@@ -61,6 +61,7 @@ exports.stopAutoYesPollingByWorktree = stopAutoYesPollingByWorktree;
61
61
  exports.isAnyPollerActiveForWorktree = isAnyPollerActiveForWorktree;
62
62
  const cli_session_1 = require("./session/cli-session");
63
63
  const response_checker_1 = require("./polling/response-checker");
64
+ const antigravity_permission_receipts_1 = require("./polling/antigravity-permission-receipts");
64
65
  const auto_yes_resolver_1 = require("./polling/auto-yes-resolver");
65
66
  const auto_yes_policy_1 = require("./polling/auto-yes-policy");
66
67
  const auto_yes_suppression_state_1 = require("./polling/auto-yes-suppression-state");
@@ -82,6 +83,16 @@ const auto_yes_state_1 = require("./auto-yes-state");
82
83
  /** In-memory storage for poller states (globalThis for hot reload persistence) */
83
84
  const autoYesPollerStates = globalThis.__autoYesPollerStates ??
84
85
  (globalThis.__autoYesPollerStates = new Map());
86
+ /**
87
+ * When `antigravity-autoyes-withheld-no-hook-receipt` was last logged, per
88
+ * compositeKey (Issue #2857). The poller re-reads a static pane every 2s, so the
89
+ * line is limited to one per {@link WITHHELD_NO_RECEIPT_LOG_INTERVAL_MS} per
90
+ * instance. globalThis for the same reason as the poller states above.
91
+ */
92
+ const withheldNoReceiptLoggedAt = globalThis.__autoYesWithheldNoReceiptLoggedAt ??
93
+ (globalThis.__autoYesWithheldNoReceiptLoggedAt = new Map());
94
+ /** Least gap between two `antigravity-autoyes-withheld-no-hook-receipt` lines for one instance. */
95
+ const WITHHELD_NO_RECEIPT_LOG_INTERVAL_MS = 60_000;
85
96
  // =============================================================================
86
97
  // Poller State Accessors (compositeKey-based)
87
98
  // =============================================================================
@@ -108,6 +119,7 @@ function getActivePollerCount() {
108
119
  function clearAllPollerStates() {
109
120
  stopAllAutoYesPolling();
110
121
  autoYesPollerStates.clear();
122
+ withheldNoReceiptLoggedAt.clear();
111
123
  }
112
124
  /**
113
125
  * Get the last server response timestamp for a composite key.
@@ -284,6 +296,50 @@ function processStopConditionDelta(compositeKey, pollerState, cleanOutput) {
284
296
  }
285
297
  return false;
286
298
  }
299
+ /**
300
+ * Say, once a minute, that Auto-Yes left an agy dialog alone for want of a hook
301
+ * receipt (Issue #2857).
302
+ *
303
+ * `detectPromptOnCleanFrame` hands a frame the receipt gate (#2849) withholds
304
+ * back as an ordinary `isPrompt: false`, so the poller cannot tell "no dialog on
305
+ * the pane" from "a dialog agy never asked us about" by looking at the result.
306
+ * The gate is the ONLY thing `receiptScope` changes, so the same frame read
307
+ * without it answering a prompt is exactly the withheld case. That second
308
+ * reading is taken only after the cheap tests: no receipt for this instance in
309
+ * the window (with one, the gate let the frame through and a no-prompt result is
310
+ * a real one) and no line for it in the last minute.
311
+ *
312
+ * Why it is worth a line: a machine whose `~/.gemini/config/hooks.json` points at
313
+ * another server (#2622) never sends us the question, and every real dialog then
314
+ * waits for a human with no sign of why. The line names the file to look at.
315
+ * The response-checker's own `antigravity-dialog-no-recent-receipt` stays at
316
+ * debug — it prints on every tick the same pane is read again.
317
+ *
318
+ * @param compositeKey - The instance's key, for the once-a-minute limit
319
+ * @param readWithoutGate - The same frame read without `receiptScope`
320
+ */
321
+ function logIfWithheldForWantOfReceipt(worktreeId, instanceId, compositeKey, readWithoutGate) {
322
+ if ((0, antigravity_permission_receipts_1.hasRecentAntigravityPermissionReceipt)(worktreeId, 'antigravity', instanceId))
323
+ return;
324
+ const now = Date.now();
325
+ const lastLoggedAt = withheldNoReceiptLoggedAt.get(compositeKey);
326
+ if (lastLoggedAt !== undefined && now - lastLoggedAt < WITHHELD_NO_RECEIPT_LOG_INTERVAL_MS)
327
+ return;
328
+ const ungated = readWithoutGate();
329
+ if (!ungated.isPrompt || !ungated.promptData)
330
+ return;
331
+ for (const [key, loggedAt] of withheldNoReceiptLoggedAt) {
332
+ if (now - loggedAt >= WITHHELD_NO_RECEIPT_LOG_INTERVAL_MS)
333
+ withheldNoReceiptLoggedAt.delete(key);
334
+ }
335
+ withheldNoReceiptLoggedAt.set(compositeKey, now);
336
+ logger.info('antigravity-autoyes-withheld-no-hook-receipt', {
337
+ worktreeId,
338
+ instanceId: instanceId ?? 'antigravity',
339
+ windowMs: antigravity_permission_receipts_1.ANTIGRAVITY_PERMISSION_RECEIPT_WINDOW_MS,
340
+ hint: 'hook の受信記録が直近に無いため Auto-Yes は答えなかった。~/.gemini/config/hooks.json の向き先(#2622)を確認',
341
+ });
342
+ }
287
343
  /**
288
344
  * Detect prompt in terminal output, resolve auto-answer, and send response.
289
345
  *
@@ -330,10 +386,25 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
330
386
  // the same tick's raw capture reached it. `precomputedLines` stays the split
331
387
  // of `cleanOutput` — the stop-condition delta and every other tool's reading
332
388
  // are measured on that string and must not move.
333
- const promptDetection = (0, response_checker_1.detectPromptOnCleanFrame)(cleanOutput, cliToolId, precomputedLines, rawOutput);
389
+ //
390
+ // Issue #2857 adds the fifth, and this is the only caller that passes it: the
391
+ // one that ANSWERS the frame. For agy the frame then reads as a prompt only
392
+ // when agy asked CommandMate about a tool call for this instance in the last
393
+ // few seconds (#2849) — a dialog it never asked about is a reply quoting one
394
+ // (#2845, #2851) or a menu the user opened, and neither is ours to answer.
395
+ // The status API, the response poller's `prompt` row and the notification
396
+ // still call without it, so a real dialog stays visible to a human on a
397
+ // machine whose hook does not reach us. The price is that Auto-Yes leaves
398
+ // such a machine's dialogs alone, which is why `logIfWithheldForWantOfReceipt`
399
+ // says so.
400
+ const receiptScope = { worktreeId, instanceId };
401
+ const promptDetection = (0, response_checker_1.detectPromptOnCleanFrame)(cleanOutput, cliToolId, precomputedLines, rawOutput, receiptScope);
334
402
  if (!promptDetection.isPrompt || !promptDetection.promptData) {
335
403
  pollerState.lastAnsweredPromptKey = null;
336
404
  pollerState.lastAnsweredAt = null;
405
+ if (cliToolId === 'antigravity' && !promptDetection.isPrompt) {
406
+ logIfWithheldForWantOfReceipt(worktreeId, instanceId, compositeKey, () => (0, response_checker_1.detectPromptOnCleanFrame)(cleanOutput, cliToolId, precomputedLines, rawOutput));
407
+ }
337
408
  return 'no_prompt';
338
409
  }
339
410
  // 2. Check for duplicate prompt (Issue #306)
@@ -8,6 +8,7 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.RELAY_SYSTEM_REQUEST_ID_PREFIX = exports.RELAY_REQUEST_ID_PREFIX = exports.MODEL_CHANGE_REQUEST_ID_PREFIX = exports.USER_TURN_CANDIDATE_LIMIT = exports.ACTIVE_FILTER = void 0;
10
10
  exports.getLastAssistantMessageAt = getLastAssistantMessageAt;
11
+ exports.getLastMessageAtByInstance = getLastMessageAtByInstance;
11
12
  exports.createMessage = createMessage;
12
13
  exports.updateMessageContent = updateMessageContent;
13
14
  exports.getMessages = getMessages;
@@ -85,6 +86,43 @@ function getLastAssistantMessageAt(db, worktreeId) {
85
86
  }
86
87
  return new Date(row.last_assistant_message_at);
87
88
  }
89
+ /**
90
+ * Latest message time per worktree and agent instance (Issue #2838).
91
+ *
92
+ * The sidebar's session rows need a time of their own: `worktrees.updated_at`
93
+ * moves on a message from ANY instance, so every row of one worktree used to
94
+ * share it. One `GROUP BY` for the whole server — the list polls it, so a
95
+ * query per worktree would multiply every poll.
96
+ *
97
+ * A row belongs to `COALESCE(instance_id, cli_tool_id, 'claude')`, the instance
98
+ * `getMessages` reads it as, so pre-#868 rows (`instance_id IS NULL`) count
99
+ * toward the primary instance. Role and `archived` are not filtered, matching
100
+ * what moves `updated_at` (`createMessage` touches it for every row).
101
+ *
102
+ * @returns worktreeId → (instanceId → latest message time); worktrees without
103
+ * messages are absent
104
+ */
105
+ function getLastMessageAtByInstance(db) {
106
+ const rows = db.prepare(`
107
+ SELECT worktree_id,
108
+ COALESCE(instance_id, cli_tool_id, 'claude') AS resolved_instance_id,
109
+ MAX(timestamp) AS last_message_at
110
+ FROM chat_messages
111
+ GROUP BY worktree_id, resolved_instance_id
112
+ `).all();
113
+ const result = new Map();
114
+ for (const row of rows) {
115
+ if (row.last_message_at === null)
116
+ continue;
117
+ let byInstance = result.get(row.worktree_id);
118
+ if (!byInstance) {
119
+ byInstance = {};
120
+ result.set(row.worktree_id, byInstance);
121
+ }
122
+ byInstance[row.resolved_instance_id] = new Date(row.last_message_at);
123
+ }
124
+ return result;
125
+ }
88
126
  /**
89
127
  * Create a new chat message
90
128
  */
@@ -2272,6 +2272,13 @@ exports.ANTIGRAVITY_SURVEY_PATTERN = /^\s*\[1\]\s*Good\s+\[2\]\s*Fine\s+\[3\]\s*
2272
2272
  * scrollback above a newer one is never the one read. Returns null when the
2273
2273
  * frame has no footer at all.
2274
2274
  *
2275
+ * Also null when agy's input box is drawn BELOW that footer (Issue #2845): agy
2276
+ * paints no `>` composer while a dialog is open — the dialog takes its place —
2277
+ * so a footer with a composer under it is the model's reply quoting a dialog,
2278
+ * or a dialog left in the scrollback, not a screen waiting for a keypress. It is
2279
+ * the same reading `isAntigravitySurveyOpen` gives the survey row (#2364), and
2280
+ * the composer is recognised by the same {@link ANTIGRAVITY_PROMPT_PATTERN}.
2281
+ *
2275
2282
  * @param lines - ANSI-stripped rows, box drawing optional
2276
2283
  */
2277
2284
  function locateAntigravityDialogRegion(lines) {
@@ -2284,6 +2291,10 @@ function locateAntigravityDialogRegion(lines) {
2284
2291
  }
2285
2292
  if (footer < 0)
2286
2293
  return null;
2294
+ for (let i = footer + 1; i < lines.length; i++) {
2295
+ if (exports.ANTIGRAVITY_PROMPT_PATTERN.test(lines[i]))
2296
+ return null;
2297
+ }
2287
2298
  const floor = Math.max(0, footer - exports.ANTIGRAVITY_DIALOG_MAX_ROWS);
2288
2299
  let start = floor;
2289
2300
  for (let i = footer - 1; i >= floor; i--) {
@@ -62,7 +62,7 @@
62
62
  * @see Issue #1784, Phase 2 of the model/effort visibility series
63
63
  */
64
64
  Object.defineProperty(exports, "__esModule", { value: true });
65
- exports.ANTIGRAVITY_MODEL_ID_EFFORT_PATTERN = exports.COMMAND_CODE_BANNER_MODELS_PATTERN = exports.OPENCODE_STEP_MODEL_PATTERN = exports.COPILOT_MODEL_CHANGE_PATTERN = exports.ANTIGRAVITY_BANNER_MODEL_PATTERN = exports.ANTIGRAVITY_STATUS_BAR_HINT_PATTERN = exports.CLAUDE_MODEL_SWITCH_PATTERN = exports.CLAUDE_STARTUP_BANNER_PATTERN = exports.CODEX_STATUS_BAR_WITH_TRAILER_PATTERN = exports.CODEX_FOOTER_MODEL_PATTERN = exports.REASONING_EFFORT_LEVELS = void 0;
65
+ exports.ANTIGRAVITY_MODEL_ID_EFFORT_PATTERN = exports.COMMAND_CODE_BANNER_MODELS_PATTERN = exports.OPENCODE_STEP_MODEL_PATTERN = exports.COPILOT_MODEL_CHANGE_PATTERN = exports.ANTIGRAVITY_BANNER_MODEL_PATTERN = exports.ANTIGRAVITY_STATUS_BAR_HINT_PATTERN = exports.CLAUDE_MODEL_SWITCH_PATTERN = exports.CLAUDE_STARTUP_BANNER_PATTERN = exports.CODEX_STATUS_BAR_WITH_TRAILER_PATTERN = exports.CODEX_FOOTER_EFFORT_SLOT_PATTERN = exports.CODEX_FOOTER_MODEL_PATTERN = exports.REASONING_EFFORT_LEVELS = void 0;
66
66
  exports.resolveEffortToken = resolveEffortToken;
67
67
  exports.extractModelInfo = extractModelInfo;
68
68
  exports.deriveEffortFromModelId = deriveEffortFromModelId;
@@ -72,11 +72,27 @@ const cli_patterns_1 = require("./cli-patterns");
72
72
  /**
73
73
  * The reasoning-effort vocabulary these tools actually print.
74
74
  *
75
- * `minimal` and `xhigh` are the ends Codex exposes; Claude prints `xhigh` too.
75
+ * `minimal` and `xhigh` are the ends Codex exposed until 0.155; Claude prints
76
+ * `xhigh` too. Issue #2835 adds the three levels above `xhigh` that codex 0.155.1
77
+ * defines (`ReasoningEffort` in `codex-rs/protocol/src/openai_models.rs`, tag
78
+ * `rust-v0.155.1`: `max` / `ultra` / `persistent`, drawn in the status bar
79
+ * verbatim by `status_line_reasoning_effort_label`). Its `None` is drawn as
80
+ * `default` and is deliberately NOT a level — it names no effort. Command Code's
81
+ * `with max effort` banner resolves through the same list.
82
+ *
76
83
  * Anything outside this list is treated as "not an effort token", which is what
77
84
  * keeps a stray word at the end of a status bar from being published as one.
78
85
  */
79
- exports.REASONING_EFFORT_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh'];
86
+ exports.REASONING_EFFORT_LEVELS = [
87
+ 'minimal',
88
+ 'low',
89
+ 'medium',
90
+ 'high',
91
+ 'xhigh',
92
+ 'max',
93
+ 'ultra',
94
+ 'persistent',
95
+ ];
80
96
  /**
81
97
  * Longest string accepted as a model name.
82
98
  *
@@ -156,7 +172,23 @@ function resolveEffortToken(token, options = {}) {
156
172
  * optional `\s+<keyword>` are adjacent, never nested (ReDoS-safe, same rule
157
173
  * `cli-patterns.ts` documents).
158
174
  */
159
- exports.CODEX_FOOTER_MODEL_PATTERN = /^\s*([^\s·]+)(?:\s+(minimal|low|medium|high|xhigh)\b)?/i;
175
+ exports.CODEX_FOOTER_MODEL_PATTERN = /^\s*([^\s·]+)(?:\s+(minimal|low|medium|high|xhigh|max|ultra|persistent)\b)?/i;
176
+ /**
177
+ * The head of a Codex status bar that has a word where the effort goes
178
+ * (Issue #2835).
179
+ *
180
+ * codex draws `<model> <effort>` and, for an effort it has no name for, still
181
+ * draws a word there: `default` for `None` (`status_line_reasoning_effort_label`,
182
+ * codex 0.155.1; live in `tests/fixtures/agent-mode-2592/codex-default.txt`), or
183
+ * a model-defined value verbatim. A head whose second token is a word is
184
+ * therefore a bar that SHOWS an effort, even when {@link CODEX_FOOTER_MODEL_PATTERN}
185
+ * cannot name it. The legacy o4-mini head (`o4-mini 50% left`) has no
186
+ * such slot — its second token starts with a digit — and neither does a status
187
+ * line configured to show the model alone.
188
+ *
189
+ * No /g. No nested quantifiers: two adjacent runs and a lookahead.
190
+ */
191
+ exports.CODEX_FOOTER_EFFORT_SLOT_PATTERN = /^\s*[^\s·]+\s+[A-Za-z][A-Za-z_-]*(?=\s|$)/;
160
192
  /**
161
193
  * A codex status bar with something AFTER the path (Issue #2592).
162
194
  *
@@ -202,7 +234,8 @@ function readCodexFooter(line) {
202
234
  const separator = line.indexOf('·');
203
235
  if (separator < 0)
204
236
  return null;
205
- const match = exports.CODEX_FOOTER_MODEL_PATTERN.exec(line.slice(0, separator));
237
+ const head = line.slice(0, separator);
238
+ const match = exports.CODEX_FOOTER_MODEL_PATTERN.exec(head);
206
239
  if (!match)
207
240
  return null;
208
241
  const model = match[1];
@@ -219,6 +252,13 @@ function readCodexFooter(line) {
219
252
  // beside it is what separates chrome from prose.
220
253
  if (effort === null && !/\d/.test(model))
221
254
  return null;
255
+ // Issue #2835: a bar that shows an effort this module cannot name says the
256
+ // latched effort is no longer current. Publishing the old one would be a
257
+ // wrong value, which is worse than none — the case that found it was a
258
+ // session moved from `xhigh` to `max` while `max` was not yet a level.
259
+ if (effort === null && exports.CODEX_FOOTER_EFFORT_SLOT_PATTERN.test(head)) {
260
+ return { model, effort: null, effortUnreadable: true };
261
+ }
222
262
  return { model, effort };
223
263
  }
224
264
  // =============================================================================
@@ -612,11 +652,10 @@ function readOpencodeStepMarker(line) {
612
652
  * footer reads `? for shortcuts · taste on`), so everything from the dot on
613
653
  * is dropped, and the dot itself is optional.
614
654
  * - **The effort is read when the banner prints one, through the shared
615
- * vocabulary.** `low` / `high` resolve; `max` is not a
616
- * {@link ReasoningEffort} and answers null rather than widening a list every
617
- * other reader and the UI type depend on. The model half is published either
618
- * way: the ` with … effort` frame delimits it cleanly, so nothing is
619
- * half-read.
655
+ * vocabulary.** `low` / `high` / `max` resolve (`max` since Issue #2835,
656
+ * which added it to {@link REASONING_EFFORT_LEVELS} for codex; before that it
657
+ * answered null). The model half is published either way: the
658
+ * ` with … effort` frame delimits it cleanly, so nothing is half-read.
620
659
  *
621
660
  * ## After a `/model` switch (measured, 1.49.0)
622
661
  *
@@ -66,6 +66,9 @@
66
66
  Object.defineProperty(exports, "__esModule", { value: true });
67
67
  exports.COMMAND_CODE_PLAN_APPROVE_CHOICE_FOOTER = exports.COMMAND_CODE_PLAN_APPROVE_CHOICE_ROW = exports.COMMAND_CODE_PLAN_REVIEW_FOOTER = exports.DISMISSABLE_PANEL_FOOTER_PATTERN = exports.COMMAND_CODE_SELECTION_LIST_FOOTER = exports.SET_AS_DEFAULT_FOOTER_PATTERN = exports.SESSION_SCOPE_FOOTER_PATTERN = exports.MAX_OPTION_NUMBER = exports.SELECTION_SHAPE_TAIL_LINE_COUNT = void 0;
68
68
  exports.isCommandCodePlanApproveChoice = isCommandCodePlanApproveChoice;
69
+ exports.hasCommandCodeSelectionListFooterAtBottom = hasCommandCodeSelectionListFooterAtBottom;
70
+ exports.hasCommandCodeDismissablePanelFooterAtBottom = hasCommandCodeDismissablePanelFooterAtBottom;
71
+ exports.hasCommandCodePlanReviewFooterAtBottom = hasCommandCodePlanReviewFooterAtBottom;
69
72
  exports.hasDismissablePanelFooter = hasDismissablePanelFooter;
70
73
  exports.readCommandCodeQuestionRegion = readCommandCodeQuestionRegion;
71
74
  exports.hasCommandCodeQuestionChrome = hasCommandCodeQuestionChrome;
@@ -282,6 +285,79 @@ function isCommandCodePlanApproveChoice(tail) {
282
285
  return (exports.COMMAND_CODE_PLAN_APPROVE_CHOICE_ROW.test(tail) &&
283
286
  exports.COMMAND_CODE_PLAN_APPROVE_CHOICE_FOOTER.test(tail));
284
287
  }
288
+ /**
289
+ * How many content rows the plan review footer's LAST row (`Cancel esc`) may
290
+ * have under it and still be the overlay's own footer (Issue #2846): the footer's
291
+ * two rows plus three.
292
+ *
293
+ * Measured over the sixteen plan review captures that carry the footer
294
+ * (`tests/fixtures/command-code-plan-review-2761/`, `-2763/`): fourteen draw ONE
295
+ * row below it, the hint bar (`type + enter to comment · …`); one draws two, an
296
+ * `Editor exited with code 127` row and then the hint bar; and the garbled
297
+ * `editor-killed-staircase.txt` draws three, because the pane wrapped the hint
298
+ * bar. So the plan review footer is NOT the last row the way the picker's is,
299
+ * and a "last two rows" rule misses every one of them.
300
+ *
301
+ * What bounds it from above is the composer. A transcript is always drawn above
302
+ * one, and its shortest measured form is four rows — rule, `❯ Ask your
303
+ * question...`, rule, status row — so a footer QUOTED in a reply has at least
304
+ * four content rows under it and its `Approve` row falls outside this window.
305
+ * The margin between the widest overlay (3) and the narrowest composer (4) is
306
+ * one row; a capture that narrows it is the reason to re-measure, not to widen
307
+ * this.
308
+ */
309
+ const COMMAND_CODE_PLAN_REVIEW_TAIL_ROW_COUNT = 5;
310
+ /**
311
+ * The last `count` rows of `text` that carry content, joined with `\n`.
312
+ *
313
+ * Blank rows are dropped rather than counted: the pane is padded with them, and
314
+ * a real footer is set off from its hint bar by one.
315
+ */
316
+ function lastContentRows(text, count) {
317
+ return text
318
+ .split('\n')
319
+ .filter((row) => row.trim() !== '')
320
+ .slice(-count)
321
+ .join('\n');
322
+ }
323
+ /**
324
+ * Whether the LAST content row of this tail is Command Code's picker footer
325
+ * (Issue #2846).
326
+ *
327
+ * {@link COMMAND_CODE_SELECTION_LIST_FOOTER} matches the sentence anywhere, and
328
+ * a reply that quotes the `/model` footer is that sentence in the middle of the
329
+ * transcript. The picker draws the footer as the last row of the pane (all four
330
+ * captures that carry it do, `tests/fixtures/chat-dialog-card-2254/`), and a
331
+ * transcript always has the composer under it — the same reasoning
332
+ * {@link COMMAND_CODE_PLAN_APPROVE_CHOICE_FOOTER} anchors with `$`.
333
+ *
334
+ * @param tail - the frame's last rows, ANSI stripped, last content row last
335
+ */
336
+ function hasCommandCodeSelectionListFooterAtBottom(tail) {
337
+ return exports.COMMAND_CODE_SELECTION_LIST_FOOTER.test(lastContentRows(tail, 1));
338
+ }
339
+ /**
340
+ * Whether the LAST content row of this tail is the dismiss-only panel's footer
341
+ * (Issue #2846). `Press Esc to close` is the panel's last row
342
+ * ({@link DISMISSABLE_PANEL_FOOTER_PATTERN}); the same row inside a reply has
343
+ * the composer under it.
344
+ *
345
+ * @param tail - the frame's last rows, ANSI stripped, last content row last
346
+ */
347
+ function hasCommandCodeDismissablePanelFooterAtBottom(tail) {
348
+ return exports.DISMISSABLE_PANEL_FOOTER_PATTERN.test(lastContentRows(tail, 1));
349
+ }
350
+ /**
351
+ * Whether the plan review footer ({@link COMMAND_CODE_PLAN_REVIEW_FOOTER}) sits
352
+ * at the bottom of this tail (Issue #2846): both of its rows are within the last
353
+ * {@link COMMAND_CODE_PLAN_REVIEW_TAIL_ROW_COUNT} content rows, which is the
354
+ * overlay's own hint bar and nothing as tall as a composer.
355
+ *
356
+ * @param tail - the frame's last rows, ANSI stripped, last content row last
357
+ */
358
+ function hasCommandCodePlanReviewFooterAtBottom(tail) {
359
+ return exports.COMMAND_CODE_PLAN_REVIEW_FOOTER.test(lastContentRows(tail, COMMAND_CODE_PLAN_REVIEW_TAIL_ROW_COUNT));
360
+ }
285
361
  /**
286
362
  * How many rows from the end of the content the dismiss footer is looked for.
287
363
  *
@@ -17,6 +17,7 @@
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.antigravityStatusDetector = exports.VERIFIED_AGAINST = void 0;
20
+ exports.isAntigravityQuotedNumberedList = isAntigravityQuotedNumberedList;
20
21
  const cli_patterns_1 = require("../../cli-patterns");
21
22
  const status_reason_1 = require("../../status-reason");
22
23
  const run_detection_1 = require("../run-detection");
@@ -61,9 +62,66 @@ function isAntigravitySurveyOpen(contentLines) {
61
62
  const { promptPattern } = (0, cli_patterns_1.getCliToolPatterns)('antigravity');
62
63
  return !tail.slice(surveyAt + 1).some(row => promptPattern.test(row));
63
64
  }
65
+ /**
66
+ * Is the screen the selection-list pattern matched the LIVE one, rather than
67
+ * words the model quoted or a screen left in the scrollback? (Issue #2845)
68
+ *
69
+ * agy paints no `>` composer while a dialog or picker is open — the screen
70
+ * replaces it — so a composer drawn below the last row the pattern matches
71
+ * means that row is not an open screen's. Without this the model quoting
72
+ * `↑/↓ Navigate` in a reply put the whole pane at `waiting` while agy sat at its
73
+ * composer, and Auto-Yes answered a dialog nobody had opened. The same reading
74
+ * `locateAntigravityDialogRegion` gives the numbered dialog's footer, and
75
+ * `isAntigravitySurveyOpen` above gives the survey row, with the same composer
76
+ * pattern.
77
+ */
78
+ function isAntigravitySelectionScreenOpen(lastLines) {
79
+ const rows = lastLines.split('\n');
80
+ let matchedAt = -1;
81
+ for (let i = rows.length - 1; i >= 0; i--) {
82
+ if (cli_patterns_1.ANTIGRAVITY_SELECTION_LIST_PATTERN.test(rows[i])) {
83
+ matchedAt = i;
84
+ break;
85
+ }
86
+ }
87
+ if (matchedAt < 0)
88
+ return false;
89
+ const { promptPattern } = (0, cli_patterns_1.getCliToolPatterns)('antigravity');
90
+ return !rows.slice(matchedAt + 1).some(row => promptPattern.test(row));
91
+ }
92
+ /**
93
+ * Is the numbered list the shared parser read a quotation, not a dialog?
94
+ * (Issue #2845)
95
+ *
96
+ * The shared pass takes any `> 1. …` run in the last rows for a menu, wherever
97
+ * on the frame it sits. agy draws its numbered screens either IN PLACE OF the
98
+ * composer (the approval dialogs) or BELOW it (`/feedback`'s category menu),
99
+ * never above it — so a numbered list with the composer drawn under its last row
100
+ * is the model's reply, or a dialog left in the scrollback. Branch 0.9 declines
101
+ * such a frame (see {@link isAntigravitySelectionScreenOpen}); without this the
102
+ * shared pass then read the quoted `Do you want to proceed?` menu itself and
103
+ * published the composer-idle pane as `waiting` / `prompt_detected`.
104
+ */
105
+ function isAntigravityQuotedNumberedList(frame, prompt) {
106
+ if (prompt.promptData?.type !== 'multiple_choice')
107
+ return false;
108
+ const rows = (0, cli_patterns_1.stripBoxDrawing)(frame.contentLines.join('\n')).split('\n');
109
+ const { promptPattern } = (0, cli_patterns_1.getCliToolPatterns)('antigravity');
110
+ let composerAt = -1;
111
+ for (let i = rows.length - 1; i >= 0; i--) {
112
+ if (promptPattern.test(rows[i])) {
113
+ composerAt = i;
114
+ break;
115
+ }
116
+ }
117
+ if (composerAt < 0)
118
+ return false;
119
+ return !rows.slice(composerAt + 1).some(row => cli_patterns_1.ANTIGRAVITY_NUMBERED_OPTION_PATTERN.test(row));
120
+ }
64
121
  exports.antigravityStatusDetector = (0, run_detection_1.createToolStatusDetector)({
65
122
  tool: 'antigravity',
66
123
  verifiedAgainst: exports.VERIFIED_AGAINST,
124
+ isStalePrompt: isAntigravityQuotedNumberedList,
67
125
  beforePrompt(frame) {
68
126
  // 0.85. Issue #2364: the survey agy draws in place of its composer after a
69
127
  // tool decision — `[1] Good [2] Fine [3] Bad [0] Skip` under `How's the
@@ -88,8 +146,14 @@ exports.antigravityStatusDetector = (0, run_detection_1.createToolStatusDetector
88
146
  // screen as "generating" and NavigationButtons would never be shown. Detecting the
89
147
  // selection list here — ahead of thinking — is the fix. Mirrors the Copilot /
90
148
  // Codex early-detection pattern.
149
+ //
150
+ // Issue #2845: only while the screen is the live one. The words alone are
151
+ // not enough — a reply that quotes the `↑/↓ Navigate` footer leaves them in
152
+ // the last rows of a pane whose composer is drawn below them.
91
153
  if (!cli_patterns_1.ANTIGRAVITY_SELECTION_LIST_PATTERN.test(frame.lastLines))
92
154
  return null;
155
+ if (!isAntigravitySelectionScreenOpen(frame.lastLines))
156
+ return null;
93
157
  // Issue #2270 / #2364: except when the frame is a NUMBERED dialog.
94
158
  //
95
159
  // #997 widened the pattern above to the bare `↑/↓ Navigate` footer so the
@@ -16,6 +16,7 @@ const composer_text_1 = require("../../composer-text");
16
16
  const prompt_detect_multiple_choice_1 = require("../../prompt-detect-multiple-choice");
17
17
  const status_reason_1 = require("../../status-reason");
18
18
  const prompt_1 = require("./prompt");
19
+ const frame_1 = require("../frame");
19
20
  const run_detection_1 = require("../run-detection");
20
21
  const patterns_1 = require("./patterns");
21
22
  /**
@@ -93,6 +94,30 @@ function readIdleEvidence(frame) {
93
94
  return 'positive';
94
95
  return 'none';
95
96
  }
97
+ /**
98
+ * The rows a selection-list footer may be read from (Issue #2847).
99
+ *
100
+ * The footer belongs to the picker, and a picker is either the input box's
101
+ * neighbour or its replacement. Above the input box is the conversation, which
102
+ * can quote the footer's wording (a reply explaining the picker, a pasted
103
+ * transcript) without any picker being open — and reading the whole 15-row tail
104
+ * turned that quotation into `waiting` on an idle composer.
105
+ *
106
+ * So when the input box is on screen, only its rows and the status bar under it
107
+ * are read (still inside the 15-row tail, never wider). When there is no input
108
+ * box, a picker has taken its place and the tail is read as before.
109
+ *
110
+ * The box is located by {@link findClaudeInputBox}, the finder
111
+ * {@link findClaudeTranscriptTail} and {@link readIdleEvidence} already use, so
112
+ * this rule cannot disagree with them about where the conversation ends.
113
+ */
114
+ function selectionFooterRows(frame) {
115
+ const box = (0, composer_text_1.findClaudeInputBox)(frame.contentLines);
116
+ if (!box)
117
+ return frame.lastLines;
118
+ const tailStart = frame.contentLines.length - frame_1.STATUS_CHECK_LINE_COUNT;
119
+ return frame.contentLines.slice(Math.max(box.openingSeparator, tailStart)).join('\n');
120
+ }
96
121
  exports.claudeStatusDetector = (0, run_detection_1.createToolStatusDetector)({
97
122
  tool: 'claude',
98
123
  verifiedAgainst: patterns_1.VERIFIED_AGAINST,
@@ -102,7 +127,9 @@ exports.claudeStatusDetector = (0, run_detection_1.createToolStatusDetector)({
102
127
  // use arrow keys + Enter to navigate and toggle, not number input.
103
128
  // The 15-line window may miss the question line, causing SEC-001a rejection above.
104
129
  // Detect via the footer instruction pattern and show NavigationButtons instead of PromptPanel.
105
- if (cli_patterns_1.CLAUDE_SELECTION_LIST_FOOTER.test(frame.lastLines)) {
130
+ // Read only the rows at or below the input box (Issue #2847): a footer quoted in the reply
131
+ // above it is conversation, not a picker.
132
+ if (cli_patterns_1.CLAUDE_SELECTION_LIST_FOOTER.test(selectionFooterRows(frame))) {
106
133
  return {
107
134
  status: 'waiting',
108
135
  confidence: 'high',
@@ -101,6 +101,7 @@ exports.CODEX_DIALOG_FOOTER_PATTERN = exports.CODEX_DIALOG_RULES_VERIFIED_AGAINS
101
101
  exports.readCodexGlyphRowKind = readCodexGlyphRowKind;
102
102
  exports.findCodexBottomGlyphRow = findCodexBottomGlyphRow;
103
103
  exports.readCodexDialogFrame = readCodexDialogFrame;
104
+ exports.isCodexComposerAtBottom = isCodexComposerAtBottom;
104
105
  exports.reportCodexDialogFooterDrift = reportCodexDialogFooterDrift;
105
106
  exports.resetCodexDialogFooterDriftForTests = resetCodexDialogFooterDriftForTests;
106
107
  const logger_1 = require("../../../../lib/logger");
@@ -384,6 +385,56 @@ function readCodexDialogFrame(raw, contentLines, contentEnd) {
384
385
  footerRecognised: exports.CODEX_DIALOG_FOOTER_PATTERN.test(footer),
385
386
  };
386
387
  }
388
+ /** A `›` row that is a numbered option (`› 1. …`), as opposed to the composer. */
389
+ const CODEX_NUMBERED_GLYPH_ROW_PATTERN = /^\s*›\s*\d{1,2}[.)]\s/;
390
+ /**
391
+ * Is the bottom of this codex pane the composer (Issue #2841)?
392
+ *
393
+ * codex draws exactly one thing between the conversation and its status bar:
394
+ * the composer, or a dialog that REPLACES the composer while it is open. Every
395
+ * measured dialog frame (`tests/fixtures/codex-dialogs-0155/`,
396
+ * `tests/fixtures/codex-live-2310/`, `codex-live-1628/`) ends on the dialog's
397
+ * rows with no `›` row below its footer. So when the composer is the pane's
398
+ * bottom, no dialog is open, whatever the conversation above it says — and the
399
+ * conversation is free text: an agent quoting another session's approval dialog
400
+ * (the reported case), a numbered list, a pasted footer.
401
+ *
402
+ * This is the one question every dialog reading in this module asks first, so
403
+ * that no reading can be satisfied by words in the transcript.
404
+ *
405
+ * Two readings, in order:
406
+ *
407
+ * 1. **raw** — the bottom-most `›` row of the RAW capture
408
+ * ({@link findCodexBottomGlyphRow}) is `composer` → true; `option` → false;
409
+ * `transcript-echo` → false (not shown to be the composer).
410
+ * 2. **stripped** (no attributes: Auto-Yes and `prompt-answer-sender` hand the
411
+ * layer a `stripAnsi`'d capture) — the last non-blank row of the content
412
+ * region starts with `›` and is not a numbered option row. A dialog's
413
+ * highlighted row can also be the last row; a numbered one is excluded here,
414
+ * and an unnumbered one keeps the reading it had before (branch 2.7 B
415
+ * already calls that frame `ready` when stripped).
416
+ *
417
+ * False only means "not shown to be the composer": callers must use it as a
418
+ * veto on a dialog reading, never as evidence of one.
419
+ *
420
+ * @param raw - The capture exactly as it arrived (`NormalizedFrame.raw`)
421
+ * @param contentLines - The ANSI-stripped rows (`NormalizedFrame.contentLines`)
422
+ * @param contentEnd - Exclusive end of the conversation region (the row above
423
+ * the status bar with padding walked off, or `contentLines.length`)
424
+ */
425
+ function isCodexComposerAtBottom(raw, contentLines, contentEnd) {
426
+ const glyphRow = findCodexBottomGlyphRow(raw);
427
+ if (glyphRow !== null)
428
+ return glyphRow.kind === 'composer';
429
+ const end = Math.min(contentEnd, contentLines.length);
430
+ let last = end - 1;
431
+ while (last >= 0 && contentLines[last].trim() === '')
432
+ last--;
433
+ if (last < 0)
434
+ return false;
435
+ const row = contentLines[last];
436
+ return row.trimStart().startsWith(exports.CODEX_GLYPH) && !CODEX_NUMBERED_GLYPH_ROW_PATTERN.test(row);
437
+ }
387
438
  /** Index of the bottom-most `›` row within the content region, or -1. */
388
439
  function findLastGlyphContentRow(contentLines, endExclusive) {
389
440
  for (let i = Math.min(endExclusive, contentLines.length) - 1; i >= 0; i--) {
@@ -166,6 +166,16 @@ exports.codexStatusDetector = (0, run_detection_1.createToolStatusDetector)({
166
166
  verifiedAgainst: exports.VERIFIED_AGAINST,
167
167
  beforePrompt(frame) {
168
168
  const { contentLines } = frame;
169
+ // 0.6. The composer is the pane's bottom → no dialog is open (Issue #2841).
170
+ // Every branch below reads dialog chrome (a pager footer, a lifecycle
171
+ // screen, a "Press enter to confirm" footer, a `›`-selected block) out of
172
+ // rows that can also be conversation text. codex hides the composer while a
173
+ // dialog is up, so a composer at the bottom vetoes all of them at once, and
174
+ // the chain goes on to the prompt / thinking / idle readings it would have
175
+ // reached had the transcript not quoted anything.
176
+ if ((0, cli_patterns_2.isCodexComposerAtBottom)(frame.raw, contentLines, findCodexContentEnd(contentLines))) {
177
+ return null;
178
+ }
169
179
  // 0.7. Codex: pager / edit-previous (transcript) mode detection (Issue #1017)
170
180
  // Codex's transcript pager renders scroll / edit key-hint footers, e.g.:
171
181
  // "↑/↓ to scroll pgup/pgdn to page home/end to jump"
@@ -321,15 +331,24 @@ exports.codexStatusDetector = (0, run_detection_1.createToolStatusDetector)({
321
331
  return null;
322
332
  },
323
333
  isStalePrompt(frame) {
324
- return isCodexStalePrompt(frame.contentLines);
334
+ // Issue #2841: a prompt above the composer is conversation text, not a
335
+ // dialog — the same veto branch 0.6 applies to the status branches.
336
+ return (isCodexStalePrompt(frame.contentLines) ||
337
+ (0, cli_patterns_2.isCodexComposerAtBottom)(frame.raw, frame.contentLines, findCodexContentEnd(frame.contentLines)));
325
338
  },
326
339
  // §4 D1 決定 4 (Issue #1928). All three inputs are readings this module
327
340
  // already performs for its status branches, passed rather than recomputed:
328
341
  // the content boundary (0.8 / 2.7), the #1160 staleness guard and the #1829
329
342
  // lifecycle screens. `prompt.ts` therefore needs no import from this file.
330
343
  detectDialog(frame) {
344
+ // Issue #2841: Auto-Yes and `respond` act on this answer, so it takes the
345
+ // same veto as the status branches — before the lifecycle screens too, whose
346
+ // wording (`Do you trust …`) can be quoted like any other.
347
+ const contentEnd = findCodexContentEnd(frame.contentLines);
348
+ if ((0, cli_patterns_2.isCodexComposerAtBottom)(frame.raw, frame.contentLines, contentEnd))
349
+ return null;
331
350
  return (0, prompt_1.detectCodexDialog)(frame, {
332
- contentEnd: findCodexContentEnd(frame.contentLines),
351
+ contentEnd,
333
352
  stalePrompt: isCodexStalePrompt(frame.contentLines),
334
353
  lifecycleDialog: (0, cli_patterns_1.getCodexLifecycleDialog)(frame.clean),
335
354
  });