salmon-loop 0.2.13 → 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/cli/argv/headless-detection.js +27 -0
  2. package/dist/cli/chat-flow.js +11 -0
  3. package/dist/cli/chat.js +160 -24
  4. package/dist/cli/commands/chat.js +14 -7
  5. package/dist/cli/commands/flow-mode.js +63 -0
  6. package/dist/cli/commands/registry.js +2 -0
  7. package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
  8. package/dist/cli/commands/run/early-errors.js +23 -0
  9. package/dist/cli/commands/run/handler.js +115 -27
  10. package/dist/cli/commands/run/headless-error-writer.js +8 -0
  11. package/dist/cli/commands/run/loop-params.js +2 -0
  12. package/dist/cli/commands/run/mode.js +2 -5
  13. package/dist/cli/commands/run/parse-options.js +16 -0
  14. package/dist/cli/commands/run/persist-session.js +10 -1
  15. package/dist/cli/commands/run/preflight.js +10 -0
  16. package/dist/cli/commands/run/reporter-factory.js +4 -0
  17. package/dist/cli/commands/run/runtime-llm.js +38 -11
  18. package/dist/cli/commands/run/runtime-options.js +2 -2
  19. package/dist/cli/commands/serve.js +91 -71
  20. package/dist/cli/commands/tool-names.js +78 -78
  21. package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
  22. package/dist/cli/headless/json-protocol.js +37 -0
  23. package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
  24. package/dist/cli/headless/protocol-metadata.js +22 -0
  25. package/dist/cli/headless/stream-json-protocol.js +34 -1
  26. package/dist/cli/index.js +6 -4
  27. package/dist/cli/locales/en.js +30 -6
  28. package/dist/cli/program-bootstrap.js +8 -3
  29. package/dist/cli/program-commands.js +5 -1
  30. package/dist/cli/reporters/anthropic-stream.js +7 -1
  31. package/dist/cli/reporters/json.js +4 -0
  32. package/dist/cli/reporters/stream-json.js +17 -2
  33. package/dist/cli/run-cli.js +5 -3
  34. package/dist/cli/slash/runtime.js +27 -12
  35. package/dist/cli/ui/components/CommandInput.js +7 -3
  36. package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
  37. package/dist/cli/utils/command-option-source.js +13 -0
  38. package/dist/cli/utils/verify-resolver.js +8 -4
  39. package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
  40. package/dist/core/adapters/fs/file-adapter.js +6 -0
  41. package/dist/core/adapters/fs/filesystem.js +2 -1
  42. package/dist/core/adapters/git/git-adapter.js +78 -1
  43. package/dist/core/benchmark/patch-artifact.js +124 -0
  44. package/dist/core/benchmark/swe-bench.js +25 -0
  45. package/dist/core/config/load.js +18 -11
  46. package/dist/core/config/resolve-llm.js +12 -0
  47. package/dist/core/config/resolvers/server.js +0 -6
  48. package/dist/core/config/validate.js +73 -21
  49. package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
  50. package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
  51. package/dist/core/context/keywords.js +18 -4
  52. package/dist/core/context/service-deps.js +2 -2
  53. package/dist/core/context/service.js +8 -0
  54. package/dist/core/context/steps/context-gather.js +38 -0
  55. package/dist/core/context/summarization/summarizer.js +55 -12
  56. package/dist/core/context/targeting/target-resolver.js +4 -4
  57. package/dist/core/extensions/index.js +23 -5
  58. package/dist/core/extensions/paths.js +31 -0
  59. package/dist/core/extensions/schemas.js +8 -5
  60. package/dist/core/facades/cli-chat.js +6 -2
  61. package/dist/core/facades/cli-command-chat.js +1 -0
  62. package/dist/core/facades/cli-command-tool-names.js +2 -0
  63. package/dist/core/facades/cli-observability.js +1 -1
  64. package/dist/core/facades/cli-run-handler.js +4 -2
  65. package/dist/core/facades/cli-run-persist-session.js +1 -0
  66. package/dist/core/facades/cli-serve.js +2 -4
  67. package/dist/core/facades/cli-utils-worktree.js +1 -1
  68. package/dist/core/failure/diagnostics.js +53 -1
  69. package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
  70. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
  71. package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
  72. package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
  73. package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
  74. package/dist/core/grizzco/engine/transaction/transaction-runner.js +165 -7
  75. package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
  76. package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
  77. package/dist/core/grizzco/steps/answer.js +13 -14
  78. package/dist/core/grizzco/steps/autopilot.js +396 -0
  79. package/dist/core/grizzco/steps/cache-sharing.js +29 -0
  80. package/dist/core/grizzco/steps/explore.js +37 -21
  81. package/dist/core/grizzco/steps/generateReview.js +2 -5
  82. package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
  83. package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
  84. package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
  85. package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
  86. package/dist/core/grizzco/steps/patch.js +105 -146
  87. package/dist/core/grizzco/steps/plan.js +101 -25
  88. package/dist/core/grizzco/steps/preflight.js +5 -6
  89. package/dist/core/grizzco/steps/request-assembly.js +78 -0
  90. package/dist/core/grizzco/steps/research.js +39 -36
  91. package/dist/core/grizzco/steps/tool-runtime.js +47 -0
  92. package/dist/core/grizzco/steps/verify-shared.js +23 -0
  93. package/dist/core/grizzco/steps/verify.js +13 -21
  94. package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
  95. package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
  96. package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
  97. package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
  98. package/dist/core/llm/ai-sdk/request-params.js +73 -0
  99. package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
  100. package/dist/core/llm/ai-sdk.js +112 -27
  101. package/dist/core/llm/capabilities.js +12 -0
  102. package/dist/core/llm/contracts/repair.js +36 -30
  103. package/dist/core/llm/errors.js +83 -2
  104. package/dist/core/llm/message-composition.js +7 -22
  105. package/dist/core/llm/phase-router.js +29 -10
  106. package/dist/core/llm/redact.js +28 -3
  107. package/dist/core/llm/registry.js +2 -0
  108. package/dist/core/llm/request-augmentation.js +55 -0
  109. package/dist/core/llm/request-envelope.js +334 -0
  110. package/dist/core/llm/shared-request-assembly.js +35 -0
  111. package/dist/core/llm/stream-utils.js +13 -4
  112. package/dist/core/llm/utils.js +18 -29
  113. package/dist/core/memory/relevant-retrieval.js +144 -0
  114. package/dist/core/observability/logger.js +11 -2
  115. package/dist/core/patch/diff.js +1 -0
  116. package/dist/core/prompts/registry.js +39 -2
  117. package/dist/core/prompts/runtime.js +50 -12
  118. package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
  119. package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
  120. package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
  121. package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
  122. package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
  123. package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
  124. package/dist/core/prompts/templates/system/main_system.hbs +4 -16
  125. package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
  126. package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
  127. package/dist/core/prompts/templates/system/research_system.hbs +2 -0
  128. package/dist/core/protocols/a2a/agent-card.js +3 -2
  129. package/dist/core/protocols/a2a/sdk/executor.js +2 -1
  130. package/dist/core/protocols/a2a/sdk/server.js +0 -1
  131. package/dist/core/protocols/acp/formal-agent.js +74 -51
  132. package/dist/core/protocols/acp/handlers.js +5 -1
  133. package/dist/core/protocols/acp/permission-provider.js +1 -1
  134. package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
  135. package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
  136. package/dist/core/public-capabilities/projections.js +29 -0
  137. package/dist/core/public-capabilities/registry.js +26 -0
  138. package/dist/core/public-capabilities/types.js +2 -0
  139. package/dist/core/runtime/agent-server-runtime.js +47 -43
  140. package/dist/core/runtime/execution-profile.js +67 -0
  141. package/dist/core/session/artifact-state.js +160 -0
  142. package/dist/core/session/compaction/index.js +183 -0
  143. package/dist/core/session/compaction/microcompact.js +78 -0
  144. package/dist/core/session/compaction/tracking.js +48 -0
  145. package/dist/core/session/compaction/types.js +11 -0
  146. package/dist/core/session/compression.js +8 -0
  147. package/dist/core/session/manager.js +244 -8
  148. package/dist/core/session/pruning-strategy.js +55 -9
  149. package/dist/core/session/replacement-preview-provider.js +24 -0
  150. package/dist/core/session/replacement-state.js +131 -0
  151. package/dist/core/session/resume-repair/pipeline.js +79 -0
  152. package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
  153. package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
  154. package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
  155. package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
  156. package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
  157. package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
  158. package/dist/core/session/resume-repair/types.js +2 -0
  159. package/dist/core/session/summary-sync.js +164 -13
  160. package/dist/core/session/token-tracker.js +6 -0
  161. package/dist/core/skills/audit.js +34 -0
  162. package/dist/core/skills/bridge.js +84 -7
  163. package/dist/core/skills/discovery.js +94 -0
  164. package/dist/core/skills/feature-flags.js +52 -0
  165. package/dist/core/skills/index.js +1 -1
  166. package/dist/core/skills/loader.js +195 -20
  167. package/dist/core/skills/parser.js +296 -24
  168. package/dist/core/skills/permissions.js +117 -0
  169. package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
  170. package/dist/core/skills/runtime/SkillRunner.js +240 -61
  171. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
  172. package/dist/core/strata/layers/worktree.js +67 -10
  173. package/dist/core/strata/runtime/synchronizer.js +29 -2
  174. package/dist/core/streaming/stream-assembler.js +75 -31
  175. package/dist/core/sub-agent/context-snapshot.js +156 -0
  176. package/dist/core/sub-agent/core/loop.js +1 -1
  177. package/dist/core/sub-agent/core/manager.js +119 -20
  178. package/dist/core/sub-agent/dispatch-policy.js +29 -0
  179. package/dist/core/sub-agent/prefix-consistency.js +48 -0
  180. package/dist/core/sub-agent/registry-defaults.js +4 -0
  181. package/dist/core/sub-agent/tools/task-spawn.js +79 -2
  182. package/dist/core/sub-agent/types.js +134 -5
  183. package/dist/core/tools/audit.js +13 -4
  184. package/dist/core/tools/builtin/ast-grep.js +1 -1
  185. package/dist/core/tools/builtin/ast.js +1 -1
  186. package/dist/core/tools/builtin/benchmark.js +360 -0
  187. package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
  188. package/dist/core/tools/builtin/code-search/executor.js +6 -1
  189. package/dist/core/tools/builtin/code-search/spec.js +26 -2
  190. package/dist/core/tools/builtin/fs.js +256 -23
  191. package/dist/core/tools/builtin/git.js +2 -2
  192. package/dist/core/tools/builtin/index.js +51 -2
  193. package/dist/core/tools/builtin/interaction.js +8 -1
  194. package/dist/core/tools/builtin/plan.js +37 -15
  195. package/dist/core/tools/builtin/shell.js +1 -1
  196. package/dist/core/tools/loader.js +39 -16
  197. package/dist/core/tools/mapper.js +17 -3
  198. package/dist/core/tools/parallel/scheduler.js +35 -4
  199. package/dist/core/tools/permissions/permission-rules.js +5 -10
  200. package/dist/core/tools/policy.js +6 -1
  201. package/dist/core/tools/recoverable-tool-errors.js +10 -0
  202. package/dist/core/tools/router.js +24 -6
  203. package/dist/core/tools/session.js +458 -48
  204. package/dist/core/tools/tool-visibility.js +62 -0
  205. package/dist/core/tools/types.js +9 -1
  206. package/dist/core/types/execution.js +4 -0
  207. package/dist/core/types/flow-mode.js +8 -0
  208. package/dist/core/utils/path.js +52 -0
  209. package/dist/core/verification/runner.js +4 -1
  210. package/dist/languages/typescript/index.js +4 -1
  211. package/dist/locales/en.js +35 -2
  212. package/dist/utils/eol.js +1 -1
  213. package/package.json +13 -6
  214. package/scripts/fix-es-abstract-compat.js +77 -0
  215. package/dist/core/runtime/fastify-server-bundle.js +0 -26
  216. package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
  217. package/dist/core/runtime/sidecar-paths.js +0 -47
  218. package/dist/core/runtime/sidecar-route-catalog.js +0 -103
