salmon-loop 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/cli/authorization/non-interactive.js +9 -13
  2. package/dist/cli/authorization/provider.js +2 -10
  3. package/dist/cli/chat.js +12 -6
  4. package/dist/cli/commands/allowlist.js +1 -1
  5. package/dist/cli/commands/chat.js +13 -13
  6. package/dist/cli/commands/config.js +2 -2
  7. package/dist/cli/commands/mode.js +2 -2
  8. package/dist/cli/commands/parallel.js +1 -1
  9. package/dist/cli/commands/run/handler.js +9 -4
  10. package/dist/cli/commands/run/loop-params.js +2 -0
  11. package/dist/cli/commands/run/parse-options.js +14 -26
  12. package/dist/cli/commands/run/runtime-llm.js +15 -12
  13. package/dist/cli/commands/run/runtime-options.js +3 -1
  14. package/dist/cli/config.js +0 -8
  15. package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
  16. package/dist/cli/locales/en.js +2 -2
  17. package/dist/cli/reporters/standard.js +12 -3
  18. package/dist/cli/reporters/stream-json.js +2 -1
  19. package/dist/cli/slash/runtime.js +2 -2
  20. package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
  21. package/dist/cli/ui/hooks/useLoopState.js +1 -1
  22. package/dist/core/adapters/fs/file-adapter.js +3 -1
  23. package/dist/core/adapters/git/git-adapter.js +6 -3
  24. package/dist/core/adapters/git/git-runner.js +5 -2
  25. package/dist/core/adapters/git/lock-manager.js +7 -4
  26. package/dist/core/ast/parser.js +18 -9
  27. package/dist/core/checkpoint-domain/manifest-store.js +21 -13
  28. package/dist/core/checkpoint-domain/service.js +3 -1
  29. package/dist/core/config/limits.js +1 -1
  30. package/dist/core/config/model-pricing.js +61 -0
  31. package/dist/core/config/schema.js +738 -0
  32. package/dist/core/config/validate.js +11 -922
  33. package/dist/core/context/ast/skeleton-extractor.js +225 -0
  34. package/dist/core/context/ast/source-outline.js +24 -1
  35. package/dist/core/context/budget/dynamic-adjuster.js +20 -5
  36. package/dist/core/context/builder.js +7 -3
  37. package/dist/core/context/cache/store-factory.js +3 -1
  38. package/dist/core/context/dependencies.js +2 -1
  39. package/dist/core/context/effectiveness/persistence.js +50 -0
  40. package/dist/core/context/effectiveness/tracker.js +24 -0
  41. package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
  42. package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
  43. package/dist/core/context/gatherers/ast-gatherer.js +34 -40
  44. package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
  45. package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
  46. package/dist/core/context/gatherers/knowledge-gatherer.js +21 -2
  47. package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
  48. package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
  49. package/dist/core/context/service.js +12 -2
  50. package/dist/core/context/steps/context-gather.js +14 -3
  51. package/dist/core/context/steps/context-targets.js +1 -0
  52. package/dist/core/context/targeting/target-resolver.js +29 -11
  53. package/dist/core/context/token/cache.js +5 -2
  54. package/dist/core/context/token/encoding-registry.js +7 -6
  55. package/dist/core/context/truncation/strategies/json.js +5 -2
  56. package/dist/core/context/truncation/type-detector.js +3 -1
  57. package/dist/core/extensions/index.js +48 -3
  58. package/dist/core/extensions/load.js +3 -2
  59. package/dist/core/extensions/merge.js +5 -1
  60. package/dist/core/extensions/paths.js +8 -2
  61. package/dist/core/extensions/schemas.js +21 -0
  62. package/dist/core/facades/cli-authorization-provider.js +1 -0
  63. package/dist/core/facades/cli-command-chat.js +2 -0
  64. package/dist/core/facades/cli-run-handler.js +1 -0
  65. package/dist/core/facades/cli-utils-serialize.js +2 -0
  66. package/dist/core/feedback/parsers.js +290 -1
  67. package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
  68. package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
  69. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
  70. package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
  71. package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
  72. package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
  73. package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
  74. package/dist/core/grizzco/execution/RejectionManager.js +7 -5
  75. package/dist/core/grizzco/runtime/apply-back-runtime.js +5 -2
  76. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
  77. package/dist/core/grizzco/services/registry.js +18 -0
  78. package/dist/core/grizzco/steps/audit.js +20 -10
  79. package/dist/core/grizzco/steps/autopilot.js +21 -32
  80. package/dist/core/grizzco/steps/display-report.js +4 -11
  81. package/dist/core/grizzco/steps/explore.js +14 -4
  82. package/dist/core/grizzco/steps/generateReview.js +3 -1
  83. package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
  84. package/dist/core/grizzco/steps/patch.js +1 -0
  85. package/dist/core/grizzco/steps/plan.js +58 -49
  86. package/dist/core/grizzco/steps/research.js +3 -1
  87. package/dist/core/grizzco/steps/tool-runtime.js +3 -0
  88. package/dist/core/grizzco/steps/verify.js +7 -1
  89. package/dist/core/grizzco/validation/AstValidationService.js +3 -1
  90. package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
  91. package/dist/core/history/input-history.js +3 -1
  92. package/dist/core/intent/chat-intent.js +3 -1
  93. package/dist/core/llm/ai-sdk/message-mapper.js +37 -26
  94. package/dist/core/llm/ai-sdk/request-params.js +2 -6
  95. package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
  96. package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
  97. package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
  98. package/dist/core/llm/contracts/repair.js +16 -8
  99. package/dist/core/llm/errors.js +18 -14
  100. package/dist/core/llm/output-policy.js +8 -0
  101. package/dist/core/llm/redact.js +1 -3
  102. package/dist/core/llm/retry-utils.js +8 -2
  103. package/dist/core/llm/stream-utils.js +5 -3
  104. package/dist/core/llm/sub-agent-factory.js +51 -0
  105. package/dist/core/llm/tool-calling-stub.js +48 -0
  106. package/dist/core/llm/utils.js +17 -6
  107. package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
  108. package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
  109. package/dist/core/mcp/bridge/tool-bridge.js +5 -14
  110. package/dist/core/mcp/catalog/discovery.js +3 -1
  111. package/dist/core/mcp/client/connection-manager.js +7 -4
  112. package/dist/core/mcp/client/transport-factory.js +7 -3
  113. package/dist/core/mcp/host/sampling-provider.js +1 -1
  114. package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
  115. package/dist/core/memory/relevant-retrieval.js +6 -4
  116. package/dist/core/observability/audit-file.js +2 -1
  117. package/dist/core/observability/audit-trail.js +3 -1
  118. package/dist/core/observability/authorization-decisions.js +13 -12
  119. package/dist/core/observability/error-mapping.js +2 -1
  120. package/dist/core/observability/logger.js +2 -1
  121. package/dist/core/observability/monitor.js +24 -0
  122. package/dist/core/observability/run-outcome-reporter.js +1 -0
  123. package/dist/core/observability/token-usage.js +5 -4
  124. package/dist/core/permission-gate/default-gate.js +5 -8
  125. package/dist/core/plan/storage.js +7 -4
  126. package/dist/core/plugin/loader.js +8 -5
  127. package/dist/core/prompts/registry.js +12 -30
  128. package/dist/core/prompts/runtime.js +3 -1
  129. package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
  130. package/dist/core/protocols/a2a/sdk/executor.js +3 -1
  131. package/dist/core/protocols/a2a/sdk/server.js +5 -4
  132. package/dist/core/protocols/acp/acp-command-runner.js +7 -6
  133. package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
  134. package/dist/core/protocols/acp/formal-agent.js +13 -6
  135. package/dist/core/protocols/acp/permission-provider.js +3 -2
  136. package/dist/core/protocols/acp/stdio-server.js +6 -6
  137. package/dist/core/reflection/engine.js +114 -14
  138. package/dist/core/runtime/agent-server-runtime.js +3 -2
  139. package/dist/core/runtime/batch-runner.js +81 -0
  140. package/dist/core/runtime/initialize.js +71 -6
  141. package/dist/core/runtime/loop-finalize.js +3 -0
  142. package/dist/core/runtime/loop-session-runner.js +5 -0
  143. package/dist/core/runtime/loop.js +4 -0
  144. package/dist/core/runtime/paths.js +9 -6
  145. package/dist/core/runtime/spawn-interactive.js +5 -4
  146. package/dist/core/security/redaction.js +3 -2
  147. package/dist/core/session/compaction/index.js +4 -3
  148. package/dist/core/session/compression.js +3 -1
  149. package/dist/core/session/manager.js +26 -38
  150. package/dist/core/session/pruning-strategy.js +2 -1
  151. package/dist/core/session/token-tracker.js +27 -9
  152. package/dist/core/skills/parser.js +3 -2
  153. package/dist/core/skills/permissions.js +2 -2
  154. package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
  155. package/dist/core/skills/runtime/SkillRunner.js +5 -2
  156. package/dist/core/slash/steps/slash-execute.js +7 -5
  157. package/dist/core/slash/strategy.js +1 -1
  158. package/dist/core/strata/checkpoint/manager.js +16 -10
  159. package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
  160. package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
  161. package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
  162. package/dist/core/strata/interaction/file-system-provider.js +2 -1
  163. package/dist/core/strata/layers/file-state-resolver.js +9 -7
  164. package/dist/core/strata/layers/immutable-git-layer.js +3 -1
  165. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
  166. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
  167. package/dist/core/strata/layers/worktree.js +9 -10
  168. package/dist/core/strata/runtime/environment.js +2 -1
  169. package/dist/core/strata/runtime/synchronizer.js +28 -26
  170. package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
  171. package/dist/core/structured-output/json-extract.js +3 -1
  172. package/dist/core/structured-output/json-schema-validator.js +1 -13
  173. package/dist/core/sub-agent/artifacts/store.js +2 -1
  174. package/dist/core/sub-agent/context-snapshot.js +12 -6
  175. package/dist/core/sub-agent/controller.js +70 -1
  176. package/dist/core/sub-agent/core/loop.js +25 -3
  177. package/dist/core/sub-agent/core/manager.js +343 -117
  178. package/dist/core/sub-agent/registry-defaults.js +12 -0
  179. package/dist/core/sub-agent/registry.js +8 -0
  180. package/dist/core/sub-agent/summary.js +96 -0
  181. package/dist/core/sub-agent/team.js +98 -0
  182. package/dist/core/sub-agent/tools/task-await.js +109 -0
  183. package/dist/core/sub-agent/tools/task-spawn.js +52 -7
  184. package/dist/core/sub-agent/tools/team.js +92 -0
  185. package/dist/core/sub-agent/types.js +11 -2
  186. package/dist/core/target-runtime/profile.js +3 -1
  187. package/dist/core/tools/audit.js +3 -2
  188. package/dist/core/tools/budget.js +7 -12
  189. package/dist/core/tools/builtin/ast.js +144 -0
  190. package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
  191. package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
  192. package/dist/core/tools/builtin/code-search/executor.js +46 -43
  193. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
  194. package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
  195. package/dist/core/tools/builtin/fs.js +90 -7
  196. package/dist/core/tools/builtin/git.js +242 -0
  197. package/dist/core/tools/builtin/glob.js +79 -0
  198. package/dist/core/tools/builtin/index.js +53 -111
  199. package/dist/core/tools/builtin/interaction.js +13 -15
  200. package/dist/core/tools/builtin/knowledge.js +146 -4
  201. package/dist/core/tools/builtin/proposal.js +14 -3
  202. package/dist/core/tools/builtin/verify.js +35 -3
  203. package/dist/core/tools/capability/executor.js +5 -5
  204. package/dist/core/tools/headless-payload.js +1 -3
  205. package/dist/core/tools/mapper.js +8 -42
  206. package/dist/core/tools/parallel/persistence.js +17 -5
  207. package/dist/core/tools/parallel/scheduler.js +23 -21
  208. package/dist/core/tools/permissions/permission-rules.js +69 -115
  209. package/dist/core/tools/plugins/loader.js +4 -3
  210. package/dist/core/tools/router.js +112 -58
  211. package/dist/core/tools/session.js +64 -102
  212. package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
  213. package/dist/core/tools/tool-visibility.js +2 -1
  214. package/dist/core/tools/types.js +10 -0
  215. package/dist/core/types/batch.js +2 -0
  216. package/dist/core/utils/error.js +79 -0
  217. package/dist/core/utils/sanitizer.js +5 -2
  218. package/dist/core/utils/serialize.js +66 -0
  219. package/dist/core/utils/zod.js +29 -0
  220. package/dist/core/verification/detect-runner.js +86 -0
  221. package/dist/core/verification/runner.js +76 -0
  222. package/dist/core/version.js +3 -1
  223. package/dist/core/workspace/capabilities.js +3 -2
  224. package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
  225. package/dist/languages/python/index.js +154 -0
  226. package/dist/locales/en.js +8 -1
  227. package/package.json +2 -1
