@gajae-code/coding-agent 0.16.3 → 0.16.6

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 (277) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/args.d.ts +2 -2
  4. package/dist/types/cli/customize-doctor.d.ts +22 -0
  5. package/dist/types/cli/root-flags.d.ts +4 -2
  6. package/dist/types/cli.d.ts +1 -1
  7. package/dist/types/commands/launch.d.ts +1 -1
  8. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  9. package/dist/types/config/file-lock.d.ts +31 -3
  10. package/dist/types/config/model-registry.d.ts +4 -0
  11. package/dist/types/config/model-resolver.d.ts +9 -0
  12. package/dist/types/config/settings-schema.d.ts +29 -1
  13. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  15. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  16. package/dist/types/discovery/helpers.d.ts +35 -0
  17. package/dist/types/exec/bash-executor.d.ts +17 -1
  18. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  19. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  20. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  21. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  22. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  23. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  24. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  25. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  26. package/dist/types/exec/isolated-shell.d.ts +19 -0
  27. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  28. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  29. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  30. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  31. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  32. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  33. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  34. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  35. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  36. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  37. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  38. package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
  39. package/dist/types/extensibility/shared-events.d.ts +1 -1
  40. package/dist/types/extensibility/skill-management.d.ts +9 -4
  41. package/dist/types/extensibility/skills.d.ts +2 -0
  42. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  43. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  44. package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
  45. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  46. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  47. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  48. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  49. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  50. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  51. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  52. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  53. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  54. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  55. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  56. package/dist/types/modes/interactive-mode.d.ts +3 -0
  57. package/dist/types/modes/types.d.ts +7 -0
  58. package/dist/types/runtime-mcp/client.d.ts +9 -1
  59. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  60. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  61. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  62. package/dist/types/runtime-mcp/types.d.ts +22 -0
  63. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  64. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  65. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  66. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  68. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  69. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  70. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  71. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  72. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  73. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  74. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  75. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  76. package/dist/types/sdk/client/client.d.ts +6 -0
  77. package/dist/types/sdk/host/host.d.ts +3 -1
  78. package/dist/types/sdk/host/session-runtime.d.ts +26 -1
  79. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  80. package/dist/types/sdk/router/session-router.d.ts +10 -2
  81. package/dist/types/sdk/session-api-key.d.ts +26 -0
  82. package/dist/types/sdk/session.d.ts +5 -1
  83. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  84. package/dist/types/session/agent-session.d.ts +11 -0
  85. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  86. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  87. package/dist/types/session/messages.d.ts +1 -0
  88. package/dist/types/session/session-manager.d.ts +3 -1
  89. package/dist/types/setup/paseo/announce.d.ts +112 -0
  90. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  91. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  92. package/dist/types/system-prompt.d.ts +10 -0
  93. package/dist/types/task/executor.d.ts +5 -1
  94. package/dist/types/tools/bash.d.ts +1 -0
  95. package/dist/types/tools/fetch.d.ts +7 -0
  96. package/dist/types/tools/github-cache.d.ts +2 -1
  97. package/dist/types/tools/index.d.ts +10 -0
  98. package/dist/types/tools/path-utils.d.ts +2 -0
  99. package/dist/types/tools/search.d.ts +6 -0
  100. package/package.json +9 -7
  101. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +11 -0
  103. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  104. package/src/capability/index.ts +14 -2
  105. package/src/capability/types.ts +4 -0
  106. package/src/cli/args.ts +20 -9
  107. package/src/cli/customize-doctor.ts +155 -12
  108. package/src/cli/root-flags.ts +5 -3
  109. package/src/cli.ts +22 -0
  110. package/src/commands/sdk.ts +74 -31
  111. package/src/config/autorouting-tier-map.ts +1 -0
  112. package/src/config/file-lock-gc.ts +40 -2
  113. package/src/config/file-lock.ts +379 -31
  114. package/src/config/model-preset-registry.ts +120 -19
  115. package/src/config/model-profiles.ts +40 -0
  116. package/src/config/model-registry.ts +73 -23
  117. package/src/config/model-resolver.ts +48 -0
  118. package/src/config/settings-schema.ts +33 -1
  119. package/src/config/settings.ts +1 -0
  120. package/src/coordinator-mcp/question-state.ts +33 -2
  121. package/src/coordinator-mcp/server.ts +477 -101
  122. package/src/crash/sanitize.ts +1 -1
  123. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  124. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  125. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  126. package/src/discovery/builtin.ts +52 -55
  127. package/src/discovery/claude-plugins.ts +12 -4
  128. package/src/discovery/helpers.ts +307 -34
  129. package/src/exec/bash-executor.ts +330 -50
  130. package/src/exec/bash-shell-guardian.ts +388 -0
  131. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  132. package/src/exec/bash-shell-signals.ts +29 -0
  133. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  134. package/src/exec/bash-shell-supervisor.ts +359 -0
  135. package/src/exec/bash-shell-worker-entry.ts +5 -0
  136. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  137. package/src/exec/bash-shell-worker.ts +116 -0
  138. package/src/exec/isolated-shell.ts +393 -0
  139. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  140. package/src/extensibility/extensions/function-hooks.ts +959 -0
  141. package/src/extensibility/extensions/index.ts +22 -0
  142. package/src/extensibility/extensions/loader.ts +53 -2
  143. package/src/extensibility/extensions/runner.ts +895 -264
  144. package/src/extensibility/extensions/types.ts +42 -0
  145. package/src/extensibility/extensions/wrapper.ts +56 -15
  146. package/src/extensibility/gjc-plugins/compiler.ts +228 -60
  147. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  148. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  149. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  150. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  152. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  153. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  154. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  155. package/src/extensibility/gjc-plugins/types.ts +25 -0
  156. package/src/extensibility/hooks/loader.ts +133 -16
  157. package/src/extensibility/hooks/types.ts +28 -0
  158. package/src/extensibility/runtime-skill-discovery.ts +149 -190
  159. package/src/extensibility/shared-events.ts +1 -1
  160. package/src/extensibility/skill-management.ts +200 -52
  161. package/src/extensibility/skills.ts +73 -25
  162. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  163. package/src/gjc-runtime/gc-runtime.ts +29 -0
  164. package/src/gjc-runtime/linux-proc.ts +14 -0
  165. package/src/gjc-runtime/repository-binding.ts +1 -1
  166. package/src/gjc-runtime/session-state-lock.ts +143 -10
  167. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  168. package/src/gjc-runtime/state-writer.ts +30 -29
  169. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  170. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  171. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  172. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  173. package/src/harness-control-plane/session-lease.ts +2 -2
  174. package/src/internal-urls/docs-index.generated.ts +12 -12
  175. package/src/lsp/config.ts +10 -3
  176. package/src/lsp/index.ts +1 -1
  177. package/src/lsp/path-trust.ts +69 -14
  178. package/src/main.ts +78 -35
  179. package/src/modes/DESIGN.md +54 -0
  180. package/src/modes/acp/acp-agent.ts +16 -3
  181. package/src/modes/components/assistant-message.ts +22 -9
  182. package/src/modes/components/bash-execution.ts +4 -1
  183. package/src/modes/components/execution-shared.ts +4 -1
  184. package/src/modes/components/model-selector.ts +166 -33
  185. package/src/modes/components/session-observer-overlay.ts +1 -1
  186. package/src/modes/components/settings-selector.ts +1075 -235
  187. package/src/modes/components/status-line/command.ts +190 -0
  188. package/src/modes/components/status-line/index.ts +1 -0
  189. package/src/modes/components/status-line/segments.ts +32 -5
  190. package/src/modes/components/status-line/types.ts +5 -0
  191. package/src/modes/components/tool-status-header.ts +396 -22
  192. package/src/modes/controllers/command-controller.ts +3 -0
  193. package/src/modes/controllers/event-controller.ts +151 -7
  194. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  195. package/src/modes/controllers/input-controller.ts +6 -3
  196. package/src/modes/controllers/selector-controller.ts +21 -0
  197. package/src/modes/interactive-mode.ts +53 -6
  198. package/src/modes/print-mode.ts +5 -1
  199. package/src/modes/prompt-action-autocomplete.ts +54 -27
  200. package/src/modes/runtime-init.ts +2 -0
  201. package/src/modes/types.ts +17 -3
  202. package/src/modes/utils/ui-helpers.ts +2 -0
  203. package/src/prompts/system/system-prompt.md +8 -0
  204. package/src/prompts/tools/search.md +2 -0
  205. package/src/prompts/tools/subagent.md +1 -0
  206. package/src/runtime-mcp/client.ts +60 -17
  207. package/src/runtime-mcp/manager.ts +189 -14
  208. package/src/runtime-mcp/pool.test.ts +218 -0
  209. package/src/runtime-mcp/pool.ts +224 -23
  210. package/src/runtime-mcp/transports/stdio.ts +158 -24
  211. package/src/runtime-mcp/types.ts +24 -0
  212. package/src/sdk/broker/broker.ts +124 -32
  213. package/src/sdk/broker/ensure.ts +248 -41
  214. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  215. package/src/sdk/broker/lifecycle.ts +56 -6
  216. package/src/sdk/broker/startup-failure.ts +15 -6
  217. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  218. package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
  219. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  220. package/src/sdk/bus/conversation-store.ts +29 -0
  221. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  222. package/src/sdk/bus/slack-conversation.ts +1 -0
  223. package/src/sdk/bus/slack-daemon.ts +478 -49
  224. package/src/sdk/bus/slack-live-provider.ts +12 -0
  225. package/src/sdk/bus/slack-provider.ts +5 -0
  226. package/src/sdk/bus/telegram-daemon.ts +16 -2
  227. package/src/sdk/bus/topic-registry.ts +19 -0
  228. package/src/sdk/cli/master-cli.ts +1 -1
  229. package/src/sdk/cli/session-cli.ts +11 -8
  230. package/src/sdk/client/client.ts +66 -17
  231. package/src/sdk/host/host.ts +29 -9
  232. package/src/sdk/host/query/revision-store.ts +19 -13
  233. package/src/sdk/host/reverse-leases.ts +7 -4
  234. package/src/sdk/host/session-runtime.test.ts +52 -10
  235. package/src/sdk/host/session-runtime.ts +383 -44
  236. package/src/sdk/lifecycle/service.ts +4 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +77 -0
  238. package/src/sdk/router/session-router.ts +89 -38
  239. package/src/sdk/session-api-key.ts +94 -0
  240. package/src/sdk/session.ts +228 -55
  241. package/src/sdk/transport/relay.ts +35 -13
  242. package/src/sdk/transport/serve-cli.ts +3 -3
  243. package/src/session/agent-session.ts +240 -90
  244. package/src/session/contribution-prep.ts +214 -21
  245. package/src/session/fallback-chain-controller.ts +38 -0
  246. package/src/session/internal/managed-session-storage.ts +19 -5
  247. package/src/session/messages.ts +5 -0
  248. package/src/session/session-manager.ts +29 -5
  249. package/src/session-import/redact.ts +5 -1
  250. package/src/setup/paseo/announce.ts +456 -0
  251. package/src/setup/paseo/setup-deps.ts +15 -3
  252. package/src/skill-state/canonical-skills.ts +12 -0
  253. package/src/skills/skills.test.ts +41 -21
  254. package/src/slash-commands/builtin-registry.ts +1 -0
  255. package/src/system-prompt.ts +29 -3
  256. package/src/task/executor.ts +7 -1
  257. package/src/task/index.ts +21 -4
  258. package/src/tools/bash.ts +63 -19
  259. package/src/tools/cron.ts +1 -1
  260. package/src/tools/fetch.ts +29 -1
  261. package/src/tools/gh.ts +26 -3
  262. package/src/tools/github-cache.ts +68 -16
  263. package/src/tools/index.ts +10 -0
  264. package/src/tools/job.ts +21 -4
  265. package/src/tools/monitor.ts +8 -2
  266. package/src/tools/path-utils.ts +7 -0
  267. package/src/tools/read.ts +14 -2
  268. package/src/tools/render-utils.ts +1 -3
  269. package/src/tools/resource-gc.ts +28 -8
  270. package/src/tools/search.ts +116 -58
  271. package/src/tools/skill-discovery.ts +5 -0
  272. package/src/tools/skill.ts +37 -2
  273. package/src/tools/subagent.ts +6 -3
  274. package/src/tools/tool-catalog.generated.ts +10 -3
  275. package/src/tools/ultragoal-ask-guard.ts +2 -2
  276. package/src/web/insane/bridge.ts +113 -23
  277. package/src/web/search/providers/perplexity.ts +102 -41
