salmon-loop 0.2.13 → 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/cli/argv/headless-detection.js +27 -0
  2. package/dist/cli/chat-flow.js +11 -0
  3. package/dist/cli/chat.js +160 -24
  4. package/dist/cli/commands/chat.js +14 -7
  5. package/dist/cli/commands/flow-mode.js +63 -0
  6. package/dist/cli/commands/registry.js +2 -0
  7. package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
  8. package/dist/cli/commands/run/early-errors.js +23 -0
  9. package/dist/cli/commands/run/handler.js +115 -27
  10. package/dist/cli/commands/run/headless-error-writer.js +8 -0
  11. package/dist/cli/commands/run/loop-params.js +2 -0
  12. package/dist/cli/commands/run/mode.js +2 -5
  13. package/dist/cli/commands/run/parse-options.js +16 -0
  14. package/dist/cli/commands/run/persist-session.js +10 -1
  15. package/dist/cli/commands/run/preflight.js +10 -0
  16. package/dist/cli/commands/run/reporter-factory.js +4 -0
  17. package/dist/cli/commands/run/runtime-llm.js +38 -11
  18. package/dist/cli/commands/run/runtime-options.js +2 -2
  19. package/dist/cli/commands/serve.js +91 -71
  20. package/dist/cli/commands/tool-names.js +78 -78
  21. package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
  22. package/dist/cli/headless/json-protocol.js +37 -0
  23. package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
  24. package/dist/cli/headless/protocol-metadata.js +22 -0
  25. package/dist/cli/headless/stream-json-protocol.js +34 -1
  26. package/dist/cli/index.js +6 -4
  27. package/dist/cli/locales/en.js +30 -6
  28. package/dist/cli/program-bootstrap.js +8 -3
  29. package/dist/cli/program-commands.js +5 -1
  30. package/dist/cli/reporters/anthropic-stream.js +7 -1
  31. package/dist/cli/reporters/json.js +4 -0
  32. package/dist/cli/reporters/stream-json.js +17 -2
  33. package/dist/cli/run-cli.js +5 -3
  34. package/dist/cli/slash/runtime.js +27 -12
  35. package/dist/cli/ui/components/CommandInput.js +7 -3
  36. package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
  37. package/dist/cli/utils/command-option-source.js +13 -0
  38. package/dist/cli/utils/verify-resolver.js +8 -4
  39. package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
  40. package/dist/core/adapters/fs/file-adapter.js +6 -0
  41. package/dist/core/adapters/fs/filesystem.js +2 -1
  42. package/dist/core/adapters/git/git-adapter.js +78 -1
  43. package/dist/core/benchmark/patch-artifact.js +124 -0
  44. package/dist/core/benchmark/swe-bench.js +25 -0
  45. package/dist/core/config/load.js +18 -11
  46. package/dist/core/config/resolve-llm.js +12 -0
  47. package/dist/core/config/resolvers/server.js +0 -6
  48. package/dist/core/config/validate.js +73 -21
  49. package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
  50. package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
  51. package/dist/core/context/keywords.js +18 -4
  52. package/dist/core/context/service-deps.js +2 -2
  53. package/dist/core/context/service.js +8 -0
  54. package/dist/core/context/steps/context-gather.js +38 -0
  55. package/dist/core/context/summarization/summarizer.js +55 -12
  56. package/dist/core/context/targeting/target-resolver.js +4 -4
  57. package/dist/core/extensions/index.js +23 -5
  58. package/dist/core/extensions/paths.js +31 -0
  59. package/dist/core/extensions/schemas.js +8 -5
  60. package/dist/core/facades/cli-chat.js +6 -2
  61. package/dist/core/facades/cli-command-chat.js +1 -0
  62. package/dist/core/facades/cli-command-tool-names.js +2 -0
  63. package/dist/core/facades/cli-observability.js +1 -1
  64. package/dist/core/facades/cli-run-handler.js +4 -2
  65. package/dist/core/facades/cli-run-persist-session.js +1 -0
  66. package/dist/core/facades/cli-serve.js +2 -4
  67. package/dist/core/facades/cli-utils-worktree.js +1 -1
  68. package/dist/core/failure/diagnostics.js +53 -1
  69. package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
  70. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
  71. package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
  72. package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
  73. package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
  74. package/dist/core/grizzco/engine/transaction/transaction-runner.js +165 -7
  75. package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
  76. package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
  77. package/dist/core/grizzco/steps/answer.js +13 -14
  78. package/dist/core/grizzco/steps/autopilot.js +396 -0
  79. package/dist/core/grizzco/steps/cache-sharing.js +29 -0
  80. package/dist/core/grizzco/steps/explore.js +37 -21
  81. package/dist/core/grizzco/steps/generateReview.js +2 -5
  82. package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
  83. package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
  84. package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
  85. package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
  86. package/dist/core/grizzco/steps/patch.js +105 -146
  87. package/dist/core/grizzco/steps/plan.js +101 -25
  88. package/dist/core/grizzco/steps/preflight.js +5 -6
  89. package/dist/core/grizzco/steps/request-assembly.js +78 -0
  90. package/dist/core/grizzco/steps/research.js +39 -36
  91. package/dist/core/grizzco/steps/tool-runtime.js +47 -0
  92. package/dist/core/grizzco/steps/verify-shared.js +23 -0
  93. package/dist/core/grizzco/steps/verify.js +13 -21
  94. package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
  95. package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
  96. package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
  97. package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
  98. package/dist/core/llm/ai-sdk/request-params.js +73 -0
  99. package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
  100. package/dist/core/llm/ai-sdk.js +112 -27
  101. package/dist/core/llm/capabilities.js +12 -0
  102. package/dist/core/llm/contracts/repair.js +36 -30
  103. package/dist/core/llm/errors.js +83 -2
  104. package/dist/core/llm/message-composition.js +7 -22
  105. package/dist/core/llm/phase-router.js +29 -10
  106. package/dist/core/llm/redact.js +28 -3
  107. package/dist/core/llm/registry.js +2 -0
  108. package/dist/core/llm/request-augmentation.js +55 -0
  109. package/dist/core/llm/request-envelope.js +334 -0
  110. package/dist/core/llm/shared-request-assembly.js +35 -0
  111. package/dist/core/llm/stream-utils.js +13 -4
  112. package/dist/core/llm/utils.js +18 -29
  113. package/dist/core/memory/relevant-retrieval.js +144 -0
  114. package/dist/core/observability/logger.js +11 -2
  115. package/dist/core/patch/diff.js +1 -0
  116. package/dist/core/prompts/registry.js +39 -2
  117. package/dist/core/prompts/runtime.js +50 -12
  118. package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
  119. package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
  120. package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
  121. package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
  122. package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
  123. package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
  124. package/dist/core/prompts/templates/system/main_system.hbs +4 -16
  125. package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
  126. package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
  127. package/dist/core/prompts/templates/system/research_system.hbs +2 -0
  128. package/dist/core/protocols/a2a/agent-card.js +3 -2
  129. package/dist/core/protocols/a2a/sdk/executor.js +2 -1
  130. package/dist/core/protocols/a2a/sdk/server.js +0 -1
  131. package/dist/core/protocols/acp/formal-agent.js +74 -51
  132. package/dist/core/protocols/acp/handlers.js +5 -1
  133. package/dist/core/protocols/acp/permission-provider.js +1 -1
  134. package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
  135. package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
  136. package/dist/core/public-capabilities/projections.js +29 -0
  137. package/dist/core/public-capabilities/registry.js +26 -0
  138. package/dist/core/public-capabilities/types.js +2 -0
  139. package/dist/core/runtime/agent-server-runtime.js +47 -43
  140. package/dist/core/runtime/execution-profile.js +67 -0
  141. package/dist/core/session/artifact-state.js +160 -0
  142. package/dist/core/session/compaction/index.js +183 -0
  143. package/dist/core/session/compaction/microcompact.js +78 -0
  144. package/dist/core/session/compaction/tracking.js +48 -0
  145. package/dist/core/session/compaction/types.js +11 -0
  146. package/dist/core/session/compression.js +8 -0
  147. package/dist/core/session/manager.js +244 -8
  148. package/dist/core/session/pruning-strategy.js +55 -9
  149. package/dist/core/session/replacement-preview-provider.js +24 -0
  150. package/dist/core/session/replacement-state.js +131 -0
  151. package/dist/core/session/resume-repair/pipeline.js +79 -0
  152. package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
  153. package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
  154. package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
  155. package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
  156. package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
  157. package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
  158. package/dist/core/session/resume-repair/types.js +2 -0
  159. package/dist/core/session/summary-sync.js +164 -13
  160. package/dist/core/session/token-tracker.js +6 -0
  161. package/dist/core/skills/audit.js +34 -0
  162. package/dist/core/skills/bridge.js +84 -7
  163. package/dist/core/skills/discovery.js +94 -0
  164. package/dist/core/skills/feature-flags.js +52 -0
  165. package/dist/core/skills/index.js +1 -1
  166. package/dist/core/skills/loader.js +195 -20
  167. package/dist/core/skills/parser.js +296 -24
  168. package/dist/core/skills/permissions.js +117 -0
  169. package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
  170. package/dist/core/skills/runtime/SkillRunner.js +240 -61
  171. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
  172. package/dist/core/strata/layers/worktree.js +67 -10
  173. package/dist/core/strata/runtime/synchronizer.js +29 -2
  174. package/dist/core/streaming/stream-assembler.js +75 -31
  175. package/dist/core/sub-agent/context-snapshot.js +156 -0
  176. package/dist/core/sub-agent/core/loop.js +1 -1
  177. package/dist/core/sub-agent/core/manager.js +119 -20
  178. package/dist/core/sub-agent/dispatch-policy.js +29 -0
  179. package/dist/core/sub-agent/prefix-consistency.js +48 -0
  180. package/dist/core/sub-agent/registry-defaults.js +4 -0
  181. package/dist/core/sub-agent/tools/task-spawn.js +79 -2
  182. package/dist/core/sub-agent/types.js +134 -5
  183. package/dist/core/tools/audit.js +13 -4
  184. package/dist/core/tools/builtin/ast-grep.js +1 -1
  185. package/dist/core/tools/builtin/ast.js +1 -1
  186. package/dist/core/tools/builtin/benchmark.js +360 -0
  187. package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
  188. package/dist/core/tools/builtin/code-search/executor.js +6 -1
  189. package/dist/core/tools/builtin/code-search/spec.js +26 -2
  190. package/dist/core/tools/builtin/fs.js +256 -23
  191. package/dist/core/tools/builtin/git.js +2 -2
  192. package/dist/core/tools/builtin/index.js +51 -2
  193. package/dist/core/tools/builtin/interaction.js +8 -1
  194. package/dist/core/tools/builtin/plan.js +37 -15
  195. package/dist/core/tools/builtin/shell.js +1 -1
  196. package/dist/core/tools/loader.js +39 -16
  197. package/dist/core/tools/mapper.js +17 -3
  198. package/dist/core/tools/parallel/scheduler.js +35 -4
  199. package/dist/core/tools/permissions/permission-rules.js +5 -10
  200. package/dist/core/tools/policy.js +6 -1
  201. package/dist/core/tools/recoverable-tool-errors.js +10 -0
  202. package/dist/core/tools/router.js +24 -6
  203. package/dist/core/tools/session.js +458 -48
  204. package/dist/core/tools/tool-visibility.js +62 -0
  205. package/dist/core/tools/types.js +9 -1
  206. package/dist/core/types/execution.js +4 -0
  207. package/dist/core/types/flow-mode.js +8 -0
  208. package/dist/core/utils/path.js +52 -0
  209. package/dist/core/verification/runner.js +4 -1
  210. package/dist/languages/typescript/index.js +4 -1
  211. package/dist/locales/en.js +35 -2
  212. package/dist/utils/eol.js +1 -1
  213. package/package.json +13 -6
  214. package/scripts/fix-es-abstract-compat.js +77 -0
  215. package/dist/core/runtime/fastify-server-bundle.js +0 -26
  216. package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
  217. package/dist/core/runtime/sidecar-paths.js +0 -47
  218. package/dist/core/runtime/sidecar-route-catalog.js +0 -103
