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
@@ -0,0 +1,46 @@
1
+ import { extractAndValidatePatch } from './diff-normalization.js';
2
+ const SALVAGEABLE_PATCH_CODES = new Set(['LLM_PATCH_NOT_UNIFIED_DIFF', 'LLM_PATCH_EMPTY']);
3
+ function errorMessage(error) {
4
+ if (error instanceof Error)
5
+ return error.message;
6
+ return String(error);
7
+ }
8
+ export function isSalvageablePatchError(error) {
9
+ if (!error || typeof error !== 'object')
10
+ return false;
11
+ const llmCode = error.llmCode;
12
+ return typeof llmCode === 'string' && SALVAGEABLE_PATCH_CODES.has(llmCode);
13
+ }
14
+ export async function salvagePatchDiff(args) {
15
+ if (!isSalvageablePatchError(args.initialError))
16
+ return null;
17
+ args.onAttempt?.({
18
+ reason: errorMessage(args.initialError),
19
+ badContentLength: args.rawContent.length,
20
+ });
21
+ const repaired = await args.repair({ badContent: args.rawContent });
22
+ const repairedContent = repaired.content || '';
23
+ try {
24
+ const validated = extractAndValidatePatch({
25
+ rawContent: repairedContent,
26
+ plannedFiles: args.plannedFiles,
27
+ });
28
+ args.onResult?.({
29
+ ok: true,
30
+ contentLength: repairedContent.length,
31
+ });
32
+ return {
33
+ ...validated,
34
+ rawContent: repairedContent,
35
+ };
36
+ }
37
+ catch (salvageError) {
38
+ args.onResult?.({
39
+ ok: false,
40
+ contentLength: repairedContent.length,
41
+ error: errorMessage(salvageError).slice(0, 400),
42
+ });
43
+ throw args.initialError;
44
+ }
45
+ }
46
+ //# sourceMappingURL=diff-salvage.js.map
@@ -0,0 +1,42 @@
1
+ import { LIMITS } from '../../../config/limits.js';
2
+ import { getPatchPrompt, getPatchSystemPrompt } from '../../../prompts/runtime.js';
3
+ import { SessionReplacementPreviewProvider } from '../../../session/replacement-preview-provider.js';
4
+ import { Phase } from '../../../types/runtime.js';
5
+ import { buildPhaseRequestEnvelope } from '../request-assembly.js';
6
+ export async function buildPatchPromptInput(args) {
7
+ const planStr = JSON.stringify(args.plan, null, 2);
8
+ const systemPrompt = await getPatchSystemPrompt(args.promptVisibleTools, {
9
+ plan: args.planRuntime,
10
+ });
11
+ const requestEnvelope = await buildPhaseRequestEnvelope({
12
+ phase: args.phase ?? Phase.PATCH,
13
+ defaultNamespace: 'patch',
14
+ context: args.context,
15
+ contextResult: args.contextResult,
16
+ cacheSharing: args.cacheSharing,
17
+ onCacheMismatch: args.onCacheMismatch,
18
+ systemPrompt,
19
+ buildUserPrompt: (contextPrompt) => getPatchPrompt(planStr, contextPrompt, LIMITS.maxFilesChanged, LIMITS.maxDiffLines, args.lastError),
20
+ conversationContext: args.conversationContext,
21
+ artifactHints: args.artifactHints,
22
+ previewProvider: new SessionReplacementPreviewProvider(args.replacementState),
23
+ toolCallingAudit: args.toolCallingAudit,
24
+ toolVisibility: args.toolVisibility,
25
+ extraAttachments: [
26
+ {
27
+ key: 'plan-json',
28
+ kind: 'plan',
29
+ label: 'Plan JSON',
30
+ content: planStr,
31
+ },
32
+ ],
33
+ });
34
+ return {
35
+ planStr,
36
+ systemPrompt,
37
+ envelope: requestEnvelope.envelope,
38
+ baseMessages: requestEnvelope.baseMessages,
39
+ cacheSurface: requestEnvelope.cacheSurface,
40
+ };
41
+ }
42
+ //# sourceMappingURL=prompt-input.js.map
@@ -1,98 +1,116 @@
1
- import { text } from '../../../locales/index.js';
2
- import { GitAdapter } from '../../adapters/git/git-adapter.js';
3
- import { LIMITS } from '../../config/limits.js';
1
+ import { supportsLlmStreaming } from '../../llm/capabilities.js';
4
2
  import { repairToUnifiedDiff } from '../../llm/contracts/repair.js';
