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
@@ -3,7 +3,7 @@ import { mapErrorForAudit } from '../../../observability/error-mapping.js';
3
3
  import { ReflectionEngine } from '../../../reflection/engine.js';
4
4
  import { resolveExecutionProfile } from '../../../runtime/execution-profile.js';
5
5
  import { executeFlowAttempt } from '../../flows/flow-dispatch.js';
6
- import { resolveAttemptFailure } from './attempt-failure.js';
6
+ import { extractErrorCode, resolveAttemptFailure } from './attempt-failure.js';
7
7
  import { buildAuthorizationSummary } from './authorization-summary.js';
8
8
  import { mapRetryExhaustedReport, mapSuccessReport, mapTerminalFailureReport, } from './report-mapper.js';
9
9
  import { evaluateRetryPolicy } from './retry-policy.js';
@@ -128,7 +128,6 @@ export class FlowTransactionRunner {
128
128
  lastRecentReadArtifacts;
129
129
  lastToolResultPreviewArtifacts;
130
130
  lastReplacementState;
131
- pendingAutopilotVerification;
132
131
  constructor(params) {
133
132
  this.params = params;
134
133
  this.lastVerifyArtifact = params.options.artifactHints?.verifyArtifact;
@@ -183,7 +182,6 @@ export class FlowTransactionRunner {
183
182
  : undefined,
184
183
  },
185
184
  replacementState: this.lastReplacementState,
186
- pendingVerification: this.pendingAutopilotVerification,
187
185
  lastError: this.currentLastError,
188
186
  applyBackRuntime: {
189
187
  activeRepoPath: this.params.env.activeRepoPath,
@@ -212,20 +210,9 @@ export class FlowTransactionRunner {
212
210
  flowReport: result,
213
211
  context: shrinkCtx,
214
212
  flowMode: this.params.flowMode,
213
+ verifyPolicy: this.params.options.verifyPolicy,
215
214
  });
216
215
  lastAttemptFailure = attemptFailure;
217
- if (this.params.flowMode === 'autopilot') {
218
- if (attemptFailure?.reasonCode === 'VERIFY_FAILED') {
219
- this.pendingAutopilotVerification = {
220
- changedFiles: terminalCtx && 'changedFiles' in terminalCtx
221
- ? (terminalCtx.changedFiles ?? undefined)
222
- : undefined,
223
- };
224
- }
225
- else if (!attemptFailure) {
226
- this.pendingAutopilotVerification = undefined;
227
- }
228
- }
229
216
  const entry = {
230
217
  attempt,
231
218
  plan: shrinkCtx?.plan ?? null,
@@ -245,27 +232,24 @@ export class FlowTransactionRunner {
245
232
  ? 'SHRINK'
246
233
  : 'APPLY_BACK';
247
234
  recordAuditEvent('loop.attempt.success', { attempt, flowMode: this.params.flowMode }, { phase: successPhase, severity: 'low', scope: 'session' });
248
- // Reflection Mechanism: trigger when multiple attempts were needed
249
- if (attempt > 1 && this.params.options.llm) {
250
- const reflectionEngine = new ReflectionEngine(this.params.options.llm);
251
- const reflectionInput = {
235
+ this.emitReflection({
236
+ success: true,
237
+ attempt,
238
+ input: {
252
239
  instruction: this.params.options.instruction,
253
240
  history: this.historyEntries,
254
241
  success: true,
255
242
  metadata: shrinkCtx?.context?.projectMetadata,
256
243
  finalPlan: shrinkCtx?.plan,
257
244
  finalPatch: shrinkCtx?.diff,
258
- };
259
- reflectionEngine
260
- .reflect(reflectionInput, this.params.options.repoPath)
261
- .catch((e) => recordAuditEvent('reflection.error', { error: String(e) }, { severity: 'medium' }));
262
- }
245
+ },
246
+ });
263
247
  return mapSuccessReport({
264
248
  attempt,
265
249
  flowReport: result,
266
250
  history: this.historyEntries,
267
251
  authorizationSummary: this.authorizationSummary,
268
- lastErrorCode: this.extractErrorCode(result.error),
252
+ lastErrorCode: extractErrorCode(result.error),
269
253
  lastContext: terminalCtx,
270
254
  lastVerifyArtifact: this.lastVerifyArtifact,
271
255
  lastSubAgentPatchArtifacts: this.lastSubAgentPatchArtifacts,
@@ -317,12 +301,20 @@ export class FlowTransactionRunner {
317
301
  if (retryDecision.retryExhausted) {
318
302
  break;
319
303
  }
304
+ this.emitReflection({
305
+ success: false,
306
+ input: {
307
+ instruction: this.params.options.instruction,
308
+ history: this.historyEntries,
309
+ success: false,
310
+ },
311
+ });
320
312
  return mapTerminalFailureReport({
321
313
  attempt,
322
314
  flowReport: result,
323
315
  history: this.historyEntries,
324
316
  authorizationSummary: this.authorizationSummary,
325
- lastErrorCode: attemptFailure.errorCode ?? this.extractErrorCode(result.error),
317
+ lastErrorCode: attemptFailure.errorCode ?? extractErrorCode(result.error),
326
318
  lastContext: terminalCtx,
327
319
  lastVerifyArtifact: this.lastVerifyArtifact,
328
320
  lastSubAgentPatchArtifacts: this.lastSubAgentPatchArtifacts,
@@ -333,7 +325,7 @@ export class FlowTransactionRunner {
333
325
  });
334
326
  }
335
327
  this.currentContext = shrinkCtx?.context;
336
- this.currentLastError = attemptFailure.reason;
328
+ this.currentLastError = shrinkCtx?.lastError ?? attemptFailure.reason;
337
329
  this.params.emit({
338
330
  type: 'retry',
339
331
  fromAttempt: attempt,
@@ -346,13 +338,21 @@ export class FlowTransactionRunner {
346
338
  if (!lastReport) {
347
339
  throw new Error('SalmonLoop execution terminated without a FlowReport');
348
340
  }
341
+ this.emitReflection({
342
+ success: false,
343
+ input: {
344
+ instruction: this.params.options.instruction,
345
+ history: this.historyEntries,
346
+ success: false,
347
+ },
348
+ });
349
349
  return mapRetryExhaustedReport({
350
350
  attempts: retries,
351
351
  flowReport: lastReport,
352
352
  history: this.historyEntries,
353
353
  authorizationSummary: this.authorizationSummary,
354
354
  failure: lastAttemptFailure,
355
- lastErrorCode: this.extractErrorCode(lastReport.error),
355
+ lastErrorCode: extractErrorCode(lastReport.error),
356
356
  lastContext: this.lastContext,
357
357
  lastVerifyArtifact: this.lastVerifyArtifact,
358
358
  lastSubAgentPatchArtifacts: this.lastSubAgentPatchArtifacts,
@@ -361,13 +361,19 @@ export class FlowTransactionRunner {
361
361
  lastToolResultPreviewArtifacts: this.lastToolResultPreviewArtifacts,
362
362
  });
363
363
  }
364
- extractErrorCode(error) {
365
- if (typeof error === 'object' && error !== null) {
366
- return (error.llmCode ??
367
- error.code ??
368
- error.name);
364
+ emitReflection(params) {
365
+ if (!this.params.options.llm)
366
+ return;
367
+ const engine = new ReflectionEngine(this.params.options.llm);
368
+ const { repoPath } = this.params.options;
369
+ const fallback = (e) => recordAuditEvent('reflection.error', { error: String(e) }, { severity: 'medium' });
370
+ if (params.success) {
371
+ const method = (params.attempt ?? 1) > 1 ? 'reflect' : 'reflectOnSuccess';
372
+ engine[method](params.input, repoPath).catch(fallback);
373
+ }
374
+ else {
375
+ engine.reflectOnFailure(params.input, repoPath).catch(fallback);
369
376
  }
370
- return undefined;
371
377
  }
372
378
  }
373
379
  //# sourceMappingURL=transaction-runner.js.map
@@ -1,5 +1,6 @@
1
1
  import * as path from 'path';
2
2
  import * as fs from '../../adapters/fs/node-fs.js';
3
+ import { getLogger } from '../../observability/logger.js';
3
4
  /**
4
5
  * RejectionManager
5
6
  * Handles creation and management of .rej files for failed merges.
@@ -46,13 +47,14 @@ export class RejectionManager {
46
47
  ...header,
47
48
  });
48
49
  }
49
- catch {
50
- // Ignore malformed files
50
+ catch (error) {
51
+ getLogger().debug(`[RejectionManager] Malformed rejection file ${file}: ${error instanceof Error ? error.message : String(error)}`);
51
52
  }
52
53
  }
53
54
  return rejections;
54
55
  }
55
- catch {
56
+ catch (error) {
57
+ getLogger().debug(`[RejectionManager] Failed to read rejection directory: ${error instanceof Error ? error.message : String(error)}`);
56
58
  return [];
57
59
  }
58
60
  }
@@ -63,8 +65,8 @@ export class RejectionManager {
63
65
  try {
64
66
  await fs.rm(this.rejectDir, { recursive: true, force: true });
65
67
  }
66
- catch {
67
- // Ignore
68
+ catch (error) {
69
+ getLogger().warn(`[RejectionManager] Failed to clear rejection directory: ${error instanceof Error ? error.message : String(error)}`);
68
70
  }
69
71
  }
70
72
  }
@@ -2,6 +2,8 @@ import { text } from '../../../locales/index.js';
2
2
  import { recordAuditEvent } from '../../observability/audit-trail.js';
3
3
  import { writeDebugArtifact } from '../../observability/debug-artifacts.js';
4
4
  import { buildErrorEnvelope, toSafeErrorSummary } from '../../observability/error-envelope.js';
5
+ import { getLogger } from '../../observability/logger.js';
6
+ import { isRecord } from '../../utils/serialize.js';
5
7
  import { collectSidecarPaths } from './apply-back-utils.js';
6
8
  export async function runApplyBackPhase(params) {
7
9
  const { checkpointRef, initialSnapshotHash, options, synchronizer, activeRepoPath, shadowTaskId, attempt, emit, } = params;
@@ -89,13 +91,14 @@ export async function runApplyBackPhase(params) {
89
91
  `safeTelemetry=${JSON.stringify(toSafeTelemetry(applyBackTelemetry), null, 2)}`,
90
92
  '',
91
93
  `errorType=${error instanceof Error ? error.name : typeof error}`,
92
- `errorCode=${error?.code ?? error?.llmCode ?? ''}`,
94
+ `errorCode=${isRecord(error) ? (error.code ?? error.llmCode ?? '') : ''}`,
93
95
  `errorMessage=${error instanceof Error ? error.message : String(error)}`,
94
96
  ].join('\n'),
95
97
  });
96
98
  }
97
- catch {
99
+ catch (artifactError) {
98
100
  // Best-effort: do not mask the primary failure if artifact writing fails.
101
+ getLogger().debug(`[ApplyBackRuntime] Failed to write debug artifact: ${artifactError instanceof Error ? artifactError.message : String(artifactError)}`);
99
102
  }
100
103
  const envelope = buildErrorEnvelope({
101
104
  domain: 'applyBack',
@@ -10,7 +10,8 @@ export class GitConfigService {
10
10
  try {
11
11
  return await git.exec(['config', '--get', key], { allowError: true });
12
12
  }
13
- catch {
13
+ catch (error) {
14
+ getLogger().debug(`[GitConfigService] git config --get failed for key "${key}": ${error instanceof Error ? error.message : String(error)}`);
14
15
  return null;
15
16
  }
16
17
  };
@@ -26,5 +26,23 @@ export const registry = {
26
26
  has(id) {
27
27
  return services.has(id);
28
28
  },
29
+ /**
30
+ * Unregister a data service by its identifier
31
+ */
32
+ unregister(id) {
33
+ return services.delete(id);
34
+ },
35
+ /**
36
+ * Clear all registered services (for testing or shutdown)
37
+ */
38
+ clear() {
39
+ services.clear();
40
+ },
41
+ /**
42
+ * List all registered service IDs
43
+ */
44
+ list() {
45
+ return Array.from(services.keys());
46
+ },
29
47
  };
30
48
  //# sourceMappingURL=registry.js.map
@@ -8,6 +8,8 @@ import { mapErrorForDisplay } from '../../observability/error-mapping.js';
8
8
  import { getLogger } from '../../observability/logger.js';
9
9
  import { getAuditDir } from '../../runtime/paths.js';
10
10
  import { SalmonError } from '../../types/errors.js';
11
+ import { errorMessage } from '../../utils/error.js';
12
+ import { isRecord } from '../../utils/serialize.js';
11
13
  function replaceRedactedTokens(value) {
12
14
  if (value === null || value === undefined)
13
15
  return value;
@@ -43,7 +45,7 @@ export async function saveAudit(report, _options) {
43
45
  });
44
46
  }
45
47
  catch (error) {
46
- const msg = error instanceof Error ? error.message : String(error);
48
+ const msg = errorMessage(error);
47
49
  getLogger().warn(`[Audit] Failed to externalize verify output: ${msg}`);
48
50
  recordAuditEvent('audit.blob.externalize.failed', { target: 'verifyResult.output', error: msg.slice(0, 500) }, { source: 'saveAudit', severity: 'low', scope: 'session', phase: 'AUDIT' });
49
51
  }
@@ -55,7 +57,7 @@ export async function saveAudit(report, _options) {
55
57
  });
56
58
  }
57
59
  catch (error) {
58
- const msg = error instanceof Error ? error.message : String(error);
60
+ const msg = errorMessage(error);
59
61
  getLogger().warn(`[Audit] Failed to externalize tool audit summaries: ${msg}`);
60
62
  recordAuditEvent('audit.blob.externalize.failed', { target: 'toolAuditLogs.*', error: msg.slice(0, 500) }, { source: 'saveAudit', severity: 'low', scope: 'session', phase: 'AUDIT' });
61
63
  }
@@ -70,7 +72,12 @@ export async function saveAudit(report, _options) {
70
72
  : errorInfo?.code || errorInfo?.llmCode,
71
73
  }
72
74
  : report.error
73
- ? { name: 'UnknownError', message: String(report.error), stack: undefined }
75
+ ? {
76
+ name: 'UnknownError',
77
+ message: String(report.error),
78
+ stack: undefined,
79
+ code: undefined,
80
+ }
74
81
  : undefined;
75
82
  const mappedErrorMeta = replaceRedactedTokens(errorMeta);
76
83
  const errorDisplay = mapErrorForDisplay({
@@ -130,7 +137,7 @@ export async function saveAudit(report, _options) {
130
137
  return `${auditDir}/${filename}`;
131
138
  }
132
139
  catch (error) {
133
- const msg = error instanceof Error ? error.message : String(error);
140
+ const msg = errorMessage(error);
134
141
  getLogger().error(`[Audit] Failed to save audit log: ${msg}`);
135
142
  return undefined;
136
143
  }
@@ -162,7 +169,7 @@ async function writeBlobBestEffort(args) {
162
169
  return { path: path.join('blobs', blobName), sha256, chars: content.length };
163
170
  }
164
171
  catch (error) {
165
- const msg = error instanceof Error ? error.message : String(error);
172
+ const msg = errorMessage(error);
166
173
  getLogger().warn(`[Audit] Failed to write blob ${blobName}: ${msg}`);
167
174
  recordAuditEvent('audit.blob.write.failed', {
168
175
  target: auditTarget,
@@ -179,7 +186,7 @@ async function externalizeVerifyOutput(args) {
179
186
  if (!sanitizedContext)
180
187
  return;
181
188
  const verifyResult = sanitizedContext.verifyResult;
182
- if (!verifyResult || typeof verifyResult !== 'object')
189
+ if (!isRecord(verifyResult))
183
190
  return;
184
191
  const output = verifyResult.output;
185
192
  if (typeof output !== 'string')
@@ -292,15 +299,18 @@ function sanitizeContext(ctx) {
292
299
  if (typed.applyBackResult)
293
300
  safe.applyBackResult = typed.applyBackResult;
294
301
  if (typed.toolCallingAudit && Array.isArray(typed.toolCallingAudit)) {
302
+ const KEYS_TO_STRIP = new Set([
303
+ 'rawArgsPreview',
304
+ 'parsedArgsPreview',
305
+ 'toolResultErrorMessage',
306
+ ]);
295
307
  safe.toolCallingAudit = typed.toolCallingAudit.map((entry) => {
296
308
  if (!entry || typeof entry !== 'object')
297
309
  return entry;
298
- const typedEntry = entry;
299
- const keepArgsPreview = typedEntry.toolResultErrorCode === 'INVALID_INPUT';
310
+ const keepArgsPreview = entry.toolResultErrorCode === 'INVALID_INPUT';
300
311
  if (keepArgsPreview)
301
312
  return entry;
302
- const { rawArgsPreview: _rawArgsPreview, parsedArgsPreview: _parsedArgsPreview, toolResultErrorMessage: _toolResultErrorMessage, ...rest } = typedEntry;
303
- return rest;
313
+ return Object.fromEntries(Object.entries(entry).filter(([k]) => !KEYS_TO_STRIP.has(k)));
304
314
  });
305
315
  }
306
316
  if (typed.toolAuditLogger?.getLogs) {
@@ -6,6 +6,7 @@ import { GitAdapter } from '../../adapters/git/git-adapter.js';
6
6
  import { LIMITS } from '../../config/limits.js';
7
7
  import { supportsLlmStreaming } from '../../llm/capabilities.js';
8
8
  import { emitLlmOutput } from '../../llm/output-policy.js';
9
+ import { getLogger } from '../../observability/logger.js';
9
10
  import { getAutopilotSystemPrompt } from '../../prompts/runtime.js';
10
11
  import { SessionReplacementPreviewProvider } from '../../session/replacement-preview-provider.js';
11
12
  import { chatWithTools, chatWithToolsStreaming } from '../../tools/session.js';
@@ -286,13 +287,11 @@ function isRuntimeGeneratedPath(path) {
286
287
  return false;
287
288
  }
288
289
  function lastFailedToolAuditEntry(entries) {
289
- if (!Array.isArray(entries))
290
+ if (!Array.isArray(entries) || entries.length === 0)
290
291
  return undefined;
291
- for (let index = entries.length - 1; index >= 0; index -= 1) {
292
- const entry = entries[index];
293
- if (entry?.toolResultStatus && entry.toolResultStatus !== 'ok') {
294
- return entry;
295
- }
292
+ const last = entries[entries.length - 1];
293
+ if (last?.toolResultStatus && last.toolResultStatus !== 'ok') {
294
+ return last;
296
295
  }
297
296
  return undefined;
298
297
  }
@@ -370,12 +369,16 @@ export async function runAutopilot(ctx) {
370
369
  try {
371
370
  workspaceFingerprintBefore = await captureWorkspaceFingerprintForContext(ctx);
372
371
  }
373
- catch {
372
+ catch (error) {
373
+ getLogger().debug(`[Autopilot] Workspace fingerprint capture failed (before): ${error instanceof Error ? error.message : String(error)}`);
374
374
  samplingFailedClosed = true;
375
375
  }
376
376
  }
377
- const assistant = supportsTools
378
- ? await (supportsStreaming ? chatWithToolsStreaming : chatWithTools)(shared.baseMessages, {
377
+ let assistant = null;
378
+ let content = '';
379
+ const messages = shared.baseMessages;
380
+ assistant = supportsTools
381
+ ? await (supportsStreaming ? chatWithToolsStreaming : chatWithTools)(messages, {
379
382
  phase: AUTOPILOT_TOOL_PHASE,
380
383
  providerHints: shared.envelope.providerHints,
381
384
  temperature: 0.2,
@@ -400,7 +403,7 @@ export async function runAutopilot(ctx) {
400
403
  },
401
404
  emit: (event) => ctx.emit({ ...event, timestamp: event.timestamp ?? new Date() }),
402
405
  })
403
- : await llmClient.chat(shared.baseMessages, {
406
+ : await llmClient.chat(messages, {
404
407
  phase: 'AUTOPILOT',
405
408
  providerHints: shared.envelope.providerHints,
406
409
  temperature: 0.2,
@@ -408,7 +411,7 @@ export async function runAutopilot(ctx) {
408
411
  tools: [],
409
412
  toolChoice: 'none',
410
413
  });
411
- const content = String(assistant?.content ?? '').trim();
414
+ content = String(assistant?.content ?? '').trim();
412
415
  if (!supportsTools) {
413
416
  emitLlmOutput({
414
417
  emit: ctx.emit,
@@ -435,28 +438,18 @@ export async function runAutopilot(ctx) {
435
438
  workspaceFingerprintBefore.head !== workspaceFingerprintAfter.head ||
436
439
  workspaceFingerprintBefore.index !== workspaceFingerprintAfter.index;
437
440
  }
438
- catch {
441
+ catch (error) {
442
+ getLogger().debug(`[Autopilot] Workspace fingerprint capture failed (after): ${error instanceof Error ? error.message : String(error)}`);
439
443
  mutated = true;
440
444
  }
441
445
  }
442
446
  }
443
- const pendingChangedFiles = ctx.pendingVerification?.changedFiles && ctx.pendingVerification.changedFiles.length > 0
444
- ? ctx.pendingVerification.changedFiles
445
- : undefined;
446
- const effectiveChangedFiles = changedFiles && changedFiles.length > 0
447
- ? changedFiles
448
- : pendingChangedFiles && pendingChangedFiles.length > 0
449
- ? pendingChangedFiles
450
- : undefined;
451
- const effectiveMutated = mutated || Boolean(ctx.pendingVerification);
447
+ const effectiveChangedFiles = changedFiles && changedFiles.length > 0 ? changedFiles : undefined;
452
448
  return {
453
449
  ...ctx,
454
- mutated: effectiveMutated,
450
+ mutated,
455
451
  changedFiles: effectiveChangedFiles,
456
- pendingVerification: effectiveMutated
457
- ? { changedFiles: effectiveChangedFiles }
458
- : ctx.pendingVerification,
459
- completion: resolveAutopilotCompletion({ content, mutated: effectiveMutated, localAudit }),
452
+ completion: resolveAutopilotCompletion({ content, mutated, localAudit }),
460
453
  toolCallingAudit: mergedAudit,
461
454
  report: {
462
455
  kind: 'answer',
@@ -473,13 +466,10 @@ export async function runAutopilotVerifyGate(ctx) {
473
466
  };
474
467
  }
475
468
  if (!ctx.options.verify) {
469
+ // Autopilot is LLM-driven: the agent verifies on its own via shell.exec.
470
+ // Skip the gate instead of failing — the agent's tool calls are the verification.
476
471
  return {
477
472
  ...ctx,
478
- completion: {
479
- status: 'verification_missing',
480
- reason: 'Autopilot changed the workspace but no verification command was configured.',
481
- errorCode: 'VERIFY_COMMAND_MISSING',
482
- },
483
473
  verifyResult: undefined,
484
474
  };
485
475
  }
@@ -491,7 +481,6 @@ export async function runAutopilotVerifyGate(ctx) {
491
481
  const nextCtx = {
492
482
  ...ctx,
493
483
  verifyResult,
494
- pendingVerification: verifyResult.ok ? undefined : ctx.pendingVerification,
495
484
  };
496
485
  return verifyArtifact ? { ...nextCtx, verifyArtifact } : nextCtx;
497
486
  }
@@ -1,18 +1,11 @@
1
1
  import { text } from '../../../locales/index.js';
2
2
  import { SalmonError } from '../../types/errors.js';
3
+ import { safeStringify } from '../../utils/serialize.js';
3
4
  export class ReportContextMissingError extends SalmonError {
4
5
  constructor(message) {
5
6
  super(message, 'REPORT_CONTEXT_MISSING');
6
7
  }
7
8
  }
8
- function safeStringify(value) {
9
- try {
10
- return JSON.stringify(value, null, 2);
11
- }
12
- catch {
13
- return String(value);
14
- }
15
- }
16
9
  function removeLeadingSpaces(content) {
17
10
  const lines = content.split('\n');
18
11
  const minIndent = lines
@@ -40,7 +33,7 @@ function normalizeSuggestions(input) {
40
33
  }
41
34
  if (isReviewSuggestion(item)) {
42
35
  const type = typeof item.type === 'string' ? item.type : 'note';
43
- const content = typeof item.content === 'string' ? item.content : safeStringify(item);
36
+ const content = typeof item.content === 'string' ? item.content : safeStringify(item, { indent: 2 });
44
37
  return { type, content };
45
38
  }
46
39
  return { type: 'note', content: String(item) };
@@ -51,10 +44,10 @@ function normalizeSuggestions(input) {
51
44
  }
52
45
  if (isReviewSuggestion(input)) {
53
46
  const type = typeof input.type === 'string' ? input.type : 'note';
54
- const content = typeof input.content === 'string' ? input.content : safeStringify(input);
47
+ const content = typeof input.content === 'string' ? input.content : safeStringify(input, { indent: 2 });
55
48
  return [{ type, content }];
56
49
  }
57
- return [{ type: 'note', content: safeStringify(input) }];
50
+ return [{ type: 'note', content: safeStringify(input, { indent: 2 }) }];
58
51
  }
59
52
  export async function displayReport(ctx) {
60
53
  if (!ctx.report || !ctx.report.kind) {
@@ -2,12 +2,14 @@ import path from 'path';
2
2
  import { text } from '../../../locales/index.js';
3
3
  import { supportsLlmStreaming } from '../../llm/capabilities.js';
4
4
  import { recordAuditEvent } from '../../observability/audit-trail.js';
5
+ import { getLogger } from '../../observability/logger.js';
5
6
  import { getExplorePrompt, getExploreSystemPrompt } from '../../prompts/runtime.js';
6
7
  import { SessionReplacementPreviewProvider } from '../../session/replacement-preview-provider.js';
7
8
  import { chatWithTools, chatWithToolsStreaming } from '../../tools/session.js';
8
9
  import { SalmonError } from '../../types/errors.js';
9
10
  import { Phase } from '../../types/runtime.js';
10
11
  import { ensureInSandbox, isSafeRelativePath, normalizePath } from '../../utils/path.js';
12
+ import { isRecord } from '../../utils/serialize.js';
11
13
  import { resolveLlmToolCallingPolicy } from '../dsl/llm-strategy.js';
12
14
  import { ContextValidator } from '../validation/ContextValidator.js';
13
15
  import { buildPhaseRequestEnvelope } from './request-assembly.js';
@@ -103,7 +105,11 @@ export const exploreCodebase = async (ctx) => {
103
105
  // Intercept tools with READ intent
104
106
  if (intent === 'READ' && result.status === 'ok') {
105
107
  const output = result.output;
106
- const content = typeof output === 'string' ? output : output?.content;
108
+ const content = typeof output === 'string'
109
+ ? output
110
+ : isRecord(output) && typeof output.content === 'string'
111
+ ? output.content
112
+ : undefined;
107
113
  if (typeof content === 'string') {
108
114
  try {
109
115
  // Attempt to parse arguments to get the file path
@@ -114,8 +120,9 @@ export const exploreCodebase = async (ctx) => {
114
120
  capturedFiles.set(filePath, content);
115
121
  }
116
122
  }
117
- catch {
123
+ catch (error) {
118
124
  // Ignore parsing errors, just don't capture
125
+ getLogger().debug(`[Explore] Failed to parse tool arguments for file capture: ${error instanceof Error ? error.message : String(error)}`);
119
126
  }
120
127
  }
121
128
  }
@@ -191,15 +198,18 @@ export const exploreCodebase = async (ctx) => {
191
198
  capturedFiles.set(rel, content);
192
199
  }
193
200
  }
194
- catch {
201
+ catch (error) {
195
202
  // Best-effort; failure here should not abort exploration.
203
+ getLogger().debug(`[Explore] Failed to read inferred file "${rel}": ${error instanceof Error ? error.message : String(error)}`);
196
204
  }
197
205
  }
198
206
  }
199
207
  // Validation: Check for exploration consistency using ContextValidator on LOCAL audit
200
208
  const validation = ContextValidator.validateExploration(localAudit, capturedFiles.size);
201
209
  if (!validation.isValid) {
202
- const msg = text.grizzco.validation[validation.errorCode] || validation.errorCode;
210
+ const validationMessages = text.grizzco.validation;
211
+ const raw = validation.errorCode ? validationMessages[validation.errorCode] : undefined;
212
+ const msg = typeof raw === 'string' ? raw : (validation.errorCode ?? 'unknown');
203
213
  ctx.emit({
204
214
  type: 'log',
205
215
  level: 'error',
@@ -1,5 +1,6 @@
1
1
  import { text } from '../../../locales/index.js';
2
2
  import { emitLlmOutput } from '../../llm/output-policy.js';
3
+ import { getLogger } from '../../observability/logger.js';
3
4
  import { getReviewPrompt } from '../../prompts/runtime.js';
4
5
  function normalizeReviewSuggestions(value) {
5
6
  if (value == null)
@@ -23,7 +24,8 @@ function parseReviewResponse(content) {
23
24
  try {
24
25
  return normalizeReviewSuggestions(JSON.parse(content));
25
26
  }
26
- catch {
27
+ catch (error) {
28
+ getLogger().debug(`[GenerateReview] Failed to parse review response as JSON: ${error instanceof Error ? error.message : String(error)}`);
27
29
  return normalizeReviewSuggestions(content);
28
30
  }
29
31
  }
@@ -5,9 +5,12 @@ import { Phase } from '../../../types/runtime.js';
5
5
  import { buildPhaseRequestEnvelope } from '../request-assembly.js';
6
6
  export async function buildPatchPromptInput(args) {
7
7
  const planStr = JSON.stringify(args.plan, null, 2);
8
- const systemPrompt = await getPatchSystemPrompt(args.promptVisibleTools, {
8
+ const baseSystemPrompt = await getPatchSystemPrompt(args.promptVisibleTools, {
9
9
  plan: args.planRuntime,
10
10
  });
11
+ const systemPrompt = args.subAgentSystemPrompt
12
+ ? [baseSystemPrompt, args.subAgentSystemPrompt]
13
+ : baseSystemPrompt;
11
14
  const requestEnvelope = await buildPhaseRequestEnvelope({
12
15
  phase: args.phase ?? Phase.PATCH,
13
16
  defaultNamespace: 'patch',
@@ -95,6 +95,7 @@ export const generatePatch = async (ctx) => {
95
95
  artifactHints: ctx.artifactHints,
96
96
  replacementState: ctx.replacementState,
97
97
  toolCallingAudit: ctx.toolCallingAudit,
98
+ subAgentSystemPrompt: ctx.options.subAgentSystemPrompt,
98
99
  });
99
100
  const { cacheSurface, envelope, baseMessages } = patchPromptInput;
100
101
  const supportsStreaming = supportsLlmStreaming(ctx.options.llm, Phase.PATCH);