salmon-loop 0.2.13 → 0.2.16

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 (218) hide show
  1. package/dist/cli/argv/headless-detection.js +27 -0
  2. package/dist/cli/chat-flow.js +11 -0
  3. package/dist/cli/chat.js +160 -24
  4. package/dist/cli/commands/chat.js +14 -7
  5. package/dist/cli/commands/flow-mode.js +63 -0
  6. package/dist/cli/commands/registry.js +2 -0
  7. package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
  8. package/dist/cli/commands/run/early-errors.js +23 -0
  9. package/dist/cli/commands/run/handler.js +115 -27
  10. package/dist/cli/commands/run/headless-error-writer.js +8 -0
  11. package/dist/cli/commands/run/loop-params.js +2 -0
  12. package/dist/cli/commands/run/mode.js +2 -5
  13. package/dist/cli/commands/run/parse-options.js +16 -0
  14. package/dist/cli/commands/run/persist-session.js +10 -1
  15. package/dist/cli/commands/run/preflight.js +10 -0
  16. package/dist/cli/commands/run/reporter-factory.js +4 -0
  17. package/dist/cli/commands/run/runtime-llm.js +38 -11
  18. package/dist/cli/commands/run/runtime-options.js +2 -2
  19. package/dist/cli/commands/serve.js +91 -71
  20. package/dist/cli/commands/tool-names.js +78 -78
  21. package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
  22. package/dist/cli/headless/json-protocol.js +37 -0
  23. package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
  24. package/dist/cli/headless/protocol-metadata.js +22 -0
  25. package/dist/cli/headless/stream-json-protocol.js +34 -1
  26. package/dist/cli/index.js +6 -4
  27. package/dist/cli/locales/en.js +30 -6
  28. package/dist/cli/program-bootstrap.js +8 -3
  29. package/dist/cli/program-commands.js +5 -1
  30. package/dist/cli/reporters/anthropic-stream.js +7 -1
  31. package/dist/cli/reporters/json.js +4 -0
  32. package/dist/cli/reporters/stream-json.js +17 -2
  33. package/dist/cli/run-cli.js +5 -3
  34. package/dist/cli/slash/runtime.js +27 -12
  35. package/dist/cli/ui/components/CommandInput.js +7 -3
  36. package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
  37. package/dist/cli/utils/command-option-source.js +13 -0
  38. package/dist/cli/utils/verify-resolver.js +8 -4
  39. package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
  40. package/dist/core/adapters/fs/file-adapter.js +6 -0
  41. package/dist/core/adapters/fs/filesystem.js +2 -1
  42. package/dist/core/adapters/git/git-adapter.js +78 -1
  43. package/dist/core/benchmark/patch-artifact.js +124 -0
  44. package/dist/core/benchmark/swe-bench.js +25 -0
  45. package/dist/core/config/load.js +18 -11
  46. package/dist/core/config/resolve-llm.js +12 -0
  47. package/dist/core/config/resolvers/server.js +0 -6
  48. package/dist/core/config/validate.js +73 -21
  49. package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
  50. package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
  51. package/dist/core/context/keywords.js +18 -4
  52. package/dist/core/context/service-deps.js +2 -2
  53. package/dist/core/context/service.js +8 -0
  54. package/dist/core/context/steps/context-gather.js +38 -0
  55. package/dist/core/context/summarization/summarizer.js +55 -12
  56. package/dist/core/context/targeting/target-resolver.js +4 -4
  57. package/dist/core/extensions/index.js +23 -5
  58. package/dist/core/extensions/paths.js +31 -0
  59. package/dist/core/extensions/schemas.js +8 -5
  60. package/dist/core/facades/cli-chat.js +6 -2
  61. package/dist/core/facades/cli-command-chat.js +1 -0
  62. package/dist/core/facades/cli-command-tool-names.js +2 -0
  63. package/dist/core/facades/cli-observability.js +1 -1
  64. package/dist/core/facades/cli-run-handler.js +4 -2
  65. package/dist/core/facades/cli-run-persist-session.js +1 -0
  66. package/dist/core/facades/cli-serve.js +2 -4
  67. package/dist/core/facades/cli-utils-worktree.js +1 -1
  68. package/dist/core/failure/diagnostics.js +53 -1
  69. package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
  70. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
  71. package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
  72. package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
  73. package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
  74. package/dist/core/grizzco/engine/transaction/transaction-runner.js +165 -7
  75. package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
  76. package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
  77. package/dist/core/grizzco/steps/answer.js +13 -14
  78. package/dist/core/grizzco/steps/autopilot.js +396 -0
  79. package/dist/core/grizzco/steps/cache-sharing.js +29 -0
  80. package/dist/core/grizzco/steps/explore.js +37 -21
  81. package/dist/core/grizzco/steps/generateReview.js +2 -5
  82. package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
  83. package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
  84. package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
  85. package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
  86. package/dist/core/grizzco/steps/patch.js +105 -146
  87. package/dist/core/grizzco/steps/plan.js +101 -25
  88. package/dist/core/grizzco/steps/preflight.js +5 -6
  89. package/dist/core/grizzco/steps/request-assembly.js +78 -0
  90. package/dist/core/grizzco/steps/research.js +39 -36
  91. package/dist/core/grizzco/steps/tool-runtime.js +47 -0
  92. package/dist/core/grizzco/steps/verify-shared.js +23 -0
  93. package/dist/core/grizzco/steps/verify.js +13 -21
  94. package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
  95. package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
  96. package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
  97. package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
  98. package/dist/core/llm/ai-sdk/request-params.js +73 -0
  99. package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
  100. package/dist/core/llm/ai-sdk.js +112 -27
  101. package/dist/core/llm/capabilities.js +12 -0
  102. package/dist/core/llm/contracts/repair.js +36 -30
  103. package/dist/core/llm/errors.js +83 -2
  104. package/dist/core/llm/message-composition.js +7 -22
  105. package/dist/core/llm/phase-router.js +29 -10
  106. package/dist/core/llm/redact.js +28 -3
  107. package/dist/core/llm/registry.js +2 -0
  108. package/dist/core/llm/request-augmentation.js +55 -0
  109. package/dist/core/llm/request-envelope.js +334 -0
  110. package/dist/core/llm/shared-request-assembly.js +35 -0
  111. package/dist/core/llm/stream-utils.js +13 -4
  112. package/dist/core/llm/utils.js +18 -29
  113. package/dist/core/memory/relevant-retrieval.js +144 -0
  114. package/dist/core/observability/logger.js +11 -2
  115. package/dist/core/patch/diff.js +1 -0
  116. package/dist/core/prompts/registry.js +39 -2
  117. package/dist/core/prompts/runtime.js +50 -12
  118. package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
  119. package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
  120. package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
  121. package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
  122. package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
  123. package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
  124. package/dist/core/prompts/templates/system/main_system.hbs +4 -16
  125. package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
  126. package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
  127. package/dist/core/prompts/templates/system/research_system.hbs +2 -0
  128. package/dist/core/protocols/a2a/agent-card.js +3 -2
  129. package/dist/core/protocols/a2a/sdk/executor.js +2 -1
  130. package/dist/core/protocols/a2a/sdk/server.js +0 -1
  131. package/dist/core/protocols/acp/formal-agent.js +74 -51
  132. package/dist/core/protocols/acp/handlers.js +5 -1
  133. package/dist/core/protocols/acp/permission-provider.js +1 -1
  134. package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
  135. package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
  136. package/dist/core/public-capabilities/projections.js +29 -0
  137. package/dist/core/public-capabilities/registry.js +26 -0
  138. package/dist/core/public-capabilities/types.js +2 -0
  139. package/dist/core/runtime/agent-server-runtime.js +47 -43
  140. package/dist/core/runtime/execution-profile.js +67 -0
  141. package/dist/core/session/artifact-state.js +160 -0
  142. package/dist/core/session/compaction/index.js +183 -0
  143. package/dist/core/session/compaction/microcompact.js +78 -0
  144. package/dist/core/session/compaction/tracking.js +48 -0
  145. package/dist/core/session/compaction/types.js +11 -0
  146. package/dist/core/session/compression.js +8 -0
  147. package/dist/core/session/manager.js +244 -8
  148. package/dist/core/session/pruning-strategy.js +55 -9
  149. package/dist/core/session/replacement-preview-provider.js +24 -0
  150. package/dist/core/session/replacement-state.js +131 -0
  151. package/dist/core/session/resume-repair/pipeline.js +79 -0
  152. package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
  153. package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
  154. package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
  155. package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
  156. package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
  157. package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
  158. package/dist/core/session/resume-repair/types.js +2 -0
  159. package/dist/core/session/summary-sync.js +164 -13
  160. package/dist/core/session/token-tracker.js +6 -0
  161. package/dist/core/skills/audit.js +34 -0
  162. package/dist/core/skills/bridge.js +84 -7
  163. package/dist/core/skills/discovery.js +94 -0
  164. package/dist/core/skills/feature-flags.js +52 -0
  165. package/dist/core/skills/index.js +1 -1
  166. package/dist/core/skills/loader.js +195 -20
  167. package/dist/core/skills/parser.js +296 -24
  168. package/dist/core/skills/permissions.js +117 -0
  169. package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
  170. package/dist/core/skills/runtime/SkillRunner.js +240 -61
  171. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
  172. package/dist/core/strata/layers/worktree.js +67 -10
  173. package/dist/core/strata/runtime/synchronizer.js +29 -2
  174. package/dist/core/streaming/stream-assembler.js +75 -31
  175. package/dist/core/sub-agent/context-snapshot.js +156 -0
  176. package/dist/core/sub-agent/core/loop.js +1 -1
  177. package/dist/core/sub-agent/core/manager.js +119 -20
  178. package/dist/core/sub-agent/dispatch-policy.js +29 -0
  179. package/dist/core/sub-agent/prefix-consistency.js +48 -0
  180. package/dist/core/sub-agent/registry-defaults.js +4 -0
  181. package/dist/core/sub-agent/tools/task-spawn.js +79 -2
  182. package/dist/core/sub-agent/types.js +134 -5
  183. package/dist/core/tools/audit.js +13 -4
  184. package/dist/core/tools/builtin/ast-grep.js +1 -1
  185. package/dist/core/tools/builtin/ast.js +1 -1
  186. package/dist/core/tools/builtin/benchmark.js +360 -0
  187. package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
  188. package/dist/core/tools/builtin/code-search/executor.js +6 -1
  189. package/dist/core/tools/builtin/code-search/spec.js +26 -2
  190. package/dist/core/tools/builtin/fs.js +256 -23
  191. package/dist/core/tools/builtin/git.js +2 -2
  192. package/dist/core/tools/builtin/index.js +51 -2
  193. package/dist/core/tools/builtin/interaction.js +8 -1
  194. package/dist/core/tools/builtin/plan.js +37 -15
  195. package/dist/core/tools/builtin/shell.js +1 -1
  196. package/dist/core/tools/loader.js +39 -16
  197. package/dist/core/tools/mapper.js +17 -3
  198. package/dist/core/tools/parallel/scheduler.js +35 -4
  199. package/dist/core/tools/permissions/permission-rules.js +5 -10
  200. package/dist/core/tools/policy.js +6 -1
  201. package/dist/core/tools/recoverable-tool-errors.js +10 -0
  202. package/dist/core/tools/router.js +24 -6
  203. package/dist/core/tools/session.js +458 -48
  204. package/dist/core/tools/tool-visibility.js +62 -0
  205. package/dist/core/tools/types.js +9 -1
  206. package/dist/core/types/execution.js +4 -0
  207. package/dist/core/types/flow-mode.js +8 -0
  208. package/dist/core/utils/path.js +52 -0
  209. package/dist/core/verification/runner.js +4 -1
  210. package/dist/languages/typescript/index.js +4 -1
  211. package/dist/locales/en.js +35 -2
  212. package/dist/utils/eol.js +1 -1
  213. package/package.json +13 -6
  214. package/scripts/fix-es-abstract-compat.js +77 -0
  215. package/dist/core/runtime/fastify-server-bundle.js +0 -26
  216. package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
  217. package/dist/core/runtime/sidecar-paths.js +0 -47
  218. package/dist/core/runtime/sidecar-route-catalog.js +0 -103
