@gajae-code/coding-agent 0.18.10 → 0.18.12

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 (270) hide show
  1. package/CHANGELOG.md +295 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/cli/accounts-cli.d.ts +2 -2
  7. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  8. package/dist/types/cli/install-activation.d.ts +10 -4
  9. package/dist/types/cli/public-command-errors.d.ts +1 -0
  10. package/dist/types/cli/update-cli.d.ts +14 -1
  11. package/dist/types/cli-ordinary.d.ts +2 -2
  12. package/dist/types/commands/accounts.d.ts +2 -1
  13. package/dist/types/commands/migrate.d.ts +1 -1
  14. package/dist/types/commands/sdk.d.ts +5 -0
  15. package/dist/types/commit/changelog/index.d.ts +1 -0
  16. package/dist/types/config/autorouting-tier-map.d.ts +30 -0
  17. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  18. package/dist/types/config/config-hot-reload.d.ts +39 -0
  19. package/dist/types/config/file-lock.d.ts +8 -1
  20. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  21. package/dist/types/config/model-profile-activation.d.ts +62 -6
  22. package/dist/types/config/model-registry.d.ts +56 -5
  23. package/dist/types/config/model-resolver.d.ts +2 -0
  24. package/dist/types/config/models-config-schema.d.ts +10 -0
  25. package/dist/types/config/settings-schema.d.ts +46 -3
  26. package/dist/types/config/settings.d.ts +33 -1
  27. package/dist/types/dap/config.d.ts +8 -4
  28. package/dist/types/debug/system-info.d.ts +6 -1
  29. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  30. package/dist/types/edit/modes/patch.d.ts +5 -0
  31. package/dist/types/edit/modes/replace.d.ts +3 -0
  32. package/dist/types/exec/bash-executor.d.ts +12 -0
  33. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  35. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  36. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  37. package/dist/types/migrate/adapters/index.d.ts +10 -1
  38. package/dist/types/migrate/executor.d.ts +3 -1
  39. package/dist/types/migrate/types.d.ts +15 -3
  40. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  41. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  42. package/dist/types/modes/theme/theme.d.ts +1 -0
  43. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  44. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  45. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  46. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  47. package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
  48. package/dist/types/sdk/broker/broker.d.ts +7 -1
  49. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  50. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  51. package/dist/types/sdk/broker/ensure.d.ts +136 -7
  52. package/dist/types/sdk/broker/hop.d.ts +36 -0
  53. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  54. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +4 -1
  56. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  57. package/dist/types/sdk/bus/index.d.ts +23 -0
  58. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  59. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  60. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  61. package/dist/types/sdk/host/session-runtime.d.ts +32 -0
  62. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  63. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  64. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  65. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  66. package/dist/types/sdk/session-list.d.ts +1 -1
  67. package/dist/types/session/agent-session.d.ts +58 -2
  68. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  69. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  70. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  71. package/dist/types/session/messages.d.ts +5 -1
  72. package/dist/types/session/session-manager.d.ts +13 -3
  73. package/dist/types/session/startup-auth-config.d.ts +24 -2
  74. package/dist/types/session/streaming-output.d.ts +11 -0
  75. package/dist/types/session/terminal-abort.d.ts +3 -1
  76. package/dist/types/session-import/service.d.ts +1 -1
  77. package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
  78. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  79. package/dist/types/task/executor.d.ts +19 -2
  80. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  81. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  82. package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
  83. package/dist/types/tools/descriptors.d.ts +2 -0
  84. package/dist/types/tools/fetch.d.ts +1 -1
  85. package/dist/types/tools/index.d.ts +2 -0
  86. package/dist/types/tools/python.d.ts +1 -1
  87. package/dist/types/tools/report-issue.d.ts +41 -0
  88. package/dist/types/tools/subagent.d.ts +23 -0
  89. package/dist/types/web/scrapers/types.d.ts +8 -1
  90. package/dist/types/web/search/providers/insane.d.ts +7 -1
  91. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  92. package/package.json +8 -8
  93. package/scripts/build-binary.ts +2 -1
  94. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  95. package/scripts/generate-docs-index.ts +8 -1
  96. package/scripts/generate-sdk-operation-inventory.ts +22 -4
  97. package/src/async/job-manager.ts +356 -56
  98. package/src/async/result-formatting.ts +40 -0
  99. package/src/autoresearch/git.ts +9 -2
  100. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  101. package/src/capability/fs.ts +27 -19
  102. package/src/capability/index.ts +2 -2
  103. package/src/cli/accounts-cli.ts +176 -8
  104. package/src/cli/auth-broker-cli.ts +21 -11
  105. package/src/cli/doctor/install-repairs.ts +35 -5
  106. package/src/cli/fast-help.ts +1 -1
  107. package/src/cli/install-activation.ts +47 -4
  108. package/src/cli/migrate-cli.ts +7 -5
  109. package/src/cli/public-command-errors.ts +4 -0
  110. package/src/cli/root-flags.ts +2 -2
  111. package/src/cli/stats-cli.ts +9 -5
  112. package/src/cli/update-cli.ts +224 -7
  113. package/src/cli-ordinary.ts +7 -2
  114. package/src/commands/accounts.ts +10 -5
  115. package/src/commands/migrate.ts +3 -3
  116. package/src/commands/sdk.ts +199 -42
  117. package/src/commands/ultragoal.ts +16 -8
  118. package/src/commit/changelog/index.ts +39 -1
  119. package/src/commit/message.ts +10 -3
  120. package/src/config/autorouting-tier-map.ts +30 -0
  121. package/src/config/config-hot-reload-trust.ts +148 -0
  122. package/src/config/config-hot-reload.ts +734 -0
  123. package/src/config/file-lock.ts +216 -56
  124. package/src/config/model-bindings-applier.ts +85 -47
  125. package/src/config/model-discovery-manager.ts +10 -0
  126. package/src/config/model-profile-activation.ts +716 -113
  127. package/src/config/model-registry.ts +1334 -158
  128. package/src/config/model-resolver.ts +3 -1
  129. package/src/config/models-config-schema.ts +6 -0
  130. package/src/config/settings-schema.ts +60 -3
  131. package/src/config/settings.ts +458 -24
  132. package/src/coordinator-mcp/policy.ts +26 -1
  133. package/src/crash/sanitize.ts +4 -0
  134. package/src/customization/import.ts +3 -1
  135. package/src/dap/config.ts +31 -11
  136. package/src/dap/defaults.json +1 -1
  137. package/src/debug/crash-diagnostics.ts +2 -1
  138. package/src/debug/report-bundle.ts +19 -0
  139. package/src/debug/system-info.ts +8 -3
  140. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  141. package/src/discovery/agents-md.ts +1 -1
  142. package/src/discovery/helpers.ts +1 -2
  143. package/src/discovery/mcp-compat.ts +120 -17
  144. package/src/edit/apply-patch/index.ts +12 -1
  145. package/src/edit/index.ts +13 -17
  146. package/src/edit/modes/patch.ts +187 -8
  147. package/src/edit/modes/replace.ts +136 -0
  148. package/src/eval/js/context-manager.ts +29 -3
  149. package/src/eval/js/worker-entry.ts +51 -18
  150. package/src/eval/py/executor.ts +14 -4
  151. package/src/eval/py/runner.py +144 -5
  152. package/src/exec/bash-executor.ts +81 -4
  153. package/src/exec/bash-shell-guardian.ts +4 -1
  154. package/src/exec/bash-shell-supervisor.ts +8 -4
  155. package/src/exec/isolated-shell.ts +5 -1
  156. package/src/export/html/index.ts +46 -6
  157. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  158. package/src/extensibility/extensions/loader.ts +8 -1
  159. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  160. package/src/gjc-runtime/gc-runtime.ts +33 -6
  161. package/src/gjc-runtime/launch-tmux.ts +7 -2
  162. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  163. package/src/gjc-runtime/launch-worktree.ts +101 -14
  164. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  165. package/src/gjc-runtime/session-resolution.ts +38 -6
  166. package/src/gjc-runtime/session-state-lock.ts +59 -14
  167. package/src/gjc-runtime/state-runtime.ts +23 -19
  168. package/src/hooks/native-skill-hook.ts +14 -11
  169. package/src/internal-urls/docs-index.generated.ts +14 -13
  170. package/src/internal-urls/local-protocol.ts +142 -8
  171. package/src/lsp/index.ts +29 -0
  172. package/src/lsp/workspace-path.ts +23 -0
  173. package/src/migrate/action-planner.ts +57 -0
  174. package/src/migrate/adapters/claude-code.ts +23 -4
  175. package/src/migrate/adapters/codex.ts +22 -4
  176. package/src/migrate/adapters/index.ts +22 -4
  177. package/src/migrate/adapters/opencode.ts +6 -1
  178. package/src/migrate/executor.ts +84 -7
  179. package/src/migrate/report.ts +19 -2
  180. package/src/migrate/types.ts +14 -4
  181. package/src/modes/DESIGN.md +51 -0
  182. package/src/modes/acp/permission-mode.ts +18 -1
  183. package/src/modes/components/assistant-message.ts +55 -1
  184. package/src/modes/components/settings-selector.ts +103 -37
  185. package/src/modes/controllers/command-controller.ts +38 -2
  186. package/src/modes/controllers/plan-mode-controller.ts +17 -7
  187. package/src/modes/controllers/selector-controller.ts +62 -6
  188. package/src/modes/interactive-mode.ts +68 -0
  189. package/src/modes/theme/theme.ts +15 -0
  190. package/src/prompts/tools/debug.md +1 -1
  191. package/src/prompts/tools/read.md +1 -1
  192. package/src/prompts/tools/report-issue.md +13 -0
  193. package/src/prompts/tools/task.md +1 -1
  194. package/src/runtime-credential-selector.ts +2 -2
  195. package/src/runtime-mcp/content-limits.ts +7 -0
  196. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  197. package/src/runtime-mcp/manager.ts +32 -0
  198. package/src/runtime-mcp/oauth-flow.ts +32 -6
  199. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  200. package/src/runtime-mcp/render.ts +16 -1
  201. package/src/runtime-mcp/tool-bridge.ts +126 -17
  202. package/src/sdk/broker/broker-exit.ts +8 -5
  203. package/src/sdk/broker/broker.ts +342 -82
  204. package/src/sdk/broker/daemon-entry.ts +338 -116
  205. package/src/sdk/broker/doctor-restart.ts +2 -1
  206. package/src/sdk/broker/ensure.ts +1143 -265
  207. package/src/sdk/broker/hop.ts +274 -0
  208. package/src/sdk/broker/lifecycle-ledger.ts +35 -11
  209. package/src/sdk/broker/lock-host-identity.ts +29 -0
  210. package/src/sdk/broker/session-index.ts +62 -12
  211. package/src/sdk/bus/chat-command-policy.ts +16 -3
  212. package/src/sdk/bus/index.ts +52 -21
  213. package/src/sdk/bus/reconciliation-store.ts +201 -8
  214. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  215. package/src/sdk/cli/session-cli.ts +21 -6
  216. package/src/sdk/host/session-runtime.test.ts +118 -23
  217. package/src/sdk/host/session-runtime.ts +352 -47
  218. package/src/sdk/lifecycle/service.ts +1 -0
  219. package/src/sdk/prompt-deadline-flush.ts +78 -5
  220. package/src/sdk/prompt-deadline-manager.ts +68 -14
  221. package/src/sdk/protocol/adapter-validation.ts +53 -11
  222. package/src/sdk/protocol/operation-inventory.generated.json +101 -2
  223. package/src/sdk/reconciliation-extensions.ts +1 -0
  224. package/src/sdk/session-list.ts +1 -0
  225. package/src/sdk/session.ts +114 -61
  226. package/src/session/agent-session.ts +1043 -81
  227. package/src/session/internal/managed-session-scope.ts +126 -39
  228. package/src/session/internal/managed-session-storage.ts +14 -11
  229. package/src/session/internal/native-publish-outcome.ts +5 -1
  230. package/src/session/messages.ts +47 -6
  231. package/src/session/session-manager.ts +236 -14
  232. package/src/session/startup-auth-config.ts +116 -8
  233. package/src/session/streaming-output.ts +262 -4
  234. package/src/session/terminal-abort.ts +14 -2
  235. package/src/session-import/service.ts +1 -0
  236. package/src/skill-state/workflow-mutation-guard.ts +491 -32
  237. package/src/slash-commands/builtin-registry.ts +15 -3
  238. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  239. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  240. package/src/task/executor.ts +103 -12
  241. package/src/task/index.ts +389 -175
  242. package/src/tools/ast-edit-write-path.ts +64 -0
  243. package/src/tools/ast-edit.ts +18 -1
  244. package/src/tools/atomic-file-write.ts +46 -15
  245. package/src/tools/auto-generated-guard.ts +3 -0
  246. package/src/tools/bash-allowed-prefixes.ts +153 -5
  247. package/src/tools/bash-interactive.ts +14 -2
  248. package/src/tools/bash.ts +14 -0
  249. package/src/tools/debug.ts +7 -6
  250. package/src/tools/descriptors.test.ts +44 -0
  251. package/src/tools/descriptors.ts +34 -2
  252. package/src/tools/fetch.ts +3 -2
  253. package/src/tools/index.ts +2 -0
  254. package/src/tools/output-meta.ts +23 -2
  255. package/src/tools/python.ts +1 -1
  256. package/src/tools/read.ts +17 -35
  257. package/src/tools/report-issue.ts +184 -0
  258. package/src/tools/subagent-render.ts +114 -12
  259. package/src/tools/subagent.ts +108 -1
  260. package/src/tools/todo-write.ts +41 -3
  261. package/src/tools/tool-catalog.generated.ts +31 -3
  262. package/src/tools/write.ts +35 -3
  263. package/src/utils/commit-message-generator.ts +9 -2
  264. package/src/utils/file-mentions.ts +29 -1
  265. package/src/utils/title-generator.ts +7 -2
  266. package/src/web/scrapers/index.ts +3 -1
  267. package/src/web/scrapers/types.ts +8 -0
  268. package/src/web/scrapers/youtube.ts +3 -2
  269. package/src/web/search/providers/insane.ts +91 -28
  270. package/src/web/search/providers/searxng.ts +62 -10
