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
@@ -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,
@@ -0,0 +1,78 @@
1
+ import { augmentPromptWithRelevantMemory } from '../../llm/request-augmentation.js';
2
+ import { buildSharedRequestEnvelope as buildSharedRequestEnvelopeCore, } from '../../llm/shared-request-assembly.js';
3
+ import { formatContextForPrompt } from '../../llm/utils.js';
4
+ import { buildRelevantMemoryCandidates, selectRelevantMemory, } from '../../memory/relevant-retrieval.js';
5
+ import { resolveVisibleToolNames, } from '../../tools/tool-visibility.js';
6
+ import { resolveCacheSharingSurface, } from './cache-sharing.js';
7
+ export function buildSharedRequestEnvelope(args) {
8
+ return buildSharedRequestEnvelopeCore(args);
9
+ }
10
+ export async function buildAugmentedRequestEnvelope(args) {
11
+ const baseContextPrompt = args.baseContextPrompt ?? args.contextResult?.prompt ?? formatContextForPrompt(args.context);
12
+ const localContextHash = args.contextResult?.meta?.contextHash ?? args.context.contextHash;
13
+ const cacheSurface = resolveCacheSharingSurface({
14
+ phase: args.phase,
15
+ defaultNamespace: args.defaultNamespace,
16
+ localContextHash,
17
+ cacheSharing: args.cacheSharing,
18
+ mismatchPolicy: args.cacheMismatchPolicy,
19
+ onMismatch: args.onCacheMismatch,
20
+ });
21
+ const relevantMemory = selectRelevantMemory({
22
+ instruction: args.context.instruction,
23
+ candidates: args.relevantMemory?.entries ?? buildRelevantMemoryCandidates(args.context),
24
+ activeToolNames: resolveVisibleToolNames({
25
+ phase: args.phase,
26
+ toolstack: args.toolVisibility?.toolstack,
27
+ runtime: args.toolVisibility?.runtime,
28
+ worktreeRoot: args.toolVisibility?.worktreeRoot,
29
+ flowMode: args.toolVisibility?.flowMode,
30
+ }),
31
+ maxItems: args.relevantMemory?.maxItems,
32
+ alreadySurfacedText: [
33
+ baseContextPrompt,
34
+ ...(Array.isArray(args.systemPrompt) ? args.systemPrompt : [args.systemPrompt]),
35
+ ...(args.conversationContext ?? []).map((message) => message.content),
36
+ ],
37
+ });
38
+ const contextPrompt = augmentPromptWithRelevantMemory({
39
+ basePrompt: baseContextPrompt,
40
+ selectedEntries: relevantMemory,
41
+ budgetTokens: args.relevantMemory?.budgetTokens,
42
+ countTokens: args.relevantMemory?.countTokens,
43
+ }).prompt;
44
+ const userPrompt = await args.buildUserPrompt(contextPrompt);
45
+ const shared = buildSharedRequestEnvelope({
46
+ defaultNamespace: cacheSurface.namespace,
47
+ contextHash: cacheSurface.contextHash,
48
+ systemPrompt: args.systemPrompt,
49
+ userPrompt,
50
+ conversationContext: args.conversationContext,
51
+ artifactHints: args.artifactHints,
52
+ toolCallingAudit: args.toolCallingAudit,
53
+ previewProvider: args.previewProvider,
54
+ attachments: [
55
+ {
56
+ key: 'context-prompt',
57
+ kind: 'context',
58
+ label: 'Context prompt',
59
+ content: contextPrompt,
60
+ cacheSafe: true,
61
+ },
62
+ ...(args.extraAttachments ?? []),
63
+ ],
64
+ providerHints: args.providerHints,
65
+ });
66
+ return {
67
+ contextPrompt,
68
+ userPrompt,
69
+ cacheSurface,
70
+ resolvedArtifactHints: shared.resolvedArtifactHints,
71
+ envelope: shared.envelope,
72
+ baseMessages: shared.baseMessages,
73
+ };
74
+ }
75
+ export async function buildPhaseRequestEnvelope(args) {
76
+ return buildAugmentedRequestEnvelope(args);
77
+ }
78
+ //# sourceMappingURL=request-assembly.js.map
@@ -1,23 +1,14 @@
1
1
  import { text } from '../../../locales/index.js';