5
- import { wrapPatchEmpty, wrapPatchInvalid, wrapPatchNotUnifiedDiff } from '../../llm/errors.js';
6
- import { composeChatMessages } from '../../llm/message-composition.js';
7
3
  import { emitLlmOutput } from '../../llm/output-policy.js';
8
- import { extractUnifiedDiffFromLLMContent, formatContextForPrompt } from '../../llm/utils.js';
9
- import { normalizeDiff, validateDiff } from '../../patch/diff.js';
10
- import { getPatchPrompt, getPatchSystemPrompt } from '../../prompts/runtime.js';
4
+ import { recordAuditEvent } from '../../observability/audit-trail.js';
11
5
  import { chatWithTools, chatWithToolsStreaming } from '../../tools/session.js';
12
- import { DiffValidationError } from '../../types/errors.js';
6
+ import { resolveVisibleToolSpecs } from '../../tools/tool-visibility.js';
13
7
  import { Phase } from '../../types/runtime.js';
14
8
  import { resolveLlmToolCallingPolicy } from '../dsl/llm-strategy.js';
15
- async function checkPatchApplies(args) {
16
- const git = new GitAdapter(args.repoRoot);
17
- return git.execMeta(['apply', '--check', '--recount', '--ignore-whitespace', '--whitespace=nowarn', '-'], {
18
- input: Buffer.from(args.diff, 'utf8'),
19
- timeoutMs: 15000,
20
- limits: { maxStdoutBytes: 0, maxStderrChars: 4000 },
9
+ import { checkPatchApplies } from './patch/apply-check.js';
10
+ import { extractAndValidatePatch } from './patch/diff-normalization.js';
11
+ import { salvagePatchDiff } from './patch/diff-salvage.js';
12
+ import { buildPatchPromptInput } from './patch/prompt-input.js';
13
+ import { buildPhaseToolRuntimeContext, buildToolVisibilityRuntime } from './tool-runtime.js';
14
+ const recordPatchSalvageAttempt = (args) => {
15
+ recordAuditEvent('patch.salvage.attempt', {
16
+ reason: args.reason,
17
+ badContentLength: args.badContentLength,
18
+ toolsDisabled: true,
19
+ }, { source: 'patch', severity: 'low', scope: 'session', phase: 'PATCH' });
20
+ };
21
+ const recordPatchSalvageResult = (args) => {
22
+ recordAuditEvent('patch.salvage.result', {
23
+ ok: args.ok,
24
+ contentLength: args.contentLength,
25
+ error: args.error,
26
+ }, {
27
+ source: 'patch',
28
+ severity: args.ok ? 'low' : 'medium',
29
+ scope: 'session',
30
+ phase: 'PATCH',
21
31
  });
22
- }
32
+ };
23
33
  export const generatePatch = async (ctx) => {
24
34
  const toolstack = ctx.toolstack;
25
35
  const toolPolicy = resolveLlmToolCallingPolicy(Phase.PATCH, ctx.options.llm);
26
36
  // Backwards-compatible fallback for non-tool-capable LLMs.
27
37
  if (!toolstack || !toolPolicy.enabled) {
28
- const patch = await ctx.options.llm.createPatch(ctx.context, ctx.plan, ctx.lastError, ctx.options.signal);
38
+ const legacyPatch = await ctx.options.llm.createPatch(ctx.context, ctx.plan, ctx.lastError, ctx.options.signal);
29
39
  emitLlmOutput({
30
40
  emit: ctx.emit,
31
41
  policy: ctx.options.llmOutput,
32
42
  kind: 'patch',
33
43
  step: 'PATCH',
34
- content: patch,
44
+ content: legacyPatch,
45
+ });
46
+ const validated = extractAndValidatePatch({
47
+ rawContent: legacyPatch,
48
+ plannedFiles: ctx.plan.files,
35
49
  });
36
- const normalizedPatch = normalizeDiff(patch);
37
- let diffMeta;
38
- try {
39
- diffMeta = validateDiff(normalizedPatch);
40
- }
41
- catch (e) {
42
- if (e instanceof DiffValidationError) {
43
- if (e.message === text.diff.notUnifiedFormat)
44
- throw wrapPatchNotUnifiedDiff();
45
- if (e.message.startsWith(text.llm.patchEmpty()))
46
- throw wrapPatchEmpty();
47
- throw wrapPatchInvalid(e.message);
48
- }
49
- throw e;
50
- }
51
50
  ctx.emit({
52
51
  type: 'log',
53
52
  level: 'debug',
54
- message: `Patch generated: ${diffMeta.changedFiles.length} files changed`,
53
+ message: `Patch generated: ${validated.diffMeta.changedFiles.length} files changed`,
55
54
  timestamp: new Date(),
56
55
  });
57
56
  return {
58
57
  ...ctx,
59
- diff: normalizedPatch,
60
- diffMeta,
61
- changedFiles: diffMeta.changedFiles,
58
+ diff: validated.normalizedPatch,
59
+ diffMeta: validated.diffMeta,
60
+ changedFiles: validated.diffMeta.changedFiles,
62
61
  };
63
62
  }
64
- const planStr = JSON.stringify(ctx.plan, null, 2);
65
- const prompt = await getPatchPrompt(planStr, formatContextForPrompt(ctx.context), LIMITS.maxFilesChanged, LIMITS.maxDiffLines, ctx.lastError);
66
- const systemPrompt = await getPatchSystemPrompt(toolstack?.registry, { plan: ctx.planRuntime });
67
- const baseMessages = composeChatMessages({
68
- system: systemPrompt,
69
- user: prompt,
63
+ const toolVisibility = buildToolVisibilityRuntime(ctx);
64
+ const promptVisibleTools = resolveVisibleToolSpecs({
65
+ phase: Phase.PATCH,
66
+ toolstack,
67
+ worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
68
+ flowMode: ctx.mode,
69
+ runtime: toolVisibility,
70
+ });
71
+ const patchPromptInput = await buildPatchPromptInput({
72
+ context: ctx.context,
73
+ contextResult: ctx.contextResult,
74
+ plan: ctx.plan,
75
+ planRuntime: ctx.planRuntime,
76
+ lastError: ctx.lastError,
77
+ promptVisibleTools,
78
+ toolVisibility: {
79
+ toolstack,
80
+ runtime: toolVisibility,
81
+ worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
82
+ flowMode: ctx.mode,
83
+ },
84
+ phase: Phase.PATCH,
85
+ cacheSharing: ctx.cacheSharing,
86
+ onCacheMismatch: (mismatch) => {
87
+ recordAuditEvent('request.cache_sharing_hash_mismatch', mismatch, {
88
+ source: 'llm',
89
+ severity: 'low',
90
+ scope: 'session',
91
+ phase: Phase.PATCH,
92
+ });
93
+ },
70
94
  conversationContext: ctx.options.conversationContext,
95
+ artifactHints: ctx.artifactHints,
96
+ replacementState: ctx.replacementState,
97
+ toolCallingAudit: ctx.toolCallingAudit,
71
98
  });
72
- const supportsStreaming = typeof ctx.options.llm.chatStream === 'function';
99
+ const { cacheSurface, envelope, baseMessages } = patchPromptInput;
100
+ const supportsStreaming = supportsLlmStreaming(ctx.options.llm, Phase.PATCH);
73
101
  const llmOutput = {
74
102
  policy: ctx.options.llmOutput,
75
103
  kind: 'patch',
76
104
  step: 'PATCH',
77
105
  };
78
106
  const response = await (supportsStreaming ? chatWithToolsStreaming : chatWithTools)(baseMessages, {
107
+ providerHints: envelope.providerHints,
79
108
  signal: ctx.options.signal,
80
109
  }, {
81
110
  phase: Phase.PATCH,
82
111
  llm: ctx.options.llm,
83
- runtime: {
84
- repoRoot: ctx.workspace.workPath,
85
- persistenceRoot: ctx.workspace.baseRepoPath || ctx.workspace.workPath,
86
- worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
87
- attemptId: ctx.attempt ?? 1,
88
- dryRun: Boolean(ctx.options?.dryRun),
89
- llm: ctx.options.llm,
90
- model: ctx.options.llm.getModelId?.() || process.env.SALMONLOOP_MODEL || process.env.S8P_MODEL,
91
- userInputProvider: ctx.options.userInputProvider,
92
- agentKind: ctx.options.agentKind ?? 'primary',
93
- languagePlugins: ctx.options.languagePlugins,
94
- subAgentController: ctx.options.subAgentController,
95
- },
112
+ runtime: buildPhaseToolRuntimeContext(ctx, Phase.PATCH, cacheSurface),
113
+ toolVisibility,
96
114
  toolstack,
97
115
  eventPayload: ctx.options.eventPayload,
98
116
  toolCallingAudit: {
@@ -107,114 +125,55 @@ export const generatePatch = async (ctx) => {
107
125
  emit: (event) => ctx.emit({ ...event, timestamp: event.timestamp ?? new Date() }),
108
126
  });
109
127
  let rawContent = response.content || '';
110
- let patch = '';
111
- let normalizedPatch = '';
112
- let diffMeta;
113
- const validateWithLlmErrors = (diffText) => {
114
- const normalized = normalizeDiff(diffText);
115
- try {
116
- return validateDiff(normalized);
117
- }
118
- catch (e) {
119
- if (e instanceof DiffValidationError) {
120
- if (e.message === text.diff.notUnifiedFormat)
121
- throw wrapPatchNotUnifiedDiff();
122
- if (e.message.startsWith(text.llm.patchEmpty()))
123
- throw wrapPatchEmpty();
124
- throw wrapPatchInvalid(e.message);
125
- }
126
- throw e;
127
- }
128
- };
128
+ let parsedPatch;
129
129
  try {
130
- patch = extractUnifiedDiffFromLLMContent(rawContent);
131
- normalizedPatch = normalizeDiff(patch);
132
- diffMeta = validateWithLlmErrors(patch);
130
+ parsedPatch = extractAndValidatePatch({
131
+ rawContent,
132
+ plannedFiles: ctx.plan.files,
133
+ });
133
134
  }
134
135
  catch (e) {
135
- const asLlmError = e;
136
- if (asLlmError instanceof Error &&
137
- 'llmCode' in asLlmError &&
138
- asLlmError.llmCode &&
139
- ['LLM_PATCH_EMPTY', 'LLM_PATCH_NOT_UNIFIED_DIFF'].includes(asLlmError.llmCode)) {
140
- let repaired;
141
- try {
142
- repaired = await repairToUnifiedDiff({
143
- llm: ctx.options.llm,
144
- baseMessages,
145
- chatOptions: { signal: ctx.options.signal },
146
- badContent: rawContent,
147
- reason: asLlmError.message,
148
- });
149
- }
150
- catch {
151
- throw asLlmError;
152
- }
153
- rawContent = repaired.content || '';
154
- patch = extractUnifiedDiffFromLLMContent(rawContent);
155
- normalizedPatch = normalizeDiff(patch);
156
- diffMeta = validateWithLlmErrors(patch);
157
- emitLlmOutput({
158
- emit: ctx.emit,
159
- policy: ctx.options.llmOutput,
160
- kind: 'patch',
161
- step: 'PATCH',
162
- content: patch,
163
- });
164
- }
165
- else {
166
- throw asLlmError;
136
+ const salvaged = await salvagePatchDiff({
137
+ initialError: e,
138
+ rawContent,
139
+ plannedFiles: ctx.plan.files,
140
+ repair: ({ badContent }) => repairToUnifiedDiff({ badContent }),
141
+ onAttempt: recordPatchSalvageAttempt,
142
+ onResult: recordPatchSalvageResult,
143
+ });
144
+ if (!salvaged) {
145
+ throw e;
167
146
  }
147
+ rawContent = salvaged.rawContent;
148
+ parsedPatch = salvaged;
149
+ emitLlmOutput({
150
+ emit: ctx.emit,
151
+ policy: ctx.options.llmOutput,
152
+ kind: 'patch',
153
+ step: 'PATCH',
154
+ content: salvaged.patch,
155
+ });
168
156
  }
169
157
  // Deterministic contract: the patch should be applicable (git apply --check) before moving on.
170
158
  // When this fails, attempt a single LLM repair pass using the git error message as feedback.
171
159
  const applyCheck = await checkPatchApplies({
172
160
  repoRoot: ctx.workspace.workPath,
173
- diff: normalizedPatch,
161
+ diff: parsedPatch.normalizedPatch,
174
162
  });
175
163
  if (!applyCheck.ok) {
176
- const details = (applyCheck.stderr || '').trim();
177
- try {
178
- const repaired = await repairToUnifiedDiff({
179
- llm: ctx.options.llm,
180
- baseMessages,
181
- chatOptions: { signal: ctx.options.signal },
182
- badContent: patch,
183
- reason: `Patch does not apply cleanly: ${details.slice(0, 1200)}`,
184
- });
185
- rawContent = repaired.content || '';
186
- patch = extractUnifiedDiffFromLLMContent(rawContent);
187
- normalizedPatch = normalizeDiff(patch);
188
- diffMeta = validateWithLlmErrors(patch);
189
- const applyCheck2 = await checkPatchApplies({
190
- repoRoot: ctx.workspace.workPath,
191
- diff: normalizedPatch,
192
- });
193
- if (applyCheck2.ok) {
194
- emitLlmOutput({
195
- emit: ctx.emit,
196
- policy: ctx.options.llmOutput,
197
- kind: 'patch',
198
- step: 'PATCH',
199
- content: patch,
200
- });
201
- }
202
- }
203
- catch {
204
- // Preserve the original patch and let VALIDATE provide the canonical failure if needed.
205
- }
164
+ // Preserve the original patch and let VALIDATE provide the canonical failure if needed.
206
165
  }
207
166
  ctx.emit({
208
167
  type: 'log',
209
168
  level: 'debug',
210
- message: `Patch generated: ${diffMeta.changedFiles.length} files changed`,
169
+ message: `Patch generated: ${parsedPatch.diffMeta.changedFiles.length} files changed`,
211
170
  timestamp: new Date(),
212
171
  });
213
172
  return {
214
173
  ...ctx,
215
- diff: normalizedPatch,
216
- diffMeta,
217
- changedFiles: diffMeta.changedFiles,
174
+ diff: parsedPatch.normalizedPatch,
175
+ diffMeta: parsedPatch.diffMeta,
176
+ changedFiles: parsedPatch.diffMeta.changedFiles,
218
177
  };
219
178
  };
220
179
  //# sourceMappingURL=patch.js.map
@@ -1,16 +1,21 @@
1
1
  import { text } from '../../../locales/index.js';
2
2
  import { LIMITS } from '../../config/limits.js';
3
+ import { supportsLlmStreaming } from '../../llm/capabilities.js';
3
4
  import { repairToJsonObject } from '../../llm/contracts/repair.js';
4
5
  import { sanitizeError } from '../../llm/errors.js';
5
- import { composeChatMessages } from '../../llm/message-composition.js';
6
6
  import { emitLlmOutput } from '../../llm/output-policy.js';
7
- import { formatContextForPrompt, parsePlanFromLLMContent } from '../../llm/utils.js';
7
+ import { parsePlanFromLLMContent } from '../../llm/utils.js';
8
+ import { recordAuditEvent } from '../../observability/audit-trail.js';
8
9
  import { logIgnoredError } from '../../observability/ignored-error.js';
9
10
  import { readPlan, updatePlan } from '../../plan/index.js';
10
11
  import { getPlanPrompt, getPlanSystemPrompt } from '../../prompts/runtime.js';
12
+ import { SessionReplacementPreviewProvider } from '../../session/replacement-preview-provider.js';
11
13
  import { chatWithTools, chatWithToolsStreaming } from '../../tools/session.js';
14
+ import { resolveVisibleToolSpecs } from '../../tools/tool-visibility.js';
12
15
  import { Phase } from '../../types/runtime.js';
13
16
  import { resolveLlmToolCallingPolicy } from '../dsl/llm-strategy.js';
17
+ import { buildPhaseRequestEnvelope } from './request-assembly.js';
18
+ import { buildPhaseToolRuntimeContext, buildToolVisibilityRuntime } from './tool-runtime.js';
14
19
  function sanitizeSubtaskText(raw) {
15
20
  const oneLine = String(raw ?? '')
16
21
  .replace(/\r?\n/g, ' ')
@@ -27,6 +32,25 @@ function sanitizeSubtaskText(raw) {
27
32
  return withoutComments;
28
33
  return withoutComments.slice(0, maxLen - 1).trimEnd() + '…';
29
34
  }
35
+ function recordPlanRepairAttempt(args) {
36
+ recordAuditEvent('plan.repair.attempt', {
37
+ reason: args.reason,
38
+ badContentLength: args.badContentLength,
39
+ toolsDisabled: true,
40
+ }, { source: 'plan', severity: 'low', scope: 'session', phase: 'PLAN' });
41
+ }
42
+ function recordPlanRepairResult(args) {
43
+ recordAuditEvent('plan.repair.result', {
44
+ ok: args.ok,
45
+ contentLength: args.contentLength,
46
+ error: args.error,
47
+ }, {
48
+ source: 'plan',
49
+ severity: args.ok ? 'low' : 'medium',
50
+ scope: 'session',
51
+ phase: 'PLAN',
52
+ });
53
+ }
30
54
  async function hydrateRuntimePlanTodos(ctx, plan) {
31
55
  const runtime = ctx.planRuntime;
32
56
  if (!runtime?.sessionId)
@@ -64,6 +88,15 @@ async function hydrateRuntimePlanTodos(ctx, plan) {
64
88
  },
65
89
  });
66
90
  }
91
+ export function hasSuccessfulPlanUpdateDuringPlan(ctx) {
92
+ const auditEntries = ctx.toolCallingAudit;
93
+ if (!Array.isArray(auditEntries) || auditEntries.length === 0)
94
+ return false;
95
+ return auditEntries.some((entry) => entry.phase === Phase.PLAN &&
96
+ entry.toolName === 'plan.update' &&
97
+ entry.toolResultStatus === 'ok' &&
98
+ entry.toolResultOutputOk === true);
99
+ }
67
100
  export const generatePlan = async (ctx) => {
68
101
  const toolstack = ctx.toolstack;
69
102
  const toolPolicy = resolveLlmToolCallingPolicy(Phase.PLAN, ctx.options.llm);
@@ -83,21 +116,53 @@ export const generatePlan = async (ctx) => {
83
116
  message: `Plan generated: ${plan.goal}`,
84
117
  timestamp: new Date(),
85
118
  });
86
- // Best-effort: keep runtime plan actionable even if the LLM doesn't call plan.* tools.
87
- await hydrateRuntimePlanTodos(ctx, plan).catch((error) => logIgnoredError('[Plan] hydrate runtime plan todos (legacy)', error));
119
+ if (ctx.planRuntime?.sessionId) {
120
+ // Transitional fallback: legacy/non-tool PLAN runs may hydrate runtime subtasks from final JSON.
121
+ await hydrateRuntimePlanTodos(ctx, plan).catch((error) => logIgnoredError('[Plan] hydrate runtime plan todos (legacy)', error));
122
+ }
88
123
  return {
89
124
  ...ctx,
90
125
  plan,
91
126
  };
92
127
  }
93
- const prompt = await getPlanPrompt(formatContextForPrompt(ctx.context), ctx.options.instruction, LIMITS.maxFilesChanged, ctx.lastError);
94
- const systemPrompt = await getPlanSystemPrompt(toolstack?.registry, { plan: ctx.planRuntime });
95
- const baseMessages = composeChatMessages({
96
- system: systemPrompt,
97
- user: prompt,
128
+ const toolVisibility = buildToolVisibilityRuntime(ctx);
129
+ const promptVisibleTools = resolveVisibleToolSpecs({
130
+ phase: Phase.PLAN,
131
+ toolstack,
132
+ worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
133
+ flowMode: ctx.mode,
134
+ runtime: toolVisibility,
135
+ });
136
+ const systemPrompt = await getPlanSystemPrompt(promptVisibleTools, toolVisibility);
137
+ const requestEnvelope = await buildPhaseRequestEnvelope({
138
+ phase: Phase.PLAN,
139
+ defaultNamespace: 'plan',
140
+ context: ctx.context,
141
+ contextResult: ctx.contextResult,
142
+ cacheSharing: ctx.cacheSharing,
143
+ onCacheMismatch: (mismatch) => {
144
+ recordAuditEvent('request.cache_sharing_hash_mismatch', mismatch, {
145
+ source: 'llm',
146
+ severity: 'low',
147
+ scope: 'session',
148
+ phase: Phase.PLAN,
149
+ });
150
+ },
151
+ systemPrompt,
152
+ buildUserPrompt: (contextPrompt) => getPlanPrompt(contextPrompt, ctx.options.instruction, LIMITS.maxFilesChanged, ctx.lastError),
98
153
  conversationContext: ctx.options.conversationContext,
154
+ artifactHints: ctx.artifactHints,
155
+ toolCallingAudit: ctx.toolCallingAudit,
156
+ previewProvider: new SessionReplacementPreviewProvider(ctx.replacementState),
157
+ toolVisibility: {
158
+ toolstack,
159
+ runtime: toolVisibility,
160
+ worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
161
+ flowMode: ctx.mode,
162
+ },
99
163
  });
100
- const supportsStreaming = typeof ctx.options.llm.chatStream === 'function';
164
+ const { cacheSurface, envelope, baseMessages } = requestEnvelope;
165
+ const supportsStreaming = supportsLlmStreaming(ctx.options.llm, Phase.PLAN);
101
166
  const llmOutput = {
102
167
  policy: ctx.options.llmOutput,
103
168
  kind: 'plan',
@@ -105,23 +170,13 @@ export const generatePlan = async (ctx) => {
105
170
  };
106
171
  const response = await (supportsStreaming ? chatWithToolsStreaming : chatWithTools)(baseMessages, {
107
172
  responseFormat: 'json_object',
173
+ providerHints: envelope.providerHints,
108
174
  signal: ctx.options.signal,
109
175
  }, {
110
176
  phase: Phase.PLAN,
111
177
  llm: ctx.options.llm,
112
- runtime: {
113
- repoRoot: ctx.workspace.workPath,
114
- persistenceRoot: ctx.workspace.baseRepoPath || ctx.workspace.workPath,
115
- worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
116
- attemptId: ctx.attempt ?? 1,
117
- dryRun: Boolean(ctx.options?.dryRun),
118
- llm: ctx.options.llm,
119
- model: ctx.options.llm.getModelId?.() || process.env.SALMONLOOP_MODEL || process.env.S8P_MODEL,
120
- userInputProvider: ctx.options.userInputProvider,
121
- agentKind: ctx.options.agentKind ?? 'primary',
122
- languagePlugins: ctx.options.languagePlugins,
123
- subAgentController: ctx.options.subAgentController,
124
- },
178
+ runtime: buildPhaseToolRuntimeContext(ctx, Phase.PLAN, cacheSurface),
179
+ toolVisibility,
125
180
  toolstack,
126
181
  eventPayload: ctx.options.eventPayload,
127
182
  toolCallingAudit: {
@@ -145,6 +200,10 @@ export const generatePlan = async (ctx) => {
145
200
  plan = parsePlanFromLLMContent(finalContent);
146
201
  }
147
202
  catch (e) {
203
+ recordPlanRepairAttempt({
204
+ reason: sanitizeError(e),
205
+ badContentLength: finalContent.length,
206
+ });
148
207
  let repaired;
149
208
  try {
150
209
  repaired = await repairToJsonObject({
@@ -156,6 +215,11 @@ export const generatePlan = async (ctx) => {
156
215
  });
157
216
  }
158
217
  catch (repairError) {
218
+ recordPlanRepairResult({
219
+ ok: false,
220
+ contentLength: 0,
221
+ error: sanitizeError(repairError).slice(0, 400),
222
+ });
159
223
  throw new Error(text.llm.planParseFailed(finalContent, sanitizeError(repairError)));
160
224
  }
161
225
  finalContent = repaired.content || '';
@@ -172,8 +236,17 @@ export const generatePlan = async (ctx) => {
172
236
  plan = parsePlanFromLLMContent(finalContent);
173
237
  }
174
238
  catch (e2) {
239
+ recordPlanRepairResult({
240
+ ok: false,
241
+ contentLength: finalContent.length,
242
+ error: sanitizeError(e2).slice(0, 400),
243
+ });
175
244
  throw new Error(text.llm.planParseFailed(finalContent, sanitizeError(e2)));
176
245
  }
246
+ recordPlanRepairResult({
247
+ ok: true,
248
+ contentLength: finalContent.length,
249
+ });
177
250
  }
178
251
  ctx.emit({
179
252
  type: 'log',
@@ -181,8 +254,11 @@ export const generatePlan = async (ctx) => {
181
254
  message: `Plan generated: ${plan.goal}`,
182
255
  timestamp: new Date(),
183
256
  });
184
- // Best-effort: keep runtime plan actionable even if the LLM doesn't call plan.* tools.
185
- await hydrateRuntimePlanTodos(ctx, plan).catch((error) => logIgnoredError('[Plan] hydrate runtime plan todos (tools)', error));
257
+ const hasModelPlanUpdate = hasSuccessfulPlanUpdateDuringPlan(ctx);
258
+ if (ctx.planRuntime?.sessionId && !hasModelPlanUpdate) {
259
+ // Transitional fallback: only hydrate when PLAN did not successfully persist via plan.update.
260
+ await hydrateRuntimePlanTodos(ctx, plan).catch((error) => logIgnoredError('[Plan] hydrate runtime plan todos (tools)', error));
261
+ }
186
262
  return {
187
263
  ...ctx,
188
264
  plan,
@@ -1,15 +1,13 @@
1
1
  import { text } from '../../../locales/index.js';
2
2
  import { recordAuditEvent } from '../../observability/audit-trail.js';
3
+ import { resolveExecutionProfile } from '../../runtime/execution-profile.js';
3
4
  import { createStandardToolstack } from '../../tools/loader.js';
4
- import { Phase } from '../../types/runtime.js';
5
5
  import { preflight } from '../../verification/runner.js';
6
6
  import { resolveLlmToolCallingPolicy } from '../dsl/llm-strategy.js';
7
7
  export const runPreflight = async (ctx) => {
8
+ const executionProfile = resolveExecutionProfile(ctx.mode);
8
9
  const result = await preflight(ctx.workspace, ctx.emit, {
9
- ignoreDirty: ctx.mode === 'review' ||
10
- ctx.mode === 'research' ||
11
- ctx.mode === 'answer' ||
12
- ctx.options.permissionMode === 'yolo',
10
+ ignoreDirty: executionProfile.ignoreDirtyPreflight,
13
11
  });
14
12
  if (!result.ok) {
15
13
  const reason = result.reason || text.loop.preflightFailedNotGit;
@@ -29,7 +27,8 @@ export const runPreflight = async (ctx) => {
29
27
  message: text.loop.preflightPassed,
30
28
  timestamp: new Date(),
31
29
  });
32
- const toolstack = resolveLlmToolCallingPolicy(Phase.PLAN, ctx.options.llm).enabled
30
+ const toolstack = resolveLlmToolCallingPolicy(executionProfile.entryPhase, ctx.options.llm)
31
+ .enabled
33
32
  ? await createStandardToolstack({
34
33
  repoRoot: ctx.workspace.workPath,
35
34
  persistenceRoot: ctx.workspace.baseRepoPath || ctx.workspace.workPath,