@@ -147,6 +147,14 @@ export class ContextService {
147
147
  if (target.path)
148
148
  deduped.add(target.path);
149
149
  }
150
+ for (const file of result.context.relatedFiles ?? []) {
151
+ if (file.path)
152
+ deduped.add(file.path);
153
+ }
154
+ for (const snippet of result.context.rgSnippets ?? []) {
155
+ if (snippet.file)
156
+ deduped.add(snippet.file);
157
+ }
150
158
  return [...deduped].sort().slice(0, ContextService.MAX_CACHE_TRACKED_FILES);
151
159
  }
152
160
  async computeTrackedFilesSignature(repoPath, files) {
@@ -1,7 +1,27 @@
1
+ import { FileAdapter } from '../../adapters/fs/file-adapter.js';
2
+ import { LIMITS } from '../../config/limits.js';
3
+ import { ensureInSandbox, normalizePath, safeJoin } from '../../utils/path.js';
4
+ import { outlineSource } from '../ast/source-outline.js';
1
5
  import { CONTEXT_AUDIT_ACTION, CONTEXT_AUDIT_PHASE } from '../audit-constants.js';
2
6
  import { recordContextAuditEvent } from '../audit.js';
3
7
  import { extractKeywords } from '../keywords.js';
4
8
  import { assertNotAborted } from '../service-helpers.js';
9
+ const fileAdapter = new FileAdapter();
10
+ async function readMatchedFileContent(req, file) {
11
+ if (req.snapshotHash && req.checkpointManager) {
12
+ return req.checkpointManager.readSnapshotFile(req.repoPath, req.snapshotHash, file);
13
+ }
14
+ try {
15
+ const fullPath = ensureInSandbox(req.repoPath, safeJoin(req.repoPath, file));
16
+ const stat = await fileAdapter.stat(fullPath);
17
+ if (!stat.isFile() || stat.size > LIMITS.largeFileThresholdBytes)
18
+ return null;
19
+ return await fileAdapter.readFile(fullPath, 'utf-8');
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
5
25
  export function buildContextGatherStep(deps) {
6
26
  return async ({ req, diffScope, primaryText }) => {
7
27
  assertNotAborted(req.signal);
@@ -24,6 +44,24 @@ export function buildContextGatherStep(deps) {
24
44
  if (ghostFiles.length > 0) {
25
45
  astRes.relatedFiles.push(...ghostFiles);
26
46
  }
47
+ const relatedSeen = new Set(astRes.relatedFiles.map((file) => file.path));
48
+ const primaryPath = req.primaryFile
49
+ ? normalizePath(req.primaryFile).replace(/^(\.\/|\/)+/, '')
50
+ : undefined;
51
+ for (const snippet of rgSnippets) {
52
+ const file = normalizePath(snippet.file).replace(/^(\.\/|\/)+/, '');
53
+ if (!file || file === primaryPath || relatedSeen.has(file))
54
+ continue;
55
+ relatedSeen.add(file);
56
+ const content = await readMatchedFileContent(req, file);
57
+ astRes.relatedFiles.push({
58
+ path: file,
59
+ kind: 'dependency',
60
+ mode: content ? 'full' : 'outline',
61
+ content: content ?? `ripgrep match at line ${snippet.line}: ${snippet.content}`,
62
+ outline: content ? outlineSource(content) : undefined,
63
+ });
64
+ }
27
65
  recordContextAuditEvent(CONTEXT_AUDIT_ACTION.gatherCompleted, {
28
66
  rgSnippets: rgSnippets.length,
29
67
  includedFiles: diffRes.includedFiles.length,
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Based on best practices from LangChain, OpenAI Realtime API, and Voice AI systems.
11
11
  */
12
- import { getLogger } from '../../observability/logger.js';
12
+ import { tryGetLogger } from '../../observability/logger.js';
13
13
  import { buildIncrementalSummaryPrompt, truncateSummary } from './prompts.js';
14
14
  import { DEFAULT_SUMMARIZATION_CONFIG } from './types.js';
15
15
  /**
@@ -49,6 +49,7 @@ export class ConversationSummarizer {
49
49
  summaryVersion: 2,
50
50
  structuredState: this.createEmptyStructuredState(),
51
51
  contextHash: undefined,
52
+ recoveryState: undefined,
52
53
  };
53
54
  }
54
55
  // ==================== Lifecycle ====================
@@ -57,7 +58,7 @@ export class ConversationSummarizer {
57
58
  */
58
59
  async initialize() {
59
60
  this.initialized = true;
60
- getLogger().debug('[Summarizer] Initialized');
61
+ tryGetLogger()?.debug('[Summarizer] Initialized');
61
62
  }
62
63
  /**
63
64
  * Dispose resources.
@@ -77,10 +78,9 @@ export class ConversationSummarizer {
77
78
  summarizedMessageIds: [...this.state.summarizedMessageIds],
78
79
  lastSummarizedAt: this.state.lastSummarizedAt,
79
80
  summaryVersion: this.state.summaryVersion,
80
- structuredState: this.state.structuredState
81
- ? { ...this.state.structuredState }
82
- : this.createEmptyStructuredState(),
81
+ structuredState: this.cloneStructuredState(this.state.structuredState),
83
82
  contextHash: this.state.contextHash,
83
+ recoveryState: this.cloneRecoveryState(this.state.recoveryState),
84
84
  };
85
85
  }
86
86
  /**
@@ -97,8 +97,9 @@ export class ConversationSummarizer {
97
97
  ? this.normalizeStructuredState(state.structuredState)
98
98
  : this.createEmptyStructuredState(),
99
99
  contextHash: state.contextHash,
100
+ recoveryState: this.cloneRecoveryState(state.recoveryState),
100
101
  };
101
- getLogger().debug(`[Summarizer] Restored state with ${state.summarizedMessageIds.length} summarized messages`);
102
+ tryGetLogger()?.debug(`[Summarizer] Restored state with ${state.summarizedMessageIds.length} summarized messages`);
102
103
  }
103
104
  /**
104
105
  * Reset summarizer state.
@@ -112,6 +113,7 @@ export class ConversationSummarizer {
112
113
  summaryVersion: 2,
113
114
  structuredState: this.createEmptyStructuredState(),
114
115
  contextHash: undefined,
116
+ recoveryState: undefined,
115
117
  };
116
118
  this.summaryInProgress = false;
117
119
  }
@@ -189,7 +191,7 @@ export class ConversationSummarizer {
189
191
  */
190
192
  async triggerSummarization(messages, contextHash) {
191
193
  if (this.summaryInProgress) {
192
- getLogger().debug('[Summarizer] Summary already in progress, skipping');
194
+ tryGetLogger()?.debug('[Summarizer] Summary already in progress, skipping');
193
195
  return null;
194
196
  }
195
197
  this.ensureStateAligned(contextHash);
@@ -200,7 +202,7 @@ export class ConversationSummarizer {
200
202
  // Fire and forget
201
203
  this.runSummarization(messages, contextHash).catch((err) => {
202
204
  const errMsg = err instanceof Error ? err.message : String(err);
203
- getLogger().warn(`[Summarizer] Async summarization failed: ${errMsg}`);
205
+ tryGetLogger()?.warn(`[Summarizer] Async summarization failed: ${errMsg}`);
204
206
  });
205
207
  return null;
206
208
  }
@@ -269,7 +271,7 @@ export class ConversationSummarizer {
269
271
  this.state.summarizedMessageIds.push(msg.id);
270
272
  }
271
273
  this.state.lastSummarizedAt = Date.now();
272
- getLogger().info(`[Summarizer] Summarized ${batch.length} messages, ` +
274
+ tryGetLogger()?.info(`[Summarizer] Summarized ${batch.length} messages, ` +
273
275
  `reduced ${beforeTokens} → ${this.state.summaryTokens} tokens`);
274
276
  return {
275
277
  summary: newSummary,
@@ -281,7 +283,7 @@ export class ConversationSummarizer {
281
283
  };
282
284
  }
283
285
  catch (error) {
284
- getLogger().error('[Summarizer] Summarization failed', error);
286
+ tryGetLogger()?.error('[Summarizer] Summarization failed', error);
285
287
  throw error;
286
288
  }
287
289
  finally {
@@ -306,7 +308,7 @@ export class ConversationSummarizer {
306
308
  return;
307
309
  if (!this.state.contextHash || this.state.contextHash === contextHash)
308
310
  return;
309
- getLogger().warn(`[Summarizer] Context hash changed (${this.state.contextHash} -> ${contextHash}), rebuilding summary state`);
311
+ tryGetLogger()?.warn(`[Summarizer] Context hash changed (${this.state.contextHash} -> ${contextHash}), rebuilding summary state`);
310
312
  this.state.summary = '';
311
313
  this.state.summaryTokens = 0;
312
314
  this.state.summarizedMessageIds = [];
@@ -327,7 +329,7 @@ export class ConversationSummarizer {
327
329
  }
328
330
  catch (error) {
329
331
  const errMsg = error instanceof Error ? error.message : String(error);
330
- getLogger().warn(`[Summarizer] Failed to parse structured summary state: ${errMsg}`);
332
+ tryGetLogger()?.warn(`[Summarizer] Failed to parse structured summary state: ${errMsg}`);
331
333
  return { summary, structuredState: this.createEmptyStructuredState() };
332
334
  }
333
335
  }
@@ -346,6 +348,47 @@ export class ConversationSummarizer {
346
348
  normalized.owner = this.ensureStringArray(source.owner);
347
349
  return normalized;
348
350
  }
351
+ cloneStructuredState(state) {
352
+ if (!state)
353
+ return this.createEmptyStructuredState();
354
+ return {
355
+ decisions: [...state.decisions],
356
+ constraints: [...state.constraints],
357
+ open_questions: [...state.open_questions],
358
+ pending_tasks: [...state.pending_tasks],
359
+ rejected_options: [...state.rejected_options],
360
+ assumptions: [...state.assumptions],
361
+ risks: [...state.risks],
362
+ owner: [...state.owner],
363
+ };
364
+ }
365
+ cloneRecoveryFailureSummary(value) {
366
+ if (!value)
367
+ return undefined;
368
+ return {
369
+ reasonCode: value.reasonCode,
370
+ diagnosticCode: value.diagnosticCode,
371
+ safeHint: value.safeHint,
372
+ failurePhase: value.failurePhase,
373
+ };
374
+ }
375
+ cloneRecoveryState(state) {
376
+ if (!state)
377
+ return undefined;
378
+ const flowMode = typeof state.flowMode === 'string' ? state.flowMode : undefined;
379
+ const recentReadFiles = Array.isArray(state.recentReadFiles)
380
+ ? state.recentReadFiles.filter((item) => typeof item === 'string')
381
+ : undefined;
382
+ const next = {};
383
+ if (flowMode)
384
+ next.flowMode = flowMode;
385
+ const lastFailureSummary = this.cloneRecoveryFailureSummary(state.lastFailureSummary);
386
+ if (lastFailureSummary)
387
+ next.lastFailureSummary = lastFailureSummary;
388
+ if (recentReadFiles && recentReadFiles.length > 0)
389
+ next.recentReadFiles = [...recentReadFiles];
390
+ return Object.keys(next).length > 0 ? next : undefined;
391
+ }
349
392
  ensureStringArray(value) {
350
393
  if (!Array.isArray(value))
351
394
  return [];
@@ -387,9 +387,6 @@ export class TargetResolver {
387
387
  debugLabel: 'context-targeting',
388
388
  maxRounds: 5,
389
389
  resolveData: async (ctx, key) => {
390
- if (!ctx.primaryFile) {
391
- return [];
392
- }
393
390
  if (key === 'explicitTargets') {
394
391
  const primary = buildPrimaryTarget(ctx.primaryFile);
395
392
  const explicit = buildExplicitTargets(req);
@@ -401,6 +398,8 @@ export class TargetResolver {
401
398
  return dedupeTargets([...primary, ...diff]);
402
399
  }
403
400
  if (key === 'symbolTargets') {
401
+ if (!ctx.primaryFile)
402
+ return [];
404
403
  const primary = buildPrimaryTarget(ctx.primaryFile);
405
404
  const res = buildSymbolTargets({
406
405
  primaryFile: ctx.primaryFile,
@@ -420,6 +419,8 @@ export class TargetResolver {
420
419
  const combined = dedupeTargets([...primary, ...imports, ...rg]);
421
420
  if (combined.length > 0)
422
421
  return combined;
422
+ if (!ctx.primaryFile)
423
+ return [];
423
424
  return [
424
425
  {
425
426
  path: ctx.primaryFile,
@@ -434,7 +435,6 @@ export class TargetResolver {
434
435
  strategy: (engine) => {
435
436
  return engine
436
437
  .phase('Dependencies')
437
- .require((c) => Boolean(c.primaryFile), 'No primary file provided')
438
438
  .requireData(['explicitTargets', 'symbolTargets', 'diffTargets', 'defaultTargets'])
439
439
  .phase('Selection')
440
440
  .when((c) => (c.data?.explicitTargets || []).some((t) => t.reason === 'explicit_path'), (p) => {
@@ -1,6 +1,8 @@
1
+ import path from 'node:path';
2
+ import { getLogger } from '../observability/logger.js';
1
3
  import { loadConfig } from './load.js';
2
4
  import { mergeScopedEntries } from './merge.js';
3
- import { expandHome, getRepoMcpConfigPath, getRepoSkillConfigPath, getRepoToolConfigPath, getUserMcpConfigPath, getUserSkillConfigPath, getUserToolConfigPath, resolveRepoRelative, resolveUserRelative, } from './paths.js';
5
+ import { expandHome, getRepoMcpConfigPath, getRepoSkillConfigPath, getRepoToolConfigPath, getUserMcpConfigPath, getUserSkillConfigPath, getUserToolConfigPath, isWithinRoot, resolveRepoRelative, resolveUserRelative, } from './paths.js';
4
6
  import { redactExtensions } from './redact.js';
5
7
  import { McpConfigSchema, SkillsConfigSchema, ToolsConfigSchema } from './schemas.js';
6
8
  function defaultEnabled(scope) {
@@ -63,16 +65,33 @@ function buildResolvedPlugins(entries, repoRoot) {
63
65
  function buildResolvedSkills(user, repo, repoRoot) {
64
66
  const repoDiscovery = repo?.discovery;
65
67
  const userDiscovery = user?.discovery;
66
- const useDefaults = repoDiscovery?.useDefaults ?? userDiscovery?.useDefaults ?? true;
67
68
  const repoPaths = repoDiscovery && Array.isArray(repoDiscovery.paths) ? repoDiscovery.paths : undefined;
68
69
  const userPaths = userDiscovery && Array.isArray(userDiscovery.paths) ? userDiscovery.paths : undefined;
69
70
  let scope = 'repo';
70
71
  let paths = [];
71
72
  if (repoPaths && repoPaths.length > 0) {
72
73
  scope = 'repo';
74
+ const root = repoRoot ?? '';
73
75
  paths = repoPaths
74
- .map((value) => resolvePathForScope(value, 'repo', repoRoot ?? ''))
75
- .filter((p) => Boolean(p));
76
+ .filter((raw) => {
77
+ // Reject absolute paths in repo scope — only user-level config may specify them
78
+ const expanded = expandHome(raw);
79
+ if (path.isAbsolute(expanded)) {
80
+ getLogger().audit('SKILL_PATH_REJECTED', { path: raw, repoRoot: root, reason: 'absolute_path_in_repo_scope' }, { source: 'skill-loader', severity: 'high', scope: 'repo' });
81
+ return false;
82
+ }
83
+ return true;
84
+ })
85
+ .map((value) => resolvePathForScope(value, 'repo', root))
86
+ .filter((p) => Boolean(p))
87
+ .filter((p) => {
88
+ // Validate resolved paths stay within repo root
89
+ if (!isWithinRoot(p, root)) {
90
+ getLogger().audit('SKILL_PATH_REJECTED', { path: p, repoRoot: root, reason: 'outside_repo_root' }, { source: 'skill-loader', severity: 'high', scope: 'repo' });
91
+ return false;
92
+ }
93
+ return true;
94
+ });
76
95
  }
77
96
  else if (userPaths && userPaths.length > 0) {
78
97
  scope = 'user';
@@ -81,7 +100,6 @@ function buildResolvedSkills(user, repo, repoRoot) {
81
100
  .filter((p) => Boolean(p));
82
101
  }
83
102
  return {
84
- useDefaults,
85
103
  paths,
86
104
  scope,
87
105
  };
@@ -1,5 +1,7 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
+ import { realpathSync } from '../adapters/fs/node-fs.js';
4
+ import { tryGetLogger } from '../observability/logger.js';
3
5
  export const REPO_CONFIG_DIR = '.salmonloop/config';
4
6
  export const USER_CONFIG_DIR = path.join(os.homedir(), '.salmonloop', 'config');
5
7
  export function expandHome(value) {
@@ -37,4 +39,33 @@ export function getUserToolConfigPath() {
37
39
  export function getUserSkillConfigPath() {
38
40
  return path.join(USER_CONFIG_DIR, 'skills-user.json');
39
41
  }
42
+ /**
43
+ * Check whether a candidate path resides within (or equals) a given root directory.
44
+ *
45
+ * Uses realpath resolution to detect symlink-based escapes when both paths exist.
46
+ * When the candidate does not yet exist on disk (e.g. configured before the
47
+ * directory is created), falls back to a lexical containment check on the
48
+ * resolved (but not realpath'd) path. This avoids silently discarding valid
49
+ * future paths while still catching traversal attacks on existing paths.
50
+ *
51
+ * @returns true when the resolved candidate is the root itself or a descendant;
52
+ * false when the path escapes the root.
53
+ */
54
+ export function isWithinRoot(candidate, root) {
55
+ const resolvedCandidate = path.resolve(candidate);
56
+ const resolvedRoot = path.resolve(root);
57
+ try {
58
+ // Happy path: both exist — use realpath to resolve symlinks
59
+ const realCandidate = realpathSync(resolvedCandidate);
60
+ const realRoot = realpathSync(resolvedRoot);
61
+ return realCandidate === realRoot || realCandidate.startsWith(realRoot + path.sep);
62
+ }
63
+ catch {
64
+ // Candidate or root does not exist yet — fall back to lexical check.
65
+ // This allows pre-declaring paths that will be created later, while
66
+ // still catching obvious traversal sequences like `../../etc`.
67
+ tryGetLogger()?.debug(`isWithinRoot: path not on disk, using lexical check for "${candidate}" against root "${root}"`);
68
+ return (resolvedCandidate === resolvedRoot || resolvedCandidate.startsWith(resolvedRoot + path.sep));
69
+ }
70
+ }
40
71
  //# sourceMappingURL=paths.js.map
@@ -59,12 +59,15 @@ export const ToolsConfigSchema = z.object({
59
59
  version: z.literal(1),
60
60
  plugins: z.record(z.string(), toolPluginSchema).optional().default({}),
61
61
  });
62
- const skillDiscoverySchema = z.object({
63
- useDefaults: z.boolean().optional(),
62
+ const skillDiscoverySchema = z
63
+ .object({
64
64
  paths: z.array(z.string()).optional(),
65
- });
66
- export const SkillsConfigSchema = z.object({
65
+ })
66
+ .strict();
67
+ export const SkillsConfigSchema = z
68
+ .object({
67
69
  version: z.literal(1),
68
70
  discovery: skillDiscoverySchema.optional().default({}),
69
- });
71
+ })
72
+ .strict();
70
73
  //# sourceMappingURL=schemas.js.map
@@ -3,9 +3,13 @@ export { routeChatIntent } from '../intent/chat-intent.js';
3
3
  export { DEFAULT_LLM_OUTPUT_POLICY, emitLlmOutput } from '../llm/output-policy.js';
4
4
  export { logIgnoredError } from '../observability/ignored-error.js';
5
5
  export { getLogger } from '../observability/logger.js';
6
+ export { resolveExecutionProfile } from '../runtime/execution-profile.js';
6
7
  export { runSalmonLoop } from '../runtime/loop.js';
8
+ export { buildSessionArtifactStateFromLoopResult } from '../session/artifact-state.js';
7
9
  export { ChatSessionManager } from '../session/manager.js';
8
- export { buildSessionConversationContext, getDefaultSessionContextBudgetTokens, } from '../session/session-context-builder.js';
9
- export { refreshSessionSummary } from '../session/summary-sync.js';
10
+ export { getDefaultSessionContextBudgetTokens } from '../session/session-context-builder.js';
11
+ export { buildEffectiveConversationContext, refreshSessionSummary, } from '../session/summary-sync.js';
12
+ export { createInitialTracking, onNormalTurnComplete } from '../session/compaction/tracking.js';
13
+ export { runCompactionPipeline, reactiveCompact } from '../session/compaction/index.js';
10
14
  export { TokenTracker } from '../session/token-tracker.js';
11
15
  //# sourceMappingURL=cli-chat.js.map
@@ -3,6 +3,7 @@ export { ExtensionConfigError, resolveExtensions } from '../extensions/index.js'
3
3
  export { createRuntimeLlm } from '../llm/factory.js';
4
4
  export { getLogger } from '../observability/logger.js';
5
5
  export { PluginLoader } from '../plugin/loader.js';
6
+ export { resolveExecutionProfile } from '../runtime/execution-profile.js';
6
7
  export { clearPluginRegistry, createPluginRegistry, setPluginRegistry, } from '../plugin/registry.js';
7
8
  export { clearPromptRegistry, createPromptRegistry, setPromptRegistry, } from '../prompts/registry.js';
8
9
  //# sourceMappingURL=cli-command-chat.js.map
@@ -1,5 +1,7 @@
1
+ export { resolveExtensions } from '../extensions/index.js';
1
2
  export { getLogger } from '../observability/logger.js';
2
3
  export { skillToToolSpec } from '../skills/bridge.js';
4
+ export { SkillLoader } from '../skills/loader.js';
3
5
  export { SkillParser } from '../skills/parser.js';
4
6
  export { registerAllBuiltins } from '../tools/builtin/index.js';
5
7
  export { ToolRegistry } from '../tools/registry.js';
@@ -1,3 +1,3 @@
1
1
  export { logIgnoredError } from '../observability/ignored-error.js';
2
- export { getLogger } from '../observability/logger.js';
2
+ export { getLogger, tryGetLogger } from '../observability/logger.js';
3
3
  //# sourceMappingURL=cli-observability.js.map
@@ -1,7 +1,9 @@
1
1
  export { normalizePermissionMode } from '../config/index.js';
2
- export { getLogger } from '../observability/logger.js';
2
+ export { getLogger, PlainReporter, SilentReporter } from '../observability/logger.js';
3
3
  export { createPluginRegistry, setPluginRegistry, } from '../plugin/registry.js';
4
4
  export { createPromptRegistry, setPromptRegistry, } from '../prompts/registry.js';
5
5
  export { getExitCode } from '../runtime/exit-codes.js';
6
- export { buildSessionConversationContext, getDefaultSessionContextBudgetTokens, } from '../session/session-context-builder.js';
6
+ export { resolveExecutionProfile } from '../runtime/execution-profile.js';
7
+ export { getDefaultSessionContextBudgetTokens } from '../session/session-context-builder.js';
8
+ export { buildEffectiveConversationContext } from '../session/summary-sync.js';
7
9
  //# sourceMappingURL=cli-run-handler.js.map
@@ -1,2 +1,3 @@
1
+ export { buildSessionArtifactStateFromLoopResult } from '../session/artifact-state.js';
1
2
  export { refreshSessionSummary } from '../session/summary-sync.js';
2
3
  //# sourceMappingURL=cli-run-persist-session.js.map
@@ -1,5 +1,3 @@
1
- export { mkdir } from '../adapters/fs/node-fs.js';
2
- export { defaultPathAdapter } from '../adapters/path/path-adapter.js';
3
1
  export { createSalmonTaskExecutor } from '../backends/salmon-loop/task-executor.js';
4
2
  export { GitSnapshotCheckpointService } from '../checkpoint-domain/service.js';
5
3
  export { resolveConfig } from '../config/resolve.js';
@@ -11,11 +9,11 @@ export { PluginLoader } from '../plugin/loader.js';
11
9
  export { clearPluginRegistry, createPluginRegistry, setPluginRegistry, } from '../plugin/registry.js';
12
10
  export { clearPromptRegistry, createPromptRegistry, setPromptRegistry, } from '../prompts/registry.js';
13
11
  export { buildA2AAgentCard } from '../protocols/a2a/agent-card.js';
12
+ export { buildA2AFlowSkills } from '../protocols/shared/flow-mode-mapping.js';
14
13
  export { createAcpFormalAgent } from '../protocols/acp/formal-agent.js';
15
14
  export { startAcpStdioServer } from '../protocols/acp/stdio-server.js';
16
15
  export { createAgentServerRuntime } from '../runtime/agent-server-runtime.js';
16
+ export { resolveExecutionProfile } from '../runtime/execution-profile.js';
17
17
  export { runSalmonLoop } from '../runtime/loop.js';
18
18
  export { getUserAcpSessionStorePath } from '../runtime/paths.js';
19
- export { getSidecarSocketPath, getSidecarListenOptions, createPipeListenOptions, createTcpListenOptions, } from '../runtime/sidecar-paths.js';
20
- export { buildSidecarRouteDescriptors, defaultSidecarRouteCatalog, } from '../runtime/sidecar-route-catalog.js';
21
19
  //# sourceMappingURL=cli-serve.js.map
@@ -1,2 +1,2 @@
1
- export { getLogger } from '../observability/logger.js';
1
+ export { getLogger, tryGetLogger } from '../observability/logger.js';
2
2
  //# sourceMappingURL=cli-utils-worktree.js.map
@@ -97,6 +97,15 @@ function buildDependencyGuidance(input) {
97
97
  }
98
98
  function buildErrorCodeGuidance(input) {
99
99
  switch (input.errorCode) {
100
+ case 'LLM_RATE_LIMITED':
101
+ return {
102
+ diagnosticCode: 'LLM_RATE_LIMITED',
103
+ safeHint: 'LLM is rate limited. Please retry in a moment.',
104
+ remediationSteps: [
105
+ 'Retry the command after a short delay.',
106
+ 'If it persists, reduce concurrency or check provider quota.',
107
+ ],
108
+ };
100
109
  case 'LLM_HTTP_REQUEST_FAILED':
101
110
  return {
102
111
  diagnosticCode: 'LLM_HTTP_REQUEST_FAILED',
@@ -106,6 +115,15 @@ function buildErrorCodeGuidance(input) {
106
115
  'If it persists, check provider status or credentials.',
107
116
  ],
108
117
  };
118
+ case 'LLM_AUTHENTICATION_FAILED':
119
+ return {
120
+ diagnosticCode: 'LLM_AUTHENTICATION_FAILED',
121
+ safeHint: 'LLM provider rejected the request credentials or access.',
122
+ remediationSteps: [
123
+ 'Check the provider API key, app id, and model access configuration.',
124
+ 'If the configuration is correct, confirm the selected model is enabled for this account.',
125
+ ],
126
+ };
109
127
  case 'LLM_HTTP_ABORTED':
110
128
  return {
111
129
  diagnosticCode: 'LLM_HTTP_ABORTED',
@@ -184,7 +202,10 @@ export function buildFailureGuidance(input) {
184
202
  const dependencyGuidance = buildDependencyGuidance(input);
185
203
  if (dependencyGuidance)
186
204
  return dependencyGuidance;
187
- const errorCodeGuidance = buildErrorCodeGuidance(input);
205
+ const errorCodeGuidance = buildErrorCodeGuidance({
206
+ ...input,
207
+ errorCode: input.rootCause ?? input.errorCode,
208
+ });
188
209
  if (errorCodeGuidance)
189
210
  return errorCodeGuidance;
190
211
  if (input.reasonCode === 'PREFLIGHT_NOT_GIT') {
@@ -210,6 +231,37 @@ export function buildFailureGuidance(input) {
210
231
  remediationSteps: ['Resolve conflicting local changes, then retry.'],
211
232
  };
212
233
  }
234
+ if (input.reasonCode === 'VERIFY_COMMAND_MISSING') {
235
+ return {
236
+ diagnosticCode: 'VERIFY_COMMAND_MISSING',
237
+ safeHint: 'Autopilot changed the workspace but no verification command was configured.',
238
+ remediationSteps: [
239
+ 'Provide a verify command that exercises the changed behavior.',
240
+ 'Retry after configuring verification for this run.',
241
+ ],
242
+ };
243
+ }
244
+ if (input.reasonCode === 'TOOL_CORRECTION_REQUIRED') {
245
+ const correctionHint = (() => {
246
+ switch (input.errorCode) {
247
+ case 'INVALID_INPUT':
248
+ case 'INVALID_TOOL_ARGUMENTS_JSON':
249
+ case 'MALFORMED_TOOL_CALL':
250
+ return {
251
+ diagnosticCode: 'TOOL_ARGUMENT_CORRECTION_NEEDED',
252
+ safeHint: 'A tool call used invalid arguments. Adjust the tool arguments and retry.',
253
+ remediationSteps: [
254
+ 'Retry with a valid JSON object that matches the tool input schema.',
255
+ 'Reuse the latest tool error details to correct only the invalid fields.',
256
+ ],
257
+ };
258
+ default:
259
+ return undefined;
260
+ }
261
+ })();
262
+ if (correctionHint)
263
+ return correctionHint;
264
+ }
213
265
  return {
214
266
  diagnosticCode: input.reasonCode,
215
267
  safeHint: input.fallbackReason,
@@ -1,9 +1,12 @@
1
+ import { resolveLlmCapabilities } from '../../llm/capabilities.js';
1
2
  import { Phase } from '../../types/runtime.js';
2
3
  import { DecisionEngine, PlanBuilder } from './DecisionEngine.js';
3
4
  function defaultMaxRoundsForPhase(phase) {
4
5
  // Explore needs more rounds to navigate the codebase
5
6
  if (phase === Phase.EXPLORE)
6
7
  return 8;
8
+ if (phase === Phase.AUTOPILOT)
9
+ return 8;
7
10
  if (phase === Phase.RESEARCH)
8
11
  return 8;
9
12
  if (phase === Phase.PLAN)
@@ -13,7 +16,7 @@ function defaultMaxRoundsForPhase(phase) {
13
16
  return 4;
14
17
  }
15
18
  export function resolveLlmToolCallingPolicy(phase, llm) {
16
- const caps = llm.getCapabilities?.();
19
+ const caps = resolveLlmCapabilities(llm, phase);
17
20
  const maxRounds = defaultMaxRoundsForPhase(phase);
18
21
  // Grizzco DSL engine is transaction-shaped; for LLM policies we inject a minimal context and keep all
19
22
  // decisions inside ctx.data to avoid coupling to file/operation semantics.