commandmate 0.25.0 → 0.26.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 (219) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +117 -117
  3. package/.next/app-path-routes-manifest.json +22 -22
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +12 -12
  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/app/update/route_client-reference-manifest.js +1 -1
  9. package/.next/server/app/api/app/update-check/route.js +1 -1
  10. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  11. package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
  12. package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
  18. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/hooks/permission-request/route.js +1 -1
  31. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  46. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  58. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/todos/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]/cli-tool/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  67. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  70. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  72. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  76. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  82. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  96. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/start-polling/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  135. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  136. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  137. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  140. package/.next/server/app/page_client-reference-manifest.js +1 -1
  141. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  143. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  144. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  145. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  146. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  147. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  148. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  149. package/.next/server/app/worktrees/[id]/page.js +1 -1
  150. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  151. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  152. package/.next/server/app-paths-manifest.json +22 -22
  153. package/.next/server/chunks/3241.js +1 -1
  154. package/.next/server/chunks/4455.js +1 -1
  155. package/.next/server/chunks/5959.js +4 -4
  156. package/.next/server/chunks/6130.js +1 -1
  157. package/.next/server/chunks/6274.js +12 -9
  158. package/.next/server/chunks/7458.js +6 -6
  159. package/.next/server/chunks/9373.js +4 -3
  160. package/.next/server/middleware-manifest.json +5 -5
  161. package/.next/server/pages/500.html +1 -1
  162. package/.next/server/pages-manifest.json +1 -1
  163. package/.next/server/server-reference-manifest.json +1 -1
  164. package/.next/static/chunks/{6381-51f9c4a128aadc0b.js → 6381-9303430933084b56.js} +1 -1
  165. package/.next/static/chunks/app/worktrees/[id]/{page-207c69540fd856ef.js → page-fbdf4621a2911ac1.js} +1 -1
  166. package/.next/trace +12 -12
  167. package/dist/cli/commands/task.d.ts.map +1 -1
  168. package/dist/cli/commands/task.js +2 -1
  169. package/dist/cli/commands/verify.d.ts +14 -0
  170. package/dist/cli/commands/verify.d.ts.map +1 -1
  171. package/dist/cli/commands/verify.js +109 -3
  172. package/dist/cli/commands/wait.d.ts +2 -0
  173. package/dist/cli/commands/wait.d.ts.map +1 -1
  174. package/dist/cli/commands/wait.js +184 -4
  175. package/dist/cli/types/api-responses.d.ts +172 -0
  176. package/dist/cli/types/api-responses.d.ts.map +1 -1
  177. package/dist/cli/types/index.d.ts +31 -0
  178. package/dist/cli/types/index.d.ts.map +1 -1
  179. package/dist/cli/types/index.js +26 -0
  180. package/dist/cli/utils/verify-runner.d.ts +36 -1
  181. package/dist/cli/utils/verify-runner.d.ts.map +1 -1
  182. package/dist/cli/utils/verify-runner.js +114 -6
  183. package/dist/server/src/lib/auto-yes-state.js +108 -3
  184. package/dist/server/src/lib/cli-tools/antigravity.js +8 -4
  185. package/dist/server/src/lib/cli-tools/codex.js +5 -1
  186. package/dist/server/src/lib/cli-tools/copilot.js +5 -1
  187. package/dist/server/src/lib/cli-tools/gemini.js +15 -15
  188. package/dist/server/src/lib/cli-tools/opencode.js +9 -3
  189. package/dist/server/src/lib/detection/upstream-faults.js +120 -0
  190. package/dist/server/src/lib/hooks/permission-decision-service.js +13 -5
  191. package/dist/server/src/lib/hooks/sources/antigravity/hooks-config.js +18 -11
  192. package/dist/server/src/lib/hooks/sources/antigravity/source.js +10 -9
  193. package/dist/server/src/lib/hooks/sources/claude/source.js +5 -2
  194. package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +26 -18
  195. package/dist/server/src/lib/hooks/sources/codex/source.js +6 -9
  196. package/dist/server/src/lib/hooks/sources/copilot/hook-settings.js +22 -12
  197. package/dist/server/src/lib/hooks/sources/copilot/source.js +1 -1
  198. package/dist/server/src/lib/hooks/sources/define-source.js +6 -4
  199. package/dist/server/src/lib/hooks/sources/gemini/settings-generator.js +14 -8
  200. package/dist/server/src/lib/hooks/sources/gemini/source.js +29 -29
  201. package/dist/server/src/lib/hooks/sources/index.js +4 -1
  202. package/dist/server/src/lib/hooks/sources/launch-command.js +61 -0
  203. package/dist/server/src/lib/hooks/sources/legacy-relay.js +2 -1
  204. package/dist/server/src/lib/hooks/sources/opencode/source.js +10 -7
  205. package/dist/server/src/lib/polling/prompt-dedup-state.js +95 -0
  206. package/dist/server/src/lib/polling/response-checker.js +12 -0
  207. package/dist/server/src/lib/polling/response-poller-core.js +11 -1
  208. package/dist/server/src/lib/session/agent-session-lifecycle.js +28 -7
  209. package/dist/server/src/lib/session/claude-session.js +5 -1
  210. package/dist/server/src/lib/session/current-output-builder.js +34 -0
  211. package/dist/server/src/lib/verification/env-snapshot.js +21 -3
  212. package/dist/server/src/lib/verification/gate-runner.js +258 -7
  213. package/dist/server/src/lib/verification/machine-lock.js +204 -0
  214. package/dist/server/src/lib/verification/scope-gate.js +126 -13
  215. package/dist/server/src/lib/verification/verify-config.js +85 -3
  216. package/dist/server/src/lib/verification/worktree-index.js +140 -0
  217. package/package.json +1 -1
  218. /package/.next/static/{ehkNc5xDMDkXlua08Bwq0 → hNdNiFes2xLls68SWhAU9}/_buildManifest.js +0 -0
  219. /package/.next/static/{ehkNc5xDMDkXlua08Bwq0 → hNdNiFes2xLls68SWhAU9}/_ssgManifest.js +0 -0