@@ -8,7 +8,7 @@ import { LIMITS } from '../../config/limits.js';
8
8
  import { logIgnoredError } from '../../observability/ignored-error.js';
9
9
  import { getLogger } from '../../observability/logger.js';
10
10
  import { GitError } from '../../types/index.js';
11
- import { isPathWithinDirectory, normalizePath } from '../../utils/path.js';
11
+ import { isPathWithinDirectory, isSafeRelativePath, normalizePath } from '../../utils/path.js';
12
12
  import { runGitCommand } from './git-runner.js';
13
13
  import { FileHandleManager } from './lock-manager.js';
14
14
  // Singleton map to ensure one lock manager per repository path
@@ -126,6 +126,32 @@ export class GitAdapter {
126
126
  this.assertQueryAllowed(args);
127
127
  return this.exec(args, options);
128
128
  }
129
+ /**
130
+ * Generate a git-formatted patch for one repo-relative file that is not in
131
+ * the index, without staging or otherwise mutating repository state.
132
+ */
133
+ async diffUntrackedFileAgainstNull(relativePath, options = {}) {
134
+ const normalizedPath = normalizePath(relativePath);
135
+ if (!isSafeRelativePath(normalizedPath)) {
136
+ throw new Error(text.git.securityViolation('diff --no-index'));
137
+ }
138
+ return this.execMeta([
139
+ 'diff',
140
+ '--no-index',
141
+ '--binary',
142
+ '--no-color',
143
+ '--no-ext-diff',
144
+ '--src-prefix=a/',
145
+ '--dst-prefix=b/',
146
+ '--',
147
+ '/dev/null',
148
+ normalizedPath,
149
+ ], {
150
+ cwd: options.cwd,
151
+ limits: options.limits,
152
+ timeoutMs: options.timeoutMs,
153
+ });
154
+ }
129
155
  // ==================== Business Layer ====================