@@ -8,7 +8,9 @@ import { CanonicalResponsesEventEmitter, } from '../streaming/canonical/canonica
8
8
  import { mapLlmStreamChunkToCanonicalStreamParts } from '../streaming/canonical/parts-from-llm-stream-chunk.js';
9
9
  import { ArtifactStore } from '../sub-agent/artifacts/store.js';
10
10
  import { Phase } from '../types/runtime.js';
11
+ import { extractNetworkCode, extractProvider, extractStatusCode } from '../utils/error.js';
11
12
  import { isSafeRelativePath, normalizePath } from '../utils/path.js';
13
+ import { isRecord } from '../utils/serialize.js';
12
14
  import { buildHeadlessToolInputPayload } from './headless-payload.js';
13
15
  import { toolToOpenAI } from './mapper.js';
14
16
  import { InMemoryLockManager } from './parallel/lock-manager.js';
@@ -17,6 +19,13 @@ import { ParallelScheduler } from './parallel/scheduler.js';
17
19
  import { isRecoverableToolInputErrorCode } from './recoverable-tool-errors.js';
18
20
  import { ToolCallAccumulator } from './streaming/ToolCallAccumulator.js';
19
21
  import { resolveVisibleToolSpecs } from './tool-visibility.js';
22
+ function findLast(array, predicate) {
23
+ for (let i = array.length - 1; i >= 0; i--) {
24
+ if (predicate(array[i]))
25
+ return array[i];
26
+ }
27
+ return undefined;
28
+ }
20
29
  function safeParseJson(argsText) {
21
30
  if (typeof argsText !== 'string') {
22
31
  return { ok: true, value: argsText };
@@ -37,8 +46,9 @@ function safeParseJson(argsText) {
37
46
  try {
38
47
  value = JSON.parse(nested);
39
48
  }
40
- catch {
49
+ catch (error) {
41
50
  // Ignore: fall back to the first parse result to preserve observability.
51
+ getLogger().debug(`[ToolSession] Double-decoded JSON parse fallback: ${error instanceof Error ? error.message : String(error)}`);
42
52
  }
43
53
  }
44
54
  }
@@ -62,7 +72,8 @@ function formatToolResultForModel(result) {
62
72
  try {
63
73
  return JSON.stringify(payload);
64
74
  }
65
- catch {
75
+ catch (error) {
76
+ getLogger().debug(`[ToolSession] Failed to serialize tool result: ${error instanceof Error ? error.message : String(error)}`);
66
77
  return JSON.stringify({
67
78
  id: result.id,
68
79
  toolName: result.toolName,
@@ -79,7 +90,8 @@ function safeStringifyForAudit(value) {
79
90
  try {
80
91
  return redactJsonString(JSON.stringify(redactValue(value)));
81
92
  }
82
- catch {
93
+ catch (error) {
94
+ getLogger().debug(`[ToolSession] Failed to stringify value for audit: ${error instanceof Error ? error.message : String(error)}`);
83
95
  return '[Unserializable]';
84
96
  }
85
97
  }
@@ -126,7 +138,7 @@ function isArtifactHandleRecord(value) {
126
138
  typeof candidate.size === 'number');
127
139
  }
128
140
  function extractArtifactHandlesFromToolOutput(output) {
129
- if (!isObjectRecord(output)) {
141
+ if (!isRecord(output)) {
130
142
  return {};
131
143
  }
132
144
  const patchArtifact = isArtifactHandleRecord(output.patchArtifact)
@@ -144,12 +156,12 @@ function extractRecentReadResult(params) {
144
156
  if (params.toolName !== 'fs.read' && params.toolName !== 'code.read') {
145
157
  return undefined;
146
158
  }
147
- if (!isObjectRecord(params.output) || typeof params.output.content !== 'string') {
159
+ if (!isRecord(params.output) || typeof params.output.content !== 'string') {
148
160
  return undefined;
149
161
  }
150
162
  const args = safeParseJson(params.rawArgs);
151
163
  const argsValue = args.ok ? args.value : params.rawArgs;
152
- if (!isObjectRecord(argsValue))
164
+ if (!isRecord(argsValue))
153
165
  return undefined;
154
166
  const file = argsValue.file ?? argsValue.file_path ?? argsValue.filePath ?? argsValue.path;
155
167
  if (typeof file !== 'string' || !file.trim())
@@ -206,7 +218,8 @@ function serializeToolResultOutputForArtifact(output) {
206
218
  fileExt: 'json',
207
219
  };
208
220
  }
209
- catch {
221
+ catch (error) {
222
+ getLogger().debug(`[ToolSession] Failed to serialize tool output for artifact: ${error instanceof Error ? error.message : String(error)}`);
210
223
  return undefined;
211
224
  }
212
225
  }
@@ -703,7 +716,7 @@ export async function chatWithTools(initialMessages, chatOptions, session) {
703
716
  tool_calls: assistant.tool_calls,
704
717
  });
705
718
  const toolCalls = assistant.tool_calls || [];
706
- if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
719
+ if (toolCalls.length === 0) {
707
720
  if (session.llmOutput) {
708
721
  emitLlmOutput({
709
722
  emit: session.emit,
@@ -728,7 +741,7 @@ export async function chatWithTools(initialMessages, chatOptions, session) {
728
741
  message: `Tool calling exceeded maximum rounds (${maxRounds}); continuing without further tool execution`,
729
742
  timestamp: new Date(),
730
743
  });
731
- const lastAssistant = [...messages].reverse().find((m) => m.role === 'assistant');
744
+ const lastAssistant = findLast(messages, (m) => m.role === 'assistant');
732
745
  if (session.llmOutput && lastAssistant?.content) {
733
746
  emitLlmOutput({
734
747
  emit: session.emit,
@@ -740,11 +753,8 @@ export async function chatWithTools(initialMessages, chatOptions, session) {
740
753
  }
741
754
  return lastAssistant || { role: 'assistant', content: '' };
742
755
  }
743
- function isObjectRecord(value) {
744
- return typeof value === 'object' && value !== null && !Array.isArray(value);
745
- }
746
756
  function isPlainObject(value) {
747
- if (!isObjectRecord(value))
757
+ if (!isRecord(value))
748
758
  return false;
749
759
  const proto = Object.getPrototypeOf(value);
750
760
  return proto === Object.prototype || proto === null;
@@ -783,67 +793,13 @@ function coercePlanUpdatePatch(args) {
783
793
  coercedPatchSource: 'stringified',
784
794
  };
785
795
  }
786
- catch {
796
+ catch (error) {
797
+ getLogger().debug(`[ToolSession] Failed to parse plan.update patch JSON: ${error instanceof Error ? error.message : String(error)}`);
787
798
  return { args, error: formatPlanUpdatePatchTypeError('string') };
788
799
  }
789
800
  }
790
801
  return { args, error: formatPlanUpdatePatchTypeError(describeValueType(patch)) };
791
802
  }
792
- function unwrapRetryError(err) {
793
- if (!err || typeof err !== 'object')
794
- return err;
795
- const candidate = err;
796
- if (candidate.lastError)
797
- return candidate.lastError;
798
- return err;
799
- }
800
- function extractStatusCode(err) {
801
- const unwrapped = unwrapRetryError(err);
802
- if (!unwrapped || typeof unwrapped !== 'object')
803
- return undefined;
804
- const meta = unwrapped?.meta;
805
- if (meta && typeof meta === 'object' && typeof meta.statusCode === 'number') {
806
- return meta.statusCode;
807
- }
808
- const statusCode = unwrapped?.statusCode;
809
- if (typeof statusCode === 'number')
810
- return statusCode;
811
- const response = unwrapped?.response;
812
- if (response && typeof response === 'object' && typeof response.status === 'number') {
813
- return response.status;
814
- }
815
- return undefined;
816
- }
817
- function extractNetworkCode(err) {
818
- const unwrapped = unwrapRetryError(err);
819
- if (!unwrapped || typeof unwrapped !== 'object')
820
- return undefined;
821
- const code = unwrapped?.code;
822
- if (typeof code === 'string')
823
- return code;
824
- const cause = unwrapped?.cause;
825
- if (cause && typeof cause === 'object' && typeof cause.code === 'string') {
826
- return cause.code;
827
- }
828
- const meta = unwrapped?.meta;
829
- if (meta && typeof meta === 'object' && typeof meta.causeName === 'string') {
830
- return meta.causeName;
831
- }
832
- return undefined;
833
- }
834
- function extractProvider(err) {
835
- const unwrapped = unwrapRetryError(err);
836
- if (!unwrapped || typeof unwrapped !== 'object')
837
- return undefined;
838
- const meta = unwrapped?.meta;
839
- if (meta && typeof meta === 'object' && typeof meta.provider === 'string') {
840
- return meta.provider;
841
- }
842
- const provider = unwrapped?.provider;
843
- if (typeof provider === 'string')
844
- return provider;
845
- return undefined;
846
- }
847
803
  const ENABLE_TOOL_ARG_REPAIR = process.env.SALMONLOOP_ENABLE_TOOL_ARG_REPAIR === '1' ||
848
804
  process.env.SALMONLOOP_ENABLE_TOOL_ARG_REPAIR === 'true';
849
805
  const SAFE_INFERRED_EXTENSIONS = new Set([
@@ -901,7 +857,7 @@ function inferHighConfidenceFiles(instruction) {
901
857
  return Array.from(new Set(candidates));
902
858
  }
903
859
  function extractInstructionText(messages) {
904
- const lastUser = [...messages].reverse().find((m) => m.role === 'user');
860
+ const lastUser = findLast(messages, (m) => m.role === 'user');
905
861
  const text = typeof lastUser?.content === 'string' ? lastUser.content : '';
906
862
  if (!text)
907
863
  return '';
@@ -921,7 +877,7 @@ function prepareToolCallRequests(calls) {
921
877
  async function runToolExecutionPlan(params) {
922
878
  const scheduler = new ParallelScheduler(params.session.toolstack.router, new InMemoryLockManager());
923
879
  const runSignal = params.signal ?? new AbortController().signal;
924
- let result = (await scheduler.run(params.plan, { ...params.session.runtime, phase: params.phase }, runSignal));
880
+ let result = await scheduler.run(params.plan, { ...params.session.runtime, phase: params.phase }, runSignal);
925
881
  const persistEnabled = process.env.NODE_ENV !== 'test';
926
882
  const persistenceRoot = params.session.runtime.persistenceRoot || params.session.runtime.repoRoot;
927
883
  if (persistEnabled) {
@@ -943,10 +899,10 @@ async function runToolExecutionPlan(params) {
943
899
  await Promise.all(result.blockedApprovals.map(async (a) => {
944
900
  await waitForAuthorization?.(a.nodeId, runSignal);
945
901
  }));
946
- result = (await scheduler.run(params.plan, { ...params.session.runtime, phase: params.phase }, runSignal, {
902
+ result = await scheduler.run(params.plan, { ...params.session.runtime, phase: params.phase }, runSignal, {
947
903
  initialResults: result.nodeResults,
948
904
  resumeBlockedApprovals: true,
949
- }));
905
+ });
950
906
  if (persistEnabled) {
951
907
  await PlanPersistence.save(persistenceRoot, params.plan, result, {
952
908
  repoRoot: params.session.runtime.repoRoot,
@@ -983,12 +939,14 @@ function applyStrictToolOutputSchemaValidation(params) {
983
939
  }
984
940
  async function executeToolCalls(session, phase, round, calls, messages, signal) {
985
941
  const prepared = prepareToolCallRequests(calls);
942
+ const specByName = new Map();
943
+ for (const spec of session.toolstack.registry.listAll()) {
944
+ specByName.set(spec.name, spec);
945
+ }
986
946
  const bucketByCallId = new Map();
987
947
  const preparedCounts = { regular: 0, agent: 0 };
988
948
  for (const item of prepared) {
989
- const spec = typeof item.toolName === 'string'
990
- ? session.toolstack.registry.listAll().find((s) => s.name === item.toolName)
991
- : undefined;
949
+ const spec = typeof item.toolName === 'string' ? specByName.get(item.toolName) : undefined;
992
950
  const bucket = spec?.intent === 'AGENT' ? 'agent' : 'regular';
993
951
  bucketByCallId.set(item.callId, bucket);
994
952
  preparedCounts[bucket]++;
@@ -1031,7 +989,7 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
1031
989
  ENABLE_TOOL_ARG_REPAIR &&
1032
990
  phase === Phase.EXPLORE &&
1033
991
  normalizedToolName === 'fs.read' &&
1034
- isObjectRecord(argsValue) &&
992
+ isRecord(argsValue) &&
1035
993
  typeof argsValue.file !== 'string') {
1036
994
  const instruction = extractInstructionText(messages);
1037
995
  const inferred = inferHighConfidenceFiles(instruction);
@@ -1040,7 +998,7 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
1040
998
  }
1041
999
  }
1042
1000
  let planUpdatePatchError;
1043
- if (parsedArgsOk && normalizedToolName === 'plan.update' && isObjectRecord(argsValue)) {
1001
+ if (parsedArgsOk && normalizedToolName === 'plan.update' && isRecord(argsValue)) {
1044
1002
  const patchGuard = coercePlanUpdatePatch(argsValue);
1045
1003
  argsValue = patchGuard.args;
1046
1004
  if (patchGuard.coercedPatchSource) {
@@ -1056,9 +1014,7 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
1056
1014
  const input = session.eventPayload?.includeToolInput && parsedArgsOk
1057
1015
  ? buildHeadlessToolInputPayload(argsValue)
1058
1016
  : undefined;
1059
- const spec = typeof toolName === 'string'
1060
- ? session.toolstack.registry.listAll().find((s) => s.name === toolName)
1061
- : undefined;
1017
+ const spec = typeof toolName === 'string' ? specByName.get(toolName) : undefined;
1062
1018
  if (typeof toolName === 'string') {
1063
1019
  session.emit?.({
1064
1020
  type: 'log',
@@ -1177,7 +1133,8 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
1177
1133
  };
1178
1134
  const patchCoercionSource = patchCoercionByCallId.get(callId);
1179
1135
  if (patchCoercionSource) {
1180
- parsedAuditEntry.coercedPatchSource = patchCoercionSource;
1136
+ parsedAuditEntry.coercedPatchSource =
1137
+ patchCoercionSource;
1181
1138
  }
1182
1139
  session.toolCallingAudit?.event(parsedAuditEntry);
1183
1140
  if (planUpdatePatchError) {
@@ -1269,9 +1226,10 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
1269
1226
  if (result.status !== 'ok' &&
1270
1227
  result.error?.code === 'INTERRUPT_REQUIRED' &&
1271
1228
  result.meta?.interrupt) {
1272
- const err = new Error(result.error.message || 'Interrupt required');
1273
- err.code = 'INTERRUPT_REQUIRED';
1274
- err.interrupt = result.meta.interrupt;
1229
+ const err = Object.assign(new Error(result.error.message || 'Interrupt required'), {
1230
+ code: 'INTERRUPT_REQUIRED',
1231
+ interrupt: result.meta.interrupt,
1232
+ });
1275
1233
  throw err;
1276
1234
  }
1277
1235
  if (result.status !== 'ok') {
@@ -1295,26 +1253,30 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
1295
1253
  };
1296
1254
  const patchCoercionSource = patchCoercionByCallId.get(callId);
1297
1255
  if (patchCoercionSource) {
1298
- errorAuditEntry.coercedPatchSource = patchCoercionSource;
1256
+ errorAuditEntry.coercedPatchSource =
1257
+ patchCoercionSource;
1299
1258
  }
1300
1259
  session.toolCallingAudit?.event(errorAuditEntry);
1301
1260
  }
1302
1261
  else {
1303
- const toolResultOutputOk = isObjectRecord(result.output) && typeof result.output.ok === 'boolean'
1262
+ const toolResultOutputOk = isRecord(result.output) && typeof result.output.ok === 'boolean'
1304
1263
  ? result.output.ok
1305
1264
  : undefined;
1306
1265
  const artifacts = extractArtifactHandlesFromToolOutput(result.output);
1307
- const recentReadArtifact = await persistRecentReadArtifact({
1308
- toolName: typeof toolName === 'string' ? toolName : 'unknown',
1309
- rawArgs,
1310
- output: result.output,
1311
- });
1312
- const toolResultPreviewArtifact = await persistToolResultPreviewArtifact({
1313
- toolName: typeof toolName === 'string' ? toolName : 'unknown',
1314
- output: result.output,
1315
- summary: result.summary,
1316
- outputSummary: result.outputSummary,
1317
- });
1266
+ const resolvedToolName = typeof toolName === 'string' ? toolName : 'unknown';
1267
+ const [recentReadArtifact, toolResultPreviewArtifact] = await Promise.all([
1268
+ persistRecentReadArtifact({
1269
+ toolName: resolvedToolName,
1270
+ rawArgs,
1271
+ output: result.output,
1272
+ }),
1273
+ persistToolResultPreviewArtifact({
1274
+ toolName: resolvedToolName,
1275
+ output: result.output,
1276
+ summary: result.summary,
1277
+ outputSummary: result.outputSummary,
1278
+ }),
1279
+ ]);
1318
1280
  session.toolCallingAudit?.event({
1319
1281
  timestamp: new Date().toISOString(),
1320
1282
  phase,
@@ -1410,9 +1372,9 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
1410
1372
  statusCode: extractStatusCode(e),
1411
1373
  networkCode: extractNetworkCode(e),
1412
1374
  errorName: e instanceof Error ? e.name : 'UnknownError',
1413
- errorCode: typeof e?.llmCode === 'string'
1375
+ errorCode: isRecord(e) && typeof e.llmCode === 'string'
1414
1376
  ? e.llmCode
1415
- : typeof e?.code === 'string'
1377
+ : isRecord(e) && typeof e.code === 'string'
1416
1378
  ? e.code
1417
1379
  : undefined,
1418
1380
  }, { source: 'llm', severity: 'low', scope: 'session', phase });
@@ -1470,7 +1432,7 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
1470
1432
  }, { source: 'llm', severity: 'low', scope: 'session', phase });
1471
1433
  messages.push(assistant);
1472
1434
  const calls = assistant.tool_calls || [];
1473
- if (!Array.isArray(calls) || calls.length === 0) {
1435
+ if (calls.length === 0) {
1474
1436
  if (session.llmOutput) {
1475
1437
  emitLlmStreamEnd({
1476
1438
  emit: session.emit,
@@ -1509,7 +1471,7 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
1509
1471
  message: `Tool calling exceeded maximum rounds (${maxRounds}); continuing without further tool execution`,
1510
1472
  timestamp: new Date(),
1511
1473
  });
1512
- const lastAssistant = [...messages].reverse().find((m) => m.role === 'assistant');
1474
+ const lastAssistant = findLast(messages, (m) => m.role === 'assistant');
1513
1475
  return lastAssistant || { role: 'assistant', content: '' };
1514
1476
  }
1515
1477
  //# sourceMappingURL=session.js.map
@@ -1,6 +1,4 @@
1
- function isRecord(value) {
2
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
3
- }
1
+ import { isRecord } from '../../utils/serialize.js';
4
2
  function mergeToolCall(prev, next) {
5
3
  if (!isRecord(next))
6
4
  return prev ?? next;
@@ -46,7 +46,8 @@ export function resolvePhaseVisibleTools(params) {
46
46
  export function resolveVisibleToolSpecs(params) {
47
47
  if (!params.toolstack)
48
48
  return [];
49
- const allowedSpecs = params.toolstack.registry.listAll().filter((spec) => params.toolstack.policy.decide(params.phase, spec, {
49
+ const { toolstack } = params;
50
+ const allowedSpecs = toolstack.registry.listAll().filter((spec) => toolstack.policy.decide(params.phase, spec, {
50
51
  worktreeRoot: params.worktreeRoot,
51
52
  flowMode: params.flowMode,
52
53
  }).allowed);
@@ -7,4 +7,14 @@ export const TOOL_INTENTS = [
7
7
  'AGENT',
8
8
  'REPORT',
9
9
  ];
10
+ /**
11
+ * Type-safe helper to create a fully-formed ToolSpec from a spec definition and executor.
12
+ * Eliminates the `{ ...spec, executor: exec as any }` boilerplate pattern.
13
+ *
14
+ * The executor parameter accepts a superset of ToolRuntimeCtx (e.g., with phase narrowed
15
+ * to non-optional) to support tools that require the router-injected phase field.
16
+ */
17
+ export function defineTool(spec, executor) {
18
+ return { ...spec, executor: executor };
19
+ }
10
20
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1,79 @@
1
+ import { isRecord } from './serialize.js';
2
+ /**
3
+ * Extract a human-readable error message from an unknown thrown value.
4
+ * Handles Error instances, strings, and falls back to String(value).
5
+ */
6
+ export function errorMessage(error) {
7
+ if (error instanceof Error)
8
+ return error.message;
9
+ if (typeof error === 'string')
10
+ return error;
11
+ return String(error);
12
+ }
13
+ /**
14
+ * Unwrap retry-style errors that wrap the real error in a `lastError` property.
15
+ */
16
+ export function unwrapRetryError(err) {
17
+ if (!isRecord(err))
18
+ return err;
19
+ if (err.lastError)
20
+ return err.lastError;
21
+ return err;
22
+ }
23
+ /**
24
+ * Extract an HTTP-style status code from an error object.
25
+ * Checks: meta.statusCode, statusCode, response.status
26
+ */
27
+ export function extractStatusCode(err) {
28
+ const unwrapped = unwrapRetryError(err);
29
+ if (!isRecord(unwrapped))
30
+ return undefined;
31
+ const meta = unwrapped.meta;
32
+ if (isRecord(meta) && typeof meta.statusCode === 'number') {
33
+ return meta.statusCode;
34
+ }
35
+ if (typeof unwrapped.statusCode === 'number')
36
+ return unwrapped.statusCode;
37
+ const response = unwrapped.response;
38
+ if (isRecord(response) && typeof response.status === 'number') {
39
+ return response.status;
40
+ }
41
+ return undefined;
42
+ }
43
+ /**
44
+ * Extract a network error code from an error object.
45
+ * Checks: code, cause.code, meta.causeName
46
+ */
47
+ export function extractNetworkCode(err) {
48
+ const unwrapped = unwrapRetryError(err);
49
+ if (!isRecord(unwrapped))
50
+ return undefined;
51
+ if (typeof unwrapped.code === 'string')
52
+ return unwrapped.code;
53
+ const cause = unwrapped.cause;
54
+ if (isRecord(cause) && typeof cause.code === 'string') {
55
+ return cause.code;
56
+ }
57
+ const meta = unwrapped.meta;
58
+ if (isRecord(meta) && typeof meta.causeName === 'string') {
59
+ return meta.causeName;
60
+ }
61
+ return undefined;
62
+ }
63
+ /**
64
+ * Extract a provider name from an error object.
65
+ * Checks: meta.provider, provider
66
+ */
67
+ export function extractProvider(err) {
68
+ const unwrapped = unwrapRetryError(err);
69
+ if (!isRecord(unwrapped))
70
+ return undefined;
71
+ const meta = unwrapped.meta;
72
+ if (isRecord(meta) && typeof meta.provider === 'string') {
73
+ return meta.provider;
74
+ }
75
+ if (typeof unwrapped.provider === 'string')
76
+ return unwrapped.provider;
77
+ return undefined;
78
+ }
79
+ //# sourceMappingURL=error.js.map
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../observability/logger.js';
1
2
  /**
2
3
  * Sanitizes any error input (object, string, or mixed) to prevent leakage
3
4
  * of sensitive technical data like Zod dumps or stack traces.
@@ -10,7 +11,8 @@ export function sanitizeErrorMessage(err) {
10
11
  try {
11
12
  msg = err instanceof Error ? err.message : typeof err === 'string' ? err : JSON.stringify(err);
12
13
  }
13
- catch {
14
+ catch (error) {
15
+ getLogger().debug(`[Sanitizer] Failed to convert error to string: ${error instanceof Error ? error.message : String(error)}`);
14
16
  msg = String(err);
15
17
  }
16
18
  // 2. Strict Whitelist Detection
@@ -87,7 +89,8 @@ export function sanitizeObject(obj, maxDepth = MAX_DEPTH, depth = 0) {
87
89
  try {
88
90
  result[key] = sanitizeObject(value, maxDepth, depth + 1);
89
91
  }
90
- catch {
92
+ catch (error) {
93
+ getLogger().debug(`[Sanitizer] Circular reference detected during object sanitization: ${error instanceof Error ? error.message : String(error)}`);
91
94
  result[key] = '[CIRCULAR]';
92
95
  }
93
96
  }
@@ -0,0 +1,66 @@
1
+ import { getLogger } from '../observability/logger.js';
2
+ /**
3
+ * Safely serialize a value to JSON string.
4
+ * Returns '[Unserializable]' if JSON.stringify throws and String() also fails.
5
+ */
6
+ export function safeStringify(value, options) {
7
+ try {
8
+ const raw = JSON.stringify(value, null, options?.indent);
9
+ if (options?.maxLength && raw.length > options.maxLength) {
10
+ return `${raw.slice(0, options.maxLength)}...`;
11
+ }
12
+ return raw;
13
+ }
14
+ catch (error) {
15
+ getLogger().debug(`[Serialize] JSON.stringify failed, falling back to String(): ${error instanceof Error ? error.message : String(error)}`);
16
+ try {
17
+ return String(value);
18
+ }
19
+ catch (innerError) {
20
+ getLogger().debug(`[Serialize] String() conversion also failed: ${innerError instanceof Error ? innerError.message : String(innerError)}`);
21
+ return '[Unserializable]';
22
+ }
23
+ }
24
+ }
25
+ /**
26
+ * Type guard: narrow an unknown value to Record<string, unknown>.
27
+ * Returns false for arrays, primitives, null, and undefined.
28
+ */
29
+ export function isRecord(value) {
30
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
31
+ }
32
+ /**
33
+ * Narrow an unknown value to a Record<string, unknown>.
34
+ * Returns an empty object for non-object inputs (arrays, primitives, null, undefined).
35
+ */
36
+ export function asRecord(value) {
37
+ return isRecord(value) ? value : {};
38
+ }
39
+ /**
40
+ * Extract a string value from a record by key.
41
+ * Returns null if the key doesn't exist or the value isn't a string.
42
+ */
43
+ export function getString(record, key) {
44
+ const value = record[key];
45
+ return typeof value === 'string' ? value : null;
46
+ }
47
+ /**
48
+ * Extract a nested record from a record by key.
49
+ * Returns null if the key doesn't exist or the value isn't a record.
50
+ */
51
+ export function getRecord(record, key) {
52
+ const value = record[key];
53
+ return isRecord(value) ? value : null;
54
+ }
55
+ /**
56
+ * Extract a human-readable error message from an unknown thrown value.
57
+ * Handles Error instances, strings, and falls back to String(value).
58
+ */
59
+ export function errorMessage(error) {
60
+ if (error instanceof Error)
61
+ return error.message;
62
+ if (typeof error === 'string')
63
+ return error;
64
+ return String(error);
65
+ }
66
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Unwrap Zod wrapper types (ZodPipe, ZodOptional, ZodNullable, ZodDefault)
4
+ * to get the underlying schema. Useful for schema generation and hint building.
5
+ */
6
+ export function unwrapZodSchema(schema) {
7
+ let current = schema;
8
+ for (let depth = 0; depth < 20; depth++) {
9
+ if (current instanceof z.ZodPipe) {
10
+ current = current.def.out;
11
+ continue;
12
+ }
13
+ if (current instanceof z.ZodOptional) {
14
+ current = current.def.innerType;
15
+ continue;
16
+ }
17
+ if (current instanceof z.ZodNullable) {
18
+ current = current.def.innerType;
19
+ continue;
20
+ }
21
+ if (current instanceof z.ZodDefault) {
22
+ current = current.def.innerType;
23
+ continue;
24
+ }
25
+ break;
26
+ }
27
+ return current;
28
+ }
29
+ //# sourceMappingURL=zod.js.map