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,3 +1,4 @@
1
+ const HEADLESS_OUTPUT_FORMATS = new Set(['json', 'stream-json']);
1
2
  function readFlagValue(tokens, index) {
2
3
  const token = tokens[index];
3
4
  const eq = token.indexOf('=');
@@ -57,4 +58,30 @@ export function detectHeadlessOutputFromArgv(argv) {
57
58
  outputProfile,
58
59
  };
59
60
  }
61
+ export function shouldForceColorForArgv(argv) {
62
+ if (process.env.NO_COLOR !== undefined)
63
+ return false;
64
+ const existingForceColor = process.env.FORCE_COLOR;
65
+ if (existingForceColor !== undefined && existingForceColor !== '')
66
+ return false;
67
+ const tokens = argv.slice(2);
68
+ for (let i = 0; i < tokens.length; i++) {
69
+ const token = tokens[i];
70
+ if (token === '--')
71
+ break;
72
+ if (token === '--output-format') {
73
+ const value = tokens[i + 1];
74
+ if (HEADLESS_OUTPUT_FORMATS.has(String(value)))
75
+ return false;
76
+ i += 1;
77
+ continue;
78
+ }
79
+ if (token.startsWith('--output-format=')) {
80
+ const value = token.slice('--output-format='.length);
81
+ if (HEADLESS_OUTPUT_FORMATS.has(value))
82
+ return false;
83
+ }
84
+ }
85
+ return true;
86
+ }
60
87
  //# sourceMappingURL=headless-detection.js.map
@@ -0,0 +1,11 @@
1
+ import { resolveExecutionProfile, } from '../core/facades/cli-chat.js';
2
+ export function resolveActiveChatFlowMode(sessionFlowMode, defaultFlowMode) {
3
+ return sessionFlowMode ?? defaultFlowMode ?? 'autopilot';
4
+ }
5
+ export function resolveChatCheckpointStrategy(flowMode, configured) {
6
+ const profile = resolveExecutionProfile(flowMode);
7
+ return profile.readOnly
8
+ ? 'direct'
9
+ : (configured ?? profile.defaultCheckpointStrategy ?? 'worktree');
10
+ }
11
+ //# sourceMappingURL=chat-flow.js.map
package/dist/cli/chat.js CHANGED
@@ -1,6 +1,7 @@
1
- import { buildSessionConversationContext, ChatSessionManager, DEFAULT_LLM_OUTPUT_POLICY, emitLlmOutput, getDefaultSessionContextBudgetTokens, InputHistoryManager, logIgnoredError, getLogger, refreshSessionSummary, routeChatIntent, runSalmonLoop, TokenTracker, } from '../core/facades/cli-chat.js';
1
+ import { buildSessionArtifactStateFromLoopResult, buildEffectiveConversationContext, ChatSessionManager, DEFAULT_LLM_OUTPUT_POLICY, emitLlmOutput, getDefaultSessionContextBudgetTokens, InputHistoryManager, logIgnoredError, getLogger, refreshSessionSummary, runSalmonLoop, TokenTracker, createInitialTracking, onNormalTurnComplete, runCompactionPipeline, reactiveCompact, } from '../core/facades/cli-chat.js';
2
2
  import { createSubAgentController } from '../core/facades/cli-subagent.js';
3
3
  import { createUiAuthorizationProvider } from './authorization/provider.js';
4
+ import { resolveActiveChatFlowMode, resolveChatCheckpointStrategy } from './chat-flow.js';
4
5
  import { commands } from './commands/registry.js';
5
6
  import { CHAT_QUEUE_CONFIG } from './config.js';
6
7
  import { text } from './locales/index.js';