130
156
  /**
131
157
  * Generates a blob hash for the given content without writing to the object database.
@@ -363,6 +389,54 @@ export class GitAdapter {
363
389
  await this.lockManager.releaseLock(this.repoPath);
364
390
  }
365
391
  }
392
+ async checkPatchApplyability(diffText, options = {}) {
393
+ const tempFile = path.join(tmpdir(), `salmon-patch-check-${Date.now()}-${randomBytes(4).toString('hex')}.patch`);
394
+ const tempIndex = path.join(tmpdir(), `salmon-patch-check-index-${Date.now()}-${randomBytes(4).toString('hex')}`);
395
+ await fs.writeFile(tempFile, diffText, 'utf8');
396
+ try {
397
+ const readTree = await this.execMeta(['read-tree', 'HEAD'], {
398
+ env: { ...options.env, GIT_INDEX_FILE: tempIndex },
399
+ limits: { maxStdoutBytes: LIMITS.maxToolOutputBytes, maxStderrChars: 16_384 },
400
+ timeoutMs: LIMITS.gitTimeoutMs,
401
+ });
402
+ if (!readTree.ok) {
403
+ const output = [readTree.stdout.toString('utf8'), readTree.stderr]
404
+ .filter(Boolean)
405
+ .join('\n')
406
+ .trim();
407
+ return {
408
+ ok: false,
409
+ exitCode: readTree.code,
410
+ output,
411
+ };
412
+ }
413
+ const args = ['apply', '--cached', '--check', '--recount'];
414
+ if (options.ignoreWhitespace)
415
+ args.push('--ignore-whitespace');
416
+ if (options.contextLines)
417
+ args.push(`-C${options.contextLines}`);
418
+ args.push(tempFile);
419
+ const res = await this.execMeta(args, {
420
+ env: { ...options.env, GIT_INDEX_FILE: tempIndex },
421
+ limits: { maxStdoutBytes: LIMITS.maxToolOutputBytes, maxStderrChars: 16_384 },
422
+ timeoutMs: LIMITS.gitTimeoutMs,
423
+ });
424
+ const output = [res.stdout.toString('utf8'), res.stderr].filter(Boolean).join('\n').trim();
425
+ return {
426
+ ok: res.ok,
427
+ exitCode: res.code,
428
+ output,
429
+ };
430
+ }
431
+ finally {
432
+ await fs
433
+ .unlink(tempFile)
434
+ .catch((error) => logIgnoredError(`[GitAdapter] cleanup ${tempFile}`, error));
435
+ await fs
436
+ .unlink(tempIndex)
437
+ .catch((error) => logIgnoredError(`[GitAdapter] cleanup ${tempIndex}`, error));
438
+ }
439
+ }
366
440
  /**
367
441
  * Precision rollback protecting staged changes.
368
442
  */