@@ -1,3 +1,4 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import type { AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
3
 
3
4
  import { sanitizeText } from "@gajae-code/utils";
@@ -28,6 +29,78 @@ const NL = "\n";
28
29
 
29
30
  const ELLIPSIS = "…";
30
31
 
32
+ // Bound the temporary parts/join allocation in #applyColumnCap for a single
33
+ // large callback. Keep batches small enough to avoid per-line retention work.
34
+ const COLUMN_CAP_BATCH_CHARS = 64 * 1024;
35
+
36
+ const outputPlainSecrets = new AsyncLocalStorage<readonly string[]>();
37
+
38
+ /** Run `fn` while column caps can see these plain secrets. */
39
+ export function runWithOutputRedaction<T>(plainSecrets: readonly string[], fn: () => T): T {
40
+ return outputPlainSecrets.run(plainSecrets, fn);
41
+ }
42
+
43
+ /** Get the active plain secrets from AsyncLocalStorage, if any are set. */
44
+ function activePlainSecrets(): readonly string[] | undefined {
45
+ const secrets = outputPlainSecrets.getStore();
46
+ return secrets && secrets.length > 0 ? secrets : undefined;
47
+ }
48
+
49
+ /**
50
+ * Pull a column-cap end back to the start of any plain secret that crosses it.
51
+ * A secret that ends on the cut stays, so a later whole-string obfuscator can match it.
52
+ */
53
+ function columnHeadEndOutsideSecrets(segment: string, end: number, secrets: readonly string[]): number {
54
+ let cut = end;
55
+ while (cut > 0) {
56
+ let earliest = cut;
57
+ for (const secret of secrets) {
58
+ const secretChars = secret.length;
59
+ if (secretChars === 0) continue;
60
+ const windowStart = Math.max(0, cut - secretChars);
61
+ const window = segment.slice(windowStart, Math.min(segment.length, cut + secretChars));
62
+ const limit = cut - windowStart;
63
+ let from = 0;
64
+ while (from < limit) {
65
+ const rel = window.indexOf(secret, from);
66
+ if (rel === -1 || rel >= limit) break;
67
+ if (rel + secretChars > limit) earliest = Math.min(earliest, windowStart + rel);
68
+ from = rel + 1;
69
+ }
70
+ }
71
+ if (earliest === cut) break;
72
+ cut = earliest;
73
+ }
74
+ return cut;
75
+ }
76
+
77
+ /**
78
+ * Pull a column-cap end off a proper prefix of a plain secret.
79
+ * Each candidate's continuation is checked from longest to shortest.
80
+ * A longer candidate that is already finished or contradicted does not hide
81
+ * a shorter open prefix. A secret that already ends on the cut stays.
82
+ */
83
+ function columnHeadEndBeforeOpenPrefix(text: string, end: number, secrets: readonly string[]): number {
84
+ let cut = end;
85
+ const after = text.slice(end);
86
+ for (const secret of secrets) {
87
+ const secretChars = secret.length;
88
+ if (secretChars < 2 || end < 1) continue;
89
+ if (end >= secretChars && text.slice(end - secretChars, end) === secret) continue;
90
+ const maxPrefix = Math.min(secretChars - 1, end);
91
+ for (let k = maxPrefix; k >= 1; k--) {
92
+ if (text.slice(end - k, end) !== secret.slice(0, k)) continue;
93
+ const rest = secret.slice(k);
94
+ const have = Math.min(after.length, rest.length);
95
+ if (have === rest.length) continue;
96
+ if (after.slice(0, have) !== rest.slice(0, have)) continue;
97
+ cut = Math.min(cut, end - k);
98
+ break;
99
+ }
100
+ }
101
+ return cut;
102
+ }
103
+
31
104
  const ARTIFACT_WRITER_DIAGNOSTIC_MAX_BYTES = 256;
32
105
 
33
106
  /**
@@ -314,6 +387,72 @@ export function truncateLine(
314
387
  return { text: `${line.slice(0, maxChars)}…`, wasTruncated: true };
315
388
  }
316
389
 
390
+ /**
391
+ * Truncate each line to max characters without leaving a configured plain-secret
392
+ * prefix in the retained text. Prior uncapped lines remain in scope because a
393
+ * later line's cut can split a multiline secret.
394
+ */
395
+ export function truncateTextWithSecretAwareness(
396
+ text: string,
397
+ maxChars: number = DEFAULT_MAX_COLUMN,
398
+ secrets?: readonly string[],
399
+ ): { text: string; wasTruncated: boolean } {
400
+ const lines = text.split(NL);
401
+ const activeSecrets = secrets ?? activePlainSecrets();
402
+ if (!activeSecrets?.some(secret => secret.length > 0)) {
403
+ let wasTruncated = false;
404
+ const truncated = lines.map(line => {
405
+ const result = truncateLine(line, maxChars);
406
+ wasTruncated ||= result.wasTruncated;
407
+ return result.text;
408
+ });
409
+ return { text: truncated.join(NL), wasTruncated };
410
+ }
411
+
412
+ const contextChars = activeSecrets.reduce((max, secret) => Math.max(max, secret.length), 0);
413
+ let context = "";
414
+ const outputParts: string[] = [];
415
+ let wasTruncated = false;
416
+ for (let index = 0; index < lines.length; index++) {
417
+ const line = lines[index] ?? "";
418
+ const newline = index < lines.length - 1 ? NL : "";
419
+ if (line.length <= maxChars) {
420
+ outputParts.push(line, newline);
421
+ context = (context + line + newline).slice(-contextChars);
422
+ continue;
423
+ }
424
+
425
+ wasTruncated = true;
426
+ const combined = context + line;
427
+ let end = context.length + maxChars;
428
+ while (end > 0) {
429
+ const next = Math.min(
430
+ columnHeadEndOutsideSecrets(combined, end, activeSecrets),
431
+ columnHeadEndBeforeOpenPrefix(combined, end, activeSecrets),
432
+ );
433
+ if (next >= end) break;
434
+ end = next;
435
+ }
436
+
437
+ if (end < context.length) {
438
+ const retract = context.slice(end);
439
+ let output = outputParts.join("");
440
+ if (output.endsWith(retract)) {
441
+ output = output.slice(0, output.length - retract.length);
442
+ output += NL.repeat(countNewlines(retract));
443
+ } else {
444
+ output = NL.repeat(countNewlines(output));
445
+ }
446
+ outputParts.length = 0;
447
+ outputParts.push(output);
448
+ }
449
+ const keptChars = Math.max(0, end - context.length);
450
+ outputParts.push(line.slice(0, keptChars), ELLIPSIS, newline);
451
+ context = "";
452
+ }
453
+ return { text: outputParts.join(""), wasTruncated };
454
+ }
455
+
317
456
  // =============================================================================
318
457
  // Content truncation (line + byte aware, no full Buffer allocation)
319
458
  // =============================================================================
@@ -952,6 +1091,13 @@ export class OutputSink {
952
1091
  #columnEllipsisAdded = false;
953
1092
  #columnDroppedBytes = 0;
954
1093
  #columnTruncatedLines = 0;
1094
+ /** Plain secrets captured when this sink is built inside an active redaction scope. */
1095
+ readonly #plainSecrets?: readonly string[];
1096
+ readonly #plainSecretContextChars: number;
1097
+ /** Text already retained for the open line. A later chunk may shorten it. */
1098
+ #columnLine = "";
1099
+ /** Bounded suffix of prior uncapped output, preserved for multiline secrets. */
1100
+ #columnLineContext = "";
955
1101
  #file?: {
956
1102
  path: string;
957
1103
  artifactId?: string;
@@ -1010,6 +1156,8 @@ export class OutputSink {
1010
1156
  this.#artifactMaxBytes = Math.max(0, artifactMaxBytes);
1011
1157
  this.#artifactPublisher = artifactPublisher;
1012
1158
  this.#coalesceSanitize = coalesceSanitize;
1159
+ this.#plainSecrets = activePlainSecrets();
1160
+ this.#plainSecretContextChars = this.#plainSecrets?.reduce((max, secret) => Math.max(max, secret.length), 0) ?? 0;
1013
1161
  }
1014
1162
 
1015
1163
  #headText(): string {
@@ -1104,10 +1252,45 @@ export class OutputSink {
1104
1252
 
1105
1253
  if (rawBytes === 0) return;
1106
1254
 
1107
- const visibleChunk = this.#maxColumns > 0 ? this.#applyColumnCap(sanitizedChunk) : sanitizedChunk;
1108
1255
  if (this.#artifactPath && this.#maxColumns > 0) this.#enqueueFileWrite(rawChunk, rawBytes);
1109
1256
  else if (this.#artifactPublisher && this.#maxColumns > 0) this.#enqueueTerminalArtifactChunk(rawChunk, rawBytes);
1257
+
1258
+ if (this.#maxColumns > 0) {
1259
+ if (this.#plainSecrets) {
1260
+ // Secret-aware cuts can retract text from an earlier line, so retain
1261
+ // each line before processing the next one.
1262
+ let cursor = 0;
1263
+ while (cursor < sanitizedChunk.length) {
1264
+ const newline = sanitizedChunk.indexOf(NL, cursor);
1265
+ const end = newline === -1 ? sanitizedChunk.length : newline + 1;
1266
+ this.#retainVisible(this.#applyColumnCap(sanitizedChunk.slice(cursor, end)));
1267
+ cursor = end;
1268
+ }
1269
+ } else {
1270
+ // Batch ordinary output while bounding #applyColumnCap's temporary
1271
+ // parts/join allocation for large single callbacks. Retaining each
1272
+ // line separately repeats accounting millions of times.
1273
+ let cursor = 0;
1274
+ while (cursor < sanitizedChunk.length) {
1275
+ let end = Math.min(cursor + COLUMN_CAP_BATCH_CHARS, sanitizedChunk.length);
1276
+ // UTF-8 byte accounting must not observe a surrogate pair as two
1277
+ // replacement characters when a batch boundary lands between them.
1278
+ if (end < sanitizedChunk.length) {
1279
+ const before = sanitizedChunk.charCodeAt(end - 1);
1280
+ const after = sanitizedChunk.charCodeAt(end);
1281
+ if (before >= 0xd800 && before <= 0xdbff && after >= 0xdc00 && after <= 0xdfff) end--;
1282
+ }
1283
+ this.#retainVisible(this.#applyColumnCap(sanitizedChunk.slice(cursor, end)));
1284
+ cursor = end;
1285
+ }
1286
+ }
1287
+ } else {
1288
+ this.#retainVisible(sanitizedChunk);
1289
+ }
1110
1290
  if (this.#columnDroppedBytes > 0) this.#createFileSink();
1291
+ }
1292
+
1293
+ #retainVisible(visibleChunk: string): void {
1111
1294
  const visibleBytes = Buffer.byteLength(visibleChunk, "utf-8");
1112
1295
  if (visibleChunk.length > 0) {
1113
1296
  this.#processedBytes += visibleBytes;
@@ -1125,6 +1308,7 @@ export class OutputSink {
1125
1308
  return;
1126
1309
  }
1127
1310
  const headSlice = truncateHeadBytes(visibleChunk, room);
1311
+ this.#headRetentionDisabled = true;
1128
1312
  if (headSlice.bytes > 0) {
1129
1313
  this.#appendHead(headSlice.text, headSlice.bytes);
1130
1314
  tailChunk = visibleChunk.substring(headSlice.text.length);
@@ -1161,34 +1345,106 @@ export class OutputSink {
1161
1345
  if (segBytes <= remaining) {
1162
1346
  parts.push(segment);
1163
1347
  this.#currentLineBytes += segBytes;
1348
+ if (this.#plainSecrets) this.#columnLine += segment;
1164
1349
  } else {
1165
1350
  // First overflow on this line: keep what fits, append ellipsis,
1166
- // arm the skip-until-newline flag.
1351
+ // arm the skip-until-newline flag. With secrets, do not emit a
1352
+ // kept-head suffix that is still a prefix of a plain secret, and
1353
+ // do not end inside one the whole-string obfuscator must see whole.
1167
1354
  const ellipsisBytes = 3; // "…" in UTF-8
1168
1355
  const headRoom = Math.max(0, remaining - ellipsisBytes);
1169
1356
  let keptBytes = 0;
1357
+ let keptText = "";
1170
1358
  if (headRoom > 0) {
1171
1359
  const sliced = truncateHeadBytes(segment, headRoom);
1360
+ keptText = sliced.text;
1172
1361
  keptBytes = sliced.bytes;
1173
- parts.push(sliced.text);
1174
1362
  }
1363
+ const secrets = this.#plainSecrets;
1364
+ let lineBytesReset = false;
1365
+ if (secrets && segment.startsWith(keptText)) {
1366
+ const prior = this.#columnLine;
1367
+ const prefix = this.#columnLineContext + prior;
1368
+ const text = prefix + segment;
1369
+ const cut = prefix.length + keptText.length;
1370
+ let end = cut;
1371
+ while (end > 0) {
1372
+ const next = Math.min(
1373
+ columnHeadEndOutsideSecrets(text, end, secrets),
1374
+ columnHeadEndBeforeOpenPrefix(text, end, secrets),
1375
+ );
1376
+ if (next >= end) break;
1377
+ end = next;
1378
+ }
1379
+ if (end < cut) {
1380
+ if (end < prefix.length) {
1381
+ this.#retractColumnSuffix(prefix.slice(end));
1382
+ if (end < this.#columnLineContext.length) {
1383
+ this.#columnLineContext = this.#columnLineContext.slice(0, end);
1384
+ this.#columnLine = "";
1385
+ } else {
1386
+ this.#columnLine = prior.slice(0, end - this.#columnLineContext.length);
1387
+ }
1388
+ keptText = "";
1389
+ keptBytes = 0;
1390
+ } else {
1391
+ keptText = segment.slice(0, end - prefix.length);
1392
+ keptBytes = Buffer.byteLength(keptText, "utf-8");
1393
+ this.#columnLine = prior + keptText;
1394
+ }
1395
+ // columnLine already includes the bytes kept from this segment.
1396
+ this.#currentLineBytes = Buffer.byteLength(this.#columnLine, "utf-8");
1397
+ lineBytesReset = true;
1398
+ } else {
1399
+ this.#columnLine = prior + keptText;
1400
+ }
1401
+ }
1402
+ if (keptText.length > 0) parts.push(keptText);
1175
1403
  parts.push(ELLIPSIS);
1176
1404
  this.#columnDroppedBytes += segBytes - keptBytes;
1177
1405
  this.#columnTruncatedLines++;
1178
- this.#currentLineBytes += keptBytes + ellipsisBytes;
1406
+ this.#currentLineBytes += (lineBytesReset ? 0 : keptBytes) + ellipsisBytes;
1179
1407
  this.#columnEllipsisAdded = true;
1180
1408
  }
1181
1409
  }
1182
1410
  }
1183
1411
  if (nlIdx === -1) break;
1184
1412
  parts.push(NL);
1413
+ if (this.#plainSecretContextChars > 0 && !this.#columnEllipsisAdded) {
1414
+ const context = this.#columnLineContext + this.#columnLine + NL;
1415
+ this.#columnLineContext = context.slice(-this.#plainSecretContextChars);
1416
+ } else {
1417
+ this.#columnLineContext = "";
1418
+ }
1185
1419
  this.#currentLineBytes = 0;
1186
1420
  this.#columnEllipsisAdded = false;
1421
+ this.#columnLine = "";
1187
1422
  cursor = nlIdx + 1;
1188
1423
  }
1189
1424
  return parts.join("");
1190
1425
  }
1191
1426
 
1427
+ /** Remove a suffix of previously retained output after a secret-aware cut-back. */
1428
+ #retractColumnSuffix(suffix: string): void {
1429
+ if (suffix.length === 0) return;
1430
+ const bytes = Buffer.byteLength(suffix, "utf-8");
1431
+ const startByte = Math.max(0, this.#processedBytes - bytes);
1432
+ const tailStartByte = Math.max(0, this.#processedBytes - this.#bufferBytes);
1433
+ const keptTailBytes = Math.max(0, Math.min(this.#bufferBytes, startByte - tailStartByte));
1434
+ if (keptTailBytes < this.#bufferBytes) {
1435
+ const nextTail = truncateHeadBytes(this.#buffer, keptTailBytes);
1436
+ this.#setTail(nextTail.text, nextTail.bytes);
1437
+ }
1438
+ if (startByte < this.#headBytes) {
1439
+ const nextHead = truncateHeadBytes(this.#head, startByte);
1440
+ this.#head = nextHead.text;
1441
+ this.#headBytes = nextHead.bytes;
1442
+ }
1443
+ this.#processedBytes = Math.max(0, this.#processedBytes - bytes);
1444
+ this.#processedLines = Math.max(0, this.#processedLines - countNewlines(suffix));
1445
+ this.#columnDroppedBytes += bytes;
1446
+ }
1447
+
1192
1448
  #retainedSummary(text: string): { text: string; bytes: number; lines: number } {
1193
1449
  return { text, bytes: Buffer.byteLength(text, "utf-8"), lines: text.length > 0 ? countNewlines(text) : 0 };
1194
1450
  }
@@ -1458,6 +1714,8 @@ export class OutputSink {
1458
1714
  */
1459
1715
  replace(text: string): void {
1460
1716
  this.#coalesceBuf = "";
1717
+ this.#columnLine = "";
1718
+ this.#columnLineContext = "";
1461
1719
  const replacementBytes = Buffer.byteLength(text, "utf-8");
1462
1720
  this.#head = "";
1463
1721
  this.#headBytes = 0;
@@ -382,6 +382,16 @@ export function isOwnedAttemptRegistrationIncomplete(lineageIdHash: string, atte
382
382
  return incompleteOwnedAttempts.has(`${lineageIdHash}\u0000${attemptEpoch}`);
383
383
  }
384
384
 
385
+ /** Move fail-closed saturation authority with all ownership state during endpoint rekey. */
386
+ export function migrateOwnedAttemptSaturationsForEndpoint(
387
+ predecessorEndpointId: string,
388
+ successorEndpointId: string,
389
+ ): void {
390
+ for (const [attemptKey, endpointId] of saturatedAttemptEndpoints) {
391
+ if (endpointId === predecessorEndpointId) saturatedAttemptEndpoints.set(attemptKey, successorEndpointId);
392
+ }
393
+ }
394
+
385
395
  /** Exact (jobId, jobGeneration) lookup for completion-origin classification. */
386
396
  export function lookupOwnedRegistration(
387
397
  jobId: string,
@@ -841,6 +851,7 @@ export function registerOwnedIfLineaged(
841
851
  toolCallId: string | undefined,
842
852
  jobId: string,
843
853
  endpointId?: string,
854
+ ownedRegistrationEndpointId?: string,
844
855
  ): void {
845
856
  try {
846
857
  let lineage = resolveToolLineage(toolCallId, endpointId);
@@ -860,9 +871,10 @@ export function registerOwnedIfLineaged(
860
871
  const job = manager.getJob?.(jobId);
861
872
  const jobGeneration = job?.generation;
862
873
  if (!jobGeneration) return;
874
+ const registrationEndpointId = ownedRegistrationEndpointId ?? lineage.endpointId;
863
875
  registerOwnedRegistration(
864
876
  {
865
- ...(lineage.endpointId ? { endpointId: lineage.endpointId } : {}),
877
+ ...(registrationEndpointId ? { endpointId: registrationEndpointId } : {}),
866
878
  endpointGeneration: lineage.endpointGeneration,
867
879
  lineageIdHash: lineage.lineageIdHash,
868
880
  promptAttemptEpoch: lineage.promptAttemptEpoch,
@@ -877,7 +889,7 @@ export function registerOwnedIfLineaged(
877
889
  // job id bg_1) must not be judged terminal using THIS manager's
878
890
  // unrelated bg_1 record (review thread P2).
879
891
  isJobTerminal: candidate => {
880
- if ((candidate.endpointId ?? "") !== (lineage.endpointId ?? "")) return undefined;
892
+ if ((candidate.endpointId ?? "") !== (registrationEndpointId ?? "")) return undefined;
881
893
  const status = manager.getJob?.(candidate.jobId)?.status;
882
894
  return status === "completed" || status === "cancelled" || status === "failed" || status === "evicted";
883
895
  },
@@ -55,6 +55,7 @@ export type SessionImportErrorCode =
55
55
  | "destination_conflict"
56
56
  | "publish_uncertain"
57
57
  | "atomic_unavailable"
58
+ | "permission_denied"
58
59
  | "durability_not_provable"
59
60
  | "durability_failed"
60
61
  | "identity_mismatch"