salmon-loop 0.4.1 → 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 (146) hide show
  1. package/dist/cli/authorization/provider.js +2 -10
  2. package/dist/cli/commands/config.js +2 -2
  3. package/dist/cli/commands/mode.js +2 -2
  4. package/dist/cli/commands/run/handler.js +3 -1
  5. package/dist/cli/commands/run/loop-params.js +1 -0
  6. package/dist/cli/commands/run/runtime-options.js +3 -1
  7. package/dist/cli/config.js +0 -8
  8. package/dist/cli/locales/en.js +2 -2
  9. package/dist/cli/reporters/standard.js +10 -0
  10. package/dist/core/adapters/fs/file-adapter.js +3 -1
  11. package/dist/core/adapters/git/git-adapter.js +6 -3
  12. package/dist/core/adapters/git/git-runner.js +5 -2
  13. package/dist/core/adapters/git/lock-manager.js +7 -4
  14. package/dist/core/checkpoint-domain/manifest-store.js +21 -13
  15. package/dist/core/checkpoint-domain/service.js +3 -1
  16. package/dist/core/config/limits.js +1 -1
  17. package/dist/core/config/model-pricing.js +61 -0
  18. package/dist/core/context/ast/skeleton-extractor.js +225 -0
  19. package/dist/core/context/ast/source-outline.js +24 -1
  20. package/dist/core/context/budget/dynamic-adjuster.js +20 -5
  21. package/dist/core/context/builder.js +7 -3
  22. package/dist/core/context/cache/store-factory.js +3 -1
  23. package/dist/core/context/dependencies.js +2 -1
  24. package/dist/core/context/effectiveness/persistence.js +50 -0
  25. package/dist/core/context/effectiveness/tracker.js +24 -0
  26. package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
  27. package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
  28. package/dist/core/context/gatherers/ast-gatherer.js +30 -28
  29. package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
  30. package/dist/core/context/gatherers/knowledge-gatherer.js +18 -2
  31. package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
  32. package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
  33. package/dist/core/context/service.js +4 -2
  34. package/dist/core/context/steps/context-gather.js +14 -3
  35. package/dist/core/context/steps/context-targets.js +1 -0
  36. package/dist/core/context/targeting/target-resolver.js +29 -11
  37. package/dist/core/context/token/cache.js +5 -2
  38. package/dist/core/context/truncation/strategies/json.js +5 -2
  39. package/dist/core/context/truncation/type-detector.js +3 -1
  40. package/dist/core/extensions/paths.js +2 -2
  41. package/dist/core/facades/cli-authorization-provider.js +1 -0
  42. package/dist/core/feedback/parsers.js +290 -1
  43. package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
  44. package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
  45. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
  46. package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
  47. package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
  48. package/dist/core/grizzco/execution/RejectionManager.js +7 -5
  49. package/dist/core/grizzco/runtime/apply-back-runtime.js +3 -1
  50. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
  51. package/dist/core/grizzco/steps/autopilot.js +21 -32
  52. package/dist/core/grizzco/steps/explore.js +5 -2
  53. package/dist/core/grizzco/steps/generateReview.js +3 -1
  54. package/dist/core/grizzco/steps/research.js +3 -1
  55. package/dist/core/grizzco/steps/verify.js +7 -1
  56. package/dist/core/grizzco/validation/AstValidationService.js +3 -1
  57. package/dist/core/history/input-history.js +3 -1
  58. package/dist/core/intent/chat-intent.js +3 -1
  59. package/dist/core/llm/ai-sdk/message-mapper.js +13 -8
  60. package/dist/core/llm/ai-sdk/request-params.js +1 -3
  61. package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
  62. package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
  63. package/dist/core/llm/errors.js +5 -4
  64. package/dist/core/llm/retry-utils.js +8 -2
  65. package/dist/core/llm/stream-utils.js +5 -3
  66. package/dist/core/llm/sub-agent-factory.js +3 -0
  67. package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
  68. package/dist/core/mcp/catalog/discovery.js +3 -1
  69. package/dist/core/mcp/client/connection-manager.js +4 -2
  70. package/dist/core/mcp/client/transport-factory.js +7 -3
  71. package/dist/core/observability/audit-file.js +2 -1
  72. package/dist/core/observability/audit-trail.js +3 -1
  73. package/dist/core/observability/logger.js +2 -1
  74. package/dist/core/observability/monitor.js +24 -0
  75. package/dist/core/observability/run-outcome-reporter.js +1 -0
  76. package/dist/core/permission-gate/default-gate.js +5 -8
  77. package/dist/core/plan/storage.js +7 -4
  78. package/dist/core/plugin/loader.js +3 -1
  79. package/dist/core/prompts/registry.js +1 -1
  80. package/dist/core/prompts/runtime.js +3 -1
  81. package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
  82. package/dist/core/protocols/a2a/sdk/executor.js +3 -1
  83. package/dist/core/protocols/a2a/sdk/server.js +3 -1
  84. package/dist/core/protocols/acp/acp-command-runner.js +7 -6
  85. package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
  86. package/dist/core/protocols/acp/formal-agent.js +3 -2
  87. package/dist/core/protocols/acp/permission-provider.js +3 -2
  88. package/dist/core/reflection/engine.js +114 -14
  89. package/dist/core/runtime/batch-runner.js +81 -0
  90. package/dist/core/runtime/initialize.js +2 -1
  91. package/dist/core/runtime/loop-finalize.js +3 -0
  92. package/dist/core/runtime/loop-session-runner.js +5 -0
  93. package/dist/core/runtime/loop.js +4 -0
  94. package/dist/core/runtime/paths.js +9 -6
  95. package/dist/core/runtime/spawn-interactive.js +5 -4
  96. package/dist/core/security/redaction.js +3 -2
  97. package/dist/core/session/compression.js +3 -1
  98. package/dist/core/session/manager.js +2 -1
  99. package/dist/core/session/pruning-strategy.js +2 -1
  100. package/dist/core/session/token-tracker.js +11 -4
  101. package/dist/core/skills/permissions.js +2 -2
  102. package/dist/core/strata/checkpoint/manager.js +16 -10
  103. package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
  104. package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
  105. package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
  106. package/dist/core/strata/interaction/file-system-provider.js +2 -1
  107. package/dist/core/strata/layers/file-state-resolver.js +9 -7
  108. package/dist/core/strata/layers/immutable-git-layer.js +3 -1
  109. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
  110. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
  111. package/dist/core/strata/layers/worktree.js +2 -1
  112. package/dist/core/strata/runtime/environment.js +2 -1
  113. package/dist/core/strata/runtime/synchronizer.js +18 -17
  114. package/dist/core/structured-output/json-extract.js +3 -1
  115. package/dist/core/sub-agent/artifacts/store.js +2 -1
  116. package/dist/core/sub-agent/core/manager.js +24 -1
  117. package/dist/core/sub-agent/registry-defaults.js +2 -2
  118. package/dist/core/sub-agent/summary.js +96 -0
  119. package/dist/core/sub-agent/tools/task-spawn.js +7 -4
  120. package/dist/core/target-runtime/profile.js +3 -1
  121. package/dist/core/tools/audit.js +3 -2
  122. package/dist/core/tools/budget.js +3 -1
  123. package/dist/core/tools/builtin/ast.js +144 -0
  124. package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
  125. package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
  126. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
  127. package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
  128. package/dist/core/tools/builtin/fs.js +76 -1
  129. package/dist/core/tools/builtin/git.js +242 -0
  130. package/dist/core/tools/builtin/glob.js +79 -0
  131. package/dist/core/tools/builtin/index.js +12 -4
  132. package/dist/core/tools/builtin/knowledge.js +146 -4
  133. package/dist/core/tools/builtin/proposal.js +3 -1
  134. package/dist/core/tools/builtin/verify.js +35 -3
  135. package/dist/core/tools/permissions/permission-rules.js +3 -1
  136. package/dist/core/tools/router.js +88 -5
  137. package/dist/core/tools/session.js +10 -5
  138. package/dist/core/types/batch.js +2 -0
  139. package/dist/core/utils/sanitizer.js +5 -2
  140. package/dist/core/utils/serialize.js +5 -2
  141. package/dist/core/verification/detect-runner.js +86 -0
  142. package/dist/core/verification/runner.js +76 -0
  143. package/dist/core/version.js +3 -1
  144. package/dist/languages/python/index.js +154 -0
  145. package/dist/locales/en.js +6 -0
  146. package/package.json +2 -1
