salmon-loop 0.2.13 → 0.3.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 (224) 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 +97 -77
  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 +10 -5
  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/backends/salmon-loop/task-executor.js +1 -0
  44. package/dist/core/benchmark/patch-artifact.js +124 -0
  45. package/dist/core/benchmark/swe-bench.js +25 -0
  46. package/dist/core/config/load.js +18 -11
  47. package/dist/core/config/resolve-llm.js +12 -0
  48. package/dist/core/config/resolvers/server.js +0 -6
  49. package/dist/core/config/validate.js +73 -21
  50. package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
  51. package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
  52. package/dist/core/context/keywords.js +18 -4
  53. package/dist/core/context/service-deps.js +2 -2
  54. package/dist/core/context/service.js +8 -0
  55. package/dist/core/context/steps/context-gather.js +38 -0
  56. package/dist/core/context/summarization/summarizer.js +55 -12
  57. package/dist/core/context/targeting/target-resolver.js +4 -4
  58. package/dist/core/extensions/index.js +23 -5
  59. package/dist/core/extensions/merge.js +14 -0
  60. package/dist/core/extensions/paths.js +31 -0
  61. package/dist/core/extensions/schemas.js +8 -5
  62. package/dist/core/facades/cli-chat.js +6 -2
  63. package/dist/core/facades/cli-command-chat.js +1 -0
  64. package/dist/core/facades/cli-command-tool-names.js +2 -0
  65. package/dist/core/facades/cli-observability.js +1 -1
  66. package/dist/core/facades/cli-program-bootstrap.js +1 -0
  67. package/dist/core/facades/cli-run-handler.js +4 -2
  68. package/dist/core/facades/cli-run-persist-session.js +1 -0
  69. package/dist/core/facades/cli-serve.js +4 -4
  70. package/dist/core/facades/cli-utils-worktree.js +1 -1
  71. package/dist/core/failure/diagnostics.js +53 -1
  72. package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
  73. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
  74. package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
  75. package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
  76. package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
  77. package/dist/core/grizzco/engine/transaction/transaction-runner.js +165 -7
  78. package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
  79. package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
  80. package/dist/core/grizzco/steps/answer.js +13 -14
  81. package/dist/core/grizzco/steps/autopilot.js +396 -0
  82. package/dist/core/grizzco/steps/cache-sharing.js +29 -0
  83. package/dist/core/grizzco/steps/explore.js +37 -21
  84. package/dist/core/grizzco/steps/generateReview.js +2 -5
  85. package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
  86. package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
  87. package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
  88. package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
  89. package/dist/core/grizzco/steps/patch.js +105 -146
  90. package/dist/core/grizzco/steps/plan.js +101 -25
  91. package/dist/core/grizzco/steps/preflight.js +5 -6
  92. package/dist/core/grizzco/steps/request-assembly.js +78 -0
  93. package/dist/core/grizzco/steps/research.js +39 -36
  94. package/dist/core/grizzco/steps/tool-runtime.js +47 -0
  95. package/dist/core/grizzco/steps/verify-shared.js +23 -0
  96. package/dist/core/grizzco/steps/verify.js +13 -21
  97. package/dist/core/interaction/orchestration/facade.js +1 -1
  98. package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
  99. package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
  100. package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
  101. package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
  102. package/dist/core/llm/ai-sdk/request-params.js +113 -1
  103. package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
  104. package/dist/core/llm/ai-sdk.js +112 -27
  105. package/dist/core/llm/capabilities.js +12 -0
  106. package/dist/core/llm/contracts/repair.js +36 -30
  107. package/dist/core/llm/errors.js +83 -2
  108. package/dist/core/llm/message-composition.js +7 -22
  109. package/dist/core/llm/phase-router.js +29 -10
  110. package/dist/core/llm/redact.js +28 -3
  111. package/dist/core/llm/registry.js +2 -0
  112. package/dist/core/llm/request-augmentation.js +55 -0
  113. package/dist/core/llm/request-envelope.js +334 -0
  114. package/dist/core/llm/shared-request-assembly.js +35 -0
  115. package/dist/core/llm/stream-utils.js +13 -4
  116. package/dist/core/llm/utils.js +18 -29
  117. package/dist/core/memory/relevant-retrieval.js +144 -0
  118. package/dist/core/observability/logger.js +11 -2
  119. package/dist/core/patch/diff.js +1 -0
  120. package/dist/core/prompts/registry.js +39 -2
  121. package/dist/core/prompts/runtime.js +50 -12
  122. package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
  123. package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
  124. package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
  125. package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
  126. package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
  127. package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
  128. package/dist/core/prompts/templates/system/main_system.hbs +4 -16
  129. package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
  130. package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
  131. package/dist/core/prompts/templates/system/research_system.hbs +2 -0
  132. package/dist/core/protocols/a2a/agent-card.js +5 -3
  133. package/dist/core/protocols/a2a/sdk/executor.js +2 -1
  134. package/dist/core/protocols/a2a/sdk/server.js +0 -1
  135. package/dist/core/protocols/acp/formal-agent.js +300 -58
  136. package/dist/core/protocols/acp/handlers.js +5 -1
  137. package/dist/core/protocols/acp/permission-provider.js +1 -1
  138. package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
  139. package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
  140. package/dist/core/public-capabilities/projections.js +29 -0
  141. package/dist/core/public-capabilities/registry.js +26 -0
  142. package/dist/core/public-capabilities/types.js +2 -0
  143. package/dist/core/runtime/agent-server-runtime.js +47 -43
  144. package/dist/core/runtime/execution-profile.js +67 -0
  145. package/dist/core/session/artifact-state.js +160 -0
  146. package/dist/core/session/compaction/index.js +183 -0
  147. package/dist/core/session/compaction/microcompact.js +78 -0
  148. package/dist/core/session/compaction/tracking.js +48 -0
  149. package/dist/core/session/compaction/types.js +11 -0
  150. package/dist/core/session/compression.js +8 -0
  151. package/dist/core/session/manager.js +244 -8
  152. package/dist/core/session/pruning-strategy.js +55 -9
  153. package/dist/core/session/replacement-preview-provider.js +24 -0
  154. package/dist/core/session/replacement-state.js +131 -0
  155. package/dist/core/session/resume-repair/pipeline.js +79 -0
  156. package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
  157. package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
  158. package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
  159. package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
  160. package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
  161. package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
  162. package/dist/core/session/resume-repair/types.js +2 -0
  163. package/dist/core/session/summary-sync.js +164 -13
  164. package/dist/core/session/token-tracker.js +6 -0
  165. package/dist/core/skills/audit.js +34 -0
  166. package/dist/core/skills/bridge.js +84 -7
  167. package/dist/core/skills/discovery.js +94 -0
  168. package/dist/core/skills/feature-flags.js +52 -0
  169. package/dist/core/skills/index.js +1 -1
  170. package/dist/core/skills/loader.js +195 -20
  171. package/dist/core/skills/parser.js +296 -24
  172. package/dist/core/skills/permissions.js +117 -0
  173. package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
  174. package/dist/core/skills/runtime/SkillRunner.js +240 -61
  175. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
  176. package/dist/core/strata/layers/worktree.js +67 -10
  177. package/dist/core/strata/runtime/synchronizer.js +29 -2
  178. package/dist/core/streaming/stream-assembler.js +75 -31
  179. package/dist/core/sub-agent/context-snapshot.js +156 -0
  180. package/dist/core/sub-agent/core/loop.js +1 -1
  181. package/dist/core/sub-agent/core/manager.js +119 -20
  182. package/dist/core/sub-agent/dispatch-policy.js +29 -0
  183. package/dist/core/sub-agent/prefix-consistency.js +48 -0
  184. package/dist/core/sub-agent/registry-defaults.js +4 -0
  185. package/dist/core/sub-agent/tools/task-spawn.js +79 -2
  186. package/dist/core/sub-agent/types.js +134 -5
  187. package/dist/core/tools/audit.js +13 -4
  188. package/dist/core/tools/builtin/ast-grep.js +1 -1
  189. package/dist/core/tools/builtin/ast.js +1 -1
  190. package/dist/core/tools/builtin/benchmark.js +360 -0
  191. package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
  192. package/dist/core/tools/builtin/code-search/executor.js +6 -1
  193. package/dist/core/tools/builtin/code-search/spec.js +26 -2
  194. package/dist/core/tools/builtin/fs.js +256 -23
  195. package/dist/core/tools/builtin/git.js +2 -2
  196. package/dist/core/tools/builtin/index.js +51 -2
  197. package/dist/core/tools/builtin/interaction.js +8 -1
  198. package/dist/core/tools/builtin/plan.js +37 -15
  199. package/dist/core/tools/builtin/shell.js +1 -1
  200. package/dist/core/tools/loader.js +39 -16
  201. package/dist/core/tools/mapper.js +17 -3
  202. package/dist/core/tools/mcp/client.js +2 -1
  203. package/dist/core/tools/parallel/scheduler.js +35 -4
  204. package/dist/core/tools/permissions/permission-rules.js +5 -10
  205. package/dist/core/tools/policy.js +6 -1
  206. package/dist/core/tools/recoverable-tool-errors.js +10 -0
  207. package/dist/core/tools/router.js +24 -6
  208. package/dist/core/tools/session.js +458 -48
  209. package/dist/core/tools/tool-visibility.js +62 -0
  210. package/dist/core/tools/types.js +9 -1
  211. package/dist/core/types/execution.js +4 -0
  212. package/dist/core/types/flow-mode.js +8 -0
  213. package/dist/core/utils/path.js +52 -0
  214. package/dist/core/verification/runner.js +4 -1
  215. package/dist/core/version.js +17 -0
  216. package/dist/languages/typescript/index.js +4 -1
  217. package/dist/locales/en.js +35 -2
  218. package/dist/utils/eol.js +1 -1
  219. package/package.json +14 -7
  220. package/scripts/fix-es-abstract-compat.js +77 -0
  221. package/dist/core/runtime/fastify-server-bundle.js +0 -26
  222. package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
  223. package/dist/core/runtime/sidecar-paths.js +0 -47
  224. package/dist/core/runtime/sidecar-route-catalog.js +0 -103
