salmon-loop 0.2.3 → 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 (234) 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 +161 -24
  4. package/dist/cli/commands/chat.js +30 -24
  5. package/dist/cli/commands/context.js +15 -3
  6. package/dist/cli/commands/flow-mode.js +63 -0
  7. package/dist/cli/commands/help-format.js +12 -0
  8. package/dist/cli/commands/registry.js +6 -7
  9. package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
  10. package/dist/cli/commands/run/config-resolution.js +30 -24
  11. package/dist/cli/commands/run/early-errors.js +23 -0
  12. package/dist/cli/commands/run/handler.js +131 -44
  13. package/dist/cli/commands/run/headless-error-writer.js +8 -0
  14. package/dist/cli/commands/run/loop-params.js +3 -0
  15. package/dist/cli/commands/run/mode.js +2 -5
  16. package/dist/cli/commands/run/parse-options.js +18 -2
  17. package/dist/cli/commands/run/persist-session.js +10 -1
  18. package/dist/cli/commands/run/preflight.js +10 -0
  19. package/dist/cli/commands/run/reporter-factory.js +4 -0
  20. package/dist/cli/commands/run/runtime-llm.js +38 -11
  21. package/dist/cli/commands/run/runtime-options.js +2 -2
  22. package/dist/cli/commands/run/validate-options.js +0 -5
  23. package/dist/cli/commands/run/verbose.js +2 -7
  24. package/dist/cli/commands/serve.js +117 -90
  25. package/dist/cli/commands/tool-names.js +78 -78
  26. package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
  27. package/dist/cli/headless/json-protocol.js +37 -0
  28. package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
  29. package/dist/cli/headless/protocol-metadata.js +22 -0
  30. package/dist/cli/headless/stream-json-protocol.js +34 -1
  31. package/dist/cli/index.js +6 -4
  32. package/dist/cli/locales/en.js +32 -6
  33. package/dist/cli/program-bootstrap.js +14 -4
  34. package/dist/cli/program-commands.js +9 -1
  35. package/dist/cli/program-options.js +1 -0
  36. package/dist/cli/reporters/anthropic-stream.js +7 -1
  37. package/dist/cli/reporters/json.js +4 -0
  38. package/dist/cli/reporters/stream-json.js +17 -2
  39. package/dist/cli/run-cli.js +5 -3
  40. package/dist/cli/slash/runtime.js +30 -15
  41. package/dist/cli/ui/components/CommandInput.js +7 -3
  42. package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
  43. package/dist/cli/utils/command-option-source.js +13 -0
  44. package/dist/cli/utils/output-format.js +6 -0
  45. package/dist/cli/utils/resolve-cli-config.js +98 -0
  46. package/dist/cli/utils/verbose-level.js +8 -0
  47. package/dist/cli/utils/verify-resolver.js +8 -4
  48. package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
  49. package/dist/core/adapters/fs/file-adapter.js +6 -0
  50. package/dist/core/adapters/fs/filesystem.js +2 -1
  51. package/dist/core/adapters/git/git-adapter.js +78 -1
  52. package/dist/core/benchmark/patch-artifact.js +124 -0
  53. package/dist/core/benchmark/swe-bench.js +25 -0
  54. package/dist/core/config/load.js +39 -18
  55. package/dist/core/config/merge.js +27 -0
  56. package/dist/core/config/paths.js +24 -5
  57. package/dist/core/config/resolve-llm.js +12 -0
  58. package/dist/core/config/resolve.js +7 -5
  59. package/dist/core/config/resolvers/server.js +0 -6
  60. package/dist/core/config/validate.js +94 -21
  61. package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
  62. package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
  63. package/dist/core/context/keywords.js +18 -4
  64. package/dist/core/context/service-deps.js +2 -2
  65. package/dist/core/context/service.js +8 -0
  66. package/dist/core/context/steps/context-gather.js +38 -0
  67. package/dist/core/context/summarization/summarizer.js +55 -12
  68. package/dist/core/context/targeting/target-resolver.js +4 -4
  69. package/dist/core/extensions/index.js +23 -5
  70. package/dist/core/extensions/paths.js +31 -0
  71. package/dist/core/extensions/schemas.js +8 -5
  72. package/dist/core/facades/cli-chat.js +6 -2
  73. package/dist/core/facades/cli-command-chat.js +2 -1
  74. package/dist/core/facades/cli-command-tool-names.js +2 -0
  75. package/dist/core/facades/cli-context.js +1 -0
  76. package/dist/core/facades/cli-observability.js +1 -1
  77. package/dist/core/facades/cli-run-handler.js +4 -2
  78. package/dist/core/facades/cli-run-persist-session.js +1 -0
  79. package/dist/core/facades/cli-serve.js +2 -4
  80. package/dist/core/facades/cli-utils-worktree.js +1 -1
  81. package/dist/core/failure/diagnostics.js +53 -1
  82. package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
  83. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
  84. package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
  85. package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
  86. package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
  87. package/dist/core/grizzco/engine/transaction/transaction-runner.js +173 -7
  88. package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
  89. package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
  90. package/dist/core/grizzco/steps/answer.js +13 -14
  91. package/dist/core/grizzco/steps/autopilot.js +396 -0
  92. package/dist/core/grizzco/steps/cache-sharing.js +29 -0
  93. package/dist/core/grizzco/steps/explore.js +37 -21
  94. package/dist/core/grizzco/steps/generateReview.js +2 -5
  95. package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
  96. package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
  97. package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
  98. package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
  99. package/dist/core/grizzco/steps/patch.js +105 -146
  100. package/dist/core/grizzco/steps/plan.js +101 -25
  101. package/dist/core/grizzco/steps/preflight.js +5 -3
  102. package/dist/core/grizzco/steps/request-assembly.js +78 -0
  103. package/dist/core/grizzco/steps/research.js +39 -36
  104. package/dist/core/grizzco/steps/tool-runtime.js +47 -0
  105. package/dist/core/grizzco/steps/verify-shared.js +23 -0
  106. package/dist/core/grizzco/steps/verify.js +13 -21
  107. package/dist/core/intent/chat-intent.js +0 -4
  108. package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
  109. package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
  110. package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
  111. package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
  112. package/dist/core/llm/ai-sdk/request-params.js +74 -1
  113. package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
  114. package/dist/core/llm/ai-sdk.js +112 -27
  115. package/dist/core/llm/capabilities.js +12 -0
  116. package/dist/core/llm/contracts/repair.js +36 -30
  117. package/dist/core/llm/errors.js +83 -2
  118. package/dist/core/llm/message-composition.js +7 -22
  119. package/dist/core/llm/phase-router.js +29 -10
  120. package/dist/core/llm/redact.js +28 -3
  121. package/dist/core/llm/registry.js +2 -0
  122. package/dist/core/llm/request-augmentation.js +55 -0
  123. package/dist/core/llm/request-envelope.js +334 -0
  124. package/dist/core/llm/shared-request-assembly.js +35 -0
  125. package/dist/core/llm/stream-utils.js +13 -4
  126. package/dist/core/llm/utils.js +18 -29
  127. package/dist/core/memory/relevant-retrieval.js +144 -0
  128. package/dist/core/observability/logger.js +11 -2
  129. package/dist/core/patch/diff.js +1 -0
  130. package/dist/core/prompts/registry.js +39 -2
  131. package/dist/core/prompts/runtime.js +50 -12
  132. package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
  133. package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
  134. package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
  135. package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
  136. package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
  137. package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
  138. package/dist/core/prompts/templates/system/main_system.hbs +4 -16
  139. package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
  140. package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
  141. package/dist/core/prompts/templates/system/research_system.hbs +2 -0
  142. package/dist/core/protocols/a2a/agent-card.js +3 -2
  143. package/dist/core/protocols/a2a/sdk/executor.js +8 -6
  144. package/dist/core/protocols/a2a/sdk/server.js +0 -1
  145. package/dist/core/protocols/acp/formal-agent.js +221 -55
  146. package/dist/core/protocols/acp/handlers.js +5 -1
  147. package/dist/core/protocols/acp/permission-provider.js +21 -1
  148. package/dist/core/protocols/shared/execution-request.js +24 -0
  149. package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
  150. package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
  151. package/dist/core/public-capabilities/projections.js +29 -0
  152. package/dist/core/public-capabilities/registry.js +26 -0
  153. package/dist/core/public-capabilities/types.js +2 -0
  154. package/dist/core/runtime/agent-server-runtime.js +47 -43
  155. package/dist/core/runtime/execution-profile.js +67 -0
  156. package/dist/core/session/artifact-state.js +160 -0
  157. package/dist/core/session/compaction/index.js +183 -0
  158. package/dist/core/session/compaction/microcompact.js +78 -0
  159. package/dist/core/session/compaction/tracking.js +48 -0
  160. package/dist/core/session/compaction/types.js +11 -0
  161. package/dist/core/session/compression.js +12 -4
  162. package/dist/core/session/manager.js +247 -10
  163. package/dist/core/session/pruning-strategy.js +55 -9
  164. package/dist/core/session/replacement-preview-provider.js +24 -0
  165. package/dist/core/session/replacement-state.js +131 -0
  166. package/dist/core/session/resume-repair/pipeline.js +79 -0
  167. package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
  168. package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
  169. package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
  170. package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
  171. package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
  172. package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
  173. package/dist/core/session/resume-repair/types.js +2 -0
  174. package/dist/core/session/summary-sync.js +164 -13
  175. package/dist/core/session/token-tracker.js +6 -0
  176. package/dist/core/skills/audit.js +34 -0
  177. package/dist/core/skills/bridge.js +84 -7
  178. package/dist/core/skills/discovery.js +94 -0
  179. package/dist/core/skills/feature-flags.js +52 -0
  180. package/dist/core/skills/index.js +1 -1
  181. package/dist/core/skills/loader.js +195 -20
  182. package/dist/core/skills/parser.js +296 -24
  183. package/dist/core/skills/permissions.js +117 -0
  184. package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
  185. package/dist/core/skills/runtime/SkillRunner.js +240 -61
  186. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
  187. package/dist/core/strata/layers/worktree.js +70 -13
  188. package/dist/core/strata/runtime/synchronizer.js +29 -2
  189. package/dist/core/streaming/stream-assembler.js +75 -31
  190. package/dist/core/sub-agent/context-snapshot.js +156 -0
  191. package/dist/core/sub-agent/core/loop.js +1 -1
  192. package/dist/core/sub-agent/core/manager.js +119 -20
  193. package/dist/core/sub-agent/dispatch-policy.js +29 -0
  194. package/dist/core/sub-agent/prefix-consistency.js +48 -0
  195. package/dist/core/sub-agent/registry-defaults.js +4 -0
  196. package/dist/core/sub-agent/tools/task-spawn.js +79 -2
  197. package/dist/core/sub-agent/types.js +134 -5
  198. package/dist/core/tools/audit.js +13 -4
  199. package/dist/core/tools/builtin/ast-grep.js +1 -1
  200. package/dist/core/tools/builtin/ast.js +1 -1
  201. package/dist/core/tools/builtin/benchmark.js +360 -0
  202. package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
  203. package/dist/core/tools/builtin/code-search/executor.js +6 -1
  204. package/dist/core/tools/builtin/code-search/spec.js +26 -2
  205. package/dist/core/tools/builtin/fs.js +256 -23
  206. package/dist/core/tools/builtin/git.js +2 -2
  207. package/dist/core/tools/builtin/index.js +51 -2
  208. package/dist/core/tools/builtin/interaction.js +8 -1
  209. package/dist/core/tools/builtin/plan.js +37 -15
  210. package/dist/core/tools/builtin/shell.js +1 -1
  211. package/dist/core/tools/loader.js +39 -16
  212. package/dist/core/tools/mapper.js +17 -3
  213. package/dist/core/tools/parallel/scheduler.js +35 -4
  214. package/dist/core/tools/permissions/permission-rules.js +5 -10
  215. package/dist/core/tools/policy.js +6 -1
  216. package/dist/core/tools/recoverable-tool-errors.js +10 -0
  217. package/dist/core/tools/router.js +24 -6
  218. package/dist/core/tools/session.js +458 -48
  219. package/dist/core/tools/tool-visibility.js +62 -0
  220. package/dist/core/tools/types.js +9 -1
  221. package/dist/core/types/execution.js +4 -0
  222. package/dist/core/types/flow-mode.js +8 -0
  223. package/dist/core/utils/path.js +52 -0
  224. package/dist/core/verification/runner.js +4 -1
  225. package/dist/interfaces/cli/task-runner.js +4 -3
  226. package/dist/languages/typescript/index.js +4 -1
  227. package/dist/locales/en.js +87 -2
  228. package/dist/utils/eol.js +1 -1
  229. package/package.json +15 -8
  230. package/scripts/fix-es-abstract-compat.js +77 -0
  231. package/dist/core/runtime/fastify-server-bundle.js +0 -26
  232. package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
  233. package/dist/core/runtime/sidecar-paths.js +0 -47
  234. 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.