2
- import { composeChatMessages } from '../../llm/message-composition.js';
2
+ import { supportsLlmStreaming } from '../../llm/capabilities.js';
3
3
  import { emitLlmOutput } from '../../llm/output-policy.js';
4
- import { formatContextForPrompt } from '../../llm/utils.js';
4
+ import { recordAuditEvent } from '../../observability/audit-trail.js';
5
+ import { getResearchPrompt, getResearchSystemPrompt } from '../../prompts/runtime.js';
6
+ import { SessionReplacementPreviewProvider } from '../../session/replacement-preview-provider.js';
5
7
  import { chatWithTools, chatWithToolsStreaming } from '../../tools/session.js';
6
8
  import { Phase } from '../../types/runtime.js';
7
9
  import { resolveLlmToolCallingPolicy } from '../dsl/llm-strategy.js';
8
- function buildResearchPrompt(contextText, instruction) {
9
- return [
10
- 'You are running in deep research mode.',
11
- 'Use available tools to gather external information as needed.',
12
- 'Return JSON with keys: researchNotes, researchFindings, sources, researchText.',
13
- 'Each researchFinding should include: summary, confidence (0-1), uncertainty (string).',
14
- 'Each source should include: toolName, summary, ok, timestamp (epoch ms).',
15
- '',
16
- `Instruction:\n${instruction}`,
17
- '',
18
- `Context:\n${contextText}`,
19
- ].join('\n');
20
- }
10
+ import { buildPhaseRequestEnvelope } from './request-assembly.js';
11
+ import { buildPhaseToolRuntimeContext, buildToolVisibilityRuntime } from './tool-runtime.js';
21
12
  function normalizeFindings(value) {
22
13
  if (!value)
23
14
  return [];
@@ -88,20 +79,43 @@ function buildSourcesFromAudit(entries) {
88
79
  }));
89
80
  }