@@ -407,6 +481,9 @@ export class GitAdapter {
407
481
  }
408
482
  }
409
483
  catch (error) {
484
+ if (!this.isShadowWorktreePath()) {
485
+ throw error;
486
+ }
410
487
  try {
411
488
  await this.resolveConflicts();
412
489
  }
@@ -0,0 +1,124 @@
1
+ import { createHash } from 'crypto';
2
+ import { EOL } from 'os';
3
+ import path from 'path';
4
+ import { FileAdapter } from '../adapters/fs/file-adapter.js';
5
+ import { GitAdapter } from '../adapters/git/git-adapter.js';
6
+ import { LIMITS } from '../config/limits.js';
7
+ import { isPathWithinDirectory, isSafeRelativePath, normalizePath } from '../utils/path.js';
8
+ export class BenchmarkPatchArtifactError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'BenchmarkPatchArtifactError';
12
+ }
13
+ }
14
+ const DIFF_ARGS = [
15
+ 'diff',
16
+ '--binary',
17
+ '--no-color',
18
+ '--no-ext-diff',
19
+ '--src-prefix=a/',
20
+ '--dst-prefix=b/',
21
+ 'HEAD',
22
+ '--',
23
+ '.',
24
+ ];
25
+ function normalizeGitPatch(text) {
26
+ const normalized = text.replace(/\r\n/g, '\n').replace(/\s+$/, '');
27
+ return normalized.length > 0 ? `${normalized}\n` : '';
28
+ }
29
+ function splitLines(text) {
30
+ return text
31
+ .split(/\r?\n/)
32
+ .map((line) => line.trim())
33
+ .filter(Boolean);
34
+ }
35
+ async function buildUntrackedFilePatch(params) {
36
+ if (!isSafeRelativePath(params.relativePath) || params.relativePath.startsWith('.salmonloop/')) {
37
+ throw new BenchmarkPatchArtifactError(`Unsafe untracked patch path: ${params.relativePath}`);
38
+ }
39
+ const result = await params.git.diffUntrackedFileAgainstNull(params.relativePath, {
40
+ cwd: params.repoPath,
41
+ limits: { maxStdoutBytes: LIMITS.maxToolOutputBytes },
42
+ });
43
+ if (result.code !== 0 && result.code !== 1) {
44
+ throw new BenchmarkPatchArtifactError(result.stderr || 'Failed to diff untracked file.');
45
+ }
46
+ return normalizeGitPatch(result.stdout.toString('utf8'));
47
+ }
48
+ function parseChangedFilesFromPatch(patch) {
49
+ const files = new Set();
50
+ for (const line of patch.split('\n')) {
51
+ const match = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
52
+ if (!match)
53
+ continue;
54
+ const next = match[2];
55
+ if (next !== '/dev/null')
56
+ files.add(next);
57
+ }
58
+ return Array.from(files).sort();
59
+ }
60
+ export async function buildBenchmarkPatchArtifact(params) {
61
+ const git = new GitAdapter(params.repoPath);
62
+ const fileAdapter = new FileAdapter();
63
+ const excluded = await resolveRepoRelativeExcludes(params.repoPath, params.excludePaths ?? []);
64
+ const pathspecs = buildPatchPathspecs(excluded);
65
+ const trackedPatch = normalizeGitPatch(await git.query([...DIFF_ARGS, ...pathspecs], {
66
+ trim: false,
67
+ limits: { maxStdoutBytes: LIMITS.maxToolOutputBytes },
68
+ }));
69
+ const candidateUntracked = new Set([
70
+ ...splitLines(await git.query(['ls-files', '--others', '--exclude-standard'])),
71
+ ...(params.changedFilesHint ?? []),
72
+ ]);
73
+ const untrackedPatches = [];
74
+ for (const relativePath of Array.from(candidateUntracked).sort()) {
75
+ const normalizedPath = normalizePath(relativePath);
76
+ if (!normalizedPath || normalizedPath.startsWith('.salmonloop/'))
77
+ continue;
78
+ if (excluded.has(normalizedPath))
79
+ continue;
80
+ const status = await git.getStatusForPath(normalizedPath);
81
+ if (!status?.untracked)
82
+ continue;
83
+ if (!(await fileAdapter.exists(path.join(params.repoPath, normalizedPath))))
84
+ continue;
85
+ untrackedPatches.push(await buildUntrackedFilePatch({
86
+ repoPath: params.repoPath,
87
+ git,
88
+ relativePath: normalizedPath,
89
+ }));
90
+ }
91
+ const patch = normalizeGitPatch([trackedPatch, ...untrackedPatches].filter(Boolean).join(EOL));
92
+ const bytes = Buffer.byteLength(patch, 'utf8');
93
+ return {
94
+ patch,
95
+ bytes,
96
+ sha256: createHash('sha256').update(patch, 'utf8').digest('hex'),
97
+ changedFiles: parseChangedFilesFromPatch(patch),
98
+ isEmpty: patch.length === 0,
99
+ };
100
+ }
101
+ function buildPatchPathspecs(excluded) {
102
+ return Array.from(excluded)
103
+ .sort()
104
+ .map((relativePath) => `:(top,literal,exclude)${relativePath}`);
105
+ }
106
+ async function resolveRepoRelativeExcludes(repoPath, paths) {
107
+ const resolvedRepoPath = path.resolve(repoPath);
108
+ const excluded = new Set();
109
+ for (const candidate of paths) {
110
+ if (!candidate)
111
+ continue;
112
+ const absolutePath = path.isAbsolute(candidate)
113
+ ? path.resolve(candidate)
114
+ : path.resolve(resolvedRepoPath, candidate);
115
+ const relativePath = normalizePath(path.relative(resolvedRepoPath, absolutePath));
116
+ if (!isSafeRelativePath(relativePath))
117
+ continue;
118
+ if (!isPathWithinDirectory(resolvedRepoPath, absolutePath))
119
+ continue;
120
+ excluded.add(relativePath);
121
+ }
122
+ return excluded;
123
+ }
124
+ //# sourceMappingURL=patch-artifact.js.map
@@ -0,0 +1,25 @@
1
+ export function buildSweBenchPrediction(input) {
2
+ return {
3
+ instance_id: input.instanceId,
4
+ model_name_or_path: input.modelNameOrPath,
5
+ model_patch: input.modelPatch,
6
+ };
7
+ }
8
+ export function encodeSweBenchPredictionJsonl(input) {
9
+ return `${JSON.stringify(buildSweBenchPrediction(input))}\n`;
10
+ }
11
+ export function parseSweBenchInstance(raw) {
12
+ const parsed = JSON.parse(raw);
13
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
14
+ throw new Error('SWE-bench instance must be a JSON object.');
15
+ }
16
+ const instance = parsed;
17
+ if (typeof instance.instance_id !== 'string' || !instance.instance_id.trim()) {
18
+ throw new Error('SWE-bench instance requires a non-empty instance_id.');
19
+ }
20
+ return {
21
+ ...instance,
22
+ instance_id: instance.instance_id,
23
+ };
24
+ }
25
+ //# sourceMappingURL=swe-bench.js.map
@@ -4,25 +4,32 @@ import { parseConfigText } from './file-format.js';
4
4
  import { getDefaultRepoConfigPaths, getDefaultUserConfigPaths, resolveConfigPath, } from './paths.js';
