@gajae-code/coding-agent 0.12.0 → 0.12.1

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 (223) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/config/settings-schema.d.ts +54 -0
  4. package/dist/types/exa/mcp-client.d.ts +10 -1
  5. package/dist/types/exec/bash-executor.d.ts +45 -12
  6. package/dist/types/extensibility/extensions/types.d.ts +16 -2
  7. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  8. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  9. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  10. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  11. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  12. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  13. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  14. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  15. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +18 -0
  16. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  17. package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
  18. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  19. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
  20. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  21. package/dist/types/modes/acp/acp-agent.d.ts +2 -19
  22. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  23. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  24. package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
  25. package/dist/types/modes/components/settings-selector.d.ts +9 -0
  26. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  27. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  28. package/dist/types/reminders/star-reminder.d.ts +3 -13
  29. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  30. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  31. package/dist/types/sdk/broker/transport.d.ts +2 -0
  32. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  33. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  34. package/dist/types/sdk/bus/config.d.ts +6 -2
  35. package/dist/types/sdk/bus/index.d.ts +29 -0
  36. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
  37. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  38. package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
  39. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  40. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  41. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  42. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  43. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  44. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  45. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  46. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  47. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  48. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  49. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  50. package/dist/types/sdk/prompt-status.d.ts +23 -3
  51. package/dist/types/sdk/session.d.ts +6 -0
  52. package/dist/types/session/agent-session.d.ts +38 -2
  53. package/dist/types/session/artifacts.d.ts +8 -0
  54. package/dist/types/session/blob-store.d.ts +57 -4
  55. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  56. package/dist/types/session/session-manager.d.ts +23 -6
  57. package/dist/types/session/session-storage.d.ts +4 -0
  58. package/dist/types/session/streaming-output.d.ts +36 -7
  59. package/dist/types/setup/credential-import.d.ts +12 -0
  60. package/dist/types/skill-state/active-state.d.ts +12 -0
  61. package/dist/types/skill-state/workflow-hud.d.ts +6 -0
  62. package/dist/types/task/executor.d.ts +3 -2
  63. package/dist/types/task/index.d.ts +3 -1
  64. package/dist/types/task/provider-retry-status.d.ts +8 -0
  65. package/dist/types/task/receipt.d.ts +7 -1
  66. package/dist/types/task/types.d.ts +23 -0
  67. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  68. package/dist/types/tools/bash-interactive.d.ts +4 -1
  69. package/dist/types/tools/bash.d.ts +3 -1
  70. package/dist/types/tools/image-gen.d.ts +4 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/output-meta.d.ts +23 -2
  73. package/dist/types/tools/path-utils.d.ts +1 -0
  74. package/dist/types/tools/read.d.ts +0 -6
  75. package/dist/types/tools/review.d.ts +5 -7
  76. package/dist/types/tools/subagent.d.ts +2 -0
  77. package/dist/types/utils/gh.d.ts +13 -0
  78. package/dist/types/utils/image-resize.d.ts +1 -0
  79. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  80. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  81. package/dist/types/web/search/providers/xai.d.ts +2 -0
  82. package/package.json +10 -8
  83. package/scripts/acp-conformance-agent.ts +218 -0
  84. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  85. package/scripts/generate-sdk-operation-inventory.ts +3 -0
  86. package/scripts/resident-memory-bench.ts +1341 -0
  87. package/scripts/run-acp-conformance.ts +285 -0
  88. package/src/async/job-manager.ts +78 -20
  89. package/src/cli/fast-help.ts +1 -1
  90. package/src/cli/plugin-cli.ts +176 -18
  91. package/src/commands/team.ts +19 -1
  92. package/src/config/file-lock.ts +11 -2
  93. package/src/config/settings-schema.ts +51 -0
  94. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  95. package/src/coordinator-mcp/server.ts +5 -12
  96. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
  97. package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
  98. package/src/defaults/gjc/skills/team/SKILL.md +2 -0
  99. package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
  100. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  101. package/src/defaults/gjc-defaults.ts +4 -4
  102. package/src/exa/mcp-client.ts +12 -3
  103. package/src/exec/bash-executor.ts +144 -27
  104. package/src/extensibility/extensions/runner.ts +15 -0
  105. package/src/extensibility/extensions/types.ts +18 -1
  106. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  107. package/src/extensibility/gjc-plugins/index.ts +19 -2
  108. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  109. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  110. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  111. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  112. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  113. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  114. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  115. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  116. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  117. package/src/extensibility/gjc-plugins/types.ts +141 -0
  118. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  119. package/src/gjc-runtime/launch-tmux.ts +25 -6
  120. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  121. package/src/gjc-runtime/ralplan-runtime.ts +411 -17
  122. package/src/gjc-runtime/team-launch.ts +8 -0
  123. package/src/gjc-runtime/team-runtime.ts +140 -32
  124. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
  126. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  127. package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
  128. package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
  129. package/src/gjc-runtime/workflow-manifest.ts +4 -68
  130. package/src/internal-urls/docs-index.generated.ts +15 -15
  131. package/src/modes/DESIGN.md +51 -0
  132. package/src/modes/acp/acp-agent.ts +298 -186
  133. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  134. package/src/modes/components/notifications-settings-editor.ts +12 -1
  135. package/src/modes/components/oauth-selector.ts +68 -30
  136. package/src/modes/components/session-observer-overlay.ts +305 -86
  137. package/src/modes/components/settings-selector.ts +56 -5
  138. package/src/modes/components/status-line/gh.ts +16 -0
  139. package/src/modes/components/tool-status-header.ts +3 -14
  140. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  141. package/src/modes/controllers/command-controller.ts +4 -0
  142. package/src/modes/controllers/extension-ui-controller.ts +7 -0
  143. package/src/modes/controllers/selector-controller.ts +45 -0
  144. package/src/modes/runtime-init.ts +3 -0
  145. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  146. package/src/prompts/system/eager-todo.md +8 -4
  147. package/src/prompts/system/system-prompt.md +11 -4
  148. package/src/prompts/tools/bash.md +5 -5
  149. package/src/prompts/tools/search-tool-bm25.md +5 -0
  150. package/src/prompts/tools/task.md +1 -1
  151. package/src/reminders/star-reminder.ts +4 -42
  152. package/src/runtime-mcp/manager.ts +19 -4
  153. package/src/sdk/acp/adapter.ts +22 -6
  154. package/src/sdk/broker/transport.ts +35 -3
  155. package/src/sdk/bus/attachment-registry.ts +5 -1
  156. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  157. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  158. package/src/sdk/bus/config.ts +18 -2
  159. package/src/sdk/bus/index.ts +661 -126
  160. package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
  161. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  162. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  163. package/src/sdk/bus/reconciliation-store.ts +95 -16
  164. package/src/sdk/bus/rich-draft.ts +1 -1
  165. package/src/sdk/bus/rich-render.ts +2 -2
  166. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  167. package/src/sdk/bus/telegram-cli.ts +33 -8
  168. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  169. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  170. package/src/sdk/bus/telegram-daemon.ts +916 -23
  171. package/src/sdk/bus/telegram-reference.ts +40 -3
  172. package/src/sdk/bus/threaded-render.ts +48 -3
  173. package/src/sdk/bus/topic-registry.ts +20 -0
  174. package/src/sdk/host/query/revision-store.ts +36 -2
  175. package/src/sdk/host/reverse-leases.ts +44 -7
  176. package/src/sdk/prompt-status.ts +16 -3
  177. package/src/sdk/protocol/operation-inventory.generated.json +22 -0
  178. package/src/sdk/session.ts +88 -9
  179. package/src/session/agent-session.ts +822 -172
  180. package/src/session/artifacts.ts +22 -0
  181. package/src/session/auth-broker-config.ts +10 -3
  182. package/src/session/blob-store.ts +915 -24
  183. package/src/session/internal/managed-session-scope.ts +44 -6
  184. package/src/session/internal/managed-session-storage.ts +48 -6
  185. package/src/session/session-manager.ts +1614 -435
  186. package/src/session/session-storage.ts +9 -1
  187. package/src/session/streaming-output.ts +238 -68
  188. package/src/setup/credential-import.ts +75 -13
  189. package/src/skill-state/active-state.ts +32 -0
  190. package/src/skill-state/workflow-hud.ts +17 -0
  191. package/src/skill-state/workflow-mutation-guard.ts +496 -2
  192. package/src/task/executor.ts +122 -39
  193. package/src/task/index.ts +23 -14
  194. package/src/task/provider-retry-status.ts +16 -0
  195. package/src/task/receipt.ts +23 -7
  196. package/src/task/render.ts +6 -2
  197. package/src/task/types.ts +108 -0
  198. package/src/tools/ask-answer-registry.ts +12 -6
  199. package/src/tools/ask.ts +86 -55
  200. package/src/tools/ast-edit.ts +1 -0
  201. package/src/tools/ast-grep.ts +1 -0
  202. package/src/tools/bash-interactive.ts +8 -3
  203. package/src/tools/bash.ts +503 -60
  204. package/src/tools/find.ts +1 -0
  205. package/src/tools/image-gen.ts +29 -5
  206. package/src/tools/index.ts +4 -0
  207. package/src/tools/output-meta.ts +108 -14
  208. package/src/tools/path-utils.ts +3 -2
  209. package/src/tools/read.ts +18 -0
  210. package/src/tools/review.ts +5 -7
  211. package/src/tools/search.ts +1 -0
  212. package/src/tools/subagent-render.ts +6 -2
  213. package/src/tools/subagent.ts +6 -0
  214. package/src/tools/telegram-send.ts +6 -3
  215. package/src/utils/gh.ts +39 -0
  216. package/src/utils/image-resize.ts +3 -0
  217. package/src/web/search/providers/anthropic.ts +4 -4
  218. package/src/web/search/providers/kimi.ts +15 -5
  219. package/src/web/search/providers/searxng.ts +47 -4
  220. package/src/web/search/providers/xai.ts +7 -2
  221. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  222. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  223. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