@@ -3,7 +3,6 @@ import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import type { AgentMessage } from "@gajae-code/agent-core";
5
5
  import type { AssistantMessage, ToolResultMessage, UserMessage } from "@gajae-code/ai/core";
6
- import { $ } from "bun";
7
6
  import { resolveGjcCommand } from "../task/gjc-command";
8
7
  import { shortenPath } from "../tools/render-utils";
9
8
 
@@ -12,6 +11,12 @@ export const CONTRIBUTION_PREP_SCHEMA_VERSION = 1;
12
11
  const MAX_TRANSCRIPT_MESSAGES = 20;
13
12
  const MAX_TEXT_CHARS = 12000;
14
13
  const MAX_GIT_OUTPUT_CHARS = 60000;
14
+ const MAX_REDACTION_INPUT_CHARS = 1_000_000;
15
+ // Keep whole Git captures within the redactor's input budget; discard an overrun
16
+ // instead of emitting a prefix.
17
+ const MAX_GIT_RAW_OUTPUT_BYTES = MAX_REDACTION_INPUT_CHARS;
18
+ const MAX_JSON_TOKENS = 20_000;
19
+ const MAX_JSON_REPLACEMENTS = 10_000;
15
20
 
16
21
  export interface ContributionPrepArtifact {
17
22
  path: string;
@@ -72,12 +77,133 @@ function replaceRegex(text: string, regex: RegExp, replacement: string, state: R
72
77
  return text.replace(regex, replacement);
73
78
  }
74
79
 
80
+ function redactAwsAccessKeyIds(text: string, state: RedactionState): string {
81
+ return replaceRegex(
82
+ text,
83
+ /(^|[^0-9A-Za-z])(?:AKIA|ASIA)[0-9A-Z]{16}(?![0-9A-Za-z])/g,
84
+ "$1[REDACTED_AWS_KEY_ID]",
85
+ state,
86
+ "aws_keys",
87
+ );
88
+ }
89
+
90
+ function isAwsSecretField(value: string): boolean {
91
+ const normalized = value.toLowerCase().replaceAll(/[_-]/g, "");
92
+ return (
93
+ normalized === "secretaccesskey" ||
94
+ normalized === "sessiontoken" ||
95
+ normalized === "awssecretaccesskey" ||
96
+ normalized === "awssessiontoken" ||
97
+ normalized === "xamzsecuritytoken"
98
+ );
99
+ }
100
+
101
+ function redactAwsLabeledValues(text: string, state: RedactionState): string {
102
+ let redacted = redactAwsAccessKeyIds(text, state);
103
+ redacted = replaceRegex(
104
+ redacted,
105
+ // STS XML responses are commonly pretty-printed, so the value sits on its own
106
+ // line between the tags. `[^<]` still refuses to cross into another element,
107
+ // while the call-wide input bound caps work without imposing a credential-size
108
+ // assumption. The lower bound leaves empty or whitespace-only bodies alone.
109
+ /(<((?:[A-Za-z_][\w.-]*:)?(?:SecretAccessKey|SessionToken))>)[^<]{8,1000000}(<\/\2>)/gi,
110
+ "$1[REDACTED_SECRET]$3",
111
+ state,
112
+ "aws_keys",
113
+ );
114
+ redacted = replaceRegex(
115
+ redacted,
116
+ /(^|[^A-Za-z0-9_-])(["']?(?:(?:aws[_-]?)?secret[_-]?access[_-]?key|(?:aws[_-]?)?session[_-]?token|x[_-]?amz[_-]?security[_-]?token)["']?\s*[=:]\s*)(\$?)(["'`])([^"'`\r\n]{8,})\4/gi,
117
+ "$1$2$3$4[REDACTED_SECRET]$4",
118
+ state,
119
+ "aws_keys",
120
+ );
121
+ return replaceRegex(
122
+ redacted,
123
+ /(^|[^A-Za-z0-9_-])(["']?(?:(?:aws[_-]?)?secret[_-]?access[_-]?key|(?:aws[_-]?)?session[_-]?token|x[_-]?amz[_-]?security[_-]?token)["']?\s*[=:]\s*)[^\s"'`,;{}[\]()&<>#]{8,}/gi,
124
+ "$1$2[REDACTED_SECRET]",
125
+ state,
126
+ "aws_keys",
127
+ );
128
+ }
129
+
130
+ function redactAwsJsonStrings(text: string, state: RedactionState, depth = 0): string {
131
+ const tokens = [...text.matchAll(/"(?:\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4})|[^"\\\r\n])*"/g)].map(match => ({
132
+ start: match.index,
133
+ end: match.index + match[0].length,
134
+ raw: match[0],
135
+ }));
136
+ if (tokens.length > MAX_JSON_TOKENS) {
137
+ state.labels.add("oversized_content");
138
+ return "[REDACTED_OVERSIZED_CONTENT]";
139
+ }
140
+ const replacements = new Map<number, { end: number; value: string }>();
141
+
142
+ for (const [index, token] of tokens.entries()) {
143
+ let decoded: string;
144
+ try {
145
+ decoded = JSON.parse(token.raw) as string;
146
+ } catch {
147
+ continue;
148
+ }
149
+
150
+ if (isAwsSecretField(decoded)) {
151
+ const separator = /^\s*:\s*/.exec(text.slice(token.end));
152
+ const valueToken = tokens[index + 1];
153
+ if (separator && valueToken?.start === token.end + separator[0].length) {
154
+ let value: string;
155
+ try {
156
+ value = JSON.parse(valueToken.raw) as string;
157
+ } catch {
158
+ value = "";
159
+ }
160
+ if (value.length >= 8)
161
+ replacements.set(valueToken.start, { end: valueToken.end, value: JSON.stringify("[REDACTED_SECRET]") });
162
+ }
163
+ }
164
+
165
+ const nestedRedacted = depth >= 3 ? "[REDACTED_NESTED_CONTENT]" : redactAwsJsonStrings(decoded, state, depth + 1);
166
+ const redacted = redactAwsLabeledValues(nestedRedacted, state);
167
+ if (redacted !== decoded && !replacements.has(token.start)) {
168
+ replacements.set(token.start, { end: token.end, value: JSON.stringify(redacted) });
169
+ }
170
+ }
171
+
172
+ if (replacements.size === 0) return text;
173
+ if (replacements.size > MAX_JSON_REPLACEMENTS) {
174
+ state.labels.add("oversized_content");
175
+ return "[REDACTED_OVERSIZED_CONTENT]";
176
+ }
177
+ state.labels.add("aws_keys");
178
+ const chunks: string[] = [];
179
+ let cursor = 0;
180
+ for (const [start, replacement] of [...replacements.entries()].sort(([left], [right]) => left - right)) {
181
+ chunks.push(text.slice(cursor, start), replacement.value);
182
+ cursor = replacement.end;
183
+ }
184
+ chunks.push(text.slice(cursor));
185
+ return chunks.join("");
186
+ }
187
+
188
+ function assertSafeArtifactPath(artifactDir: string): void {
189
+ const state: RedactionState = { labels: new Set() };
190
+ redactContributionPrepText(artifactDir, path.join(artifactDir, "__gjc_path_probe__"), state);
191
+ if (["tokens", "aws_keys", "provider_keys", "auth_headers", "cookies"].some(label => state.labels.has(label))) {
192
+ throw new Error("Contribution prep artifact path contains credential-like material.");
193
+ }
194
+ }
195
+
75
196
  export function redactContributionPrepText(
76
197
  text: string,
77
198
  cwd: string,
78
199
  state: RedactionState = { labels: new Set() },
79
200
  ): string {
80
- let redacted = text;
201
+ if (text.length > MAX_REDACTION_INPUT_CHARS) {
202
+ state.labels.add("oversized_content");
203
+ return "[REDACTED_OVERSIZED_CONTENT]";
204
+ }
205
+ let redacted = redactAwsJsonStrings(text, state);
206
+ redacted = redactAwsLabeledValues(redacted, state);
81
207
  redacted = replaceRegex(
82
208
  redacted,
83
209
  /\b(?:sk|pk|rk|xox[baprs])-[-_A-Za-z0-9]{12,}\b/g,
@@ -150,17 +276,73 @@ function formatMessage(message: AgentMessage): string {
150
276
  return `## ${message.role}\n\n${JSON.stringify(message)}\n`;
151
277
  }
152
278
 
153
- async function gitOutput(cwd: string, args: string[], maxChars = MAX_GIT_OUTPUT_CHARS): Promise<string> {
279
+ interface BoundedGitOutput {
280
+ text: string;
281
+ oversized: boolean;
282
+ }
283
+
284
+ async function readBoundedGitOutput(stream: ReadableStream<Uint8Array>, maxBytes: number): Promise<BoundedGitOutput> {
285
+ const reader = stream.getReader();
286
+ const chunks: Uint8Array[] = [];
287
+ let totalBytes = 0;
288
+ try {
289
+ for (;;) {
290
+ const { done, value } = await reader.read();
291
+ if (done) break;
292
+ totalBytes += value.byteLength;
293
+ if (totalBytes > maxBytes) return { text: "", oversized: true };
294
+ chunks.push(value);
295
+ }
296
+ } finally {
297
+ reader.releaseLock();
298
+ }
299
+
300
+ const bytes = new Uint8Array(totalBytes);
301
+ let offset = 0;
302
+ for (const chunk of chunks) {
303
+ bytes.set(chunk, offset);
304
+ offset += chunk.byteLength;
305
+ }
306
+ return { text: new TextDecoder().decode(bytes), oversized: false };
307
+ }
308
+
309
+ async function gitOutput(
310
+ cwd: string,
311
+ args: string[],
312
+ state: RedactionState,
313
+ maxChars = MAX_GIT_OUTPUT_CHARS,
314
+ ): Promise<string> {
154
315
  try {
155
- const output = await $`git ${args}`.cwd(cwd).quiet().text();
156
- return limitText(output.trim(), maxChars);
316
+ const proc = Bun.spawn(["git", ...args], { cwd, stdin: "ignore", stdout: "pipe", stderr: "ignore" });
317
+ const stop = (): void => {
318
+ try {
319
+ proc.kill();
320
+ } catch {
321
+ // The process already exited.
322
+ }
323
+ };
324
+ try {
325
+ const output = await readBoundedGitOutput(proc.stdout, MAX_GIT_RAW_OUTPUT_BYTES);
326
+ if (output.oversized) {
327
+ stop();
328
+ await proc.exited;
329
+ state.labels.add("oversized_content");
330
+ return "[REDACTED_OVERSIZED_CONTENT]";
331
+ }
332
+ if ((await proc.exited) !== 0) return "";
333
+ return limitText(redactContributionPrepText(output.text.trim(), cwd, state), maxChars);
334
+ } catch {
335
+ stop();
336
+ await proc.exited;
337
+ return "";
338
+ }
157
339
  } catch {
158
340
  return "";
159
341
  }
160
342
  }
161
343
 
162
- async function changedFiles(cwd: string): Promise<string[]> {
163
- const output = await gitOutput(cwd, ["status", "--short"]);
344
+ async function changedFiles(cwd: string, state: RedactionState): Promise<string[]> {
345
+ const output = await gitOutput(cwd, ["status", "--short"], state);
164
346
  return output
165
347
  .split("\n")
166
348
  .map(line => line.trim())
@@ -180,15 +362,23 @@ async function writeArtifact(
180
362
  }
181
363
 
182
364
  export function buildContributionPrepWorkerPrompt(manifestPath: string): string {
365
+ const manifestName = path.basename(manifestPath);
183
366
  return [
184
367
  "Prepare a maintainer-friendly contribution draft from the redacted context dump.",
185
- "Read the manifest and referenced artifact file pointers. Do not assume transcript context was inlined here.",
186
- `Manifest: ${manifestPath}`,
368
+ "Read the manifest beside this worker prompt and its relative artifact file pointers. Do not assume transcript context was inlined here.",
369
+ `Manifest: ${manifestName}`,
187
370
  "Produce structured markdown with: title, problem summary, reproduction/context, proposed fix or implementation plan, affected files, tests to run, and uncertainty/remaining risks.",
188
371
  "Do not create GitHub issues, open PRs, push branches, or perform remote writes unless the user explicitly confirms that action in this fresh session.",
189
372
  ].join("\n");
190
373
  }
191
374
 
375
+ function safeWorkspaceLabel(cwd: string): string {
376
+ const resolved = path.resolve(cwd);
377
+ const home = os.homedir();
378
+ if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) return shortenPath(resolved);
379
+ return path.basename(resolved) || ".";
380
+ }
381
+
192
382
  export async function prepareContributionPrep(
193
383
  context: ContributionPrepContext,
194
384
  options: ContributionPrepOptions = {},
@@ -199,12 +389,14 @@ export async function prepareContributionPrep(
199
389
  options.artifactRoot ?? path.join(context.cwd, ".gjc", "contribution-prep"),
200
390
  safeTimestamp,
201
391
  );
392
+ assertSafeArtifactPath(artifactDir);
202
393
  await fs.mkdir(artifactDir, { recursive: true });
203
394
 
204
395
  const redactions: RedactionState = { labels: new Set() };
205
396
  const recentMessages = context.messages.slice(-MAX_TRANSCRIPT_MESSAGES);
206
397
  const artifacts: ContributionPrepArtifact[] = [];
207
398
  const redact = (text: string) => redactContributionPrepText(text, context.cwd, redactions);
399
+ const workspaceLabel = safeWorkspaceLabel(context.cwd);
208
400
 
209
401
  artifacts.push(
210
402
  await writeArtifact(
@@ -223,8 +415,8 @@ export async function prepareContributionPrep(
223
415
  [
224
416
  `# Contribution prep context`,
225
417
  `Source session: ${context.sessionId}`,
226
- `Session file: ${context.sessionFile ?? "(none)"}`,
227
- `Working directory: ${context.cwd}`,
418
+ `Session file: ${context.sessionFile ? path.basename(context.sessionFile) : "(none)"}`,
419
+ `Working directory: ${workspaceLabel}`,
228
420
  options.customInstructions || context.customInstructions
229
421
  ? `Custom instructions: ${options.customInstructions ?? context.customInstructions}`
230
422
  : "Custom instructions: (none)",
@@ -233,8 +425,8 @@ export async function prepareContributionPrep(
233
425
  ),
234
426
  );
235
427
 
236
- const gitHead = (await gitOutput(context.cwd, ["rev-parse", "HEAD"])) || null;
237
- const files = await changedFiles(context.cwd);
428
+ const gitHead = (await gitOutput(context.cwd, ["rev-parse", "HEAD"], redactions)) || null;
429
+ const files = await changedFiles(context.cwd, redactions);
238
430
  artifacts.push(
239
431
  await writeArtifact(artifactDir, "changed-files.txt", "Changed files from git status", redact(files.join("\n"))),
240
432
  );
@@ -243,7 +435,7 @@ export async function prepareContributionPrep(
243
435
  artifactDir,
244
436
  "git-diff.patch",
245
437
  "Bounded redacted git diff",
246
- redact(await gitOutput(context.cwd, ["diff", "--no-ext-diff"])),
438
+ await gitOutput(context.cwd, ["diff", "--no-ext-diff"], redactions),
247
439
  ),
248
440
  );
249
441
  artifacts.push(
@@ -253,7 +445,7 @@ export async function prepareContributionPrep(
253
445
  "Redacted environment and reproduction metadata",
254
446
  redact(
255
447
  [
256
- `cwd: ${context.cwd}`,
448
+ `cwd: ${workspaceLabel}`,
257
449
  `git_head: ${gitHead ?? "unknown"}`,
258
450
  `platform: ${process.platform}`,
259
451
  `arch: ${process.arch}`,
@@ -266,15 +458,16 @@ export async function prepareContributionPrep(
266
458
  const manifestPath = path.join(artifactDir, "manifest.json");
267
459
  const workerPromptPath = path.join(artifactDir, "worker-prompt.md");
268
460
  await Bun.write(workerPromptPath, `${buildContributionPrepWorkerPrompt(manifestPath)}\n`);
461
+ const manifestArtifacts = artifacts.map(artifact => ({ ...artifact, path: path.basename(artifact.path) }));
269
462
 
270
463
  const manifest: ContributionPrepManifest = {
271
464
  schema_version: CONTRIBUTION_PREP_SCHEMA_VERSION,
272
- source_session_id: context.sessionId,
465
+ source_session_id: redact(context.sessionId),
273
466
  created_at: createdAt,
274
- cwd: redact(context.cwd),
467
+ cwd: redact(workspaceLabel),
275
468
  git_head: gitHead,
276
469
  changed_files: files.map(redact),
277
- artifacts,
470
+ artifacts: manifestArtifacts,
278
471
  redactions: [...redactions.labels].sort(),
279
472
  recommended_output: [
280
473
  "title",
@@ -285,7 +478,7 @@ export async function prepareContributionPrep(
285
478
  "tests to run",
286
479
  "uncertainty / remaining risks",
287
480
  ],
288
- worker_prompt_path: workerPromptPath,
481
+ worker_prompt_path: path.basename(workerPromptPath),
289
482
  };
290
483
  await Bun.write(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
291
484
 
@@ -309,8 +502,8 @@ export async function prepareContributionPrep(
309
502
  });
310
503
  const command = resolveGjcCommand();
311
504
  await spawn(
312
- [command.cmd, ...command.args, "--no-skills", "--", `@${workerPromptPath}`],
313
- context.cwd,
505
+ [command.cmd, ...command.args, "--no-skills", "--", `@${path.basename(workerPromptPath)}`],
506
+ artifactDir,
314
507
  command.shell,
315
508
  );
316
509
  spawned = true;
@@ -1,3 +1,4 @@
1
+ import { cleanReason } from "@gajae-code/ai/auth-broker/redact";
1
2
  import type { FallbackTriggerClass } from "@gajae-code/ai/utils/fallback-transport";
2
3
 
3
4
  /** Immutable configured fallback intent. Transient attempt state never belongs here. */
@@ -265,6 +266,43 @@ export function compactionRetryDelay(
265
266
  return Math.max(0, bounded);
266
267
  }
267
268
 
269
+ export interface CompactionCandidateFailure {
270
+ readonly model: string;
271
+ readonly message: string;
272
+ }
273
+
274
+ function sanitizeCompactionCandidateFailureMessage(message: string): string {
275
+ const cleaned = cleanReason(message) ?? "Compaction candidate failed.";
276
+ return cleaned.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s<>'"]+/gi, "[redacted URL]");
277
+ }
278
+
279
+ /**
280
+ * Error for an auto-compaction run whose every candidate failed.
281
+ *
282
+ * The candidate chain starts at the session model and ends on a same-provider
283
+ * largest-context fallback, so surfacing only the final error named a model
284
+ * the user never chose and hid that their own model had already failed the
285
+ * same way. The message leads with the first failure (the one the user can act
286
+ * on), then lists every candidate that was tried with its own error. `cause`
287
+ * is the final error so callers that classify by type keep the same object.
288
+ */
289
+ export function describeCompactionCandidateFailures(
290
+ failures: readonly CompactionCandidateFailure[],
291
+ lastError: unknown,
292
+ ): Error {
293
+ if (failures.length === 0)
294
+ return new Error("Compaction failed: no candidate failure details available.", { cause: lastError });
295
+ const first = failures[0]!;
296
+ const firstMessage = sanitizeCompactionCandidateFailureMessage(first.message);
297
+ const lines = failures.map(
298
+ failure => ` ${failure.model}: ${sanitizeCompactionCandidateFailureMessage(failure.message)}`,
299
+ );
300
+ return new Error(
301
+ `${firstMessage} (${first.model}); ${failures.length} compaction candidates failed:\n${lines.join("\n")}`,
302
+ { cause: lastError },
303
+ );
304
+ }
305
+
268
306
  /** Retry-After is intentionally uncapped. */
269
307
  export function effectiveFallbackDelay(
270
308
  baseDelayMs: number,
@@ -52,6 +52,20 @@ const LOCK_LEASE_MS = 60_000;
52
52
  const LOCK_HEARTBEAT_MS = 10_000;
53
53
  const LOCK_WAIT_MS = 5_000;
54
54
 
55
+ /** A pre-mutation append fence rejection: no bytes from this append were written. */
56
+ export class ManagedAppendIdentityMismatchError extends Error {
57
+ readonly code = "managed_append_identity_mismatch";
58
+ readonly relativePath: string;
59
+ readonly operatorMessage =
60
+ "Session was resumed by another process; this resume did not run. Retry, or resume a different session.";
61
+
62
+ constructor(relativePath: string) {
63
+ super("managed_append_identity_mismatch");
64
+ this.name = "ManagedAppendIdentityMismatchError";
65
+ this.relativePath = relativePath;
66
+ }
67
+ }
68
+
55
69
  export class ManagedPublishError extends Error {
56
70
  readonly classification:
57
71
  | "destination_conflict"
@@ -1806,7 +1820,7 @@ export class ManagedSessionDescendantStore {
1806
1820
  current.size.toString() === expected.size &&
1807
1821
  current.sha256 === expected.sha256;
1808
1822
  if (!identityMatches) {
1809
- throw new Error("managed_append_identity_mismatch");
1823
+ throw new ManagedAppendIdentityMismatchError(relativePath);
1810
1824
  }
1811
1825
  // mtime/ctime drift on the same file object is harmless. The append
1812
1826
  // helper performs its own descriptor-bound mutation check.
@@ -1820,7 +1834,7 @@ export class ManagedSessionDescendantStore {
1820
1834
  return managedAppendReceiptFromIdentity(appended);
1821
1835
  }
1822
1836
  const current = this.captureBoundedAppendExpectation(relativePath);
1823
- if (!current) throw new Error("managed_append_identity_mismatch");
1837
+ if (!current) throw new ManagedAppendIdentityMismatchError(relativePath);
1824
1838
  if (
1825
1839
  current.dev !== expected.dev ||
1826
1840
  current.ino !== expected.ino ||
@@ -1828,7 +1842,7 @@ export class ManagedSessionDescendantStore {
1828
1842
  current.size !== expected.size ||
1829
1843
  current.sha256 !== expected.sha256
1830
1844
  )
1831
- throw new Error("managed_append_identity_mismatch");
1845
+ throw new ManagedAppendIdentityMismatchError(relativePath);
1832
1846
  const descriptorMatches =
1833
1847
  current.dev === expected.dev &&
1834
1848
  current.ino === expected.ino &&
@@ -1862,7 +1876,7 @@ export class ManagedSessionDescendantStore {
1862
1876
  expected: ManagedFileIdentity,
1863
1877
  ): ManagedAppendReceipt {
1864
1878
  const bounded = this.captureBoundedAppendExpectation(relativePath);
1865
- if (!bounded) throw new Error("managed_append_identity_mismatch");
1879
+ if (!bounded) throw new ManagedAppendIdentityMismatchError(relativePath);
1866
1880
  const descriptorMatches =
1867
1881
  bounded.dev === expected.dev.toString() &&
1868
1882
  bounded.ino === expected.ino.toString() &&
@@ -1882,7 +1896,7 @@ export class ManagedSessionDescendantStore {
1882
1896
  bounded.size !== String(expected.size) ||
1883
1897
  bounded.sha256 !== expected.sha256
1884
1898
  )
1885
- throw new Error("managed_append_identity_mismatch");
1899
+ throw new ManagedAppendIdentityMismatchError(relativePath);
1886
1900
  }
1887
1901
  return this.appendExpectedSync(relativePath, bytes, bounded);
1888
1902
  }
@@ -212,6 +212,7 @@ export interface BashExecutionMessage {
212
212
  command: string;
213
213
  output: string;
214
214
  exitCode: number | undefined;
215
+ signal?: string;
215
216
  cancelled: boolean;
216
217
  truncated: boolean;
217
218
  meta?: OutputMeta;
@@ -313,6 +314,10 @@ export function bashExecutionToText(msg: BashExecutionMessage): string {
313
314
  }
314
315
  if (msg.cancelled) {
315
316
  text += "\n\n(command cancelled)";
317
+ } else if (msg.signal) {
318
+ text += `\n\nCommand terminated by ${msg.signal}${
319
+ msg.exitCode !== null && msg.exitCode !== undefined ? ` (exit code ${msg.exitCode})` : ""
320
+ }`;
316
321
  } else if (msg.exitCode !== null && msg.exitCode !== undefined && msg.exitCode !== 0) {
317
322
  text += `\n\nCommand exited with code ${msg.exitCode}`;
318
323
  }
@@ -112,6 +112,7 @@ import {
112
112
  captureManagedFileNoFollow,
113
113
  fsyncManagedArtifactTree,
114
114
  MANAGED_ARTIFACT_MAX_FILE_BYTES,
115
+ ManagedAppendIdentityMismatchError,
115
116
  type ManagedAppendReceipt,
116
117
  type ManagedBoundedAppendExpectation,
117
118
  ManagedCommittedMutationError,
@@ -11873,6 +11874,10 @@ export class SessionManager {
11873
11874
  return false;
11874
11875
  }
11875
11876
 
11877
+ /** Ownership loss remains observable even when an agent event listener aborts the turn. */
11878
+ getAppendIdentityMismatch(): ManagedAppendIdentityMismatchError | undefined {
11879
+ return this.#persistError instanceof ManagedAppendIdentityMismatchError ? this.#persistError : undefined;
11880
+ }
11876
11881
  #recordPersistError(err: unknown): Error {
11877
11882
  const normalized = toError(err);
11878
11883
  if (!this.#persistError) this.#persistError = normalized;
@@ -16255,7 +16260,11 @@ export class SessionManager {
16255
16260
  }
16256
16261
  }
16257
16262
  const outcome = await this.closeStrict();
16258
- return priorFlushError && outcome.kind === "closed" ? { kind: "close_unknown", error: priorFlushError } : outcome;
16263
+ return priorFlushError &&
16264
+ !(priorFlushError instanceof ManagedAppendIdentityMismatchError) &&
16265
+ outcome.kind === "closed"
16266
+ ? { kind: "close_unknown", error: priorFlushError }
16267
+ : outcome;
16259
16268
  }
16260
16269
 
16261
16270
  /**
@@ -16280,6 +16289,21 @@ export class SessionManager {
16280
16289
  });
16281
16290
  }
16282
16291
  if (preparedCleanupError) return { kind: "close_failed_retryable", error: preparedCleanupError };
16292
+ await this.#persistChain;
16293
+ // Managed appends are synchronous and reject identity drift before writing.
16294
+ // Disown the stale transcript, not an uncertain OS writer: never rewrite a
16295
+ // successor owned by another process while disposing this rejected resume.
16296
+ if (
16297
+ this.destination.kind === "managed" &&
16298
+ !this.#persistWriter &&
16299
+ this.#persistError instanceof ManagedAppendIdentityMismatchError
16300
+ ) {
16301
+ this.#needsFullRewriteOnNextPersist = false;
16302
+ this.#strictResumeMutationPending = false;
16303
+ this.#managedPersistExpectedIdentity = undefined;
16304
+ this.#persistError = undefined;
16305
+ this.#persistErrorReported = false;
16306
+ }
16283
16307
  let priorPersistError = this.#persistError;
16284
16308
  if (this.#needsFullRewriteOnNextPersist && (!this.#readOnlyResume || this.#strictResumeMutationPending)) {
16285
16309
  await this.#persistChain.catch(() => {});
@@ -17379,10 +17403,7 @@ export class SessionManager {
17379
17403
  receipt = store.appendExpectedIdentitySync(relativePath, bytes, this.#managedPersistExpectedIdentity);
17380
17404
  } catch (err) {
17381
17405
  const predecessorMissing = store.descriptorExpected(relativePath) === null;
17382
- if (
17383
- !isEnoent(err) &&
17384
- (!(err instanceof Error) || err.message !== "managed_append_identity_mismatch" || !predecessorMissing)
17385
- )
17406
+ if (!isEnoent(err) && (!(err instanceof ManagedAppendIdentityMismatchError) || !predecessorMissing))
17386
17407
  throw err;
17387
17408
  // Appending only the new records would create a truncated transcript.
17388
17409
  // Recreate the missing file from the complete resident entry set instead.
@@ -17724,6 +17745,9 @@ export class SessionManager {
17724
17745
  if (entry.type === "label") this.#labelRevision++;
17725
17746
  if (sidecarAppendCharge > 0) activeRuntime?.accountant.release(sidecarAppendCharge);
17726
17747
  if (sidecarTailCharge > 0) activeRuntime?.tailCache.release(sidecarTailCharge);
17748
+ // The pre-write fence is an expected ownership loss, not an uncertain
17749
+ // append. Preserve its type after rolling back the rejected entry.
17750
+ if (error instanceof ManagedAppendIdentityMismatchError) throw error;
17727
17751
  throw new SessionAppendPersistenceError(
17728
17752
  priorPersistenceError ? "prior_failure" : "current_append",
17729
17753
  residentEntry.id,
@@ -59,7 +59,11 @@ const REDACTION_RULES: readonly RedactionRule[] = [
59
59
  // Long hex secrets (64+ hex chars: webhook secrets, signing keys).
60
60
  { id: "hex-secret", pattern: new RegExp(`\\b${HEX}{64,}\\b`, "g") },
61
61
  // Basic-auth credentials embedded in URLs (https://user:pass@host).
62
- { id: "url-credential", pattern: /([a-z][a-z0-9+.-]*:\/\/)[^/\s:@]{1,256}:[^/\s@]{1,256}@/gi },
62
+ // The scheme repetition is bounded. Unbounded `[a-z0-9+.-]*` in front of the
63
+ // literal `://` re-tries every prefix of a long alphabetic run before failing,
64
+ // which is quadratic in the input length: 200 KB of ordinary prose costs ~10s.
65
+ // IANA's longest registered scheme is well under 16 characters.
66
+ { id: "url-credential", pattern: /([a-z][a-z0-9+.-]{0,15}:\/\/)[^/\s:@]{1,256}:[^/\s@]{1,256}@/gi },
63
67
  // Sensitive env/KEY assignments: OPENAI_API_KEY=..., token: ..., password = ...
64
68
  // The name prefix is optional so a bare sensitive name (`password: …`) also matches.
65
69
  {