@@ -11,7 +11,7 @@
11
11
  * auto-yes-poller.ts -> auto-yes-state.ts -> path-validator.ts
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.THINKING_CHECK_LINE_COUNT = exports.MAX_CONCURRENT_POLLERS = exports.MAX_CONSECUTIVE_ERRORS = exports.MAX_BACKOFF_MS = exports.DUPLICATE_RETRY_EXPIRY_MS = exports.COOLDOWN_INTERVAL_MS = exports.POLLING_INTERVAL_MS = exports.executeRegexWithTimeout = exports.COMPOSITE_KEY_SEPARATOR = void 0;
14
+ exports.THINKING_CHECK_LINE_COUNT = exports.MAX_CONCURRENT_POLLERS = exports.MAX_CONSECUTIVE_ERRORS = exports.MAX_BACKOFF_MS = exports.DUPLICATE_RETRY_EXPIRY_MS = exports.COOLDOWN_INTERVAL_MS = exports.POLLING_INTERVAL_MS = exports.STOP_MATCH_EXCERPT_CONTEXT_LINES = exports.STOP_MATCH_EXCERPT_TRUNCATION_MARKER = exports.STOP_MATCH_EXCERPT_MAX_BYTES = exports.executeRegexWithTimeout = exports.COMPOSITE_KEY_SEPARATOR = void 0;
15
15
  exports.buildCompositeKey = buildCompositeKey;
16
16
  exports.extractWorktreeId = extractWorktreeId;
17
17
  exports.extractCliToolId = extractCliToolId;
@@ -21,6 +21,7 @@ exports.getAutoYesState = getAutoYesState;
21
21
  exports.setAutoYesEnabled = setAutoYesEnabled;
22
22
  exports.disableAutoYes = disableAutoYes;
23
23
  exports.clearAllAutoYesStates = clearAllAutoYesStates;
24
+ exports.truncateToUtf8Bytes = truncateToUtf8Bytes;
24
25
  exports.checkStopCondition = checkStopCondition;
25
26
  exports.deleteAutoYesState = deleteAutoYesState;
26
27
  exports.getAutoYesStateCompositeKeys = getAutoYesStateCompositeKeys;