@@ -49,7 +50,10 @@ export async function startChatMode(options) {
49
50
  let hideTimer = null;
50
51
  let currentInstruction = null;
51
52
  let lastInterruptedInput = null;
53
+ let currentFlowMode = null;
54
+ let lastInterruptedFlowMode = null;
52
55
  let currentLlmOutputPolicy = options.llmOutput ?? DEFAULT_LLM_OUTPUT_POLICY;
56
+ let currentCompactionTracking = createInitialTracking();
53
57
  const authorizationProvider = createUiAuthorizationProvider({
54
58
  emit: (event) => {
55
59
  latestEmit?.({ ...event, timestamp: new Date() });
@@ -156,8 +160,28 @@ export async function startChatMode(options) {
156
160
  return false;
157
161
  return /cancelled by user/i.test(reason);
158
162
  };
159
- const markInterrupted = (input) => {
163
+ const buildRecoveryFailureSummary = (result) => {
164
+ if (result.success)
165
+ return null;
166
+ const isRecoveryCandidate = result.reasonCode === 'TOOL_CORRECTION_REQUIRED' ||
167
+ result.reasonCode === 'AWAITING_INPUT' ||
168
+ isInterruptResult(result.reason);
169
+ if (!isRecoveryCandidate)
170
+ return null;
171
+ const summary = {};
172
+ if (typeof result.reasonCode === 'string')
173
+ summary.reasonCode = result.reasonCode;
174
+ if (typeof result.diagnosticCode === 'string')
175
+ summary.diagnosticCode = result.diagnosticCode;
176
+ if (typeof result.safeHint === 'string')
177
+ summary.safeHint = result.safeHint;
178
+ if (typeof result.failurePhase === 'string')
179
+ summary.failurePhase = result.failurePhase;
180
+ return Object.keys(summary).length > 0 ? summary : null;
181
+ };
182
+ const markInterrupted = (input, flowMode) => {
160
183
  lastInterruptedInput = input;
184
+ lastInterruptedFlowMode = flowMode ?? null;
161
185
  queue.pause();
162
186
  latestEmit?.({
163
187
  type: 'log',
@@ -172,6 +196,8 @@ export async function startChatMode(options) {
172
196
  return;
173
197
  if (!latestDispatch)
174
198
  return;
199
+ const queuedFlowMode = queueOptions?.flowMode ??
200
+ resolveActiveChatFlowMode(sessionManager.getChatFlowMode(), options.defaultFlowMode);
175
201
  try {
176
202
  const currentSessionId = sessionManager.getCurrent().meta.id;
177
203
  historyManager
@@ -203,13 +229,30 @@ export async function startChatMode(options) {
203
229
  started = true;
204
230
  latestDispatch?.({ type: 'SHIFT_QUEUE_MESSAGE' });
205
231
  currentInstruction = trimmed;
232
+ currentFlowMode = queuedFlowMode;
206
233
  const timeoutAbort = new AbortController();
207
234
  const mergedSignal = mergeAbortSignals([latestGuiOptions?.signal, timeoutAbort.signal]);
235
+ // Run compaction pipeline (Level 1 Autocompact) before building context
236
+ const compactionResult = await runCompactionPipeline({
237
+ sessionManager,
238
+ llm: options.llm,
239
+ tracking: currentCompactionTracking,
240
+ signal: mergedSignal.signal,
241
+ });
242
+ if (compactionResult.performed) {
243
+ currentCompactionTracking = compactionResult.tracking;
244
+ }
208
245
  const modelIdForBudget = options.llm.getModelId?.() || process.env.SALMONLOOP_MODEL || process.env.S8P_MODEL;
209
- const conversationContext = buildSessionConversationContext(sessionManager.getMessages(), {
210
- budgetTokens: getDefaultSessionContextBudgetTokens({ modelId: modelIdForBudget }),
211
- summaryState: sessionManager.getSummaryState(),
246
+ const baseSessionBudgetTokens = getDefaultSessionContextBudgetTokens({
247
+ modelId: modelIdForBudget,
248
+ });
249
+ const conversationContext = buildEffectiveConversationContext({
250
+ llm: options.llm,
251
+ sessionManager,
252
+ budgetTokens: baseSessionBudgetTokens,
212
253
  });
254
+ const artifactHints = sessionManager.getArtifactState();
255
+ const replacementState = sessionManager.getReplacementState();
213
256
  // Single source of truth: chat runtime owns when a user message is appended to the UI list.
214
257
  // The UI layer must not also append user messages (to avoid duplicates).
215
258
  latestDispatch?.({
@@ -228,27 +271,15 @@ export async function startChatMode(options) {
228
271
  timestamp: Date.now(),
229
272
  });
230
273
  const execution = await withTimeout((async () => {
231
- const intentDecision = await routeChatIntent(trimmed, {
232
- llm: options.llm,
233
- signal: mergedSignal.signal,
234
- });
235
- latestEmit?.({
236
- type: 'log',
237
- level: 'info',
238
- message: text.cli.chatIntentRouted(intentDecision.intent, intentDecision.confidence, intentDecision.reason),
239
- timestamp: new Date(),
240
- });
241
- const nonMutating = intentDecision.intent === 'review' ||
242
- intentDecision.intent === 'research' ||
243
- intentDecision.intent === 'answer';
244
- const strategy = nonMutating ? 'direct' : options.checkpointStrategy || 'worktree';
274
+ const flowMode = queuedFlowMode;
275
+ const strategy = resolveChatCheckpointStrategy(flowMode, options.checkpointStrategy);
245
276
  const verboseLevel = options.verbose === true ? 'basic' : options.verbose;
246
277
  const result = await runSalmonLoop({
247
278
  instruction: trimmed,
248
279
  verify: options.verifyCommand,
249
280
  repoPath: options.repoPath,
250
281
  llm: options.llm,
251
- mode: intentDecision.intent,
282
+ mode: flowMode,
252
283
  strategy,
253
284
  verbose: verboseLevel,
254
285
  onEvent: latestEmit,
@@ -257,6 +288,8 @@ export async function startChatMode(options) {
257
288
  outcomeReporter: options.outcomeReporter,
258
289
  auditScope: options.auditScope,
259
290
  conversationContext: conversationContext.length > 0 ? conversationContext : undefined,
291
+ artifactHints,
292
+ replacementState,
260
293
  astValidation: options.astValidation,
261
294
  languagePlugins: options.languagePlugins,
262
295
  // Resolve sessionId at call time to support `/session` switching.
@@ -267,15 +300,99 @@ export async function startChatMode(options) {
267
300
  userInputProvider,
268
301
  subAgentController,
269
302
  permissionMode: options.permissionMode,
303
+ }).catch(async (error) => {
304
+ // Level 2: Reactive Compact
305
+ // If the provider reports prompt-too-long, attempt emergency compaction AND retry ONCE
306
+ // with a smaller session context budget to guarantee prompt reduction.
307
+ const isContextOverflow = (() => {
308
+ if (!error || typeof error !== 'object')
309
+ return false;
310
+ if ('llmCode' in error &&
311
+ error.llmCode === 'LLM_CONTEXT_LENGTH_EXCEEDED') {
312
+ return true;
313
+ }
314
+ const message = error instanceof Error
315
+ ? error.message
316
+ : typeof error.message === 'string'
317
+ ? String(error.message)
318
+ : '';
319
+ const lower = message.toLowerCase();
320
+ return (lower.includes('maximum context length') ||
321
+ lower.includes('context length') ||
322
+ lower.includes('too many tokens') ||
323
+ lower.includes('prompt is too long') ||
324
+ lower.includes('input is too long') ||
325
+ lower.includes('please reduce'));
326
+ })();
327
+ if (!isContextOverflow) {
328
+ throw error;
329
+ }
330
+ const reactiveResult = await reactiveCompact({
331
+ sessionManager,
332
+ llm: options.llm,
333
+ error,
334
+ tracking: currentCompactionTracking,
335
+ signal: mergedSignal.signal,
336
+ });
337
+ currentCompactionTracking = reactiveResult.tracking;
338
+ // Rebuild context and retry runSalmonLoop.
339
+ // Important: avoid duplicating the current instruction inside `conversationContext`,
340
+ // since `instruction: trimmed` is already provided as the primary input.
341
+ const history = sessionManager.getMessages();
342
+ const retryMessages = history.length > 0 &&
343
+ history[history.length - 1]?.role === 'user' &&
344
+ history[history.length - 1]?.content === trimmed
345
+ ? history.slice(0, -1)
346
+ : history;
347
+ const reactiveBudgetTokens = Math.max(64, Math.min(baseSessionBudgetTokens - 1, Math.floor(baseSessionBudgetTokens * 0.5)));
348
+ getLogger().audit('COMPACTION_REACTIVE_RETRY', {
349
+ reason: 'context_overflow',
350
+ baseSessionBudgetTokens,
351
+ reactiveBudgetTokens,
352
+ baseIsMinimum: baseSessionBudgetTokens <= 256,
353
+ modelId: modelIdForBudget ?? 'unknown',
354
+ }, { source: 'chat', severity: 'low', scope: 'session', phase: 'COMPACTION' });
355
+ const newContext = buildEffectiveConversationContext({
356
+ llm: options.llm,
357
+ sessionManager,
358
+ messages: retryMessages,
359
+ budgetTokens: reactiveBudgetTokens,
360
+ });
361
+ return await runSalmonLoop({
362
+ instruction: trimmed,
363
+ verify: options.verifyCommand,
364
+ repoPath: options.repoPath,
365
+ llm: options.llm,
366
+ mode: flowMode,
367
+ strategy,
368
+ verbose: verboseLevel,
369
+ onEvent: latestEmit,
370
+ signal: mergedSignal.signal,
371
+ llmOutput: currentLlmOutputPolicy,
372
+ outcomeReporter: options.outcomeReporter,
373
+ auditScope: options.auditScope,
374
+ conversationContext: newContext.length > 0 ? newContext : undefined,
375
+ artifactHints,
376
+ replacementState,
377
+ astValidation: options.astValidation,
378
+ languagePlugins: options.languagePlugins,
379
+ langfuseSessionId: options.langfuseSessionId || sessionManager.getCurrent().meta.id,
380
+ langfuseUserId: options.langfuseUserId,
381
+ authorizationProvider,
382
+ authorizationMode: 'deferred',
383
+ userInputProvider,
384
+ subAgentController,
385
+ permissionMode: options.permissionMode,
386
+ });
270
387
  });
271
- return { kind: 'flow', mode: intentDecision.intent, result };
388
+ return { kind: 'flow', mode: flowMode, result };
272
389
  })(), CHAT_QUEUE_CONFIG.TASK_TIMEOUT_MS, () => timeoutAbort.abort()).finally(() => {
273
390
  mergedSignal.cleanup();
274
391
  });
275
392
  const result = execution.result;
276
393
  const mode = execution.mode;
277
394
  if (!result.success && isInterruptResult(result.reason)) {
278
- markInterrupted(trimmed);
395
+ markInterrupted(trimmed, mode);
279
396
  }
280
397
  // Add assistant message & iteration info
281
398
  const changedFiles = result.changedFiles ?? [];
@@ -313,23 +430,38 @@ export async function startChatMode(options) {
313
430
  if (usage) {
314
431
  TokenTracker.accumulate(sessionManager.getCurrent(), usage);
315
432
  }
433
+ sessionManager.mergeArtifactState(buildSessionArtifactStateFromLoopResult(result));
434
+ for (const preview of result.artifactHints?.toolResultPreviewArtifacts ?? []) {
435
+ sessionManager.freezeReplacementDecision({
436
+ toolResultId: `${preview.label}::${preview.artifact.handle}`,
437
+ decision: 'replaced',
438
+ preview: preview.label,
439
+ sourceArtifactHandle: preview.artifact.handle,
440
+ });
441
+ }
316
442
  await refreshSessionSummary({
317
443
  sessionManager,
318
444
  llm: options.llm,
319
445
  contextHash: result.contextHash,
320
446
  strategy: 'auto',
447
+ recoveryStatePatch: {
448
+ lastFailureSummary: buildRecoveryFailureSummary(result),
449
+ },
321
450
  });
451
+ currentCompactionTracking = onNormalTurnComplete(currentCompactionTracking);
322
452
  await sessionManager.save();
323
453
  currentInstruction = null;
454
+ currentFlowMode = null;
324
455
  return result;
325
456
  }).catch((error) => {
326
457
  if (!started) {
327
458
  latestDispatch?.({ type: 'REMOVE_QUEUE_MESSAGE', payload: { id: queueMessageId } });
328
459
  }
329
460
  if (isInterruptError(error) && currentInstruction) {
330
- markInterrupted(currentInstruction);
461
+ markInterrupted(currentInstruction, currentFlowMode ?? undefined);
331
462
  }
332
463
  currentInstruction = null;
464
+ currentFlowMode = null;
333
465
  throw error;
334
466
  });
335
467
  };
@@ -341,11 +473,13 @@ export async function startChatMode(options) {
341
473
  resume: () => {
342
474
  queue.resume();
343
475
  lastInterruptedInput = null;
476
+ lastInterruptedFlowMode = null;
344
477
  getLogger().audit('QUEUE_RESUME', { status: 'resumed' }, { source: 'chat', severity: 'low', scope: 'session' });
345
478
  },
346
479
  clear: () => {
347
480
  const cleared = queue.clear();
348
481
  lastInterruptedInput = null;
482
+ lastInterruptedFlowMode = null;
349
483
  latestDispatch?.({ type: 'CLEAR_QUEUE_MESSAGES' });
350
484
  getLogger().audit('QUEUE_CLEAR', { cleared }, { source: 'chat', severity: 'low', scope: 'session' });
351
485
  return cleared;
@@ -354,9 +488,11 @@ export async function startChatMode(options) {
354
488
  if (!lastInterruptedInput)
355
489
  return false;
356
490
  const retryInput = lastInterruptedInput;
491
+ const retryFlowMode = lastInterruptedFlowMode ?? undefined;
357
492
  lastInterruptedInput = null;
493
+ lastInterruptedFlowMode = null;
358
494
  queue.resume();
359
- enqueueInput(retryInput, { front: true });
495
+ enqueueInput(retryInput, { front: true, flowMode: retryFlowMode });
360
496
  return true;
361
497
  },
362
498
  status: () => {
@@ -1,5 +1,6 @@
1
- import { createPluginRegistry, createPromptRegistry, createRuntimeLlm, setPluginRegistry, setPromptRegistry, ExtensionConfigError, getLogger, normalizePermissionMode, PluginLoader, resolveExtensions, } from '../../core/facades/cli-command-chat.js';
1
+ import { createPluginRegistry, createPromptRegistry, createRuntimeLlm, setPluginRegistry, setPromptRegistry, ExtensionConfigError, getLogger, normalizePermissionMode, PluginLoader, resolveExecutionProfile, resolveExtensions, } from '../../core/facades/cli-command-chat.js';
2
2
  import { text } from '../locales/index.js';
3
+ import { getOptionValueSourceWithGlobalFallback } from '../utils/command-option-source.js';
3
4
  import { resolveLlmOutputPolicyFromCli } from '../utils/llm-output.js';
4
5
  import { createOutcomeReporter } from '../utils/outcome-reporter.js';
5
6
  import { resolveCliConfig } from '../utils/resolve-cli-config.js';
@@ -40,7 +41,14 @@ export async function handleChatCommand(options, command) {
40
41
  setPluginRegistry(languagePlugins);
41
42
  setPromptRegistry(createPromptRegistry());
42
43
  await PluginLoader.loadPlugins(languagePlugins, runPath);
43
- const rawPermissionMode = allOptions.mode ?? resolvedConfig.permissionMode ?? 'interactive';
44
+ const defaultFlowMode = 'autopilot';
45
+ const defaultFlowProfile = resolveExecutionProfile(defaultFlowMode);
46
+ const modeOptionSource = getOptionValueSourceWithGlobalFallback(command, 'mode');
47
+ const checkpointStrategyOptionSource = getOptionValueSourceWithGlobalFallback(command, 'checkpointStrategy');
48
+ const rawPermissionMode = (modeOptionSource === 'cli' ? allOptions.mode : undefined) ??
49
+ resolvedConfig.permissionMode ??
50
+ defaultFlowProfile.defaultPermissionMode ??
51
+ 'interactive';
44
52
  const permissionMode = normalizePermissionMode(rawPermissionMode);
45
53
  if (!permissionMode) {
46
54
  getLogger().error(`Invalid --mode "${String(rawPermissionMode)}". Expected "interactive" or "yolo".`);
@@ -86,11 +94,10 @@ export async function handleChatCommand(options, command) {
86
94
  repoPath: runPath,
87
95
  llm,
88
96
  verifyCommand,
89
- checkpointStrategy: permissionMode === 'yolo' &&
90
- typeof command.getOptionValueSource === 'function' &&
91
- command.getOptionValueSource('checkpointStrategy') !== 'cli'
92
- ? 'direct'
93
- : allOptions.checkpointStrategy || 'worktree',
97
+ defaultFlowMode,
98
+ checkpointStrategy: checkpointStrategyOptionSource === 'cli'
99
+ ? allOptions.checkpointStrategy || 'worktree'
100
+ : undefined,
94
101
  continue: continueSession,
95
102
  resumeSessionId,
96
103
  verbose: verboseLevel,
@@ -0,0 +1,63 @@
1
+ import { FLOW_MODES, parseFlowMode } from '../../core/types/flow-mode.js';
2
+ import { text } from '../locales/index.js';
3
+ import { parseSuggestionContext } from './utils.js';
4
+ export const flowModeCommand = {
5
+ name: '/flow-mode',
6
+ description: text.cli.commandFlowMode,
7
+ order: 55,
8
+ getSuggestions: ({ input }) => {
9
+ const { argIndex, currentPrefix } = parseSuggestionContext(input);
10
+ if (argIndex !== 1)
11
+ return [];
12
+ const search = currentPrefix.toLowerCase();
13
+ return FLOW_MODES.filter((mode) => mode.startsWith(search)).map((mode) => ({
14
+ name: mode,
15
+ description: text.cli.flowModeSuggestion(mode),
16
+ }));
17
+ },
18
+ execute: async ({ emit, input, sessionManager }) => {
19
+ const args = input.trim().split(/\s+/).slice(1);
20
+ const rawValue = args[0];
21
+ if (!rawValue) {
22
+ const current = sessionManager.getChatFlowMode() ?? 'autopilot';
23
+ emit({
24
+ type: 'log',
25
+ level: 'info',
26
+ message: text.cli.flowModeCurrent(current),
27
+ timestamp: new Date(),
28
+ });
29
+ emit({
30
+ type: 'log',
31
+ level: 'info',
32
+ message: text.cli.flowModeUsage,
33
+ timestamp: new Date(),
34
+ });
35
+ return;
36
+ }
37
+ const normalized = parseFlowMode(rawValue);
38
+ if (!normalized) {
39
+ emit({
40
+ type: 'log',
41
+ level: 'error',
42
+ message: text.cli.flowModeInvalid(rawValue),
43
+ timestamp: new Date(),
44
+ });
45
+ emit({
46
+ type: 'log',
47
+ level: 'info',
48
+ message: text.cli.flowModeUsage,
49
+ timestamp: new Date(),
50
+ });
51
+ return;
52
+ }
53
+ sessionManager.updateChatFlowMode(normalized);
54
+ await sessionManager.save();
55
+ emit({
56
+ type: 'log',
57
+ level: 'info',
58
+ message: text.cli.flowModeUpdated(normalized),
59
+ timestamp: new Date(),
60
+ });
61
+ },
62
+ };
63
+ //# sourceMappingURL=flow-mode.js.map
@@ -2,6 +2,7 @@ import { text } from '../locales/index.js';
2
2
  import { allowlistCommand } from './allowlist.js';
3
3
  import { configCommand } from './config.js';
4
4
  import { exitCommand } from './exit.js';
5
+ import { flowModeCommand } from './flow-mode.js';
5
6
  import { formatHelpRows } from './help-format.js';
6
7
  import { llmOutputCommand } from './llm-output.js';
7
8
  import { logModeCommand } from './log-mode.js';
@@ -21,6 +22,7 @@ const baseCommands = [
21
22
  allowlistCommand,
22
23
  modeCommand,
23
24
  logModeCommand,
25
+ flowModeCommand,
24
26
  configCommand,
25
27
  subAgentCommand,
26
28
  newCommand,
@@ -0,0 +1,41 @@
1
+ import { FileAdapter } from '../../../core/adapters/fs/file-adapter.js';
2
+ import { buildBenchmarkPatchArtifact } from '../../../core/benchmark/patch-artifact.js';
3
+ import { encodeSweBenchPredictionJsonl } from '../../../core/benchmark/swe-bench.js';
4
+ export async function attachRunBenchmarkArtifacts(params) {
5
+ if (!params.exportPatchPath && !params.sweBenchPredictionsPath)
6
+ return;
7
+ const fileAdapter = new FileAdapter();
8
+ const artifact = await buildBenchmarkPatchArtifact({
9
+ repoPath: params.repoPath,
10
+ changedFilesHint: params.result.changedFiles,
11
+ excludePaths: [params.exportPatchPath, params.sweBenchPredictionsPath].filter((path) => typeof path === 'string'),
12
+ });
13
+ if (params.exportPatchPath) {
14
+ await fileAdapter.writeFile(params.exportPatchPath, artifact.patch);
15
+ }
16
+ params.result.benchmarkPatchArtifact = {
17
+ kind: 'git-unified-diff',
18
+ path: params.exportPatchPath,
19
+ sha256: artifact.sha256,
20
+ bytes: artifact.bytes,
21
+ changedFiles: artifact.changedFiles,
22
+ isEmpty: artifact.isEmpty,
23
+ };
24
+ if (params.sweBenchPredictionsPath) {
25
+ if (!params.sweBenchInstanceId || !params.sweBenchModelName) {
26
+ throw new Error('SWE-bench predictions require instance id and model name.');
27
+ }
28
+ await fileAdapter.appendFile(params.sweBenchPredictionsPath, encodeSweBenchPredictionJsonl({
29
+ instanceId: params.sweBenchInstanceId,
30
+ modelNameOrPath: params.sweBenchModelName,
31
+ modelPatch: artifact.patch,
32
+ }));
33
+ params.result.benchmarkArtifact = {
34
+ provider: 'swe-bench',
35
+ instanceId: params.sweBenchInstanceId,
36
+ modelNameOrPath: params.sweBenchModelName,
37
+ predictionsPath: params.sweBenchPredictionsPath,
38
+ };
39
+ }
40
+ }
41
+ //# sourceMappingURL=benchmark-artifacts.js.map
@@ -57,6 +57,7 @@ export function handleEarlyRunCommandErrors(params) {
57
57
  instruction: params.instruction,
58
58
  message: text.cli.outputProfileRequiresStreamJson,
59
59
  exitCode: 1,
60
+ errorCode: 'USAGE_ERROR',
60
61
  });
61
62
  }
62
63
  return { ok: false, exitCode: 1 };
@@ -103,6 +104,28 @@ export function handleEarlyRunCommandErrors(params) {
103
104
  }
104
105
  return { ok: false, exitCode: 1 };
105
106
  }
107
+ if (params.sweBenchPredictionsPath && !params.sweBenchInstanceId) {
108
+ getLogger().error(text.cli.sweBenchInstanceRequired);
109
+ if (params.headlessOutput) {
110
+ params.headlessErrorWriter.writeUsageError({
111
+ sessionId: params.sessionIdForOutput ?? params.resumeSessionId,
112
+ message: text.cli.sweBenchInstanceRequired,
113
+ instruction: params.instruction,
114
+ });
115
+ }
116
+ return { ok: false, exitCode: 1 };
117
+ }
118
+ if (params.sweBenchPredictionsPath && !params.sweBenchModelName) {
119
+ getLogger().error(text.cli.sweBenchModelRequired);
120
+ if (params.headlessOutput) {
121
+ params.headlessErrorWriter.writeUsageError({
122
+ sessionId: params.sessionIdForOutput ?? params.resumeSessionId,
123
+ message: text.cli.sweBenchModelRequired,
124
+ instruction: params.instruction,
125
+ });
126
+ }
127
+ return { ok: false, exitCode: 1 };
128
+ }
106
129
  return { ok: true };
107
130
  }
108
131
  //# sourceMappingURL=early-errors.js.map