@@ -0,0 +1,18 @@
1
+ import { Pipeline } from '../engine/pipeline/pipeline.js';
2
+ import { saveAudit } from '../steps/audit.js';
3
+ import { runAutopilot, runAutopilotVerifyGate } from '../steps/autopilot.js';
4
+ import { displayReport } from '../steps/display-report.js';
5
+ import { runPreflight } from '../steps/preflight.js';
6
+ export async function executeAutopilotFlow(initCtx) {
7
+ const pipeline = Pipeline.of(initCtx)
8
+ .step('PREFLIGHT', runPreflight)
9
+ .step('AUTOPILOT', runAutopilot)
10
+ .step('VERIFY_GATE', runAutopilotVerifyGate)
11
+ .step('REPORT', displayReport);
12
+ const report = await pipeline.execute();
13
+ report.auditPath = await saveAudit(report, initCtx.options);
14
+ report.strategyName = initCtx.mode;
15
+ report.fsMode = initCtx.mode;
16
+ return report;
17
+ }
18
+ //# sourceMappingURL=AutopilotFlow.js.map
@@ -0,0 +1,11 @@
1
+ import { resolveExecutionProfile } from '../../runtime/execution-profile.js';
2
+ import { executeAutopilotFlow } from './AutopilotFlow.js';
3
+ import { executeSalmonLoopFlow } from './SalmonLoopFlow.js';
4
+ export async function executeFlowAttempt(initCtx) {
5
+ const profile = resolveExecutionProfile(initCtx.mode);
6
+ if (profile.driver === 'agent') {
7
+ return executeAutopilotFlow(initCtx);
8
+ }
9
+ return executeSalmonLoopFlow(initCtx);
10
+ }
11
+ //# sourceMappingURL=flow-dispatch.js.map
@@ -1,16 +1,8 @@
1
- import { composeChatMessages } from '../../llm/message-composition.js';
2
1
  import { emitLlmOutput } from '../../llm/output-policy.js';