@@ -204,9 +205,12 @@ function setAutoYesEnabled(worktreeId, cliToolId, enabled, duration, stopPattern
204
205
  * @param cliToolId - CLI tool type (default: 'claude')
205
206
  * @param reason - Optional reason for disabling ('expired' | 'stop_pattern_matched')
206
207
  * @param instanceId - Optional instance identifier (Issue #896; defaults to primary)
208
+ * @param stopMatchedText - Optional excerpt of what the stop pattern matched
209
+ * (Issue #1694). Deliberately *not* carried over from the existing state: it
210
+ * belongs to `reason`, so a disable for any other reason stores none.
207
211
  * @returns Updated auto-yes state
208
212
  */
209
- function disableAutoYes(worktreeId, cliToolId = 'claude', reason, instanceId) {
213
+ function disableAutoYes(worktreeId, cliToolId = 'claude', reason, instanceId, stopMatchedText) {
210
214
  const key = buildCompositeKey(worktreeId, cliToolId, instanceId);
211
215
  const existing = autoYesStates.get(key);
212
216
  const state = {
@@ -215,6 +219,7 @@ function disableAutoYes(worktreeId, cliToolId = 'claude', reason, instanceId) {
215
219
  expiresAt: existing?.expiresAt ?? 0,
216
220
  stopPattern: existing?.stopPattern,
217
221
  stopReason: reason,
222
+ stopMatchedText,
218
223
  };
219
224
  autoYesStates.set(key, state);
220
225
  return state;
@@ -235,6 +240,103 @@ function clearAllAutoYesStates() {
235
240
  */
236
241
  var auto_yes_config_2 = require("../config/auto-yes-config");
237
242
  Object.defineProperty(exports, "executeRegexWithTimeout", { enumerable: true, get: function () { return auto_yes_config_2.executeRegexWithTimeout; } });
243
+ /**
244
+ * Maximum size of the stop-pattern excerpt, in UTF-8 bytes, marker included
245
+ * (Issue #1694).
246
+ *
247
+ * The excerpt answers one question — "is this a real stop or did a build log
248
+ * happen to contain the pattern?" (#1678 A-5) — and a couple of lines answer
249
+ * it. The cap is a hard bound rather than a hint because `cleanOutput` is a
250
+ * whole terminal frame and a greedy pattern can match all of it, and because
251
+ * the value rides in every `capture --json` poll for the rest of the session.
252
+ */
253
+ exports.STOP_MATCH_EXCERPT_MAX_BYTES = 400;
254
+ /**
255
+ * Appended when the excerpt was cut to fit {@link STOP_MATCH_EXCERPT_MAX_BYTES}.
256
+ *
257
+ * Cutting silently would make a fragment read as the whole match, which is the
258
+ * wrong conclusion in exactly the triage this field exists for.
259
+ */
260
+ exports.STOP_MATCH_EXCERPT_TRUNCATION_MARKER = '…[truncated]';
261
+ /** Lines of context kept on either side of the match (Issue #1694). */
262
+ exports.STOP_MATCH_EXCERPT_CONTEXT_LINES = 1;
263
+ /** Shared encoder for byte-length math (UTF-8 by spec). */
264
+ const utf8Encoder = new TextEncoder();
265
+ /** UTF-8 byte length of a string. */
266
+ function utf8ByteLength(text) {
267
+ return utf8Encoder.encode(text).length;
268
+ }
269
+ /**
270
+ * Cut `text` down to `maxBytes` UTF-8 bytes, marker included, never splitting a
271
+ * character (Issue #1694).
272
+ *
273
+ * The budget is measured in bytes rather than characters because the excerpt is
274
+ * copied verbatim into a JSON payload, and a Japanese terminal frame costs 3
275
+ * bytes per character — a character cap would let one frame publish three times
276
+ * the intended payload.
277
+ *
278
+ * @internal Exported for testing purposes only.
279
+ * @param text - Text to bound
280
+ * @param maxBytes - Byte ceiling for the returned string (marker included)
281
+ * @returns `text` unchanged when it already fits, otherwise a prefix of it with
282
+ * {@link STOP_MATCH_EXCERPT_TRUNCATION_MARKER} appended
283
+ */
284
+ function truncateToUtf8Bytes(text, maxBytes = exports.STOP_MATCH_EXCERPT_MAX_BYTES) {
285
+ if (utf8ByteLength(text) <= maxBytes)
286
+ return text;
287
+ const budget = maxBytes - utf8ByteLength(exports.STOP_MATCH_EXCERPT_TRUNCATION_MARKER);
288
+ if (budget <= 0)
289
+ return exports.STOP_MATCH_EXCERPT_TRUNCATION_MARKER;
290
+ let used = 0;
291
+ let kept = '';
292
+ // for..of iterates code points, so a surrogate pair is never halved.
293
+ for (const char of text) {
294
+ const size = utf8ByteLength(char);
295
+ if (used + size > budget)
296
+ break;
297
+ kept += char;
298
+ used += size;
299
+ }
300
+ return kept + exports.STOP_MATCH_EXCERPT_TRUNCATION_MARKER;
301
+ }
302
+ /**
303
+ * Build the excerpt shown for a stop-pattern hit (Issue #1694).
304
+ *
305
+ * Whole lines, not the raw match: a pattern like `error` matched against a
306
+ * terminal frame yields the six characters `error`, which tells the operator
307
+ * nothing about whether it came from the agent asking a question or from a
308
+ * dependency's build log scrolling past. The surrounding line is the smallest
309
+ * unit that carries that.
310
+ *
311
+ * @param regex - The already-constructed stop pattern (no `g` flag, so `exec`
312
+ * is position-independent and does not disturb the caller's `test`)
313
+ * @param output - ANSI-stripped terminal output the pattern matched
314
+ * @returns Bounded excerpt, or null when the match cannot be located
315
+ */
316
+ function buildStopMatchExcerpt(regex, output) {
317
+ let match;
318
+ try {
319
+ match = regex.exec(output);
320
+ }
321
+ catch {
322
+ return null;
323
+ }
324
+ if (!match)
325
+ return null;
326
+ const matchStart = match.index;
327
+ const matchEnd = matchStart + match[0].length;
328
+ const lines = output.split('\n');
329
+ const firstLine = output.slice(0, matchStart).split('\n').length - 1;
330
+ const lastLine = output.slice(0, matchEnd).split('\n').length - 1;
331
+ const from = Math.max(0, firstLine - exports.STOP_MATCH_EXCERPT_CONTEXT_LINES);
332
+ const to = Math.min(lines.length - 1, lastLine + exports.STOP_MATCH_EXCERPT_CONTEXT_LINES);
333
+ const raw = lines.slice(from, to + 1).join('\n');
334
+ // Terminal frames are padded with blank lines and trailing spaces; those are
335
+ // pure noise here and eat the byte budget. A whitespace-only match keeps its
336
+ // raw form rather than becoming an empty, unexplained field.
337
+ const trimmed = raw.trim();
338
+ return truncateToUtf8Bytes(trimmed.length > 0 ? trimmed : raw);
339
+ }
238
340
  /**
239
341
  * Check if the terminal output matches the stop condition pattern.
240
342
  * If matched, disables auto-yes for the worktree/agent.
@@ -275,7 +377,10 @@ function checkStopCondition(compositeKey, cleanOutput, onStopMatched) {
275
377
  return false;
276
378
  }
277
379
  if (matched) {
278
- disableAutoYes(worktreeId, cliToolId, 'stop_pattern_matched', instanceId);
380
+ // Issue #1694: what matched, not just that something did. Built here
381
+ // because this is the only frame that still holds the output.
382
+ const stopMatchedText = buildStopMatchExcerpt(regex, cleanOutput) ?? undefined;
383
+ disableAutoYes(worktreeId, cliToolId, 'stop_pattern_matched', instanceId, stopMatchedText);
279
384
  if (onStopMatched) {
280
385
  onStopMatched(compositeKey);
281
386
  }
@@ -136,15 +136,19 @@ class AntigravityTool extends base_1.BaseCLITool {
136
136
  await new Promise((resolve) => setTimeout(resolve, cli_tool_timing_config_1.TUI_SESSION_CREATE_WAIT_MS));
137
137
  // Start agy in interactive mode, optionally pinned to a model.
138
138
  //
139
- // Issue #1762: `prepareAgentLaunch` merges CommandMate's named hook into
139
+ // Issue #1762: the launch merges CommandMate's named hook into
140
140
  // `~/.gemini/config/hooks.json` — agy's single global config, shared with
141
141
  // gemini's tree and with whatever the user has in it — and prefixes
142
142
  // `CM_HOOK_URL`. That variable is the *only* correlation channel agy has:
143
143
  // its payloads carry no `cwd`, its hooks run in `~/.gemini/config`, and
144
144
  // one file serves every worktree on the machine. `--model` is appended
145
- // after, so the env prefix stays in front of the command.
146
- // `CM_AGENT_HOOKS_INJECT=0` returns bare `agy`, unchanged from before.
147
- const base = (0, agent_session_lifecycle_1.prepareAgentLaunch)({ worktreeId, cliToolId: sources_1.ANTIGRAVITY_CLI_TOOL_ID, instanceId }, this.command).command;
145
+ // after the rendered line, so the env assignments stay in front of the
146
+ // command. `CM_AGENT_HOOKS_INJECT=0` returns bare `agy`, unchanged.
147
+ const base = (0, agent_session_lifecycle_1.buildAgentLaunchCommandLine)({
148
+ target: { worktreeId, cliToolId: sources_1.ANTIGRAVITY_CLI_TOOL_ID, instanceId },
149
+ executablePath: this.command,
150
+ worktreePath,
151
+ });
148
152
  const launchCommand = model ? `${base} --model ${shellSingleQuote(model)}` : base;
149
153
  await (0, tmux_1.sendKeys)(sessionName, launchCommand, true);
150
154
  // Wait for agy to initialize
@@ -166,7 +166,11 @@ class CodexTool extends base_1.BaseCLITool {
166
166
  // Falls back to the bare command on any failure and when
167
167
  // `CM_AGENT_HOOKS_INJECT=0`; a session that starts without hooks is the
168
168
  // pre-#1760 status quo, and a session that fails to start is not.
169
- const launchCommand = (0, agent_session_lifecycle_1.prepareAgentLaunch)({ worktreeId, cliToolId: sources_1.CODEX_CLI_TOOL_ID, instanceId }, this.command).command;
169
+ const launchCommand = (0, agent_session_lifecycle_1.buildAgentLaunchCommandLine)({
170
+ target: { worktreeId, cliToolId: sources_1.CODEX_CLI_TOOL_ID, instanceId },
171
+ executablePath: this.command,
172
+ worktreePath,
173
+ });
170
174
  // Start Codex CLI in interactive mode
171
175
  await (0, tmux_1.sendKeys)(sessionName, launchCommand, true);
172
176
  // Wait for Codex to initialize
@@ -149,7 +149,11 @@ class CopilotTool extends base_1.BaseCLITool {
149
149
  // Fails open in every branch: with `CM_AGENT_HOOKS_INJECT=0`, or with a
150
150
  // settings file that cannot be read or written, this is byte-for-byte the
151
151
  // `gh copilot` this line has always sent.
152
- const launchCommand = (0, agent_session_lifecycle_1.prepareAgentLaunch)({ worktreeId, cliToolId: tool_id_1.COPILOT_CLI_TOOL_ID, instanceId }, hook_settings_1.COPILOT_LAUNCH_COMMAND).command;
152
+ const launchCommand = (0, agent_session_lifecycle_1.buildAgentLaunchCommandLine)({
153
+ target: { worktreeId, cliToolId: tool_id_1.COPILOT_CLI_TOOL_ID, instanceId },
154
+ executablePath: hook_settings_1.COPILOT_LAUNCH_COMMAND,
155
+ worktreePath,
156
+ });
153
157
  // Start Copilot CLI in interactive mode
154
158
  await (0, tmux_1.sendKeys)(sessionName, launchCommand, true);
155
159
  // Wait for Copilot to initialize
@@ -16,7 +16,6 @@ const submit_verified_sender_1 = require("./submit-verified-sender");
16
16
  const tmux_capture_cache_1 = require("../tmux/tmux-capture-cache");
17
17
  const cli_patterns_1 = require("../detection/cli-patterns");
18
18
  const sources_1 = require("../../lib/hooks/sources");
19
- const source_1 = require("../../lib/hooks/sources/gemini/source");
20
19
  const agent_session_lifecycle_1 = require("../../lib/session/agent-session-lifecycle");
21
20
  const logger_1 = require("../../lib/logger");
22
21
  const cli_tool_timing_config_1 = require("../../config/cli-tool-timing-config");
@@ -92,23 +91,24 @@ class GeminiTool extends base_1.BaseCLITool {
92
91
  });
93
92
  // Wait a moment for the session to be created
94
93
  await new Promise((resolve) => setTimeout(resolve, cli_tool_timing_config_1.TUI_SESSION_CREATE_WAIT_MS));
95
- // Issue #1762: hand this worktree its own hooks config, merged into any
96
- // `.gemini/settings.json` that is already there. Written from here rather
97
- // than from the source's `prepareLaunch` because the worktree path is the
98
- // one input `AgentInstanceRef` does not carry, and gemini is the only tool
99
- // whose config is scoped to a worktree. Fail-open: a config that cannot be
100
- // written costs the events and nothing else.
101
- (0, source_1.injectGeminiHookSettings)(worktreePath, {
102
- worktreeId,
103
- cliToolId: sources_1.GEMINI_CLI_TOOL_ID,
104
- instanceId,
105
- });
106
94
  // Start Gemini CLI in interactive mode (no flags = interactive REPL).
107
- // `prepareAgentLaunch` prefixes `CM_HOOK_URL`, which is what tells the
95
+ //
96
+ // Issue #1762 hands this worktree its own hooks config, merged into any
97
+ // `.gemini/settings.json` that is already there; Issue #1846 moved that
98
+ // write inside `prepareLaunch`, where it belongs — the worktree path is
99
+ // now part of the launch context, so the second entry point this file
100
+ // used to call (`injectGeminiHookSettings`) is gone.
101
+ //
102
+ // The rendered line prefixes `CM_HOOK_URL`, which is what tells the
108
103
  // receiver which instance an event came from — `.gemini/settings.json` is
109
104
  // per worktree and cannot. `CM_AGENT_HOOKS_INJECT=0` returns the bare
110
- // command, unchanged from before this Issue.
111
- const launchCommand = (0, agent_session_lifecycle_1.prepareAgentLaunch)({ worktreeId, cliToolId: sources_1.GEMINI_CLI_TOOL_ID, instanceId }, this.command).command;
105
+ // command, unchanged from before these Issues. Fail-open throughout: a
106
+ // config that cannot be written costs the events and nothing else.
107
+ const launchCommand = (0, agent_session_lifecycle_1.buildAgentLaunchCommandLine)({
108
+ target: { worktreeId, cliToolId: sources_1.GEMINI_CLI_TOOL_ID, instanceId },
109
+ executablePath: this.command,
110
+ worktreePath,
111
+ });
112
112
  await (0, tmux_1.sendKeys)(sessionName, launchCommand, true);
113
113
  // Wait for Gemini to initialize (minimum wait for banner/auth)
114
114
  await new Promise((resolve) => setTimeout(resolve, GEMINI_INIT_WAIT_MS));
@@ -148,9 +148,15 @@ class OpenCodeTool extends base_1.BaseCLITool {
148
148
  // that nothing can read back (§5.9). Null means "no structured events",
149
149
  // and the launch below is then the pre-#1763 bare command.
150
150
  await (0, runtime_1.reserveOpencodeServerPort)(target, worktreePath);
151
- // Start OpenCode TUI. `prepareAgentLaunch` asks the tool's own
152
- // `AgentEventSource` for the command line (S3/S4/S5) and never throws.
153
- const launchCommand = (0, agent_session_lifecycle_1.prepareAgentLaunch)(target, this.command).command;
151
+ // Start OpenCode TUI. `buildAgentLaunchCommandLine` asks the tool's own
152
+ // `AgentEventSource` for the plan (S3/S4/S5) and never throws. opencode's
153
+ // environment is empty it is the one source with no correlation
154
+ // variable, because CommandMate holds the connection (#1846).
155
+ const launchCommand = (0, agent_session_lifecycle_1.buildAgentLaunchCommandLine)({
156
+ target,
157
+ executablePath: this.command,
158
+ worktreePath,
159
+ });
154
160
  await (0, tmux_1.sendKeys)(sessionName, launchCommand, true);
155
161
  // Wait for OpenCode to initialize (GPU model loading via Ollama)
156
162
  await new Promise((resolve) => setTimeout(resolve, exports.OPENCODE_INIT_WAIT_MS));
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * Signatures of an upstream (model API) fault, as they appear on an agent's
4
+ * terminal frame (Issue #1839).
5
+ *
6
+ * Moved here from `scripts/canary/expectations.ts`, where Issue #1727 first
7
+ * wrote them, because the canary is no longer the only reader: `capture --json`
8
+ * publishes the match as `upstreamFault` and `wait --fail-on-upstream-fault`
9
+ * branches on it. Two copies of a pattern list whose whole job is *not* to be
10
+ * over-eager would drift, and the drift would be silent on both sides.
11
+ *
12
+ * A leaf module by design — it imports nothing but {@link stripAnsi}, so the
13
+ * canary (which runs outside Next) and the server payload builder can share it
14
+ * without either pulling in the other's dependencies.
15
+ *
16
+ * ## What a match means, and what it does not
17
+ *
18
+ * A match means the signature was on the frame. A **non**-match means nothing at
19
+ * all about upstream health: the pane may have scrolled, the agent may render a
20
+ * failure with wording nobody has seen yet, and Issue #1834 measured a case
21
+ * where the pane went blank instead of carrying a banner. Nothing in this
22
+ * module — and nothing reading it — may report "no fault" as "upstream is
23
+ * healthy".
24
+ *
25
+ * @module lib/detection/upstream-faults
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.UPSTREAM_FAULT_TRUNCATION_MARKER = exports.UPSTREAM_FAULT_EXCERPT_MAX_BYTES = exports.UPSTREAM_FAULTS = void 0;
29
+ exports.matchUpstreamFault = matchUpstreamFault;
30
+ exports.findUpstreamFault = findUpstreamFault;
31
+ const ansi_1 = require("./ansi");
32
+ /**
33
+ * Patterns are anchored on ERROR wording, not on the words "usage limit".
34
+ * Claude's own banner advertises "up to 50% of your weekly usage limit on
35
+ * Fable 5" on a perfectly healthy frame — a looser pattern marked every
36
+ * scenario of a run as `blocked` (measured 2026-08-06), which would have hidden
37
+ * real regressions behind a fake infrastructure excuse.
38
+ *
39
+ * Order is precedence: the first match wins, so the specific overload wording
40
+ * is listed ahead of the generic `API Error` catch-all. The line Claude 2.1.236
41
+ * prints on a 529 storm contains both (`API Error: Repeated 529 Overloaded
42
+ * errors …`, measured for Issue #1839), and `overloaded` is the more useful of
43
+ * the two answers because it carries `selfRetrying: true`.
44
+ */
45
+ exports.UPSTREAM_FAULTS = [
46
+ { id: 'overloaded', pattern: /\b5\d{2}\s+Overloaded\b/i, selfRetrying: true },
47
+ { id: 'retrying', pattern: /Retrying in \d+s\s*[·•]\s*attempt \d+\/\d+/i, selfRetrying: true },
48
+ { id: 'limit-reached', pattern: /\blimit reached\b/i, selfRetrying: false },
49
+ { id: 'api-error', pattern: /\bAPI Error(?::|\s+\d{3})/i, selfRetrying: false },
50
+ ];
51
+ /**
52
+ * Bound on {@link UpstreamFaultMatch.matchedText}, in UTF-8 **bytes**.
53
+ *
54
+ * Bytes rather than characters for the reason Issue #1694 gives for its own
55
+ * excerpt: a Japanese frame runs three bytes to the character, so a
56
+ * character-counted bound publishes three times the payload it promises. The
57
+ * excerpt rides every `current-output` response of a faulted session, and those
58
+ * responses are polled every few seconds.
59
+ */
60
+ exports.UPSTREAM_FAULT_EXCERPT_MAX_BYTES = 200;
61
+ /** Appended when {@link UPSTREAM_FAULT_EXCERPT_MAX_BYTES} cut the excerpt. */
62
+ exports.UPSTREAM_FAULT_TRUNCATION_MARKER = '…[truncated]';
63
+ /**
64
+ * Truncate to a UTF-8 byte budget on a code-point boundary.
65
+ *
66
+ * A local copy rather than `truncateToUtf8Bytes` from `lib/auto-yes-state`: that
67
+ * module reaches the database and the Auto-Yes state machine, and this one is a
68
+ * leaf the canary imports from outside Next. Accumulating code points (never
69
+ * UTF-16 units) is what keeps a surrogate pair whole.
70
+ */
71
+ function truncateExcerpt(text) {
72
+ const encoder = new TextEncoder();
73
+ if (encoder.encode(text).length <= exports.UPSTREAM_FAULT_EXCERPT_MAX_BYTES)
74
+ return text;
75
+ // The marker is inside the budget, not on top of it: the bound is a promise
76
+ // about the published field, and a caller sizing a log line by it must not be
77
+ // handed twelve more characters than it asked for.
78
+ const budget = exports.UPSTREAM_FAULT_EXCERPT_MAX_BYTES - encoder.encode(exports.UPSTREAM_FAULT_TRUNCATION_MARKER).length;
79
+ let bytes = 0;
80
+ let out = '';
81
+ for (const char of text) {
82
+ const size = encoder.encode(char).length;
83
+ if (bytes + size > budget)
84
+ break;
85
+ bytes += size;
86
+ out += char;
87
+ }
88
+ return `${out}${exports.UPSTREAM_FAULT_TRUNCATION_MARKER}`;
89
+ }
90
+ /**
91
+ * The first upstream fault visible in the frame, with its line.
92
+ *
93
+ * ANSI is stripped first: the signature words are routinely split by colour
94
+ * sequences on a live pane.
95
+ */
96
+ function matchUpstreamFault(frame) {
97
+ const clean = (0, ansi_1.stripAnsi)(frame);
98
+ for (const fault of exports.UPSTREAM_FAULTS) {
99
+ const match = fault.pattern.exec(clean);
100
+ // The patterns are unanchored and stateless (no /g), so `exec` cannot carry
101
+ // a lastIndex between calls — but reading `index` requires exec regardless.
102
+ if (!match)
103
+ continue;
104
+ const lineStart = clean.lastIndexOf('\n', match.index) + 1;
105
+ const lineEnd = clean.indexOf('\n', match.index);
106
+ const line = clean.slice(lineStart, lineEnd === -1 ? undefined : lineEnd).trim();
107
+ return { fault, matchedText: truncateExcerpt(line || match[0]) };
108
+ }
109
+ return null;
110
+ }
111
+ /**
112
+ * First upstream fault visible in the frame, if any.
113
+ *
114
+ * Kept alongside {@link matchUpstreamFault} because the canary only ever wanted
115
+ * the classification (`id` / `selfRetrying`), and a caller that does not need
116
+ * the excerpt should not have to reach through a wrapper for it.
117
+ */
118
+ function findUpstreamFault(frame) {
119
+ return matchUpstreamFault(frame)?.fault ?? null;
120
+ }
@@ -63,6 +63,10 @@ const NO_DECISION = (reason) => ({
63
63
  behavior: null,
64
64
  reason,
65
65
  });
66
+ /** Production policy source: the active task's contract, TTL-cached. */
67
+ function readContractPolicy(session) {
68
+ return (0, auto_yes_policy_1.getSessionAutoYesPolicy)(session.worktreeId, session.cliToolId, session.instanceId);
69
+ }
66
70
  /**
67
71
  * The verdict for one permission request. Pure apart from reading Auto-Yes
68
72
  * state and the (TTL-cached) contract policy; the recording side effects live
@@ -80,8 +84,10 @@ const NO_DECISION = (reason) => ({
80
84
  *
81
85
  * @param session - Worktree / tool / instance the request came from
82
86
  * @param payload - Parsed payload, or null when it could not be parsed
87
+ * @param deps - Substitutions for the database-backed lookups; see
88
+ * {@link PermissionDecisionDeps}. Omit in the server.
83
89
  */
84
- function decidePermissionRequest(session, payload) {
90
+ function decidePermissionRequest(session, payload, deps = {}) {
85
91
  if (!payload) {
86
92
  return NO_DECISION('unknown-payload');
87
93
  }
@@ -98,7 +104,7 @@ function decidePermissionRequest(session, payload) {
98
104
  if (!state?.enabled) {
99
105
  return NO_DECISION('auto-yes-disabled');
100
106
  }
101
- const policy = (0, auto_yes_policy_1.getSessionAutoYesPolicy)(worktreeId, cliToolId, instanceId);
107
+ const policy = (deps.readPolicy ?? readContractPolicy)({ worktreeId, cliToolId, instanceId });
102
108
  const denial = (0, auto_yes_resolver_1.evaluatePolicyAgainstTexts)(exports.PERMISSION_REQUEST_PROMPT_TYPE, (0, permission_request_payload_1.collectToolInputMatchTexts)(payload.toolName, payload.toolInput), policy);
103
109
  if (denial) {
104
110
  return {
@@ -214,10 +220,12 @@ function reportPendingDialog(session, payload) {
214
220
  *
215
221
  * @param session - Worktree / tool / instance the request came from
216
222
  * @param payload - Parsed payload, or null when it could not be parsed
223
+ * @param deps - Substitutions for the database-backed lookups; see
224
+ * {@link PermissionDecisionDeps}. Omit in the server.
217
225
  * @returns The verdict the route serialises
218
226
  */
219
- function resolvePermissionRequest(session, payload) {
220
- const decision = decidePermissionRequest(session, payload);
227
+ function resolvePermissionRequest(session, payload, deps = {}) {
228
+ const decision = decidePermissionRequest(session, payload, deps);
221
229
  // Issue #1726: `AskUserQuestion` raises a `PermissionRequest` carrying the
222
230
  // same `tool_input` its `PreToolUse` does (§5.6), so the questions are
223
231
  // recorded from here too. Redundant on a session this server started — both
@@ -263,7 +271,7 @@ function resolvePermissionRequest(session, payload) {
263
271
  });
264
272
  }
265
273
  if (decision.behavior === 'allow' && payload) {
266
- recordAllowedPermission(session, payload);
274
+ (deps.recordAllow ?? recordAllowedPermission)(session, payload);
267
275
  }
268
276
  return decision;
269
277
  }
@@ -87,6 +87,7 @@ const logger_1 = require("../../../../lib/logger");
87
87
  // The `~/.gemini` tree belongs to gemini; agy lives inside it. The merge that
88
88
  // keeps the two from erasing each other is written once, there.
89
89
  const shared_config_tree_1 = require("../gemini/shared-config-tree");
90
+ const launch_command_1 = require("../launch-command");
90
91
  const tool_id_1 = require("./tool-id");
91
92
  const logger = (0, logger_1.createLogger)('lib/hooks/sources/antigravity/hooks-config');
92
93
  /**
@@ -347,26 +348,32 @@ function writeAntigravityHooksConfig(options = {}) {
347
348
  * terminal, so the variable is set here and nowhere else.
348
349
  *
349
350
  * The executable is quoted but not otherwise touched, so a caller may append its
350
- * own flags — `AntigravityTool.startSession` adds `--model` — after the value
351
- * this returns.
351
+ * own flags — `AntigravityTool.startSession` adds `--model` — after the rendered
352
+ * line. Since Issue #1846 the two variables come back as `env` rather than as a
353
+ * prefix on the command, which is also what keeps `--model` appendable: the
354
+ * caller appends to the *rendered* line and the assignments stay in front of it
355
+ * by construction rather than by the caller remembering to.
352
356
  *
353
- * Never throws, and returns the executable unchanged when injection is off.
357
+ * Never throws, and returns the executable with an empty environment when
358
+ * injection is off.
354
359
  *
355
360
  * @param executablePath - `agy`, or a resolved path to it
356
361
  * @param target - The instance being started
357
- * @returns The command line to type into the pane
362
+ * @returns The command to type into the pane and the environment it needs
358
363
  */
359
364
  function buildAntigravityLaunchCommand(executablePath, target, options = {}) {
360
365
  if (!(0, hook_settings_generator_1.isHookInjectionEnabled)())
361
- return executablePath;
366
+ return { command: executablePath, env: {} };
362
367
  if (!(0, types_1.isValidInstanceId)((0, hook_settings_generator_1.resolveTargetInstanceId)(target))) {
363
368
  logger.warn('antigravity-hook-invalid-instance-id', { worktreeId: target.worktreeId });
364
- return executablePath;
369
+ return { command: executablePath, env: {} };
365
370
  }
366
371
  const scoped = { ...target, cliToolId: tool_id_1.ANTIGRAVITY_CLI_TOOL_ID };
367
- const url = (0, hook_settings_generator_1.buildAgentEventUrl)(scoped, options);
368
- const permissionUrl = (0, hook_settings_generator_1.buildPermissionRequestUrl)(scoped, options);
369
- return (`CM_HOOK_URL=${(0, hook_settings_generator_1.shellQuote)(url)} ` +
370
- `${exports.ANTIGRAVITY_PERMISSION_URL_ENV_VAR}=${(0, hook_settings_generator_1.shellQuote)(permissionUrl)} ` +
371
- `${(0, hook_settings_generator_1.shellQuote)(executablePath)}`);
372
+ return {
373
+ command: (0, hook_settings_generator_1.shellQuote)(executablePath),
374
+ env: {
375
+ [launch_command_1.AGENT_EVENT_URL_ENV_VAR]: (0, hook_settings_generator_1.buildAgentEventUrl)(scoped, options),
376
+ [exports.ANTIGRAVITY_PERMISSION_URL_ENV_VAR]: (0, hook_settings_generator_1.buildPermissionRequestUrl)(scoped, options),
377
+ },
378
+ };
372
379
  }
@@ -215,15 +215,16 @@ exports.antigravityAgentEventSource = (0, define_source_1.definePushHookSource)(
215
215
  encodeVerdict: encodeAntigravityVerdict,
216
216
  // Unlike gemini, the whole config is reachable from here: the file is global,
217
217
  // so no worktree path is needed and `prepareLaunch` really does write it.
218
- prepareLaunch: (target, executablePath) => {
218
+ prepareLaunch: ({ target, executablePath }) => {
219
219
  const settingsPath = (0, hooks_config_1.writeAntigravityHooksConfig)();
220
- return {
221
- command: (0, hooks_config_1.buildAntigravityLaunchCommand)(executablePath, {
222
- worktreeId: target.worktreeId,
223
- instanceId: target.instanceId,
224
- cliToolId: tool_id_1.ANTIGRAVITY_CLI_TOOL_ID,
225
- }),
226
- settingsPath,
227
- };
220
+ // #1846: the two correlation URLs are the plan's `env`. `worktreePath` is
221
+ // in the context now and deliberately unused here — agy's config is one
222
+ // file for the machine, so there is nothing per-worktree to write.
223
+ const { command, env } = (0, hooks_config_1.buildAntigravityLaunchCommand)(executablePath, {
224
+ worktreeId: target.worktreeId,
225
+ instanceId: target.instanceId,
226
+ cliToolId: tool_id_1.ANTIGRAVITY_CLI_TOOL_ID,
227
+ });
228
+ return { command, settingsPath, env };
228
229
  },
229
230
  });
@@ -128,7 +128,7 @@ exports.claudeAgentEventSource = (0, define_source_1.definePushHookSource)({
128
128
  // S3 / S4 / S5. Delegated rather than reimplemented: `buildClaudeLaunchCommand`
129
129
  // writes the settings file, falls back to the bare path on any failure, and is
130
130
  // covered byte-for-byte by `tests/unit/hooks/hook-settings-generator.test.ts`.
131
- prepareLaunch: (target, executablePath) => {
131
+ prepareLaunch: ({ target, executablePath }) => {
132
132
  const command = (0, hook_settings_generator_1.buildClaudeLaunchCommand)(executablePath, {
133
133
  worktreeId: target.worktreeId,
134
134
  instanceId: target.instanceId,
@@ -145,6 +145,9 @@ exports.claudeAgentEventSource = (0, define_source_1.definePushHookSource)({
145
145
  cliToolId: target.cliToolId,
146
146
  })
147
147
  : null;
148
- return { command, settingsPath };
148
+ // Empty, and the only source for which that is uninteresting: `--settings`
149
+ // carries the correlation keys inside the file, so Claude never needed the
150
+ // environment prefix the other four sources reached for (#1846).
151
+ return { command, settingsPath, env: {} };
149
152
  },
150
153
  });