@@ -266,15 +299,100 @@ export async function startChatMode(options) {
266
299
  authorizationMode: 'deferred',
267
300
  userInputProvider,
268
301
  subAgentController,
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
+ });
269
387
  });
270
- return { kind: 'flow', mode: intentDecision.intent, result };
388
+ return { kind: 'flow', mode: flowMode, result };
271
389
  })(), CHAT_QUEUE_CONFIG.TASK_TIMEOUT_MS, () => timeoutAbort.abort()).finally(() => {
272
390
  mergedSignal.cleanup();
273
391
  });
274
392
  const result = execution.result;
275
393
  const mode = execution.mode;
276
394
  if (!result.success && isInterruptResult(result.reason)) {
277
- markInterrupted(trimmed);
395
+ markInterrupted(trimmed, mode);
278
396
  }
279
397
  // Add assistant message & iteration info
280
398
  const changedFiles = result.changedFiles ?? [];
@@ -312,23 +430,38 @@ export async function startChatMode(options) {
312
430
  if (usage) {
313
431
  TokenTracker.accumulate(sessionManager.getCurrent(), usage);
314
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
+ }
315
442
  await refreshSessionSummary({
316
443
  sessionManager,
317
444
  llm: options.llm,
318
445
  contextHash: result.contextHash,
319
446
  strategy: 'auto',
447
+ recoveryStatePatch: {
448
+ lastFailureSummary: buildRecoveryFailureSummary(result),
449
+ },
320
450
  });
451
+ currentCompactionTracking = onNormalTurnComplete(currentCompactionTracking);
321
452
  await sessionManager.save();
322
453
  currentInstruction = null;
454
+ currentFlowMode = null;
323
455
  return result;
324
456
  }).catch((error) => {
325
457
  if (!started) {
326
458
  latestDispatch?.({ type: 'REMOVE_QUEUE_MESSAGE', payload: { id: queueMessageId } });
327
459
  }
328
460
  if (isInterruptError(error) && currentInstruction) {
329
- markInterrupted(currentInstruction);
461
+ markInterrupted(currentInstruction, currentFlowMode ?? undefined);
330
462
  }
331
463
  currentInstruction = null;
464
+ currentFlowMode = null;
332
465
  throw error;
333
466
  });