90
81
  export async function generateResearch(ctx) {
91
- const contextText = formatContextForPrompt(ctx.context);
92
- const prompt = buildResearchPrompt(contextText, ctx.options.instruction);
93
- const systemPrompt = 'You are a research assistant. Prefer evidence-backed claims.';
94
- const baseMessages = composeChatMessages({
95
- system: systemPrompt,
96
- user: prompt,
82
+ const systemPrompt = await getResearchSystemPrompt();
83
+ const toolVisibility = buildToolVisibilityRuntime(ctx);
84
+ const requestEnvelope = await buildPhaseRequestEnvelope({
85
+ phase: Phase.RESEARCH,
86
+ defaultNamespace: 'research',
87
+ context: ctx.context,
88
+ contextResult: ctx.contextResult,
89
+ cacheSharing: ctx.cacheSharing,
90
+ onCacheMismatch: (mismatch) => {
91
+ recordAuditEvent('request.cache_sharing_hash_mismatch', mismatch, {
92
+ source: 'llm',
93
+ severity: 'low',
94
+ scope: 'session',
95
+ phase: Phase.RESEARCH,
96
+ });
97
+ },
98
+ systemPrompt,
99
+ buildUserPrompt: async (contextText) => await getResearchPrompt(contextText, ctx.options.instruction),
97
100
  conversationContext: ctx.options.conversationContext,
101
+ artifactHints: ctx.artifactHints,
102
+ toolCallingAudit: ctx.toolCallingAudit,
103
+ previewProvider: new SessionReplacementPreviewProvider(ctx.replacementState),
104
+ toolVisibility: {
105
+ toolstack: ctx.toolstack,
106
+ runtime: toolVisibility,
107
+ worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
108
+ flowMode: ctx.mode,
109
+ },
98
110
  });
111
+ const { cacheSurface, envelope, baseMessages } = requestEnvelope;
99
112
  const toolPolicy = resolveLlmToolCallingPolicy(Phase.RESEARCH, ctx.options.llm);
100
- const supportsStreaming = typeof ctx.options.llm.chatStream === 'function';
113
+ const supportsStreaming = supportsLlmStreaming(ctx.options.llm, Phase.RESEARCH);
101
114
  const localAudit = [];
102
115
  const sourcesFromAudit = () => buildSourcesFromAudit(localAudit);
103
116
  if (!ctx.toolstack || !toolPolicy.enabled) {
104
117
  const response = await ctx.options.llm.chat(baseMessages, {
118
+ providerHints: envelope.providerHints,
105
119
  signal: ctx.options.signal,
106
120
  phase: Phase.RESEARCH,
107
121
  });
@@ -133,22 +147,11 @@ export async function generateResearch(ctx) {
133
147
  kind: 'research',
134
148
  step: 'RESEARCH',
135
149
  };
136
- const response = await (supportsStreaming ? chatWithToolsStreaming : chatWithTools)(baseMessages, { signal: ctx.options.signal }, {
150
+ const response = await (supportsStreaming ? chatWithToolsStreaming : chatWithTools)(baseMessages, { providerHints: envelope.providerHints, signal: ctx.options.signal }, {
137
151
  phase: Phase.RESEARCH,
138
152
  llm: ctx.options.llm,
139
- runtime: {
140
- repoRoot: ctx.workspace.workPath,
141
- persistenceRoot: ctx.workspace.baseRepoPath || ctx.workspace.workPath,
142
- worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
143
- attemptId: ctx.attempt ?? 1,
144
- dryRun: Boolean(ctx.options?.dryRun),
145
- llm: ctx.options.llm,
146
- model: ctx.options.llm.getModelId?.() || process.env.SALMONLOOP_MODEL || process.env.S8P_MODEL,
147
- userInputProvider: ctx.options.userInputProvider,
148
- agentKind: ctx.options.agentKind ?? 'primary',
149
- languagePlugins: ctx.options.languagePlugins,
150
- subAgentController: ctx.options.subAgentController,
151
- },
153
+ runtime: buildPhaseToolRuntimeContext(ctx, Phase.RESEARCH, cacheSurface),
154
+ toolVisibility,
152
155
  toolstack: ctx.toolstack,
153
156
  eventPayload: ctx.options.eventPayload,
154
157
  toolCallingAudit: {
@@ -0,0 +1,47 @@
1
+ import { buildSystemPrefixDigest, buildToolSchemaHash, } from '../../sub-agent/prefix-consistency.js';
2
+ export function buildPhaseToolRuntimeContext(ctx, phase, cacheSurface) {
3
+ return {
4
+ repoRoot: ctx.workspace.workPath,
5
+ persistenceRoot: ctx.workspace.baseRepoPath || ctx.workspace.workPath,
6
+ worktreeRoot: ctx.workspace.strategy === 'worktree' ? ctx.workspace.workPath : undefined,
7
+ flowMode: ctx.mode,
8
+ attemptId: ctx.attempt ?? 1,
9
+ dryRun: Boolean(ctx.options?.dryRun),
10
+ llm: ctx.options.llm,
11
+ model: ctx.options.llm.getModelId?.() || process.env.SALMONLOOP_MODEL || process.env.S8P_MODEL,
12
+ userInputProvider: ctx.options.userInputProvider,
13
+ agentKind: ctx.options.agentKind ?? 'primary',
14
+ languagePlugins: ctx.options.languagePlugins,
15
+ subAgentController: ctx.options.subAgentController,
16
+ phase,
17
+ contextSnapshot: {
18
+ conversationContext: ctx.options.conversationContext,
19
+ artifactHints: ctx.artifactHints,
20
+ toolCallingAudit: ctx.toolCallingAudit,
21
+ replacementState: ctx.replacementState,
22
+ planRuntime: ctx.planRuntime,
23
+ cacheSharing: {
24
+ namespace: cacheSurface.namespace,
25
+ contextHash: cacheSurface.contextHash,
26
+ toolSchemaHash: buildToolSchemaHash({
27
+ phase,
28
+ allowedToolNames: ctx.options.allowedToolNames,
29
+ }),
30
+ systemPrefixDigest: buildSystemPrefixDigest({
31
+ phase,
32
+ namespace: cacheSurface.namespace,
33
+ contextHash: cacheSurface.contextHash,
34
+ }),
35
+ },
36
+ },
37
+ };
38
+ }
39
+ export function buildToolVisibilityRuntime(ctx) {
40
+ if (!ctx.planRuntime) {
41
+ return undefined;
42
+ }
43
+ return {
44
+ plan: ctx.planRuntime,
45
+ };
46
+ }
47
+ //# sourceMappingURL=tool-runtime.js.map
@@ -0,0 +1,23 @@
1
+ import { ArtifactStore } from '../../sub-agent/artifacts/store.js';
2
+ import { runVerify as runVerifyCommand } from '../../verification/runner.js';
3
+ export async function executeVerifyForWorkspace(params) {
4
+ const verifyResult = await runVerifyCommand(params.workspacePath, params.verify, undefined, params.signal);
5
+ let verifyArtifact;
6
+ if (!verifyResult.ok && verifyResult.output) {
7
+ try {
8
+ verifyArtifact = await ArtifactStore.saveText({
9
+ content: verifyResult.output,
10
+ mimeType: 'text/plain',
11
+ fileExt: 'log',
12
+ });
13
+ }
14
+ catch {
15
+ // Best-effort only; keep verifyResult.output in-memory for shrink/error classification.
16
+ }
17
+ }
18
+ return {
19
+ verifyResult,
20
+ ...(verifyArtifact ? { verifyArtifact } : {}),
21
+ };
22
+ }
23
+ //# sourceMappingURL=verify-shared.js.map
@@ -1,8 +1,7 @@
1
1
  import { text } from '../../../locales/index.js';
2
2
  import { collectBudgetMetrics, evaluateBudgetAlert, getGlobalAdjuster, recordBudgetAlert, } from '../../context/budget/integration.js';
3
3
  import { recordAuditEvent } from '../../observability/audit-trail.js';
4
- import { ArtifactStore } from '../../sub-agent/artifacts/store.js';
5
- import { runVerify as runVerifyCommand } from '../../verification/runner.js';
4
+ import { executeVerifyForWorkspace } from './verify-shared.js';
6
5
  function extractCommandProgram(command) {
7
6
  const trimmed = command.trim();
8
7
  if (!trimmed)
@@ -16,8 +15,11 @@ export const runVerify = async (ctx) => {
16
15
  verifyResult: { ok: true, output: text.loop.verificationSkipped, exitCode: null },
17
16
  };
18
17
  }
19
- const verifyResult = await runVerifyCommand(ctx.workspace.workPath, ctx.options.verify, undefined, ctx.options.signal);
20
- let verifyArtifact;
18
+ const { verifyResult, verifyArtifact } = await executeVerifyForWorkspace({
19
+ workspacePath: ctx.workspace.workPath,
20
+ verify: ctx.options.verify,
21
+ signal: ctx.options.signal,
22
+ });
21
23
  recordAuditEvent('verify.summary', {
22
24
  ok: verifyResult.ok,
23
25
  exitCode: verifyResult.exitCode,
@@ -92,23 +94,13 @@ export const runVerify = async (ctx) => {
92
94
  message: text.loop.verificationFailedSummary,
93
95
  timestamp: new Date(),
94
96
  });
95
- if (verifyResult.output) {
96
- try {
97
- verifyArtifact = await ArtifactStore.saveText({
98
- content: verifyResult.output,
99
- mimeType: 'text/plain',
100
- fileExt: 'log',
101
- });
102
- ctx.emit({
103
- type: 'log',
104
- level: 'debug',
105
- message: text.loop.verificationOutputStored(verifyArtifact.handle),
106
- timestamp: new Date(),
107
- });
108
- }
109
- catch {
110
- // Best-effort only; keep verifyResult.output in-memory for shrink/error classification.
111
- }
97
+ if (verifyArtifact) {
98
+ ctx.emit({
99
+ type: 'log',
100
+ level: 'debug',
101
+ message: text.loop.verificationOutputStored(verifyArtifact.handle),
102
+ timestamp: new Date(),
103
+ });
112
104
  }
113
105
  // We don't throw here, because we want to trigger rollback/shrink in the pipeline
114
106
  // But wait, the Pipeline abstraction propagates errors immediately.
@@ -20,7 +20,7 @@ export function createInteractionFacade(deps) {
20
20
  id: input.taskId ?? `task_${Date.now()}`,
21
21
  capability: input.capability,
22
22
  state: 'accepted',
23
- request: input.request,
23
+ request: { ...input.request, extensions: input.extensions },
24
24
  createdAt: new Date().toISOString(),
25
25
  attempt: 1,
26
26
  };
@@ -34,6 +34,7 @@ export async function executeAiSdkChatRequest(input) {
34
34
  options: input.options,
35
35
  headers: attemptCtx.langfuseHeaders,
36
36
  abortSignal: attemptCtx.abortSignal,
37
+ providerOptionsKey: input.providerOptionsKey,
37
38
  }));
38
39
  const usage = extractUsageFromAiSdkResult(result);
39
40
  if (usage) {
@@ -74,6 +75,7 @@ export async function* executeAiSdkChatStreamRequest(input) {
74
75
  options: input.options,
75
76
  headers: attemptCtx.langfuseHeaders,
76
77
  abortSignal: attemptCtx.abortSignal,
78
+ providerOptionsKey: input.providerOptionsKey,
77
79
  }));
78
80
  yield* mapAiSdkStreamResultToChunks(result.fullStream);
79
81
  },
@@ -0,0 +1,63 @@
1
+ import { LIMITS } from '../../config/limits.js';
2
+ import { getPatchPrompt, getPlanPrompt } from '../../prompts/runtime.js';
3
+ import { wrapPlanEmpty, sanitizeError, LlmError } from '../errors.js';
4
+ import { extractUnifiedDiffFromLLMContent, parsePlanFromLLMContent } from '../utils.js';
5
+ export const HIGH_LEVEL_PHASE_NAMES = [
6
+ 'plan',
7
+ 'patch',
8
+ ];
9
+ function buildContextPromptAttachment(contextPrompt) {
10
+ return {
11
+ key: 'context-prompt',
12
+ kind: 'context',
13
+ label: 'Context prompt',
14
+ content: contextPrompt,
15
+ cacheSafe: true,
16
+ };
17
+ }
18
+ function buildPatchAttachments(contextPrompt, planStr) {
19
+ return [
20
+ buildContextPromptAttachment(contextPrompt),
21
+ {
22
+ key: 'plan-json',
23
+ kind: 'plan',
24
+ label: 'Plan JSON',
25
+ content: planStr,
26
+ },
27
+ ];
28
+ }
29
+ function parsePlanResult(content) {
30
+ if (!content) {
31
+ throw wrapPlanEmpty();
32
+ }
33
+ try {
34
+ return parsePlanFromLLMContent(content);
35
+ }
36
+ catch (e) {
37
+ throw new LlmError('LLM plan parsing failed', 'LLM_PLAN_INVALID_JSON', {
38
+ causeMessage: sanitizeError(e),
39
+ });
40
+ }
41
+ }
42
+ function parsePatchResult(content) {
43
+ return extractUnifiedDiffFromLLMContent(content ?? '');
44
+ }
45
+ export const HIGH_LEVEL_PHASE_SPECS = {
46
+ plan: {
47
+ name: 'plan',
48
+ namespace: 'plan',
49
+ observationName: 'PLAN:plan-json',
50
+ buildPrompt: async ({ contextPrompt, instruction, lastError }) => getPlanPrompt(contextPrompt, instruction, LIMITS.maxFilesChanged, lastError),
51
+ buildAttachments: ({ contextPrompt }) => [buildContextPromptAttachment(contextPrompt)],
52
+ parseResult: (content) => parsePlanResult(content),
53
+ },
54
+ patch: {
55
+ name: 'patch',
56
+ namespace: 'patch',
57
+ observationName: 'PATCH:unified-diff',
58
+ buildPrompt: async ({ planStr, contextPrompt, lastError }) => getPatchPrompt(planStr, contextPrompt, LIMITS.maxFilesChanged, LIMITS.maxDiffLines, lastError),
59
+ buildAttachments: ({ contextPrompt, planStr }) => buildPatchAttachments(contextPrompt, planStr),
60
+ parseResult: (content) => parsePatchResult(content),
61
+ },
62
+ };
63
+ //# sourceMappingURL=high-level-phase-specs.js.map