package/src/task/types.ts CHANGED
@@ -212,6 +212,14 @@ export interface ReviewFinding {
212
212
  line_end: number;
213
213
  }
214
214
 
215
+ /** Durable full-fidelity review findings artifact associated with a task result. */
216
+ export interface ReviewFindingsArtifactRef {
217
+ uri: `artifact://${string}`;
218
+ sizeBytes: number;
219
+ sha256: string;
220
+ findingCount: number;
221
+ }
222
+
215
223
  /** Review summary submitted by the reviewer agent */
216
224
  export interface ReviewSummary {
217
225
  overall_correctness: "correct" | "incorrect";
@@ -283,6 +291,8 @@ export interface AgentProgress {
283
291
  durationMs: number;
284
292
  modelOverride?: string | string[];
285
293
  modelSubstitutionWarning?: ModelSubstitutionWarning;
294
+ /** Whether the resolved subagent model runs under the effective fast service tier. */
295
+ fastMode?: boolean;
286
296
  /** Data extracted by registered subprocess tool handlers (keyed by tool name) */
287
297
  extractedToolData?: Record<string, unknown[]>;
288
298
  /**
@@ -313,6 +323,8 @@ export interface AgentProgress {
313
323
  attempt: number;
314
324
  errorMessage: string;
315
325
  };
326
+ /** Safe diagnostic retained in terminal progress when setup fails before the first LLM request. */
327
+ setupFailure?: SetupFailureSummary;
316
328
  /**
317
329
  * Snapshot of the most recent `task` tool call's in-flight `TaskToolDetails`,
318
330
  * captured from `tool_execution_update`. Lets the parent UI surface live
@@ -323,6 +335,96 @@ export interface AgentProgress {
323
335
  inflightTaskDetails?: TaskToolDetails;
324
336
  }
325
337
 
338
+ /** Bounded diagnostic retained when subagent setup fails before an LLM request starts. */
339
+ export interface SetupFailureSummary {
340
+ summary: string;
341
+ }
342
+
343
+ const SETUP_FAILURE_SUMMARY_MAX_CHARS = 280;
344
+ const SETUP_FAILURE_SUMMARY_MAX_BYTES = 1_024;
345
+ const AUTHORIZATION_HEADER_VALUE_PATTERN = /(["']?(?:Proxy-)?Authorization\b["']?\s*:\s*)[^\r\n]*/gi;
346
+ const COOKIE_HEADER_VALUE_PATTERN = /(["']?(?:Set-)?Cookie\b["']?\s*:\s*)[^\r\n]*/gi;
347
+ const URL_CREDENTIAL_PATTERN = /([a-z][a-z0-9+.-]*:\/\/)[^/?#\s:@]+:[^@/?#\s]+@/gi;
348
+ const API_KEY_LABEL_VALUE_PATTERN = /(["']?api\s+key["']?\s*:\s*)(?:"[^"]*"|'[^']*'|[^\s&]+)/gi;
349
+ const SENSITIVE_SETUP_FAILURE_VALUE_PATTERN =
350
+ /(["']?(?:(?:[A-Za-z][A-Za-z0-9]*[_.-])*?)(?:access[_-]?token|refresh[_-]?token|session[_-]?token|id[_-]?token|api[_-]?key|client[_-]?secret|private[_-]?key|signing[_-]?key|secret|password|passwd|pwd|authorization|credential|token)(?:[_.-][A-Za-z0-9]+)*["']?)(\s*[=:]\s*)(?:"[^"]*"|'[^']*'|[^\s&]+)/gi;
351
+ const BARE_PROVIDER_TOKEN_PATTERN =
352
+ /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|sk-(?:proj-)?[A-Za-z0-9_-]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|AIza[A-Za-z0-9_-]{35}|AKIA[A-Z0-9]{16})\b/g;
353
+ const LOCAL_ABSOLUTE_PATH_PATTERN = /(^|[\s("'`=])((?:\/(?!\/)[^\s/:?"'`()[\]{},;<>]+){2,})/g;
354
+ const WINDOWS_ABSOLUTE_PATH_PATTERN =
355
+ /(^|[\s("'`=])([A-Za-z]:\\(?:[^\\/:?"'`()[\]{},;<>\s]+\\)+[^\\/:?"'`()[\]{},;<>\s]+)/g;
356
+ const LOCAL_FILE_URI_PATTERN = /\bfile:\/\/(?:localhost)?(?:\/[^\s/:?"'`()[\]{},;<>]+)+/gi;
357
+ const SENSITIVE_PATH_BASENAME_PATTERN =
358
+ /(?:^|[_.-])(?:access[_-]?token|refresh[_-]?token|session[_-]?token|id[_-]?token|api[_-]?key|client[_-]?secret|private[_-]?key|signing[_-]?key|secrets?|password|passwd|pwd|authorization|credential|token)(?:[_.-]|$)|^(?:\.env(?:\..*)?|credentials?(?:\.[A-Za-z0-9_-]+)?|id_(?:rsa|ed25519)|.+\.(?:pem|p12|pfx|key))$|^(?:sk|pk|rk|gh[opsur]|github_pat|xox[baprs])[_-][A-Za-z0-9_-]+$/i;
359
+
360
+ function redactPathBasename(path: string): string {
361
+ const basename = path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1);
362
+ return SENSITIVE_PATH_BASENAME_PATTERN.test(basename) ? "[redacted]" : basename;
363
+ }
364
+
365
+ function redactLocalAbsolutePath(_match: string, prefix: string, absolutePath: string): string {
366
+ return `${prefix}<path>/${redactPathBasename(absolutePath)}`;
367
+ }
368
+
369
+ function redactLocalFileUri(uri: string): string {
370
+ return `file://<path>/${redactPathBasename(uri)}`;
371
+ }
372
+
373
+ function normalizeSetupFailureText(value: string): string {
374
+ return value
375
+ .normalize("NFKC")
376
+ .replace(/\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007\u001B]*(?:\u0007|\u001B\\))/g, "")
377
+ .replace(/[\u200B-\u200D\u2060\uFEFF]/g, "")
378
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, "")
379
+ .replace(/\t/g, " ");
380
+ }
381
+
382
+ function capSetupFailureSummary(value: string): string {
383
+ const ellipsis = "…";
384
+ const ellipsisBytes = Buffer.byteLength(ellipsis, "utf8");
385
+ const chunks: string[] = [];
386
+ let chars = 0;
387
+ let bytes = 0;
388
+ for (const codePoint of value) {
389
+ const codePointBytes = Buffer.byteLength(codePoint, "utf8");
390
+ if (chars + 1 > SETUP_FAILURE_SUMMARY_MAX_CHARS || bytes + codePointBytes > SETUP_FAILURE_SUMMARY_MAX_BYTES) {
391
+ while (
392
+ chunks.length > 0 &&
393
+ (chars + 1 > SETUP_FAILURE_SUMMARY_MAX_CHARS || bytes + ellipsisBytes > SETUP_FAILURE_SUMMARY_MAX_BYTES)
394
+ ) {
395
+ const removed = chunks.pop()!;
396
+ chars -= 1;
397
+ bytes -= Buffer.byteLength(removed, "utf8");
398
+ }
399
+ return `${chunks.join("")}${ellipsis}`;
400
+ }
401
+ chunks.push(codePoint);
402
+ chars += 1;
403
+ bytes += codePointBytes;
404
+ }
405
+ return chunks.join("");
406
+ }
407
+
408
+ /** Create a bounded diagnostic that preserves a setup failure's cause without exposing credentials or local paths. */
409
+ export function createSetupFailureSummary(error: unknown): SetupFailureSummary {
410
+ const message = normalizeSetupFailureText(error instanceof Error ? error.message : String(error));
411
+ const summary = message
412
+ .replace(AUTHORIZATION_HEADER_VALUE_PATTERN, "$1[redacted]")
413
+ .replace(COOKIE_HEADER_VALUE_PATTERN, "$1[redacted]")
414
+ .replace(URL_CREDENTIAL_PATTERN, "$1[redacted]@")
415
+ .replace(API_KEY_LABEL_VALUE_PATTERN, "$1[redacted]")
416
+ .replace(LOCAL_FILE_URI_PATTERN, redactLocalFileUri)
417
+ .replace(LOCAL_ABSOLUTE_PATH_PATTERN, redactLocalAbsolutePath)
418
+ .replace(WINDOWS_ABSOLUTE_PATH_PATTERN, redactLocalAbsolutePath)
419
+ .replace(SENSITIVE_SETUP_FAILURE_VALUE_PATTERN, "$1$2[redacted]")
420
+ .replace(BARE_PROVIDER_TOKEN_PATTERN, "[redacted]")
421
+ .replace(/\s+/g, " ")
422
+ .trim();
423
+ return {
424
+ summary: capSetupFailureSummary(summary) || "Subagent setup failed.",
425
+ };
426
+ }
427
+
326
428
  /** Result from a single agent execution */
327
429
  export interface SingleResult {
328
430
  index: number;
@@ -346,7 +448,11 @@ export interface SingleResult {
346
448
  contextWindow?: number;
347
449
  modelOverride?: string | string[];
348
450
  modelSubstitutionWarning?: ModelSubstitutionWarning;
451
+ /** Whether the resolved subagent model ran under the effective fast service tier. */
452
+ fastMode?: boolean;
349
453
  error?: string;
454
+ /** Safe diagnostic for a failure before the subagent sent its first LLM request. */
455
+ setupFailure?: SetupFailureSummary;
350
456
  aborted?: boolean;
351
457
  abortReason?: string;
352
458
  paused?: boolean;
@@ -366,6 +472,8 @@ export interface SingleResult {
366
472
  producedChanges?: boolean;
367
473
  /** Data extracted by registered subprocess tool handlers (keyed by tool name) */
368
474
  extractedToolData?: Record<string, unknown[]>;
475
+ /** Full wrapper-owned review evidence, kept separate from caller completion data. */
476
+ reviewFindingsRef?: ReviewFindingsArtifactRef;
369
477
  /**
370
478
  * Terminal retry failure, when the subagent exited because the auto-retry
371
479
  * loop gave up (retry-after exceeded the cap, or all attempts exhausted).
@@ -11,21 +11,27 @@ import { logger } from "@gajae-code/utils";
11
11
  import type { WorkflowGateEmitter } from "../modes/shared/agent-wire/workflow-gate-broker";
12
12
  import type { AskAnswerSource } from "./index";
13
13
 
14
- const sources = new Map<string, AskAnswerSource>();
14
+ const sources = new Map<string, AskAnswerSource[]>();
15
15
  const workflowGateEmitters = new Map<string, WorkflowGateEmitter>();
16
16
  const workflowGateListeners = new Map<string, Set<(emitter: WorkflowGateEmitter | undefined) => void>>();
17
17
 
18
- /** Register `source` for `sessionId`. Returns a disposer that clears it. */
18
+ /** Register `source` for `sessionId`. Returns a disposer that restores the prior source. */
19
19
  export function registerAskAnswerSource(sessionId: string, source: AskAnswerSource): () => void {
20
- sources.set(sessionId, source);
20
+ const stack = sources.get(sessionId) ?? [];
21
+ stack.push(source);
22
+ sources.set(sessionId, stack);
21
23
  return () => {
22
- if (sources.get(sessionId) === source) sources.delete(sessionId);
24
+ const current = sources.get(sessionId);
25
+ if (!current) return;
26
+ const index = current.lastIndexOf(source);
27
+ if (index >= 0) current.splice(index, 1);
28
+ if (current.length === 0) sources.delete(sessionId);
23
29
  };
24
30
  }
25
31
 
26
- /** The answer source for `sessionId`, if one is registered. */
32
+ /** The highest-priority answer source for `sessionId`, if one is registered. */
27
33
  export function getAskAnswerSource(sessionId: string): AskAnswerSource | undefined {
28
- return sources.get(sessionId);
34
+ return sources.get(sessionId)?.at(-1);
29
35
  }
30
36
 
31
37
  /** Publish a session's current workflow-gate emitter after mode initialization. */
package/src/tools/ask.ts CHANGED
@@ -1281,9 +1281,11 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1281
1281
  // attended TUI asks would route to emitGate() and hang forever waiting on
1282
1282
  // a remote responder.
1283
1283
  const hasInteractiveUi = context?.hasUI === true && !!context.ui;
1284
- const canUseWorkflowGate = !hasInteractiveUi && gateEmitter?.supportsRemoteGateAnswers() === true;
1285
- // Headless fallback: SDK workflow gates are the non-TUI answer path.
1286
- if (!canUseWorkflowGate && (!context?.hasUI || !context.ui)) {
1284
+ const initialAnswerSource = this.session.getAskAnswerSource?.();
1285
+ const canUseWorkflowGate =
1286
+ !hasInteractiveUi && !initialAnswerSource && gateEmitter?.supportsRemoteGateAnswers() === true;
1287
+ // Headless fallback: an SDK answer source or workflow gate is the non-TUI answer path.
1288
+ if (!hasInteractiveUi && !initialAnswerSource && !canUseWorkflowGate) {
1287
1289
  context?.abort();
1288
1290
  throw new ToolAbortError("Ask tool requires interactive mode");
1289
1291
  }
@@ -1291,9 +1293,11 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1291
1293
  const extensionUi = context?.ui;
1292
1294
  const ui: UIContext = {
1293
1295
  select: (prompt, options, dialogOptions) => {
1294
- if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
1295
1296
  const source = this.session.getAskAnswerSource?.();
1296
- if (!source) return extensionUi.select(prompt, options, dialogOptions);
1297
+ if (!source) {
1298
+ if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
1299
+ return extensionUi.select(prompt, options, dialogOptions);
1300
+ }
1297
1301
  // Race the local UI against a remote answer (e.g. an SDK reply) so asks
1298
1302
  // can be answered without local UI interaction. The first valid answer
1299
1303
  // wins; the loser is aborted so neither side is left hanging:
@@ -1322,7 +1326,10 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1322
1326
  : source.awaitAnswer(prompt, options, remoteController.signal)
1323
1327
  )
1324
1328
  .then((answer): RemoteRaceResult | Promise<RemoteRaceResult> => {
1325
- if (answer === undefined) return new Promise<never>(() => {});
1329
+ if (answer === undefined) {
1330
+ if (!extensionUi) throw new ToolAbortError("Ask was cancelled by the remote client");
1331
+ return new Promise<never>(() => {});
1332
+ }
1326
1333
  const receipt = typeof answer === "string" ? legacyAskReceipt(answer) : answer;
1327
1334
  if (generation !== remoteGeneration) {
1328
1335
  return receipt
@@ -1375,40 +1382,49 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1375
1382
  }
1376
1383
  return { winner: "remote" as const, value: selectedValue, receipt };
1377
1384
  })
1378
- .catch(ignoreRemoteAnswerFailure);
1379
-
1380
- const local = extensionUi
1381
- .select(prompt, options, { ...dialogOptions, signal: localController.signal })
1382
- .then(answer => {
1383
- if (generation === remoteGeneration) remoteGeneration++;
1384
- remoteController.abort();
1385
- return { winner: "local" as const, value: answer };
1386
- })
1387
1385
  .catch(error => {
1388
- if (generation === remoteGeneration) remoteGeneration++;
1389
- remoteController.abort();
1390
- throw error;
1386
+ if (!extensionUi) throw error;
1387
+ return ignoreRemoteAnswerFailure(error);
1391
1388
  });
1389
+
1390
+ const local = extensionUi
1391
+ ? extensionUi
1392
+ .select(prompt, options, { ...dialogOptions, signal: localController.signal })
1393
+ .then(answer => {
1394
+ if (generation === remoteGeneration) remoteGeneration++;
1395
+ remoteController.abort();
1396
+ return { winner: "local" as const, value: answer };
1397
+ })
1398
+ .catch(error => {
1399
+ if (generation === remoteGeneration) remoteGeneration++;
1400
+ remoteController.abort();
1401
+ throw error;
1402
+ })
1403
+ : new Promise<never>(() => {});
1392
1404
  // The losing selector may reject when aborted after the race already settled;
1393
1405
  // swallow that so it is not an unhandled rejection (the race result is unaffected).
1394
1406
  void local.catch(() => undefined);
1395
- return Promise.race([local, remote]).then(async result => {
1396
- if (result.winner === "remote") {
1397
- localController.abort();
1398
- if (result.settlement) await result.receipt.settle(result.settlement);
1399
- else activeRemoteReceipt = result.receipt;
1400
- } else {
1401
- void remote.then(remoteResult =>
1402
- remoteResult.receipt.settle({ kind: "resolve_without_commit", reason: "aborted" }),
1403
- );
1404
- }
1405
- return result.value;
1406
- });
1407
+ return Promise.race([local, remote])
1408
+ .then(async result => {
1409
+ if (result.winner === "remote") {
1410
+ localController.abort();
1411
+ if (result.settlement) await result.receipt.settle(result.settlement);
1412
+ else activeRemoteReceipt = result.receipt;
1413
+ } else {
1414
+ void remote.then(remoteResult =>
1415
+ remoteResult.receipt.settle({ kind: "resolve_without_commit", reason: "aborted" }),
1416
+ );
1417
+ }
1418
+ return result.value;
1419
+ })
1420
+ .finally(() => toolSignal?.removeEventListener("abort", abortRace));
1407
1421
  },
1408
1422
  editor: (title, prefill, dialogOptions, editorOptions) => {
1409
- if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
1410
1423
  const source = this.session.getAskAnswerSource?.();
1411
- if (!source) return extensionUi.editor(title, prefill, dialogOptions, editorOptions);
1424
+ if (!source) {
1425
+ if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
1426
+ return extensionUi.editor(title, prefill, dialogOptions, editorOptions);
1427
+ }
1412
1428
  // Race the local editor against a remote free-text answer so "Other / type
1413
1429
  // your own" custom input can be provided remotely (e.g. a typed Telegram
1414
1430
  // reply) instead of blocking on the local-only editor. Mirrors `select`.
@@ -1439,7 +1455,10 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1439
1455
  : source.awaitAnswer(title, [], remoteController.signal)
1440
1456
  )
1441
1457
  .then((answer): RemoteRaceResult | Promise<RemoteRaceResult> => {
1442
- if (answer === undefined) return new Promise<never>(() => {});
1458
+ if (answer === undefined) {
1459
+ if (!extensionUi) throw new ToolAbortError("Ask was cancelled by the remote client");
1460
+ return new Promise<never>(() => {});
1461
+ }
1443
1462
  const receipt = typeof answer === "string" ? legacyAskReceipt(answer) : answer;
1444
1463
  if (generation !== remoteGeneration) {
1445
1464
  return receipt
@@ -1450,31 +1469,43 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1450
1469
  receipt.interaction.kind === "control" ? REMOTE_NAVIGATION_FORWARD : receipt.interaction.value;
1451
1470
  return { winner: "remote" as const, value, receipt };
1452
1471
  })
1453
- .catch(ignoreRemoteAnswerFailure);
1454
- const local = extensionUi
1455
- .editor(title, prefill, { ...(dialogOptions ?? {}), signal: localController.signal }, editorOptions)
1456
- .then(answer => {
1457
- if (generation === remoteGeneration) remoteGeneration++;
1458
- remoteController.abort();
1459
- return { winner: "local" as const, value: answer };
1460
- })
1461
1472
  .catch(error => {
1462
- if (generation === remoteGeneration) remoteGeneration++;
1463
- remoteController.abort();
1464
- throw error;
1473
+ if (!extensionUi) throw error;
1474
+ return ignoreRemoteAnswerFailure(error);
1465
1475
  });
1476
+ const local = extensionUi
1477
+ ? extensionUi
1478
+ .editor(
1479
+ title,
1480
+ prefill,
1481
+ { ...(dialogOptions ?? {}), signal: localController.signal },
1482
+ editorOptions,
1483
+ )
1484
+ .then(answer => {
1485
+ if (generation === remoteGeneration) remoteGeneration++;
1486
+ remoteController.abort();
1487
+ return { winner: "local" as const, value: answer };
1488
+ })
1489
+ .catch(error => {
1490
+ if (generation === remoteGeneration) remoteGeneration++;
1491
+ remoteController.abort();
1492
+ throw error;
1493
+ })
1494
+ : new Promise<never>(() => {});
1466
1495
  void local.catch(() => undefined);
1467
- return Promise.race([local, remote]).then(result => {
1468
- if (result.winner === "remote") {
1469
- activeRemoteReceipt = result.receipt;
1470
- localController.abort();
1471
- } else {
1472
- void remote.then(remoteResult =>
1473
- remoteResult.receipt.settle({ kind: "resolve_without_commit", reason: "aborted" }),
1474
- );
1475
- }
1476
- return result.value;
1477
- });
1496
+ return Promise.race([local, remote])
1497
+ .then(result => {
1498
+ if (result.winner === "remote") {
1499
+ activeRemoteReceipt = result.receipt;
1500
+ localController.abort();
1501
+ } else {
1502
+ void remote.then(remoteResult =>
1503
+ remoteResult.receipt.settle({ kind: "resolve_without_commit", reason: "aborted" }),
1504
+ );
1505
+ }
1506
+ return result.value;
1507
+ })
1508
+ .finally(() => toolSignal?.removeEventListener("abort", abortRace));
1478
1509
  },
1479
1510
  };
1480
1511
 
@@ -205,6 +205,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
205
205
  rawPaths: params.paths,
206
206
  cwd: this.session.cwd,
207
207
  getArtifactsDir: this.session.getArtifactsDir,
208
+ getAuthorizedArtifactsDirs: this.session.getAuthorizedArtifactsDirs,
208
209
  internalUrlAction: "rewrite",
209
210
  });
210
211
  const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
@@ -152,6 +152,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
152
152
  rawPaths: params.paths,
153
153
  cwd: this.session.cwd,
154
154
  getArtifactsDir: this.session.getArtifactsDir,
155
+ getAuthorizedArtifactsDirs: this.session.getAuthorizedArtifactsDirs,
155
156
  internalUrlAction: "search",
156
157
  });
157
158
  const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
@@ -15,9 +15,9 @@ import type { Terminal as XtermTerminalType } from "@xterm/headless";
15
15
  import { Settings } from "../config/settings";
16
16
  import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
17
17
  import type { Theme } from "../modes/theme/theme";
18
- import { OutputSink, type OutputSummary } from "../session/streaming-output";
18
+ import { OutputSink, type OutputSummary, type TerminalArtifactPublisher } from "../session/streaming-output";
19
19
  import { sanitizeWithOptionalSixelPassthrough } from "../utils/sixel";
20
- import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "./output-meta";
20
+ import { resolveBashOutputSinkHeadBytes, resolveBashOutputSinkTailBytes, resolveOutputMaxColumns } from "./output-meta";
21
21
  import { formatStatusIcon, replaceTabs } from "./render-utils";
22
22
 
23
23
  export interface BashInteractiveResult extends OutputSummary {
@@ -293,6 +293,9 @@ export async function runInteractiveBashPty(
293
293
  env?: Record<string, string>;
294
294
  artifactPath?: string;
295
295
  artifactId?: string;
296
+ artifactPublisher?: TerminalArtifactPublisher;
297
+ spillThreshold?: number;
298
+ headBytes?: number;
296
299
  },
297
300
  ): Promise<BashInteractiveResult> {
298
301
  const settings = await Settings.init();
@@ -300,7 +303,9 @@ export async function runInteractiveBashPty(
300
303
  const sink = new OutputSink({
301
304
  artifactPath: options.artifactPath,
302
305
  artifactId: options.artifactId,
303
- headBytes: resolveOutputSinkHeadBytes(settings),
306
+ artifactPublisher: options.artifactPublisher,
307
+ spillThreshold: options.spillThreshold ?? resolveBashOutputSinkTailBytes(settings),
308
+ headBytes: options.headBytes ?? resolveBashOutputSinkHeadBytes(settings),
304
309
  maxColumns: resolveOutputMaxColumns(settings),
305
310
  });
306
311
  const { default: xterm } = await import("@xterm/headless");