5
5
  import { validateConfigFileV1 } from './validate.js';
6
6
  async function loadFromCandidates(candidatePaths, required) {
7
- for (let i = 0; i < candidatePaths.length; i++) {
8
- const absPath = candidatePaths[i];
7
+ const results = await Promise.all(candidatePaths.map(async (absPath) => {
9
8
  try {
10
9
  const raw = await readFile(absPath, 'utf8');
11
10
  const parsed = parseConfigText(raw, absPath);
12
11
  const config = validateConfigFileV1(parsed);
13
- return { path: absPath, config };
12
+ return { ok: true, absPath, loaded: { path: absPath, config } };
14
13
  }
15
14
  catch (e) {
16
- if ((e && typeof e === 'object' && 'code' in e ? e.code : undefined) ===
17
- 'ENOENT') {
18
- const isLast = i === candidatePaths.length - 1;
19
- if (required && isLast) {
20
- throw new ConfigError('CONFIG_FILE_NOT_FOUND', { path: absPath });
21
- }
22
- continue;
15
+ return { ok: false, absPath, error: e };
16
+ }
17
+ }));
18
+ for (const [i, result] of results.entries()) {
19
+ if (result.ok) {
20
+ return result.loaded;
21
+ }
22
+ const code = result.error && typeof result.error === 'object' && 'code' in result.error
23
+ ? result.error.code
24
+ : undefined;
25
+ if (code === 'ENOENT') {
26
+ const isLast = i === results.length - 1;
27
+ if (required && isLast) {
28
+ throw new ConfigError('CONFIG_FILE_NOT_FOUND', { path: result.absPath });
23
29
  }
24
- throw e;
30
+ continue;
25
31
  }
32
+ throw result.error;
26
33
  }
27
34
  return null;
28
35
  }
@@ -1,6 +1,14 @@
1
1
  import { resolveBaseUrl } from '../llm/base-url.js';
2
2
  import { ConfigError } from './errors.js';
3
3
  import { firstProviderRef, resolveApiKey, resolveModelId } from './resolve-env.js';
4
+ function mergeCapabilities(providerCapabilities, modelCapabilities) {
5
+ if (!providerCapabilities && !modelCapabilities)
6
+ return undefined;
7
+ return {
8
+ ...providerCapabilities,
9
+ ...modelCapabilities,
10
+ };
11
+ }
4
12
  export function resolveLlmFromConfig(raw) {
5
13
  const llm = raw?.llm;
6
14
  const providers = llm?.providers || {};
@@ -49,6 +57,7 @@ export function resolveLlmFromConfig(raw) {
49
57
  const apiKeyResolution = resolveApiKey(provider.api?.apiKey);
50
58
  const baseUrl = resolveBaseUrl(provider.api?.baseUrl);
51
59
  const selectedModelId = resolveModelId(activeProfile.id);
60
+ const activeCapabilities = mergeCapabilities(provider.capabilities, activeProfile.capabilities);
52
61
  const routing = llm?.routing;
53
62
  const phaseToProviderModel = routing?.phaseToModel && typeof routing.phaseToModel === 'object'
54
63
  ? Object.fromEntries(Object.entries(routing.phaseToModel)
@@ -76,6 +85,7 @@ export function resolveLlmFromConfig(raw) {
76
85
  });
77
86
  }
78
87
  const phaseKey = resolveApiKey(phaseProvider.api?.apiKey);
88
+ const capabilities = mergeCapabilities(phaseProvider.capabilities, profile.capabilities);
79
89
  return [
80
90
  phase,
81
91
  {
@@ -93,6 +103,7 @@ export function resolveLlmFromConfig(raw) {
93
103
  id: resolveModelId(profile.id),
94
104
  slot: profileSlot,
95
105
  },
106
+ capabilities,
96
107
  },
97
108
  ];
98
109
  })
@@ -124,6 +135,7 @@ export function resolveLlmFromConfig(raw) {
124
135
  selectedModelId,
125
136
  selectedModelSlot: activeModelSlot,
126
137
  },
138
+ capabilities: activeCapabilities,
127
139
  routing: resolvedRouting,
128
140
  };