@@ -1,4 +1,8 @@
1
+ /**
2
+ * Utility for retrying asynchronous operations with exponential backoff.
3
+ */
1
4
  import { LIMITS } from '../config/limits.js';
5
+ import { getLogger } from '../observability/logger.js';
2
6
  const DEFAULT_OPTIONS = {
3
7
  maxRetries: LIMITS.retry.api.maxAttempts,
4
8
  initialDelayMs: LIMITS.retry.api.initialDelayMs,
@@ -42,8 +46,9 @@ export async function withRetry(fn, options = {}) {
42
46
  try {
43
47
  await opts.onRetry({ attempt: attempt + 1, delayMs: effectiveDelay, error });
44
48
  }
45
- catch {
49
+ catch (retryHandlerError) {
46
50
  // Ignore onRetry handler failures.
51
+ getLogger().debug(`[RetryUtils] onRetry handler failed: ${retryHandlerError instanceof Error ? retryHandlerError.message : String(retryHandlerError)}`);
47
52
  }
48
53
  await new Promise((resolve, reject) => {
49
54
  const timer = setTimeout(() => {
@@ -91,8 +96,9 @@ export async function* withStreamRetry(streamFactory, options = {}) {
91
96
  try {
92
97
  await opts.onRetry({ attempt: attempt + 1, delayMs: effectiveDelay, error });
93
98
  }
94
- catch {
99
+ catch (retryHandlerError) {
95
100
  // Ignore onRetry handler failures.
101
+ getLogger().debug(`[RetryUtils] onRetry handler failed (stream): ${retryHandlerError instanceof Error ? retryHandlerError.message : String(retryHandlerError)}`);
96
102
  }
97
103
  await new Promise((resolve, reject) => {
98
104
  const timer = setTimeout(() => {
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../observability/logger.js';
1
2
  function normalizeToolInput(raw) {
2
3
  if (typeof raw !== 'string')
3
4
  return raw;
@@ -12,14 +13,15 @@ function normalizeToolInput(raw) {
12
13
  try {
13
14
  parsed = JSON.parse(nested);
14
15
  }
15
- catch {
16
- // ignored
16
+ catch (error) {
17
+ getLogger().debug(`[StreamUtils] Failed to parse nested JSON string: ${error instanceof Error ? error.message : String(error)}`);
17
18
  }
18
19
  }
19
20
  }
20
21
  return parsed;
21
22
  }
22
- catch {
23
+ catch (error) {
24
+ getLogger().debug(`[StreamUtils] Failed to normalize tool input JSON: ${error instanceof Error ? error.message : String(error)}`);
23
25
  return raw;
24
26
  }
25
27
  }
@@ -23,6 +23,9 @@ function resolveModelId(alias) {
23
23
  */
24
24
  export function createSubAgentLlmFactory(baseProvider) {
25
25
  return (modelAlias) => {
26
+ // 'inherit' means use the parent LLM — don't create a new instance.
27
+ if (!modelAlias || modelAlias === 'inherit')
28
+ return undefined;
26
29
  const modelId = resolveModelId(modelAlias);
27
30
  if (baseProvider.type === 'openai-compatible' || baseProvider.type === 'openai') {
28
31
  const clientPackage = baseProvider.clientPackage === '@ai-sdk/openai'
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../../observability/logger.js';
1
2
  import { ResourceCache } from '../cache/resource-cache.js';
2
3
  export class ResourceContextProviderError extends Error {
3
4
  diagnostic;
@@ -252,7 +253,8 @@ function normalizeText(text, mimeType) {
252
253
  try {
253
254
  return JSON.stringify(JSON.parse(text), null, 2);
254
255
  }
255
- catch {
256
+ catch (error) {
257
+ getLogger().debug(`[ResourceContextProvider] Failed to normalize JSON text: ${error instanceof Error ? error.message : String(error)}`);
256
258
  return text;
257
259
  }
258
260
  }
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../../observability/logger.js';
1
2
  import { withPromptServer } from './prompt-catalog.js';
2
3
  import { withResourceServer, withResourceTemplateServer } from './resource-catalog.js';
3
4
  import { withToolServer } from './tool-catalog.js';
@@ -5,7 +6,8 @@ async function safeList(fn, fallback) {
5
6
  try {
6
7
  return await fn();
7
8
  }
8
- catch {
9
+ catch (error) {
10
+ getLogger().debug(`[McpDiscovery] safeList call failed, returning fallback: ${error instanceof Error ? error.message : String(error)}`);
9
11
  return fallback;
10
12
  }
11
13
  }
@@ -208,8 +208,9 @@ export class McpConnectionManager {
208
208
  try {
209
209
  await entry.client.unsubscribeResource({ uri }, { timeout: LIMITS.defaultToolTimeoutMs });
210
210
  }
211
- catch {
211
+ catch (error) {
212
212
  // best-effort unsubscribe during shutdown
213
+ getLogger().debug(`[McpConnectionManager] Failed to unsubscribe resource ${uri}: ${error instanceof Error ? error.message : String(error)}`);
213
214
  }
214
215
  }
215
216
  entry.subscribedResources.clear();
@@ -224,8 +225,9 @@ export class McpConnectionManager {
224
225
  }
225
226
  await entry.client.close();
226
227
  }
227
- catch {
228
+ catch (error) {
228
229
  // best-effort shutdown
230
+ getLogger().debug(`[McpConnectionManager] Error during connection shutdown: ${error instanceof Error ? error.message : String(error)}`);
229
231
  }
230
232
  }
231
233
  view(entry) {
@@ -1,6 +1,7 @@
1
1
  import { PassThrough } from 'node:stream';
2
2
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
3
  import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/sdk/shared/stdio.js';
4
+ import { getLogger } from '../../observability/logger.js';
4
5
  import { spawnInteractiveProcess } from '../../runtime/process-runner.js';
5
6
  export function buildStrictStdioEnvironment(env) {
6
7
  return { ...env };
@@ -81,16 +82,18 @@ export class StrictStdioClientTransport {
81
82
  try {
82
83
  child.stdin?.end?.();
83
84
  }
84
- catch {
85
+ catch (error) {
85
86
  // best-effort shutdown
87
+ getLogger().debug(`[TransportFactory] Error ending stdin: ${error instanceof Error ? error.message : String(error)}`);
86
88
  }
87
89
  await Promise.race([closePromise, unrefTimeout(2_000)]);
88
90
  if (child.exitCode === null) {
89
91
  try {
90
92
  child.kill('SIGTERM');
91
93
  }
92
- catch {
94
+ catch (error) {
93
95
  // best-effort shutdown
96
+ getLogger().debug(`[TransportFactory] Error sending SIGTERM: ${error instanceof Error ? error.message : String(error)}`);
94
97
  }
95
98
  await Promise.race([closePromise, unrefTimeout(2_000)]);
96
99
  }
@@ -98,8 +101,9 @@ export class StrictStdioClientTransport {
98
101
  try {
99
102
  child.kill('SIGKILL');
100
103
  }
101
- catch {
104
+ catch (error) {
102
105
  // best-effort shutdown
106
+ getLogger().debug(`[TransportFactory] Error sending SIGKILL: ${error instanceof Error ? error.message : String(error)}`);
103
107
  }
104
108
  }
105
109
  this.readBuffer.clear();
@@ -28,8 +28,9 @@ async function writeJsonAtomic(targetPath, data) {
28
28
  try {
29
29
  await rename(tmpPath, targetPath);
30
30
  }
31
- catch {
31
+ catch (error) {
32
32
  // Retry once with a fresh temp file to reduce transient rename failures.
33
+ getLogger().debug(`[AuditFile] Atomic rename failed, retrying: ${error instanceof Error ? error.message : String(error)}`);
33
34
  const retryTmpPath = path.join(dir, `.${base}.tmp-${process.pid}-${Date.now()}-retry`);
34
35
  await writeFile(retryTmpPath, payload);
35
36
  await rename(retryTmpPath, targetPath);
@@ -1,3 +1,4 @@
1
+ import { getLogger } from './logger.js';
1
2
  const auditTrail = [];
2
3
  const auditContext = {};
3
4
  const DEFAULT_BUFFER_LIMITS = {
@@ -19,7 +20,8 @@ function estimateEventSize(event) {
19
20
  try {
20
21
  return Buffer.byteLength(JSON.stringify(event), 'utf-8');
21
22
  }
22
- catch {
23
+ catch (error) {
24
+ getLogger().debug(`[AuditTrail] Failed to estimate event size: ${error instanceof Error ? error.message : String(error)}`);
23
25
  return 0;
24
26
  }
25
27
  }
@@ -307,8 +307,9 @@ export class Logger {
307
307
  // Only remove from queue if write was successful
308
308
  this.logQueue.splice(0, contentToFlush.length);
309
309
  }
310
- catch {
310
+ catch (error) {
311
311
  // Keep logs in queue for next retry
312
+ this.debug(`[Logger] Failed to flush logs to file: ${error instanceof Error ? error.message : String(error)}`);
312
313
  }
313
314
  finally {
314
315
  this.isFlushing = false;
@@ -177,6 +177,30 @@ export class Monitor {
177
177
  /**
178
178
  * Generate metrics report
179
179
  */
180
+ getStructuredReport() {
181
+ const durations = [...this.applyBackMetrics.durations].sort((a, b) => a - b);
182
+ return {
183
+ checkpoint: {
184
+ createAttempts: this.checkpointMetrics.createAttempts,
185
+ createFailures: this.checkpointMetrics.createFailures,
186
+ createFailureRate: this.getCheckpointCreateFailureRate(),
187
+ cleanupAttempts: this.checkpointMetrics.cleanupAttempts,
188
+ cleanupFailures: this.checkpointMetrics.cleanupFailures,
189
+ },
190
+ applyBack: {
191
+ attempts: this.applyBackMetrics.attempts,
192
+ failures: this.applyBackMetrics.failures,
193
+ avgDurationMs: this.getApplyBackAvgDuration(),
194
+ p50DurationMs: durations.length > 0 ? durations[Math.floor(durations.length * 0.5)] : 0,
195
+ p95DurationMs: durations.length > 0 ? durations[Math.floor(durations.length * 0.95)] : 0,
196
+ },
197
+ errors: this.errorHistory.toArray().map((e) => ({
198
+ type: e.type,
199
+ message: e.message,
200
+ timestamp: e.timestamp.toISOString(),
201
+ })),
202
+ };
203
+ }
180
204
  getMetricsReport() {
181
205
  let report = `\n${text.monitor.metricsTitle}\n`;
182
206
  report += `\n${text.monitor.checkpointCreation}\n`;
@@ -7,6 +7,7 @@ export function buildRunOutcomeReport(result) {
7
7
  safeHint: result.safeHint,
8
8
  remediationSteps: result.remediationSteps,
9
9
  attempts: result.attempts,
10
+ durationMs: result.durationMs,
10
11
  failurePhase: result.failurePhase,
11
12
  errorCode: result.errorCode,
12
13
  changedFiles: result.changedFiles,
@@ -93,19 +93,16 @@ class DefaultPermissionGate {
93
93
  };
94
94
  }
95
95
  mapAuthorizationDecision(decision) {
96
+ const source = decision.source === 'auto' || decision.source === 'allowlist' ? 'policy' : decision.source;
96
97
  if (decision.outcome === 'deny') {
97
- return {
98
- kind: 'deny',
99
- reason: decision.reason ?? 'denied',
100
- source: decision.source === 'auto' || decision.source === 'allowlist'
101
- ? 'policy'
102
- : decision.source,
103
- };
98
+ return { kind: 'deny', reason: decision.reason ?? 'denied', source };
104
99
  }
105
100
  return {
106
101
  kind: 'allow',
107
102
  reason: decision.reason,
108
- source: decision.source === 'auto' || decision.source === 'allowlist' ? 'policy' : decision.source,
103
+ source,
104
+ ttlMs: decision.ttlMs,
105
+ persist: decision.persist,
109
106
  };
110
107
  }
111
108
  }
@@ -1,6 +1,7 @@
1
1
  import { randomBytes, createHash } from 'crypto';
2
2
  import path from 'path';
3
3
  import { mkdir, readFile, rename, stat, writeFile } from '../adapters/fs/node-fs.js';
4
+ import { getLogger } from '../observability/logger.js';
4
5
  const SESSION_ID_RE = /^[a-zA-Z0-9_-]{6,64}$/;
5
6
  export function assertValidSessionId(sessionId) {
6
7
  if (!SESSION_ID_RE.test(sessionId)) {
@@ -31,8 +32,8 @@ async function resolveGitDir(repoRoot) {
31
32
  if (st.isDirectory())
32
33
  return dotGit;
33
34
  }
34
- catch {
35
- // ignore
35
+ catch (error) {
36
+ getLogger().debug(`[PlanStorage] .git stat failed: ${error instanceof Error ? error.message : String(error)}`);
36
37
  }
37
38
  try {
38
39
  const raw = await readFile(dotGit, 'utf-8');
@@ -43,7 +44,8 @@ async function resolveGitDir(repoRoot) {
43
44
  const gitdir = m[1].trim();
44
45
  return path.isAbsolute(gitdir) ? gitdir : path.resolve(repoRoot, gitdir);
45
46
  }
46
- catch {
47
+ catch (error) {
48
+ getLogger().debug(`[PlanStorage] .git read failed: ${error instanceof Error ? error.message : String(error)}`);
47
49
  return null;
48
50
  }
49
51
  }
@@ -61,7 +63,8 @@ export async function ensureSalmonloopIgnored(repoRoot) {
61
63
  try {
62
64
  existing = await readFile(excludePath, 'utf-8');
63
65
  }
64
- catch {
66
+ catch (error) {
67
+ getLogger().debug(`[PlanStorage] Failed to read git exclude file: ${error instanceof Error ? error.message : String(error)}`);
65
68
  existing = '';
66
69
  }
67
70
  if (existing.split('\n').some((l) => l.trim() === '.salmonloop/'))
@@ -1,4 +1,5 @@
1
1
  import { join } from 'path';
2
+ import { pythonPlugin } from '../../languages/python/index.js';
2
3
  import { typescriptPlugin, tsxPlugin, javascriptPlugin } from '../../languages/typescript/index.js';
3
4
  import { readdir } from '../adapters/fs/node-fs.js';
4
5
  import { getLogger } from '../observability/logger.js';
@@ -19,10 +20,11 @@ export class PluginLoader {
19
20
  try {
20
21
  // Phase 1: Manually register TypeScript/JavaScript plugins
21
22
  getLogger().debug('Loading built-in plugins...');
23
+ this.registerWithValidation(registry, pythonPlugin);
22
24
  this.registerWithValidation(registry, typescriptPlugin);
23
25
  this.registerWithValidation(registry, tsxPlugin);
24
26
  this.registerWithValidation(registry, javascriptPlugin);
25
- getLogger().debug(`Plugins loaded: ${typescriptPlugin.meta.name}, ${tsxPlugin.meta.name}, ${javascriptPlugin.meta.name}`);
27
+ getLogger().debug(`Plugins loaded: ${pythonPlugin.meta.name}, ${typescriptPlugin.meta.name}, ${tsxPlugin.meta.name}, ${javascriptPlugin.meta.name}`);
26
28
  // Phase 2: Load user plugins from .salmonloop/languages/
27
29
  if (repoPath) {
28
30
  await this.loadUserPlugins(registry, repoPath);
@@ -156,7 +156,7 @@ export class PromptRegistry {
156
156
  return this.render('explore_system', { tools: this.getToolsForTemplate() });
157
157
  }
158
158
  renderAutopilotSystem() {
159
- return this.render('autopilot_system', {});
159
+ return this.render('autopilot_system', { tools: this.getToolsForTemplate() });
160
160
  }
161
161
  renderAnswerSystem() {
162
162
  return this.render('answer_system', {});
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../observability/logger.js';
1
2
  import { resolvePhaseVisibleTools } from '../tools/tool-visibility.js';
2
3
  import { Phase } from '../types/runtime.js';
3
4
  import { getPromptRegistry } from './registry.js';
@@ -16,7 +17,8 @@ function extractTargetFiles(plan) {
16
17
  const files = parsed.files.filter((f) => typeof f === 'string');
17
18
  return files.length > 0 ? files.join(', ') : undefined;
18
19
  }
19
- catch {
20
+ catch (error) {
21
+ getLogger().debug(`[PromptRuntime] Failed to extract target files from plan JSON: ${error instanceof Error ? error.message : String(error)}`);
20
22
  return undefined;
21
23
  }
22
24
  }
@@ -1,11 +1,35 @@
1
1
  You are a coding assistant running in "autopilot" mode.
2
- Drive the task forward autonomously and answer in the same language as the user.
3
- Use the repository context available in the current turn when present.
4
- If no repository action is possible yet, explain the next best action succinctly.
5
- Prefer taking a reasonable repository action over asking the user for confirmation.
2
+ Your job is to complete the given task by using the available tools to inspect and modify the repository.
3
+ You MUST use tools to accomplish the task do not just describe what needs to be done, actually do it.
4
+
5
+ ## Problem-Solving Strategy
6
+
7
+ 1. **Understand**: Read the problem statement carefully. Identify what is failing and what the expected behavior should be.
8
+ 2. **Locate**: Find the source file to fix AND all test files that will be affected. Use `code.search` to search for the rule name, function name, or error message across the entire codebase. Pay special attention to CLI integration tests, snapshot tests, and end-to-end tests that assert on the behavior you are changing. Read ALL affected test files before writing any code.
9
+ 3. **Diagnose**: Understand the root cause before writing any code. Read surrounding context, not just the error site.
10
+ 4. **Fix**: Write the minimal change that fixes the issue. Update ALL affected test files to match the new behavior. Do not refactor unrelated code.
11
+ 5. **Verify**: Run the FULL test suite with `shell.exec` (e.g. `pytest`). If tests fail, analyze the output and iterate. Do NOT run only targeted tests — behavioral changes cascade to other tests.
12
+
13
+ ## Efficiency Rules
14
+ - Do NOT reproduce the bug. The problem statement already describes it. Go straight from understanding to fixing.
15
+ - Do NOT run multiple exploratory shell commands. Read the source code, understand the logic, write the fix.
16
+ - If you understand the root cause after reading the source, write the fix immediately. Do not verify the bug first.
17
+ - IMPORTANT: The Locate step is not optional. Spending time finding all affected tests upfront saves rounds later.
18
+
19
+ ## Rules
20
+ - After writing code, you MUST run tests with `shell.exec` to verify your changes. This is not optional — a fix without verification is incomplete.
21
+ - If tests fail, read the failure output carefully, diagnose the issue, and fix it. Do not stop until tests pass. A failing test means your fix is incomplete — find all affected tests and update them.
22
+ - Read test files before modifying code — tests define the expected behavior.
23
+ - Do not modify test files unless the test itself is incorrect. If your fix changes behavior correctly, update the test's expected output to match.
24
+ - Before writing any code, use `code.search` to find ALL tests that reference the changed behavior. Search for: the rule name (e.g. "L031"), the function name, error messages, and expected test output. CLI integration tests and snapshot tests often contain hardcoded expected output that must be updated when behavior changes.
25
+ - Make the smallest possible change. Avoid unrelated refactoring or style changes.
26
+ - Drive the task forward autonomously and answer in the same language as the user.
27
+
6
28
  Treat simple repo-relative paths like "smoke.txt", "README.md", or "src/index.ts" as valid paths from the repository root.
7
29
  Do not ask the user to validate a path that is already a valid relative path or can be inferred directly from the instruction.
8
30
  Use `interaction.ask_user` only when the task is genuinely ambiguous and you cannot safely infer the next action.
9
31
  When calling `interaction.ask_user`, the arguments MUST be valid JSON with exactly this shape: {"questions":[{"header":"Short header","question":"One clear question","options":[{"label":"Option 1","description":"Why pick it"},{"label":"Option 2","description":"Why pick it"}],"multiSelect":false}]}.
10
32
  Do not call `interaction.ask_user` with free-form strings, a single question field, string-only options, or empty options arrays.
11
33
  For simple file creation or edits, call `fs.write_file` directly with {"file":"relative/path.ext","content":"..."} instead of asking the user for the path again.
34
+
35
+ {{> tool_defs}}
@@ -1,4 +1,5 @@
1
1
  import { InMemoryTaskStore } from '@a2a-js/sdk/server';
2
+ import { getLogger } from '../../../observability/logger.js';
2
3
  import { buildCanonicalExecutionRequest, buildInstructionFromParts, } from '../../shared/execution-request.js';
3
4
  import { parseA2ASkillFlowMode } from '../../shared/flow-mode-mapping.js';
4
5
  export function createA2AInteractionExecutor(deps) {
@@ -346,7 +347,8 @@ export function createA2AInteractionExecutor(deps) {
346
347
  data: JSON.parse(artifact.content),
347
348
  };
348
349
  }
349
- catch {
350
+ catch (error) {
351
+ getLogger().debug(`[A2AExecutor] Failed to parse artifact content as JSON: ${error instanceof Error ? error.message : String(error)}`);
350
352
  // Fall through to text
351
353
  }
352
354
  }
@@ -406,6 +406,7 @@ const normalizeA2AExtensionHeadersByVersion = (req, res, next) => {
406
406
  });
407
407
  next();
408
408
  };
409
+ import { getLogger } from '../../../observability/logger.js';
409
410
  import { isRecord } from '../../../utils/serialize.js';
410
411
  const isObjectRecord = isRecord;
411
412
  function looksLikeTask(result) {
@@ -825,7 +826,8 @@ function normalizeSseJsonRpcChunkByMethod(chunk, method) {
825
826
  const payload = JSON.parse(line.slice('data: '.length));
826
827
  return `data: ${JSON.stringify(normalizeJsonRpcPayloadByMethod(method, payload))}`;
827
828
  }
828
- catch {
829
+ catch (error) {
830
+ getLogger().debug(`[A2AServer] Failed to parse SSE JSON-RPC chunk: ${error instanceof Error ? error.message : String(error)}`);
829
831
  return line;
830
832
  }
831
833
  })
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../../observability/logger.js';
1
2
  function sleep(ms) {
2
3
  if (ms <= 0)
3
4
  return Promise.resolve();
@@ -72,8 +73,8 @@ async function safeRelease(terminal) {
72
73
  try {
73
74
  await terminal.release();
74
75
  }
75
- catch {
76
- // Ignore release errors.
76
+ catch (error) {
77
+ getLogger().debug(`[AcpCommandRunner] Failed to release terminal: ${error instanceof Error ? error.message : String(error)}`);
77
78
  }
78
79
  }
79
80
  export function createAcpCommandRunner(params) {
@@ -134,8 +135,8 @@ export function createAcpCommandRunner(params) {
134
135
  try {
135
136
  await terminal.kill();
136
137
  }
137
- catch {
138
- // Ignore kill errors.
138
+ catch (error) {
139
+ getLogger().debug(`[AcpCommandRunner] Failed to kill terminal on abort: ${error instanceof Error ? error.message : String(error)}`);
139
140
  }
140
141
  break;
141
142
  }
@@ -144,8 +145,8 @@ export function createAcpCommandRunner(params) {
144
145
  try {
145
146
  await terminal.kill();
146
147
  }
147
- catch {
148
- // Ignore kill errors.
148
+ catch (error) {
149
+ getLogger().debug(`[AcpCommandRunner] Failed to kill terminal on timeout: ${error instanceof Error ? error.message : String(error)}`);
149
150
  }
150
151
  break;
151
152
  }
@@ -1,6 +1,7 @@
1
1
  import { mkdir, open, readFile, rename, stat, unlink, writeFile, } from '../../adapters/fs/node-fs.js';
2
2
  import { defaultPathAdapter } from '../../adapters/path/path-adapter.js';
3
3
  import { recordAuditEvent } from '../../observability/audit-trail.js';
4
+ import { getLogger } from '../../observability/logger.js';
4
5
  import { hashRepoPath, isPermissionPolicyValue, parseTimestamp, } from './acp-types.js';
5
6
  export function createAcpSessionPersistence(options) {
6
7
  const deletedSessionIds = new Map();
@@ -219,7 +220,8 @@ export function createAcpSessionPersistence(options) {
219
220
  phase: 'PREFLIGHT',
220
221
  });
221
222
  }
222
- catch {
223
+ catch (error) {
224
+ getLogger().debug(`[AcpSessionPersistence] Failed to parse stale lock file: ${error instanceof Error ? error.message : String(error)}`);
223
225
  try {
224
226
  const lockStat = await stat(lockPath);
225
227
  const ageMs = Date.now() - lockStat.mtimeMs;
@@ -231,8 +233,8 @@ export function createAcpSessionPersistence(options) {
231
233
  }, { source: 'acp', severity: 'medium', scope: 'session', phase: 'PREFLIGHT' });
232
234
  }
233
235
  }
234
- catch {
235
- // ignore
236
+ catch (innerError) {
237
+ getLogger().debug(`[AcpSessionPersistence] Failed to stat lock file for age check: ${innerError instanceof Error ? innerError.message : String(innerError)}`);
236
238
  }
237
239
  }
238
240
  };
@@ -246,7 +248,8 @@ export function createAcpSessionPersistence(options) {
246
248
  await lockHandle.writeFile(JSON.stringify({ pid: process.pid, createdAtMs: Date.now() }), 'utf8');
247
249
  break;
248
250
  }
249
- catch {
251
+ catch (error) {
252
+ getLogger().debug(`[AcpSessionPersistence] Lock acquire attempt failed: ${error instanceof Error ? error.message : String(error)}`);
250
253
  await tryClearStaleLock();
251
254
  const delayMs = Math.min(250, 20 * (attempt + 1));
252
255
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -271,8 +274,8 @@ export function createAcpSessionPersistence(options) {
271
274
  const existingRaw = await readFile(options.path, 'utf8');
272
275
  existing = normalizePersistedSessionStore(JSON.parse(existingRaw));
273
276
  }
274
- catch {
275
- // ignore read failure; writing fresh payload is acceptable
277
+ catch (error) {
278
+ getLogger().debug(`[AcpSessionPersistence] Failed to read existing session store for merge: ${error instanceof Error ? error.message : String(error)}`);
276
279
  }
277
280
  const merged = new Map();
278
281
  const mergedDeletedSessions = pruneDeletedSessionRecords([
@@ -311,14 +314,14 @@ export function createAcpSessionPersistence(options) {
311
314
  try {
312
315
  await lockHandle.close();
313
316
  }
314
- catch {
315
- // ignore
317
+ catch (error) {
318
+ getLogger().debug(`[AcpSessionPersistence] Failed to close lock handle: ${error instanceof Error ? error.message : String(error)}`);
316
319
  }
317
320
  try {
318
321
  await unlink(lockPath);
319
322
  }
320
- catch {
321
- // ignore
323
+ catch (error) {
324
+ getLogger().debug(`[AcpSessionPersistence] Failed to unlink lock file: ${error instanceof Error ? error.message : String(error)}`);
322
325
  }
323
326
  }
324
327
  }
@@ -3,6 +3,7 @@ import { text } from '../../../locales/index.js';
3
3
  import { defaultPathAdapter } from '../../adapters/path/path-adapter.js';
4
4
  import { inferTurnStopReasonFromFailure } from '../../interaction/turn-stop-reason.js';
5
5
  import { recordAuditEvent } from '../../observability/audit-trail.js';
6
+ import { getLogger } from '../../observability/logger.js';
6
7
  import { toAcpPublicModes } from '../../public-capabilities/projections.js';
7
8
  import { buildPublicCapabilityRegistry } from '../../public-capabilities/registry.js';
8
9
  import { parseSlashInput } from '../../slash/parser.js';
@@ -641,8 +642,8 @@ export function createAcpFormalAgent(deps) {
641
642
  try {
642
643
  await emitSessionUpdate(sessionId, update);
643
644
  }
644
- catch {
645
- // Best-effort: do not fail the request due to notification delivery issues.
645
+ catch (error) {
646
+ getLogger().debug(`[AcpFormalAgent] Best-effort session info update failed: ${error instanceof Error ? error.message : String(error)}`);
646
647
  }
647
648
  }
648
649
  async function emitRuntimePlanUpdateIfNeeded(params) {
@@ -1,4 +1,5 @@
1
1
  import { text } from '../../../locales/index.js';
2
+ import { getLogger } from '../../observability/logger.js';
2
3
  import { mapToolKind } from './tool-kind-mapping.js';
3
4
  function buildPermissionOptions() {
4
5
  return [
@@ -45,8 +46,8 @@ export function createAcpToolAuthorizationProvider(params) {
45
46
  };
46
47
  await params.conn.sessionUpdate({ sessionId: params.sessionId, update });
47
48
  }
48
- catch {
49
- // Best-effort: do not block authorization if the client can't accept updates.
49
+ catch (error) {
50
+ getLogger().debug(`[AcpPermissionProvider] Best-effort in_progress update failed: ${error instanceof Error ? error.message : String(error)}`);
50
51
  }
51
52
  };
52
53
  return {