@@ -1,3 +1,4 @@
1
+ import { isRecoverableToolInputErrorCode } from '../recoverable-tool-errors.js';
1
2
  import { IsolationManager } from './isolation.js';
2
3
  import { resolveArgsWithResults } from './resolve-args.js';
3
4
  import { processResource, repoResource } from './resource-helpers.js';
@@ -20,6 +21,25 @@ export class ParallelScheduler {
20
21
  node.spec = spec;
21
22
  return spec;
22
23
  }
24
+ normalizeArgsForSpec(spec, args) {
25
+ if (!spec.inputSchema || typeof spec.inputSchema.safeParse !== 'function')
26
+ return args;
27
+ const parsed = spec.inputSchema.safeParse(args);
28
+ return parsed.success ? parsed.data : args;
29
+ }
30
+ shouldFallbackFromComputeResources(spec, args, error) {
31
+ const parsed = spec.inputSchema.safeParse(args);
32
+ if (parsed.success)
33
+ return false;
34
+ const issueCode = parsed.error.issues[0]?.code;
35
+ if (issueCode === 'invalid_type' || issueCode === 'invalid_union' || issueCode === 'custom') {
36
+ return true;
37
+ }
38
+ const errorCode = typeof error === 'object' && error !== null && 'code' in error
39
+ ? error.code
40
+ : undefined;
41
+ return isRecoverableToolInputErrorCode(errorCode);
42
+ }
23
43
  deriveDefaultResources(spec, ctx) {
24
44
  const writeEffects = new Set(['fs_write', 'git_write', 'snapshot_mutate']);
25
45
  const hasWrite = spec.sideEffects.some((effect) => writeEffects.has(effect));
@@ -163,13 +183,14 @@ export class ParallelScheduler {
163
183
  }
164
184
  // 1. Resolve Arguments
165
185
  const resolvedArgs = resolveArgsWithResults(node.args, nodeResults);
186
+ const normalizedArgs = this.normalizeArgsForSpec(spec, resolvedArgs);
166
187
  // 1.5 Deferred authorization preflight (avoid holding locks while waiting for user)
167
188
  const preflight = typeof this.router.preflightDeferredAuthorization === 'function'
168
189
  ? await this.router.preflightDeferredAuthorization({
169
190
  id: nodeId,
170
191
  phase: baseCtx.phase || 'execute',
171
192
  toolName: node.toolName,
172
- args: resolvedArgs,
193
+ args: normalizedArgs,
173
194
  ctx: baseCtx,
174
195
  })
175
196
  : null;
@@ -205,8 +226,18 @@ export class ParallelScheduler {
205
226
  }
206
227
  // 2. Compute Resources (JIT)
207
228
  const resources = node.resources ??
208
- spec.computeResources?.(resolvedArgs, baseCtx) ??
209
- this.deriveDefaultResources(spec, baseCtx);
229
+ (() => {
230
+ try {
231
+ return (spec.computeResources?.(normalizedArgs, baseCtx) ??
232
+ this.deriveDefaultResources(spec, baseCtx));
233
+ }
234
+ catch (error) {
235
+ if (!this.shouldFallbackFromComputeResources(spec, normalizedArgs, error)) {
236
+ throw error;
237
+ }
238
+ return this.deriveDefaultResources(spec, baseCtx);
239
+ }
240
+ })();
210
241
  node.resources = resources;
211
242
  // 3. Acquire Locks
212
243
  const mode = lane === 'read' ? 'read' : 'write';
@@ -221,7 +252,7 @@ export class ParallelScheduler {
221
252
  id: nodeId,
222
253
  phase: baseCtx.phase || 'execute',
223
254
  toolName: node.toolName,
224
- args: resolvedArgs,
255
+ args: normalizedArgs,
225
256
  ctx: isolatedEnv
226
257
  ? { ...baseCtx, env: { ...baseCtx.env, ...isolatedEnv.env } }
227
258
  : baseCtx,
@@ -1,6 +1,7 @@
1
1
  import { text } from '../../../locales/index.js';
2
2
  import { normalizeDiff, validateDiff } from '../../patch/diff.js';
3
3
  import { ArtifactStore } from '../../sub-agent/artifacts/store.js';
4
+ import { normalizeRepoRelativePath } from '../../utils/path.js';
4
5
  const DEFAULT_TOOL_ALIASES = {
5
6
  bash: 'Bash',
6
7
  read: 'Read',
@@ -15,7 +16,7 @@ const ALIAS_TOOL_TO_INTERNAL_TOOL_NAMES = {
15
16
  Bash: ['shell.exec', 'test.run'],
16
17
  Read: ['fs.read', 'code.read', 'git.cat', 'artifact.read'],
17
18
  Edit: ['proposal.apply'],
18
- LS: ['fs.list', 'git.status'],
19
+ LS: ['fs.list', 'fs.list_directory', 'fs.list_files', 'git.status'],
19
20
  Grep: ['code.search'],
20
21
  Glob: ['code.search'],
21
22
  WebFetch: [],
@@ -182,14 +183,6 @@ function compileBashMatcher(specifier) {
182
183
  };
183
184
  return { kind: 'pattern', rawSpecifier: specifier, matches, isExactMatch };
184
185
  }
185
- function normalizeRepoRelativePath(input) {
186
- const raw = String(input ?? '')
187
- .replace(/\\/g, '/')
188
- .trim();
189
- const withoutDot = raw.replace(/^\.\//, '');
190
- const withoutLeadingSlash = withoutDot.replace(/^\/+/, '');
191
- return withoutLeadingSlash.replace(/\/{2,}/g, '/');
192
- }
193
186
  function compilePathMatcher(specifier) {
194
187
  const spec = normalizeRepoRelativePath(String(specifier ?? '').trim());
195
188
  if (!spec || spec === '*') {
@@ -243,6 +236,8 @@ function compileRule(effect, parsed) {
243
236
  tool === 'code.read' ||
244
237
  tool === 'git.cat' ||
245
238
  tool === 'fs.list' ||
239
+ tool === 'fs.list_directory' ||
240
+ tool === 'fs.list_files' ||
246
241
  tool === 'artifact.read' ||
247
242
  asAlias === 'Read' ||
248
243
  asAlias === 'LS';
@@ -335,7 +330,7 @@ function extractPrimaryPathArg(toolName, args) {
335
330
  return typeof obj.file === 'string' ? obj.file : undefined;
336
331
  if (toolName === 'git.cat')
337
332
  return typeof obj.file === 'string' ? obj.file : undefined;
338
- if (toolName === 'fs.list')
333
+ if (toolName === 'fs.list' || toolName === 'fs.list_directory' || toolName === 'fs.list_files')
339
334
  return typeof obj.path === 'string' ? obj.path : undefined;
340
335
  return undefined;
341
336
  }
@@ -1,3 +1,4 @@
1
+ import { resolveExecutionProfile } from '../runtime/execution-profile.js';
1
2
  import { Phase } from '../types/runtime.js';
2
3
  export class ToolPolicy {
3
4
  /**
@@ -15,6 +16,10 @@ export class ToolPolicy {
15
16
  const hasRuntimeWrite = spec.sideEffects.includes('runtime_write');
16
17
  const hasProcess = spec.sideEffects.includes('process');
17
18
  const hasNetwork = spec.sideEffects.includes('network');
19
+ const profile = ctx.flowMode ? resolveExecutionProfile(ctx.flowMode) : undefined;
20
+ const autopilotDirect = phase === Phase.AUTOPILOT &&
21
+ profile?.mode === 'autopilot' &&
22
+ profile.failurePolicy === 'preserve';
18
23
  // 3. APPLY phase is strictly for patch application, NO tool calls allowed
19
24
  if (phase === Phase.APPLY) {
20
25
  return {
@@ -32,7 +37,7 @@ export class ToolPolicy {
32
37
  }
33
38
  // 5. Worktree Requirement for Side Effects
34
39
  // Any repo-mutating tool or process/network execution MUST have a worktree.
35
- if ((hasRepoWrite || hasProcess || hasNetwork) && !ctx.worktreeRoot) {
40
+ if ((hasRepoWrite || hasProcess || hasNetwork) && !ctx.worktreeRoot && !autopilotDirect) {
36
41
  return {
37
42
  allowed: false,
38
43
  denyReason: `Tool ${spec.name} has side effects [${spec.sideEffects.join(',')}] and requires worktree isolation`,
@@ -0,0 +1,10 @@
1
+ export const RECOVERABLE_TOOL_INPUT_ERROR_CODES = [
2
+ 'INVALID_INPUT',
3
+ 'INVALID_TOOL_ARGUMENTS_JSON',
4
+ 'MALFORMED_TOOL_CALL',
5
+ ];
6
+ const RECOVERABLE_TOOL_INPUT_ERROR_CODE_SET = new Set(RECOVERABLE_TOOL_INPUT_ERROR_CODES);
7
+ export function isRecoverableToolInputErrorCode(code) {
8
+ return typeof code === 'string' && RECOVERABLE_TOOL_INPUT_ERROR_CODE_SET.has(code);
9
+ }
10
+ //# sourceMappingURL=recoverable-tool-errors.js.map
@@ -185,7 +185,7 @@ export class ToolRouter {
185
185
  }
186
186
  // 5. Budget Gating & Execution: Concurrency control, timeout, and execution
187
187
  const rawOutput = await this.budget.runWithGuards({
188
- timeoutMs: LIMITS.defaultToolTimeoutMs,
188
+ timeoutMs: spec.defaultTimeoutMs ?? LIMITS.defaultToolTimeoutMs,
189
189
  maxOutputBytes: LIMITS.maxToolOutputBytes,
190
190
  phase: normalizedEnvelope.phase,
191
191
  toolName: spec.name,
@@ -303,7 +303,7 @@ export class ToolRouter {
303
303
  if (permissionDecision.kind === 'allow') {
304
304
  return { kind: 'ready' };
305
305
  }
306
- const cacheKey = this.buildAuthorizationKey(normalizedEnvelope);
306
+ const cacheKey = this.buildAuthorizationKey(normalizedEnvelope, spec);
307
307
  if (this.isAuthorizationCached(cacheKey)) {
308
308
  return { kind: 'ready' };
309
309
  }
@@ -370,8 +370,24 @@ export class ToolRouter {
370
370
  },
371
371
  };
372
372
  }
373
- buildAuthorizationKey(envelope) {
374
- return `${envelope.toolName}:${envelope.phase}`;
373
+ buildAuthorizationKey(envelope, spec) {
374
+ const base = `${envelope.toolName}:${envelope.phase}`;
375
+ if (this.isHighRiskTool(spec)) {
376
+ const argsHash = this.hashArgs(envelope.args);
377
+ return argsHash ? `${base}:${argsHash}` : base;
378
+ }
379
+ // Low-risk (read-only) tools use toolName:phase only — a single approval
380
+ // covers all argument variations since these tools have no dangerous side effects.
381
+ return base;
382
+ }
383
+ /**
384
+ * Determines whether a tool is high-risk based on its declared side effects.
385
+ * High-risk tools (process, fs_write, network) require stricter authorization
386
+ * cache scoping — see {@link buildAuthorizationKey}.
387
+ */
388
+ isHighRiskTool(spec) {
389
+ const HIGH_RISK_EFFECTS = ['process', 'fs_write', 'network'];
390
+ return (spec.sideEffects ?? []).some((e) => HIGH_RISK_EFFECTS.includes(e));
375
391
  }
376
392
  isAuthorizationCached(key) {
377
393
  const entry = this.authorizationCache.get(key);
@@ -401,6 +417,8 @@ export class ToolRouter {
401
417
  return undefined;
402
418
  try {
403
419
  const raw = JSON.stringify(args);
420
+ // Full SHA-256 hex digest (64 chars / 256-bit) for authorization cache keys.
421
+ // Truncation to 16 hex was insufficient collision resistance for security use.
404
422
  return crypto.createHash('sha256').update(raw).digest('hex');
405
423
  }
406
424
  catch {
@@ -410,7 +428,7 @@ export class ToolRouter {
410
428
  async authorizeToolCall(envelope, spec) {
411
429
  if (!this.authorization)
412
430
  return { kind: 'allow' };
413
- const cacheKey = this.buildAuthorizationKey(envelope);
431
+ const cacheKey = this.buildAuthorizationKey(envelope, spec);
414
432
  if (this.isAuthorizationCached(cacheKey)) {
415
433
  this.audit.onAuthorization({
416
434
  callId: envelope.id,
@@ -486,7 +504,7 @@ export class ToolRouter {
486
504
  }
487
505
  if (decision.outcome === 'allow_session' || decision.outcome === 'allow') {
488
506
  const expiresAt = typeof decision.ttlMs === 'number' ? Date.now() + decision.ttlMs : undefined;
489
- const cacheKey = this.buildAuthorizationKey(envelope);
507
+ const cacheKey = this.buildAuthorizationKey(envelope, spec);
490
508
  this.authorizationCache.set(cacheKey, { expiresAt });
491
509
  }
492
510
  return { kind: 'allow' };