129
141
  }
@@ -10,12 +10,6 @@ export function resolveServerConfig(raw) {
10
10
  tokens: serverRaw.a2a.tokens,
11
11
  };
12
12
  }
13
- if (serverRaw.sidecar) {
14
- server.sidecar = {
15
- socket: serverRaw.sidecar.socket,
16
- allowConditional: serverRaw.sidecar.allowConditional,
17
- };
18
- }
19
13
  if (serverRaw.acp) {
20
14
  server.acp = {
21
15
  sessionStore: {
@@ -26,6 +26,59 @@ function isValidMarkdownTheme(value) {
26
26
  function isValidMarkdownRenderMode(value) {
27
27
  return typeof value === 'string' && MARKDOWN_RENDER_MODES.includes(value);
28
28
  }
29
+ function validateLlmCapabilities(input, context) {
30
+ if (input === undefined)
31
+ return undefined;
32
+ if (!isRecord(input)) {
33
+ throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITIES', {
34
+ ...context,
35
+ expected: 'object',
36
+ });
37
+ }
38
+ const allowedKeys = new Set(['toolCalling', 'responseFormatJsonObject', 'streaming']);
39
+ for (const key of Object.keys(input)) {
40
+ if (!allowedKeys.has(key)) {
41
+ throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY', {
42
+ ...context,
43
+ capability: key,
44
+ expected: 'toolCalling|responseFormatJsonObject|streaming',
45
+ });
46
+ }
47
+ }
48
+ const cfg = {};
49
+ for (const key of ['toolCalling', 'responseFormatJsonObject', 'streaming']) {
50
+ if (input[key] !== undefined && !isBoolean(input[key])) {
51
+ throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY', {
52
+ ...context,
53
+ capability: key,
54
+ expected: 'boolean',
55
+ });
56
+ }
57
+ if (input[key] !== undefined) {
58
+ cfg[key] = input[key];
59
+ }
60
+ }
61
+ return cfg;
62
+ }
63
+ function normalizeLlmModelParams(input, context) {
64
+ if (!isRecord(input))
65
+ return undefined;
66
+ for (const key of [
67
+ 'capabilities',
68
+ 'toolCalling',
69
+ 'responseFormatJsonObject',
70
+ 'streaming',
71
+ ]) {
72
+ if (input[key] !== undefined) {
73
+ throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY_LOCATION', {
74
+ ...context,
75
+ capability: key,
76
+ expected: 'model.capabilities',
77
+ });
78
+ }
79
+ }
80
+ return input;
81
+ }
29
82
  export function validateConfigFileV1(input) {
30
83
  if (!isRecord(input)) {
31
84
  throw new ConfigError('CONFIG_INVALID_ROOT', { expected: 'object' });
@@ -164,6 +217,11 @@ export function validateConfigFileV1(input) {
164
217
  if (!isRecord(serverRaw)) {
165
218
  throw new ConfigError('CONFIG_INVALID_SERVER', { expected: 'object' });
166
219
  }
220
+ for (const key of Object.keys(serverRaw)) {
221
+ if (key !== 'a2a' && key !== 'acp') {
222
+ throw new ConfigError('CONFIG_INVALID_SERVER_UNKNOWN_KEY', { key });
223
+ }
224
+ }
167
225
  const server = {};
168
226
  if (serverRaw.a2a !== undefined) {
169
227
  if (!isRecord(serverRaw.a2a)) {
@@ -187,24 +245,6 @@ export function validateConfigFileV1(input) {
187
245
  tokens: a2aRaw.tokens,
188
246
  };
189
247
  }
190
- if (serverRaw.sidecar !== undefined) {
191
- if (!isRecord(serverRaw.sidecar)) {
192
- throw new ConfigError('CONFIG_INVALID_SERVER_SIDECAR', { expected: 'object' });
193
- }
194
- const sidecarRaw = serverRaw.sidecar;
195
- if (sidecarRaw.socket !== undefined && !isString(sidecarRaw.socket)) {
196
- throw new ConfigError('CONFIG_INVALID_SERVER_SIDECAR_SOCKET', { expected: 'string' });
197
- }
198
- if (sidecarRaw.allowConditional !== undefined && !isBoolean(sidecarRaw.allowConditional)) {
199
- throw new ConfigError('CONFIG_INVALID_SERVER_SIDECAR_ALLOW_CONDITIONAL', {
200
- expected: 'boolean',
201
- });
202
- }
203
- server.sidecar = {
204
- socket: sidecarRaw.socket,
205
- allowConditional: sidecarRaw.allowConditional,
206
- };
207
- }
208
248
  if (serverRaw.acp !== undefined) {
209
249
  if (!isRecord(serverRaw.acp)) {
210
250
  throw new ConfigError('CONFIG_INVALID_SERVER_ACP', { expected: 'object' });
@@ -561,6 +601,12 @@ export function validateConfigFileV1(input) {
561
601
  hint: 'use llm.models with provider references',
562
602
  });
563
603
  }
604
+ const providerCapabilities = validateLlmCapabilities(rawProvider.capabilities, {
605
+ provider: id,
606
+ });
607
+ if (providerCapabilities) {
608
+ p.capabilities = providerCapabilities;
609
+ }
564
610
  cfg.llm.providers[id] = p;
565
611
  }
566
612
  }
@@ -590,12 +636,18 @@ export function validateConfigFileV1(input) {
590
636
  expected: 'non_empty_string',
591
637
  });
592
638
  }