2
+ import { getAnswerSystemPrompt } from '../../prompts/runtime.js';
3
3
  import { chatWithTools } from '../../tools/session.js';
4
4
  import { Phase } from '../../types/index.js';
5
- function buildSystemPrompt() {
6
- return [
7
- 'You are a coding assistant in "answer" mode.',
8
- 'You may use read-only tools to inspect the repository when helpful.',
9
- 'Never write files, never apply patches, and never run shell commands.',
10
- 'If repository inspection is not required, answer directly without tools.',
11
- 'Answer in the same language as the user.',
12
- ].join('\n');
13
- }
5
+ import { buildSharedRequestEnvelope } from './request-assembly.js';
14
6
  export async function generateAnswer(ctx) {
15
7
  const instruction = String(ctx.options.instruction ?? '').trim();
16
8
  if (!instruction) {
@@ -19,15 +11,21 @@ export async function generateAnswer(ctx) {
19
11
  report: { kind: 'answer', summary: '', timestamp: Date.now() },
20
12
  };
21
13
  }
22
- const messages = composeChatMessages({
23
- system: buildSystemPrompt(),
24
- user: instruction,
14
+ const shared = buildSharedRequestEnvelope({
15
+ defaultNamespace: 'answer',
16
+ systemPrompt: await getAnswerSystemPrompt(),
17
+ userPrompt: instruction,
25
18
  conversationContext: ctx.options.conversationContext,
26
19
  });
20
+ const messages = shared.baseMessages;
27
21
  const llmClient = ctx.options.llm;
28
22
  const supportsTools = Boolean(ctx.toolstack);
29
23
  const assistant = supportsTools
30
- ? await chatWithTools(messages, { temperature: 0.2, signal: ctx.options.signal }, {
24
+ ? await chatWithTools(messages, {
25
+ providerHints: shared.envelope.providerHints,
26
+ temperature: 0.2,
27
+ signal: ctx.options.signal,
28
+ }, {
31
29
  phase: Phase.EXPLORE,
32
30
  llm: llmClient,
33
31
  runtime: {
@@ -48,6 +46,7 @@ export async function generateAnswer(ctx) {
48
46
  },
49
47
  })
50
48
  : await llmClient.chat(messages, {
49
+ providerHints: shared.envelope.providerHints,
51
50
  temperature: 0.2,
52
51
  signal: ctx.options.signal,
53
52
  tools: [],
@@ -0,0 +1,396 @@
1
+ import { createHash } from 'crypto';
2
+ import { join } from 'path';
3
+ import { text } from '../../../locales/index.js';
4
+ import { lstat, readlink } from '../../adapters/fs/node-fs.js';
5
+ import { GitAdapter } from '../../adapters/git/git-adapter.js';
6
+ import { LIMITS } from '../../config/limits.js';
7
+ import { supportsLlmStreaming } from '../../llm/capabilities.js';
8
+ import { emitLlmOutput } from '../../llm/output-policy.js';
9
+ import { getAutopilotSystemPrompt } from '../../prompts/runtime.js';
10
+ import { SessionReplacementPreviewProvider } from '../../session/replacement-preview-provider.js';
11
+ import { chatWithTools, chatWithToolsStreaming } from '../../tools/session.js';
12
+ import { Phase } from '../../types/runtime.js';
13
+ import { resolveLlmToolCallingPolicy } from '../dsl/llm-strategy.js';
14
+ import { buildAugmentedRequestEnvelope } from './request-assembly.js';
15
+ import { buildPhaseToolRuntimeContext, buildToolVisibilityRuntime } from './tool-runtime.js';
16
+ import { executeVerifyForWorkspace } from './verify-shared.js';
17
+ const AUTOPILOT_TOOL_PHASE = Phase.AUTOPILOT;
18
+ const WORKSPACE_SAMPLE_LIMITS = {
19
+ maxStdoutBytes: LIMITS.maxToolOutputBytes,
20
+ maxStderrChars: 16_384,
21
+ };
22
+ const GIT_HASH_OUTPUT_LIMITS = {
23
+ maxStdoutBytes: 256,
24
+ maxStderrChars: 4_096,
25
+ };
26
+ function hashFingerprintValue(value) {
27
+ return createHash('sha256').update(value).digest('hex');
28
+ }
29
+ function hashFingerprintBuffer(buffer) {
30
+ return createHash('sha256').update(buffer).digest('hex');
31
+ }
32
+ function decodeNulSeparatedRecords(buffer) {
33
+ return buffer
34
+ .toString('utf8')
35
+ .split('\0')
36
+ .filter((value) => value.length > 0);
37
+ }
38
+ function readSpaceDelimitedField(record, fieldIndex) {
39
+ let fieldStart = 0;
40
+ let currentField = 0;
41
+ for (let index = 0; index <= record.length; index += 1) {
42
+ const atSeparator = index === record.length || record[index] === ' ';
43
+ if (!atSeparator) {
44
+ continue;
45
+ }
46
+ if (currentField === fieldIndex) {
47
+ return record.slice(fieldStart, index);
48
+ }
49
+ currentField += 1;
50
+ fieldStart = index + 1;
51
+ }
52
+ throw new Error(`Malformed status record: ${record}`);
53
+ }
54
+ function readPathAfterFieldCount(record, fieldCount) {
55
+ let spacesSeen = 0;
56
+ for (let index = 0; index < record.length; index += 1) {
57
+ if (record[index] !== ' ') {
58
+ continue;
59
+ }
60
+ spacesSeen += 1;
61
+ if (spacesSeen === fieldCount) {
62
+ return record.slice(index + 1);
63
+ }
64
+ }
65
+ throw new Error(`Malformed status record: ${record}`);
66
+ }
67
+ function collectWorkspaceStatusEntries(statusOutput) {
68
+ const records = decodeNulSeparatedRecords(statusOutput);
69
+ const entries = [];
70
+ for (let index = 0; index < records.length; index += 1) {
71
+ const record = records[index];
72
+ const kind = record[0];
73
+ if (kind === '?') {
74
+ entries.push({
75
+ path: readPathAfterFieldCount(record, 1),
76
+ statusFingerprint: hashFingerprintValue(record),
77
+ hashable: true,
78
+ });
79
+ continue;
80
+ }
81
+ if (kind === '!') {
82
+ continue;
83
+ }
84
+ if (kind === '1') {
85
+ entries.push({
86
+ path: readPathAfterFieldCount(record, 8),
87
+ statusFingerprint: hashFingerprintValue(record),
88
+ hashable: readSpaceDelimitedField(record, 5) !== '000000',
89
+ });
90
+ continue;
91
+ }
92
+ if (kind === '2') {
93
+ const path = readPathAfterFieldCount(record, 9);
94
+ const originalPath = records[index + 1] ?? '';
95
+ const statusFingerprint = hashFingerprintValue(`${record}\0${originalPath}`);
96
+ entries.push({
97
+ path,
98
+ statusFingerprint,
99
+ hashable: readSpaceDelimitedField(record, 5) !== '000000',
100
+ });
101
+ if (originalPath) {
102
+ entries.push({
103
+ path: originalPath,
104
+ statusFingerprint: hashFingerprintValue(`rename-source:${record}\0${originalPath}`),
105
+ hashable: false,
106
+ });
107
+ }
108
+ index += 1;
109
+ continue;
110
+ }
111
+ if (kind === 'u') {
112
+ entries.push({
113
+ path: readPathAfterFieldCount(record, 10),
114
+ statusFingerprint: hashFingerprintValue(record),
115
+ hashable: readSpaceDelimitedField(record, 6) !== '000000',
116
+ });
117
+ continue;
118
+ }
119
+ throw new Error(`Unsupported status record: ${record}`);
120
+ }
121
+ return entries;
122
+ }
123
+ async function runBoundedGit(git, workspacePath, args, limits) {
124
+ const result = await git.execMeta(args, {
125
+ cwd: workspacePath,
126
+ limits,
127
+ timeoutMs: LIMITS.gitTimeoutMs,
128
+ });
129
+ if (result.stdoutTruncated) {
130
+ throw new Error(text.git.outputTruncated(limits.maxStdoutBytes));
131
+ }
132
+ if (!result.ok) {
133
+ throw new Error(result.error?.message || result.stderr.trim() || `git ${args.join(' ')} failed`);
134
+ }
135
+ return result.stdout;
136
+ }
137
+ async function hashWorkingPath(git, workspacePath, filePath) {
138
+ const output = await runBoundedGit(git, workspacePath, ['hash-object', '--no-filters', '--', filePath], GIT_HASH_OUTPUT_LIMITS);
139
+ return output.toString('utf8').trim();
140
+ }
141
+ async function fingerprintWorkingPath(git, workspacePath, filePath) {
142
+ const absolutePath = join(workspacePath, filePath);
143
+ const stats = await lstat(absolutePath);
144
+ if (stats.isSymbolicLink()) {
145
+ return `symlink:${hashFingerprintValue(await readlink(absolutePath))}`;
146
+ }
147
+ return `file:${await hashWorkingPath(git, workspacePath, filePath)}`;
148
+ }
149
+ async function captureWorkspaceFingerprint(workspacePath) {
150
+ const git = new GitAdapter(workspacePath);
151
+ const head = (await runBoundedGit(git, workspacePath, ['rev-parse', 'HEAD'], GIT_HASH_OUTPUT_LIMITS))
152
+ .toString('utf8')
153
+ .trim();
154
+ const index = (await runBoundedGit(git, workspacePath, ['write-tree'], GIT_HASH_OUTPUT_LIMITS))
155
+ .toString('utf8')
156
+ .trim();
157
+ const statusOutput = await runBoundedGit(git, workspacePath, ['status', '--porcelain=v2', '-z', '--untracked-files=all', '--ignored=no'], WORKSPACE_SAMPLE_LIMITS);
158
+ const statusEntries = collectWorkspaceStatusEntries(statusOutput);
159
+ const workingEntries = [];
160
+ for (const entry of statusEntries) {
161
+ if (!entry.hashable) {
162
+ continue;
163
+ }
164
+ workingEntries.push([entry.path, await fingerprintWorkingPath(git, workspacePath, entry.path)]);
165
+ }
166
+ const workingContent = hashFingerprintValue(workingEntries.map(([path, fingerprint]) => `${path}:${fingerprint}`).join('\n'));
167
+ return {
168
+ head,
169
+ index,
170
+ statusMetadata: hashFingerprintBuffer(statusOutput),
171
+ workingContent,
172
+ statusEntries: statusEntries.map(({ path, statusFingerprint }) => [path, statusFingerprint]),
173
+ workingEntries,
174
+ };
175
+ }
176
+ function collectChangedWorkspacePaths(before, after) {
177
+ const beforeStatusEntries = new Map(before.statusEntries);
178
+ const afterStatusEntries = new Map(after.statusEntries);
179
+ const beforeEntries = new Map(before.workingEntries);
180
+ const afterEntries = new Map(after.workingEntries);
181
+ const paths = new Set([
182
+ ...beforeStatusEntries.keys(),
183
+ ...afterStatusEntries.keys(),
184
+ ...beforeEntries.keys(),
185
+ ...afterEntries.keys(),
186
+ ]);
187
+ return [...paths]
188
+ .filter((path) => beforeStatusEntries.get(path) !== afterStatusEntries.get(path) ||
189
+ beforeEntries.get(path) !== afterEntries.get(path))
190
+ .filter((path) => !isRuntimeGeneratedPath(path))
191
+ .sort((left, right) => left.localeCompare(right));
192
+ }
193
+ function isRuntimeGeneratedPath(path) {
194
+ if (path === '.salmonloop' || path.startsWith('.salmonloop/'))
195
+ return true;
196
+ if (path === 'headless.jsonl' || path === 'headless.stderr')
197
+ return true;
198
+ return false;
199
+ }
200
+ function lastFailedToolAuditEntry(entries) {
201
+ if (!Array.isArray(entries))
202
+ return undefined;
203
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
204
+ const entry = entries[index];
205
+ if (entry?.toolResultStatus && entry.toolResultStatus !== 'ok') {
206
+ return entry;
207
+ }
208
+ }
209
+ return undefined;
210
+ }
211
+ function resolveAutopilotCompletion(params) {
212
+ if (params.mutated)
213
+ return { status: 'changed' };
214
+ const failedTool = lastFailedToolAuditEntry(params.localAudit);
215
+ if (failedTool) {
216
+ const reason = failedTool.toolResultErrorMessage
217
+ ? `Tool ${failedTool.toolName} failed: ${failedTool.toolResultErrorMessage}`
218
+ : `Tool ${failedTool.toolName} failed.`;
219
+ return {
220
+ status: 'tool_failure',
221
+ reason,
222
+ errorCode: failedTool.toolResultErrorCode,
223
+ };
224
+ }
225
+ if (params.content.trim()) {
226
+ return { status: 'read_only_answer' };
227
+ }
228
+ return {
229
+ status: 'no_effect',
230
+ reason: 'Autopilot completed without changing files or producing an answer.',
231
+ };
232
+ }
233
+ function buildAutopilotRequestContext(ctx, instruction) {
234
+ const maybeContext = ctx.context;
235
+ if (maybeContext?.repoPath && Array.isArray(maybeContext.rgSnippets)) {
236
+ return maybeContext;
237
+ }
238
+ return {
239
+ repoPath: ctx.workspace.workPath,
240
+ instruction,
241
+ contextHash: `autopilot:${ctx.workspace.workPath}`,
242
+ rgSnippets: [],
243
+ };
244
+ }
245
+ export async function runAutopilot(ctx) {
246
+ const instruction = String(ctx.options.instruction ?? '').trim();
247
+ if (!instruction) {
248
+ return {
249
+ ...ctx,
250
+ mutated: false,
251
+ report: { kind: 'answer', summary: '', timestamp: Date.now() },
252
+ };
253
+ }
254
+ const toolVisibility = buildToolVisibilityRuntime(ctx);
255
+ const requestContext = buildAutopilotRequestContext(ctx, instruction);
256
+ const shared = await buildAugmentedRequestEnvelope({
257
+ phase: AUTOPILOT_TOOL_PHASE,
258
+ defaultNamespace: 'autopilot',
259
+ systemPrompt: await getAutopilotSystemPrompt(),
260
+ context: requestContext,
261
+ baseContextPrompt: instruction,
262
+ buildUserPrompt: async (contextPrompt) => contextPrompt,
263
+ conversationContext: ctx.options.conversationContext,
264
+ artifactHints: ctx.artifactHints,
265
+ toolCallingAudit: ctx.toolCallingAudit,
266
+ previewProvider: new SessionReplacementPreviewProvider(ctx.replacementState),
267
+ toolVisibility: {
268
+ toolstack: ctx.toolstack,
269
+ runtime: toolVisibility,
270
+ worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
271
+ flowMode: ctx.mode,
272
+ },
273
+ });
274
+ const llmClient = ctx.options.llm;
275
+ const toolPolicy = resolveLlmToolCallingPolicy(AUTOPILOT_TOOL_PHASE, llmClient);
276
+ const localAudit = [];
277
+ const supportsStreaming = supportsLlmStreaming(llmClient, AUTOPILOT_TOOL_PHASE);
278
+ const supportsTools = Boolean(ctx.toolstack && toolPolicy.enabled);
279
+ let workspaceFingerprintBefore = null;
280
+ let samplingFailedClosed = false;
281
+ if (supportsTools) {
282
+ try {
283
+ workspaceFingerprintBefore = await captureWorkspaceFingerprint(ctx.workspace.workPath);
284
+ }
285
+ catch {
286
+ samplingFailedClosed = true;
287
+ }
288
+ }
289
+ const assistant = supportsTools
290
+ ? await (supportsStreaming ? chatWithToolsStreaming : chatWithTools)(shared.baseMessages, {
291
+ phase: AUTOPILOT_TOOL_PHASE,
292
+ providerHints: shared.envelope.providerHints,
293
+ temperature: 0.2,
294
+ signal: ctx.options.signal,
295
+ }, {
296
+ phase: AUTOPILOT_TOOL_PHASE,
297
+ llm: llmClient,
298
+ runtime: buildPhaseToolRuntimeContext(ctx, AUTOPILOT_TOOL_PHASE, shared.cacheSurface),
299
+ toolVisibility,
300
+ toolstack: ctx.toolstack,
301
+ eventPayload: ctx.options.eventPayload,
302
+ toolCallingAudit: {
303
+ event: (entry) => {
304
+ localAudit.push(entry);
305
+ },
306
+ },
307
+ maxRounds: toolPolicy.maxRounds,
308
+ llmOutput: {
309
+ policy: ctx.options.llmOutput,
310
+ kind: 'assistant_message',
311
+ step: 'REPORT',
312
+ },
313
+ emit: (event) => ctx.emit({ ...event, timestamp: event.timestamp ?? new Date() }),
314
+ })
315
+ : await llmClient.chat(shared.baseMessages, {
316
+ phase: 'AUTOPILOT',
317
+ providerHints: shared.envelope.providerHints,
318
+ temperature: 0.2,
319
+ signal: ctx.options.signal,
320
+ tools: [],
321
+ toolChoice: 'none',
322
+ });
323
+ const content = String(assistant?.content ?? '').trim();
324
+ if (!supportsTools) {
325
+ emitLlmOutput({
326
+ emit: ctx.emit,
327
+ policy: ctx.options.llmOutput,
328
+ kind: 'assistant_message',
329
+ step: 'REPORT',
330
+ content,
331
+ });
332
+ }
333
+ const mergedAudit = localAudit.length > 0 ? [...(ctx.toolCallingAudit ?? []), ...localAudit] : ctx.toolCallingAudit;
334
+ let mutated = false;
335
+ let changedFiles;
336
+ if (supportsTools) {
337
+ if (samplingFailedClosed || !workspaceFingerprintBefore) {
338
+ mutated = true;
339
+ }
340
+ else {
341
+ try {
342
+ const workspaceFingerprintAfter = await captureWorkspaceFingerprint(ctx.workspace.workPath);
343
+ changedFiles = collectChangedWorkspacePaths(workspaceFingerprintBefore, workspaceFingerprintAfter);
344
+ mutated =
345
+ changedFiles.length > 0 ||
346
+ workspaceFingerprintBefore.head !== workspaceFingerprintAfter.head ||
347
+ workspaceFingerprintBefore.index !== workspaceFingerprintAfter.index;
348
+ }
349
+ catch {
350
+ mutated = true;
351
+ }
352
+ }
353
+ }
354
+ return {
355
+ ...ctx,
356
+ mutated,
357
+ changedFiles: changedFiles && changedFiles.length > 0 ? changedFiles : undefined,
358
+ completion: resolveAutopilotCompletion({ content, mutated, localAudit }),
359
+ toolCallingAudit: mergedAudit,
360
+ report: {
361
+ kind: 'answer',
362
+ summary: content,
363
+ timestamp: Date.now(),
364
+ },
365
+ };
366
+ }
367
+ export async function runAutopilotVerifyGate(ctx) {
368
+ if (!ctx.mutated) {
369
+ return {
370
+ ...ctx,
371
+ verifyResult: undefined,
372
+ };
373
+ }
374
+ if (!ctx.options.verify) {
375
+ return {
376
+ ...ctx,
377
+ completion: {
378
+ status: 'verification_missing',
379
+ reason: 'Autopilot changed the workspace but no verification command was configured.',
380
+ errorCode: 'VERIFY_COMMAND_MISSING',
381
+ },
382
+ verifyResult: undefined,
383
+ };
384
+ }
385
+ const { verifyResult, verifyArtifact } = await executeVerifyForWorkspace({
386
+ workspacePath: ctx.workspace.workPath,
387
+ verify: ctx.options.verify,
388
+ signal: ctx.options.signal,
389
+ });
390
+ const nextCtx = {
391
+ ...ctx,
392
+ verifyResult,
393
+ };
394
+ return verifyArtifact ? { ...nextCtx, verifyArtifact } : nextCtx;
395
+ }
396
+ //# sourceMappingURL=autopilot.js.map
@@ -0,0 +1,29 @@
1
+ export function resolveCacheSharingSurface(args) {
2
+ const namespace = args.cacheSharing?.namespace ?? args.defaultNamespace;
3
+ const sharedContextHash = args.cacheSharing?.contextHash;
4
+ const localContextHash = args.localContextHash;
5
+ const mismatchPolicy = args.mismatchPolicy ?? 'prefer_local';
6
+ if (typeof localContextHash === 'string' &&
7
+ localContextHash.length > 0 &&
8
+ typeof sharedContextHash === 'string' &&
9
+ sharedContextHash.length > 0 &&
10
+ sharedContextHash !== localContextHash) {
11
+ args.onMismatch?.({
12
+ phase: args.phase,
13
+ namespace,
14
+ localContextHash,
15
+ sharedContextHash,
16
+ });
17
+ if (mismatchPolicy === 'prefer_local') {
18
+ return {
19
+ namespace: args.defaultNamespace,
20
+ contextHash: localContextHash,
21
+ };
22
+ }
23
+ }
24
+ return {
25
+ namespace,
26
+ contextHash: sharedContextHash ?? localContextHash,
27
+ };
28
+ }
29
+ //# sourceMappingURL=cache-sharing.js.map
@@ -1,14 +1,17 @@
1
1
  import path from 'path';
2
2
  import { text } from '../../../locales/index.js';
3
- import { composeChatMessages } from '../../llm/message-composition.js';
4
- import { formatContextForPrompt } from '../../llm/utils.js';
3
+ import { supportsLlmStreaming } from '../../llm/capabilities.js';
4
+ import { recordAuditEvent } from '../../observability/audit-trail.js';
5
5
  import { getExplorePrompt, getExploreSystemPrompt } from '../../prompts/runtime.js';
6
+ import { SessionReplacementPreviewProvider } from '../../session/replacement-preview-provider.js';
6
7
  import { chatWithTools, chatWithToolsStreaming } from '../../tools/session.js';
7
8
  import { SalmonError } from '../../types/errors.js';
8
9
  import { Phase } from '../../types/runtime.js';
9
10
  import { ensureInSandbox, isSafeRelativePath, normalizePath } from '../../utils/path.js';
10
11
  import { resolveLlmToolCallingPolicy } from '../dsl/llm-strategy.js';
11
12
  import { ContextValidator } from '../validation/ContextValidator.js';
13
+ import { buildPhaseRequestEnvelope } from './request-assembly.js';
14
+ import { buildPhaseToolRuntimeContext, buildToolVisibilityRuntime } from './tool-runtime.js';
12
15
  const SAFE_INFERRED_EXTENSIONS = new Set([
13
16
  '.ts',
14
17
  '.tsx',
@@ -76,9 +79,8 @@ export const exploreCodebase = async (ctx) => {
76
79
  });
77
80
  return { ...ctx };
78
81
  }
79
- const prompt = await getExplorePrompt(formatContextForPrompt(ctx.context), ctx.options.instruction, ctx.lastError);
80
- const systemPrompt = await getExploreSystemPrompt(toolstack.registry, { plan: ctx.planRuntime });
81
- const supportsStreaming = typeof ctx.options.llm.chatStream === 'function';
82
+ const systemPrompt = await getExploreSystemPrompt({ plan: ctx.planRuntime });
83
+ const supportsStreaming = supportsLlmStreaming(ctx.options.llm, Phase.EXPLORE);
82
84
  const llmOutput = {
83
85
  policy: ctx.options.llmOutput,
84
86
  kind: 'explore',
@@ -128,29 +130,43 @@ export const exploreCodebase = async (ctx) => {
128
130
  router: proxiedRouter,
129
131
  };
130
132
  const localAudit = [];
131
- const baseMessages = composeChatMessages({
132
- system: systemPrompt,
133
- user: prompt,
133
+ const toolVisibility = buildToolVisibilityRuntime(ctx);
134
+ const requestEnvelope = await buildPhaseRequestEnvelope({
135
+ phase: Phase.EXPLORE,
136
+ defaultNamespace: 'explore',
137
+ context: ctx.context,
138
+ contextResult: ctx.contextResult,
139
+ cacheSharing: ctx.cacheSharing,
140
+ onCacheMismatch: (mismatch) => {
141
+ recordAuditEvent('request.cache_sharing_hash_mismatch', mismatch, {
142
+ source: 'llm',
143
+ severity: 'low',
144
+ scope: 'session',
145
+ phase: Phase.EXPLORE,
146
+ });
147
+ },
148
+ systemPrompt,
149
+ buildUserPrompt: (contextPrompt) => getExplorePrompt(contextPrompt, ctx.options.instruction, ctx.lastError),
134
150
  conversationContext: ctx.options.conversationContext,
151
+ artifactHints: ctx.artifactHints,
152
+ toolCallingAudit: ctx.toolCallingAudit,
153
+ previewProvider: new SessionReplacementPreviewProvider(ctx.replacementState),
154
+ toolVisibility: {
155
+ toolstack,
156
+ runtime: toolVisibility,
157
+ worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
158
+ flowMode: ctx.mode,
159
+ },
135
160
  });
161
+ const { cacheSurface, envelope, baseMessages } = requestEnvelope;
136
162
  await (supportsStreaming ? chatWithToolsStreaming : chatWithTools)(baseMessages, {
163
+ providerHints: envelope.providerHints,
137
164
  signal: ctx.options.signal,
138
165
  }, {
139
166
  phase: Phase.EXPLORE,
140
167
  llm: ctx.options.llm,
141
- runtime: {
142
- repoRoot: ctx.workspace.workPath,
143
- persistenceRoot: ctx.workspace.baseRepoPath || ctx.workspace.workPath,
144
- worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
145
- attemptId: ctx.attempt ?? 1,
146
- dryRun: Boolean(ctx.options?.dryRun),
147
- llm: ctx.options.llm,
148
- model: ctx.options.llm.getModelId?.() || process.env.SALMONLOOP_MODEL || process.env.S8P_MODEL,
149
- userInputProvider: ctx.options.userInputProvider,
150
- agentKind: ctx.options.agentKind ?? 'primary',
151
- languagePlugins: ctx.options.languagePlugins,
152
- subAgentController: ctx.options.subAgentController,
153
- },
168
+ runtime: buildPhaseToolRuntimeContext(ctx, Phase.EXPLORE, cacheSurface),
169
+ toolVisibility,
154
170
  toolstack: proxiedToolstack,
155
171
  eventPayload: ctx.options.eventPayload,
156
172
  toolCallingAudit: {
@@ -1,9 +1,6 @@
1
1
  import { text } from '../../../locales/index.js';
2
2
  import { emitLlmOutput } from '../../llm/output-policy.js';
3
- function buildReviewPrompt(context) {
4
- const summary = JSON.stringify(context, null, 2);
5
- return `Please review the following context and provide suggestions for improvement:\n\n${summary}`;
6
- }
3
+ import { getReviewPrompt } from '../../prompts/runtime.js';
7
4
  function normalizeReviewSuggestions(value) {
8
5
  if (value == null)
9
6
  return null;
@@ -31,7 +28,7 @@ function parseReviewResponse(content) {
31
28
  }
32
29
  }
33
30
  export async function generateReview(ctx) {
34
- const reviewPrompt = buildReviewPrompt(ctx.context);
31
+ const reviewPrompt = await getReviewPrompt(JSON.stringify(ctx.context, null, 2));
35
32
  const llmClient = ctx.options.llm;
36
33
  const response = await llmClient.chat([{ role: 'user', content: reviewPrompt }], {
37
34
  tools: [],
@@ -0,0 +1,10 @@
1
+ import { GitAdapter } from '../../../adapters/git/git-adapter.js';
2
+ export async function checkPatchApplies(args, deps) {
3
+ const git = deps?.createGitAdapter?.(args.repoRoot) ?? new GitAdapter(args.repoRoot);
4
+ return git.execMeta(['apply', '--check', '--recount', '--ignore-whitespace', '--whitespace=nowarn', '-'], {
5
+ input: Buffer.from(args.diff, 'utf8'),
6
+ timeoutMs: 15000,
7
+ limits: { maxStdoutBytes: 0, maxStderrChars: 4000 },
8
+ });
9
+ }
10
+ //# sourceMappingURL=apply-check.js.map