334
467
  };
@@ -340,11 +473,13 @@ export async function startChatMode(options) {
340
473
  resume: () => {
341
474
  queue.resume();
342
475
  lastInterruptedInput = null;
476
+ lastInterruptedFlowMode = null;
343
477
  getLogger().audit('QUEUE_RESUME', { status: 'resumed' }, { source: 'chat', severity: 'low', scope: 'session' });
344
478
  },
345
479
  clear: () => {
346
480
  const cleared = queue.clear();
347
481
  lastInterruptedInput = null;
482
+ lastInterruptedFlowMode = null;
348
483
  latestDispatch?.({ type: 'CLEAR_QUEUE_MESSAGES' });
349
484
  getLogger().audit('QUEUE_CLEAR', { cleared }, { source: 'chat', severity: 'low', scope: 'session' });
350
485
  return cleared;
@@ -353,9 +488,11 @@ export async function startChatMode(options) {
353
488
  if (!lastInterruptedInput)
354
489
  return false;
355
490
  const retryInput = lastInterruptedInput;
491
+ const retryFlowMode = lastInterruptedFlowMode ?? undefined;
356
492
  lastInterruptedInput = null;
493
+ lastInterruptedFlowMode = null;
357
494
  queue.resume();
358
- enqueueInput(retryInput, { front: true });
495
+ enqueueInput(retryInput, { front: true, flowMode: retryFlowMode });
359
496
  return true;
360
497
  },
361
498
  status: () => {
@@ -1,13 +1,26 @@
1
- import { resolve } from 'path';
2
- import { createPluginRegistry, createPromptRegistry, createRuntimeLlm, setPluginRegistry, setPromptRegistry, ExtensionConfigError, getLogger, normalizePermissionMode, PluginLoader, resolveConfig, 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';
3
2
  import { text } from '../locales/index.js';
4
- import { resolveAuditScope } from '../utils/audit-scope.js';
3
+ import { getOptionValueSourceWithGlobalFallback } from '../utils/command-option-source.js';
5
4
  import { resolveLlmOutputPolicyFromCli } from '../utils/llm-output.js';
6
5
  import { createOutcomeReporter } from '../utils/outcome-reporter.js';
6
+ import { resolveCliConfig } from '../utils/resolve-cli-config.js';
7
7
  import { resolveVerifyOption } from '../utils/verify-resolver.js';
8
8
  export async function handleChatCommand(options, command) {
9
9
  const allOptions = command.optsWithGlobals();
10
- const runPath = resolve(allOptions.repo || process.cwd());
10
+ const configResult = await resolveCliConfig({
11
+ repo: allOptions.repo,
12
+ cwd: process.cwd(),
13
+ configPath: allOptions.config,
14
+ enableConfigFile: allOptions.configFile !== false,
15
+ auditScope: allOptions.auditScope,
16
+ verbose: allOptions.verbose,
17
+ logMode: allOptions.logMode,
18
+ });
19
+ if (!configResult.ok) {
20
+ getLogger().error(configResult.message, true);
21
+ process.exit(1);
22
+ }
23
+ const { resolvedConfig, auditScope, repoPath: runPath, verboseLevel } = configResult;
11
24
  const printInstruction = typeof allOptions.print === 'string'
12
25
  ? allOptions.print
13
26
  : undefined;
@@ -28,20 +41,14 @@ export async function handleChatCommand(options, command) {
28
41
  setPluginRegistry(languagePlugins);
29
42
  setPromptRegistry(createPromptRegistry());
30
43
  await PluginLoader.loadPlugins(languagePlugins, runPath);
31
- const resolvedConfig = await resolveConfig({
32
- repoRoot: runPath,
33
- enableConfigFile: true,
34
- });
35
- const auditScopeResolution = resolveAuditScope({
36
- cliValue: allOptions.auditScope,
37
- configValue: resolvedConfig.observability.audit.scope,
38
- });
39
- if (!auditScopeResolution.ok) {
40
- getLogger().error(text.cli.invalidAuditScope(auditScopeResolution.invalid), true);
41
- process.exit(1);
42
- }
43
- const auditScope = auditScopeResolution.value;
44
- 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';
45
52
  const permissionMode = normalizePermissionMode(rawPermissionMode);
46
53
  if (!permissionMode) {
47
54
  getLogger().error(`Invalid --mode "${String(rawPermissionMode)}". Expected "interactive" or "yolo".`);
@@ -87,14 +94,13 @@ export async function handleChatCommand(options, command) {
87
94
  repoPath: runPath,
88
95
  llm,
89
96
  verifyCommand,
90
- checkpointStrategy: permissionMode === 'yolo' &&
91
- typeof command.getOptionValueSource === 'function' &&
92
- command.getOptionValueSource('checkpointStrategy') !== 'cli'
93
- ? 'direct'
94
- : allOptions.checkpointStrategy || 'worktree',
97
+ defaultFlowMode,
98
+ checkpointStrategy: checkpointStrategyOptionSource === 'cli'
99
+ ? allOptions.checkpointStrategy || 'worktree'
100
+ : undefined,
95
101
  continue: continueSession,
96
102
  resumeSessionId,
97
- verbose: allOptions.verbose,
103
+ verbose: verboseLevel,
98
104
  llmOutput,
99
105
  markdownTheme: resolvedConfig.markdownTheme,
100
106
  markdownRenderMode: resolvedConfig.markdownRenderMode,
@@ -1,8 +1,21 @@
1
- import { createContextCacheStore, createDefaultPermissionGate, ContextService, defaultPathAdapter, getLogger, resolveConfig, setChurnRankingPolicy, } from '../../core/facades/cli-context.js';
1
+ import { createContextCacheStore, createDefaultPermissionGate, ContextService, getLogger, setChurnRankingPolicy, } from '../../core/facades/cli-context.js';
2
2
  import { text } from '../locales/index.js';
3
+ import { resolveCliConfig } from '../utils/resolve-cli-config.js';
3
4
  export async function handleContextCommand(options, command) {
4
5
  const allOptions = command.optsWithGlobals();
5
- const repoPath = defaultPathAdapter.resolve(allOptions.repo || process.cwd());
6
+ const configResult = await resolveCliConfig({
7
+ repo: allOptions.repo,
8
+ cwd: process.cwd(),
9
+ configPath: allOptions.config,
10
+ enableConfigFile: allOptions.configFile !== false,
11
+ auditScope: allOptions.auditScope,
12
+ logMode: allOptions.logMode,
13
+ });
14
+ if (!configResult.ok) {
15
+ getLogger().error(configResult.message, true);
16
+ process.exit(1);
17
+ }
18
+ const { resolvedConfig, repoPath } = configResult;
6
19
  if (options.file && options.selection) {
7
20
  getLogger().error(text.cli.fileSelectionConflict, true);
8
21
  process.exit(1);
@@ -26,7 +39,6 @@ export async function handleContextCommand(options, command) {
26
39
  }
27
40
  budgetChars = parsed;
28
41
  }
29
- const resolvedConfig = await resolveConfig({ repoRoot: repoPath });
30
42
  setChurnRankingPolicy({
31
43
  primaryBoost: resolvedConfig.raw?.context?.churn?.weight?.primary,
32
44
  rerankWeight: resolvedConfig.raw?.context?.churn?.weight?.rerank,
@@ -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
@@ -0,0 +1,12 @@
1
+ export function formatHelpRows(items) {
2
+ const rows = items.map((item) => {
3
+ const aliases = item.aliases?.length ? ` (${item.aliases.join(', ')})` : '';
4
+ return {
5
+ label: `${item.name}${aliases}`,
6
+ description: item.description,
7
+ };
8
+ });
9
+ const maxName = Math.max(...rows.map((row) => row.label.length), 0);
10
+ return rows.map((row) => `${row.label}`.padEnd(maxName + 2) + row.description).join('\n');
11
+ }
12
+ //# sourceMappingURL=help-format.js.map
@@ -1,6 +1,9 @@
1
+ import { text } from '../locales/index.js';
1
2
  import { allowlistCommand } from './allowlist.js';
2
3
  import { configCommand } from './config.js';
3
4
  import { exitCommand } from './exit.js';
5
+ import { flowModeCommand } from './flow-mode.js';
6
+ import { formatHelpRows } from './help-format.js';
4
7
  import { llmOutputCommand } from './llm-output.js';
5
8
  import { logModeCommand } from './log-mode.js';
6
9
  import { modeCommand } from './mode.js';
@@ -19,6 +22,7 @@ const baseCommands = [
19
22
  allowlistCommand,
20
23
  modeCommand,
21
24
  logModeCommand,
25
+ flowModeCommand,
22
26
  configCommand,
23
27
  subAgentCommand,
24
28
  newCommand,
@@ -29,16 +33,11 @@ const baseCommands = [
29
33
  order: 80,
30
34
  execute: ({ emit }) => {
31
35
  const visible = commands.filter((c) => !c.hidden);
32
- const maxName = Math.max(...visible.map((cmd) => cmd.name.length), 0);
33
- const rows = visible.map((cmd) => {
34
- const paddedName = `${cmd.name}`.padEnd(maxName + 2);
35
- return `${paddedName}${cmd.description}`;
36
- });
37
- const helpMsg = rows.join('\n');
36
+ const helpMsg = formatHelpRows(visible);
38
37
  emit({
39
38
  type: 'log',
40
39
  level: 'info',
41
- message: `Available Commands:\n${helpMsg}`,
40
+ message: text.cli.helpAvailableCommands(helpMsg),
42
41
  timestamp: new Date(),
43
42
  });
44
43
  },
@@ -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