639
+ const params = normalizeLlmModelParams(rawModel.params, {
640
+ model: slot,
641
+ location: 'params',
642
+ });
643
+ const capabilities = validateLlmCapabilities(rawModel.capabilities, {
644
+ model: slot,
645
+ });
593
646
  cfg.llm.models[slot] = {
594
647
  provider: provider,
595
648
  id: rawModel.id,
596
- params: isRecord(rawModel.params)
597
- ? rawModel.params
598
- : undefined,
649
+ params,
650
+ capabilities,
599
651
  };
600
652
  }
601
653
  }
@@ -38,6 +38,7 @@ export class MetadataGatherer {
38
38
  'tsconfig.json',
39
39
  'eslint.config.js',
40
40
  '.prettierrc',
41
+ '.oxfmtrc.json',
41
42
  'vitest.config.ts',
42
43
  'jest.config.js',
43
44
  'bun.lock',
@@ -1,8 +1,89 @@
1
+ import { FileAdapter } from '../../adapters/fs/file-adapter.js';
1
2
  import { LIMITS } from '../../config/limits.js';
2
3
  import { getLogger } from '../../observability/logger.js';
3
4
  import { spawnCommand } from '../../runtime/process-runner.js';
4
- import { normalizePath } from '../../utils/path.js';
5
+ import { ensureInSandbox, normalizePath, safeJoin, safeRelative } from '../../utils/path.js';
6
+ const FALLBACK_EXCLUDED_DIRS = new Set(['.git', 'node_modules']);
7
+ const FALLBACK_MAX_FILES = 2000;
8
+ const FALLBACK_MAX_FILE_BYTES = Math.max(LIMITS.largeFileThresholdBytes, 64 * 1024);
9
+ const fileAdapter = new FileAdapter();
10
+ function isHiddenPathSegment(name) {
11
+ return name.startsWith('.');
12
+ }
13
+ function isBinaryLike(content) {
14
+ return content.includes('\0');
15
+ }
5
16
  export class RipgrepGatherer {
17
+ async searchFileSystem(query, cwd, signal) {
18
+ const needle = query.toLowerCase();
19
+ if (!needle)
20
+ return [];
21
+ const results = [];
22
+ let scannedFiles = 0;
23
+ const pending = ['.'];
24
+ while (pending.length > 0 && scannedFiles < FALLBACK_MAX_FILES) {
25
+ if (signal?.aborted)
26
+ throw new Error('Operation cancelled by user');
27
+ const current = pending.shift();
28
+ const absoluteCurrent = ensureInSandbox(cwd, safeJoin(cwd, current));
29
+ let entries;
30
+ try {
31
+ entries = await fileAdapter.readdirWithTypes(absoluteCurrent);
32
+ }
33
+ catch {
34
+ continue;
35
+ }
36
+ entries.sort((a, b) => a.name.localeCompare(b.name));
37
+ for (const entry of entries) {
38
+ if (signal?.aborted)
39
+ throw new Error('Operation cancelled by user');
40
+ if (isHiddenPathSegment(entry.name))
41
+ continue;
42
+ if (entry.isSymbolicLink())
43
+ continue;
44
+ const relativePath = normalizePath(safeJoin(current, entry.name)).replace(/^(\.\/|\/)+/, '');
45
+ if (!relativePath)
46
+ continue;
47
+ if (entry.isDirectory()) {
48
+ if (FALLBACK_EXCLUDED_DIRS.has(entry.name))
49
+ continue;
50
+ pending.push(relativePath);
51
+ continue;
52
+ }
53
+ if (!entry.isFile())
54
+ continue;
55
+ scannedFiles += 1;
56
+ if (scannedFiles > FALLBACK_MAX_FILES)
57
+ break;
58
+ const absoluteFile = ensureInSandbox(cwd, safeJoin(cwd, relativePath));
59
+ try {
60
+ const stat = await fileAdapter.stat(absoluteFile);
61
+ if (!stat.isFile() || stat.size > FALLBACK_MAX_FILE_BYTES)
62
+ continue;
63
+ const content = await fileAdapter.readFile(absoluteFile, 'utf-8');
64
+ if (isBinaryLike(content))
65
+ continue;
66
+ const lines = content.split(/\r?\n/);
67
+ for (let index = 0; index < lines.length; index += 1) {
68
+ const line = lines[index] ?? '';
69
+ if (!line.toLowerCase().includes(needle))
70
+ continue;
71
+ results.push({
72
+ file: normalizePath(safeRelative(cwd, absoluteFile)),
73
+ line: index + 1,
74
+ content: line,
75
+ });
76
+ if (results.length >= LIMITS.defaultSearchMatches)
77
+ return results;
78
+ }
79
+ }
80
+ catch {
81
+ continue;
82
+ }
83
+ }
84
+ }
85
+ return results;
86
+ }
6
87
  async runRipgrep(query, cwd, signal) {
7
88
  getLogger().trace(` [RG] Searching for: "${query}" in ${cwd}`);
8
89
  if (signal?.aborted) {
@@ -38,7 +119,8 @@ export class RipgrepGatherer {
38
119
  getLogger().trace(` [RG] Process closed with code ${result.code}. Output length: ${output.length}`);
39
120
  if (result.error) {
40
121
  if (result.error.code === 'ENOENT') {
41
- getLogger().error('Error: ripgrep (rg) not found in PATH. Context gathering may be incomplete.');
122
+ getLogger().error('Error: ripgrep (rg) not found in PATH. Falling back to bounded filesystem search.');
123
+ return await this.searchFileSystem(query, cwd, signal);
42
124
  }
43
125
  else {
44
126
  getLogger().error(`Error running ripgrep: ${result.error.message}`);
@@ -85,7 +85,7 @@ function extractPathLikeTokens(input) {
85
85
  }
86
86
  function extractBacktickedTokens(input) {
87
87
  const matches = [];
88
- const re = /`([^`]{1,64})`/g;
88
+ const re = /(?<!`)`([^`\n]{1,64})`(?!`)/g;
89
89
  let m;
90
90
  while ((m = re.exec(input)) !== null) {
91
91
  const val = m[1]?.trim();
@@ -106,7 +106,7 @@ function extractErrorLikeTokens(input) {
106
106
  }
107
107
  function extractIdentifierTokens(input) {
108
108
  const matches = [];
109
- const re = /\b[A-Za-z_][A-Za-z0-9_]{2,}\b/g;
109
+ const re = /\b(?:[A-Z][A-Z0-9_]{1,}\d+[A-Z0-9_]*|[A-Za-z_][A-Za-z0-9_]{2,})\b/g;
110
110
  let m;
111
111
  while ((m = re.exec(input)) !== null) {
112
112
  if (m[0])
@@ -114,6 +114,17 @@ function extractIdentifierTokens(input) {
114
114
  }
115
115
  return matches;
116
116
  }
117
+ function extractQuotedPhrases(input) {
118
+ const matches = [];
119
+ const re = /["“”]([^"“”\n]{8,96})["“”]/g;
120
+ let m;
121
+ while ((m = re.exec(input)) !== null) {
122
+ const val = m[1]?.trim();
123
+ if (val)
124
+ matches.push(val);
125
+ }
126
+ return matches;
127
+ }
117
128
  function isCjk(char) {
118
129
  return /\p{Script=Han}|\p{Script=Hiragana}|\p{Script=Katakana}|\p{Script=Hangul}/u.test(char);
119
130
  }
@@ -143,9 +154,11 @@ export function extractKeywords(instruction) {
143
154
  const backticked = extractBacktickedTokens(raw);
144
155
  // Priority 3: Error-like tokens (strong signal)
145
156
  const errorLike = extractErrorLikeTokens(raw);
146
- // Priority 4: Identifiers (code-related)
157
+ // Priority 4: Quoted diagnostics and messages (strong signal in bug reports)
158
+ const quotedPhrases = extractQuotedPhrases(raw);
159
+ // Priority 5: Identifiers (code-related)
147
160
  const identifiers = extractIdentifierTokens(raw);
148
- // Priority 5: Word tokens
161
+ // Priority 6: Word tokens
149
162
  const wordTokens = raw
150
163
  .toLowerCase()
151
164
  .split(/[^\p{L}\p{N}_-]+/u)
@@ -155,6 +168,7 @@ export function extractKeywords(instruction) {
155
168
  ...pathLike,
156
169
  ...backticked,
157
170
  ...errorLike,
171
+ ...quotedPhrases,
158
172
  ...identifiers,
159
173
  ...wordTokens,
160
174
  ]);
@@ -1,5 +1,5 @@
1
1
  import { DefaultPromptAssembler } from './assembly/default-prompt-assembler.js';
2
- import { PromptCachingManager } from './cache/prompt-caching.js';
2
+ import { getPromptCachingManager } from './cache/prompt-caching.js';
3
3
  import { ArchitectureGatherer } from './gatherers/architecture-gatherer.js';
4
4
  import { ArtifactGatherer } from './gatherers/artifact-gatherer.js';
5
5
  import { AstGatherer } from './gatherers/ast-gatherer.js';
@@ -26,7 +26,7 @@ export function defaultContextServiceDeps() {
26
26
  ghostDependencyGatherer: new GhostDependencyGatherer(ripgrepGatherer),
27
27
  targetResolver: new TargetResolver(),
28
28
  assembler: new DefaultPromptAssembler(),
29
- promptCachingManager: new PromptCachingManager(),
29
+ promptCachingManager: getPromptCachingManager(),
30
30
  };
31
31
  }
32
32
  //# sourceMappingURL=service-deps.js.map