salmon-loop 0.2.13 → 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/cli/argv/headless-detection.js +27 -0
  2. package/dist/cli/chat-flow.js +11 -0
  3. package/dist/cli/chat.js +160 -24
  4. package/dist/cli/commands/chat.js +14 -7
  5. package/dist/cli/commands/flow-mode.js +63 -0
  6. package/dist/cli/commands/registry.js +2 -0
  7. package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
  8. package/dist/cli/commands/run/early-errors.js +23 -0
  9. package/dist/cli/commands/run/handler.js +115 -27
  10. package/dist/cli/commands/run/headless-error-writer.js +8 -0
  11. package/dist/cli/commands/run/loop-params.js +2 -0
  12. package/dist/cli/commands/run/mode.js +2 -5
  13. package/dist/cli/commands/run/parse-options.js +16 -0
  14. package/dist/cli/commands/run/persist-session.js +10 -1
  15. package/dist/cli/commands/run/preflight.js +10 -0
  16. package/dist/cli/commands/run/reporter-factory.js +4 -0
  17. package/dist/cli/commands/run/runtime-llm.js +38 -11
  18. package/dist/cli/commands/run/runtime-options.js +2 -2
  19. package/dist/cli/commands/serve.js +91 -71
  20. package/dist/cli/commands/tool-names.js +78 -78
  21. package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
  22. package/dist/cli/headless/json-protocol.js +37 -0
  23. package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
  24. package/dist/cli/headless/protocol-metadata.js +22 -0
  25. package/dist/cli/headless/stream-json-protocol.js +34 -1
  26. package/dist/cli/index.js +6 -4
  27. package/dist/cli/locales/en.js +30 -6
  28. package/dist/cli/program-bootstrap.js +8 -3
  29. package/dist/cli/program-commands.js +5 -1
  30. package/dist/cli/reporters/anthropic-stream.js +7 -1
  31. package/dist/cli/reporters/json.js +4 -0
  32. package/dist/cli/reporters/stream-json.js +17 -2
  33. package/dist/cli/run-cli.js +5 -3
  34. package/dist/cli/slash/runtime.js +27 -12
  35. package/dist/cli/ui/components/CommandInput.js +7 -3
  36. package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
  37. package/dist/cli/utils/command-option-source.js +13 -0
  38. package/dist/cli/utils/verify-resolver.js +8 -4
  39. package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
  40. package/dist/core/adapters/fs/file-adapter.js +6 -0
  41. package/dist/core/adapters/fs/filesystem.js +2 -1
  42. package/dist/core/adapters/git/git-adapter.js +78 -1
  43. package/dist/core/benchmark/patch-artifact.js +124 -0
  44. package/dist/core/benchmark/swe-bench.js +25 -0
  45. package/dist/core/config/load.js +18 -11
  46. package/dist/core/config/resolve-llm.js +12 -0
  47. package/dist/core/config/resolvers/server.js +0 -6
  48. package/dist/core/config/validate.js +73 -21
  49. package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
  50. package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
  51. package/dist/core/context/keywords.js +18 -4
  52. package/dist/core/context/service-deps.js +2 -2
  53. package/dist/core/context/service.js +8 -0
  54. package/dist/core/context/steps/context-gather.js +38 -0
  55. package/dist/core/context/summarization/summarizer.js +55 -12
  56. package/dist/core/context/targeting/target-resolver.js +4 -4
  57. package/dist/core/extensions/index.js +23 -5
  58. package/dist/core/extensions/paths.js +31 -0
  59. package/dist/core/extensions/schemas.js +8 -5
  60. package/dist/core/facades/cli-chat.js +6 -2
  61. package/dist/core/facades/cli-command-chat.js +1 -0
  62. package/dist/core/facades/cli-command-tool-names.js +2 -0
  63. package/dist/core/facades/cli-observability.js +1 -1
  64. package/dist/core/facades/cli-run-handler.js +4 -2
  65. package/dist/core/facades/cli-run-persist-session.js +1 -0
  66. package/dist/core/facades/cli-serve.js +2 -4
  67. package/dist/core/facades/cli-utils-worktree.js +1 -1
  68. package/dist/core/failure/diagnostics.js +53 -1
  69. package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
  70. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
  71. package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
  72. package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
  73. package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
  74. package/dist/core/grizzco/engine/transaction/transaction-runner.js +165 -7
  75. package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
  76. package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
  77. package/dist/core/grizzco/steps/answer.js +13 -14
  78. package/dist/core/grizzco/steps/autopilot.js +396 -0
  79. package/dist/core/grizzco/steps/cache-sharing.js +29 -0
  80. package/dist/core/grizzco/steps/explore.js +37 -21
  81. package/dist/core/grizzco/steps/generateReview.js +2 -5
  82. package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
  83. package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
  84. package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
  85. package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
  86. package/dist/core/grizzco/steps/patch.js +105 -146
  87. package/dist/core/grizzco/steps/plan.js +101 -25
  88. package/dist/core/grizzco/steps/preflight.js +5 -6
  89. package/dist/core/grizzco/steps/request-assembly.js +78 -0
  90. package/dist/core/grizzco/steps/research.js +39 -36
  91. package/dist/core/grizzco/steps/tool-runtime.js +47 -0
  92. package/dist/core/grizzco/steps/verify-shared.js +23 -0
  93. package/dist/core/grizzco/steps/verify.js +13 -21
  94. package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
  95. package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
  96. package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
  97. package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
  98. package/dist/core/llm/ai-sdk/request-params.js +73 -0
  99. package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
  100. package/dist/core/llm/ai-sdk.js +112 -27
  101. package/dist/core/llm/capabilities.js +12 -0
  102. package/dist/core/llm/contracts/repair.js +36 -30
  103. package/dist/core/llm/errors.js +83 -2
  104. package/dist/core/llm/message-composition.js +7 -22
  105. package/dist/core/llm/phase-router.js +29 -10
  106. package/dist/core/llm/redact.js +28 -3
  107. package/dist/core/llm/registry.js +2 -0
  108. package/dist/core/llm/request-augmentation.js +55 -0
  109. package/dist/core/llm/request-envelope.js +334 -0
  110. package/dist/core/llm/shared-request-assembly.js +35 -0
  111. package/dist/core/llm/stream-utils.js +13 -4
  112. package/dist/core/llm/utils.js +18 -29
  113. package/dist/core/memory/relevant-retrieval.js +144 -0
  114. package/dist/core/observability/logger.js +11 -2
  115. package/dist/core/patch/diff.js +1 -0
  116. package/dist/core/prompts/registry.js +39 -2
  117. package/dist/core/prompts/runtime.js +50 -12
  118. package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
  119. package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
  120. package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
  121. package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
  122. package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
  123. package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
  124. package/dist/core/prompts/templates/system/main_system.hbs +4 -16
  125. package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
  126. package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
  127. package/dist/core/prompts/templates/system/research_system.hbs +2 -0
  128. package/dist/core/protocols/a2a/agent-card.js +3 -2
  129. package/dist/core/protocols/a2a/sdk/executor.js +2 -1
  130. package/dist/core/protocols/a2a/sdk/server.js +0 -1
  131. package/dist/core/protocols/acp/formal-agent.js +74 -51
  132. package/dist/core/protocols/acp/handlers.js +5 -1
  133. package/dist/core/protocols/acp/permission-provider.js +1 -1
  134. package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
  135. package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
  136. package/dist/core/public-capabilities/projections.js +29 -0
  137. package/dist/core/public-capabilities/registry.js +26 -0
  138. package/dist/core/public-capabilities/types.js +2 -0
  139. package/dist/core/runtime/agent-server-runtime.js +47 -43
  140. package/dist/core/runtime/execution-profile.js +67 -0
  141. package/dist/core/session/artifact-state.js +160 -0
  142. package/dist/core/session/compaction/index.js +183 -0
  143. package/dist/core/session/compaction/microcompact.js +78 -0
  144. package/dist/core/session/compaction/tracking.js +48 -0
  145. package/dist/core/session/compaction/types.js +11 -0
  146. package/dist/core/session/compression.js +8 -0
  147. package/dist/core/session/manager.js +244 -8
  148. package/dist/core/session/pruning-strategy.js +55 -9
  149. package/dist/core/session/replacement-preview-provider.js +24 -0
  150. package/dist/core/session/replacement-state.js +131 -0
  151. package/dist/core/session/resume-repair/pipeline.js +79 -0
  152. package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
  153. package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
  154. package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
  155. package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
  156. package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
  157. package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
  158. package/dist/core/session/resume-repair/types.js +2 -0
  159. package/dist/core/session/summary-sync.js +164 -13
  160. package/dist/core/session/token-tracker.js +6 -0
  161. package/dist/core/skills/audit.js +34 -0
  162. package/dist/core/skills/bridge.js +84 -7
  163. package/dist/core/skills/discovery.js +94 -0
  164. package/dist/core/skills/feature-flags.js +52 -0
  165. package/dist/core/skills/index.js +1 -1
  166. package/dist/core/skills/loader.js +195 -20
  167. package/dist/core/skills/parser.js +296 -24
  168. package/dist/core/skills/permissions.js +117 -0
  169. package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
  170. package/dist/core/skills/runtime/SkillRunner.js +240 -61
  171. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
  172. package/dist/core/strata/layers/worktree.js +67 -10
  173. package/dist/core/strata/runtime/synchronizer.js +29 -2
  174. package/dist/core/streaming/stream-assembler.js +75 -31
  175. package/dist/core/sub-agent/context-snapshot.js +156 -0
  176. package/dist/core/sub-agent/core/loop.js +1 -1
  177. package/dist/core/sub-agent/core/manager.js +119 -20
  178. package/dist/core/sub-agent/dispatch-policy.js +29 -0
  179. package/dist/core/sub-agent/prefix-consistency.js +48 -0
  180. package/dist/core/sub-agent/registry-defaults.js +4 -0
  181. package/dist/core/sub-agent/tools/task-spawn.js +79 -2
  182. package/dist/core/sub-agent/types.js +134 -5
  183. package/dist/core/tools/audit.js +13 -4
  184. package/dist/core/tools/builtin/ast-grep.js +1 -1
  185. package/dist/core/tools/builtin/ast.js +1 -1
  186. package/dist/core/tools/builtin/benchmark.js +360 -0
  187. package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
  188. package/dist/core/tools/builtin/code-search/executor.js +6 -1
  189. package/dist/core/tools/builtin/code-search/spec.js +26 -2
  190. package/dist/core/tools/builtin/fs.js +256 -23
  191. package/dist/core/tools/builtin/git.js +2 -2
  192. package/dist/core/tools/builtin/index.js +51 -2
  193. package/dist/core/tools/builtin/interaction.js +8 -1
  194. package/dist/core/tools/builtin/plan.js +37 -15
  195. package/dist/core/tools/builtin/shell.js +1 -1
  196. package/dist/core/tools/loader.js +39 -16
  197. package/dist/core/tools/mapper.js +17 -3
  198. package/dist/core/tools/parallel/scheduler.js +35 -4
  199. package/dist/core/tools/permissions/permission-rules.js +5 -10
  200. package/dist/core/tools/policy.js +6 -1
  201. package/dist/core/tools/recoverable-tool-errors.js +10 -0
  202. package/dist/core/tools/router.js +24 -6
  203. package/dist/core/tools/session.js +458 -48
  204. package/dist/core/tools/tool-visibility.js +62 -0
  205. package/dist/core/tools/types.js +9 -1
  206. package/dist/core/types/execution.js +4 -0
  207. package/dist/core/types/flow-mode.js +8 -0
  208. package/dist/core/utils/path.js +52 -0
  209. package/dist/core/verification/runner.js +4 -1
  210. package/dist/languages/typescript/index.js +4 -1
  211. package/dist/locales/en.js +35 -2
  212. package/dist/utils/eol.js +1 -1
  213. package/package.json +13 -6
  214. package/scripts/fix-es-abstract-compat.js +77 -0
  215. package/dist/core/runtime/fastify-server-bundle.js +0 -26
  216. package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
  217. package/dist/core/runtime/sidecar-paths.js +0 -47
  218. package/dist/core/runtime/sidecar-route-catalog.js +0 -103
@@ -0,0 +1,62 @@
1
+ import { Phase } from '../types/runtime.js';
2
+ const PLAN_RUNTIME_TOOL_NAMES = new Set(['plan.read', 'plan.update']);
3
+ const PATCH_VISIBLE_TOOL_NAMES = new Set(['fs.read', 'code.search']);
4
+ function isPhaseAllowed(tool, phase) {
5
+ return Array.isArray(tool.allowedPhases) && tool.allowedPhases.includes(phase);
6
+ }
7
+ export function resolvePlanVisibleTools(tools, runtime) {
8
+ const hasRuntimePlan = Boolean(runtime?.plan);
9
+ return tools.filter((tool) => {
10
+ if (!isPhaseAllowed(tool, Phase.PLAN))
11
+ return false;
12
+ if (!tool.name.startsWith('plan.'))
13
+ return true;
14
+ if (!hasRuntimePlan)
15
+ return false;
16
+ return PLAN_RUNTIME_TOOL_NAMES.has(tool.name);
17
+ });
18
+ }
19
+ export function resolvePatchVisibleTools(tools) {
20
+ return tools.filter((tool) => isPhaseAllowed(tool, Phase.PATCH) && PATCH_VISIBLE_TOOL_NAMES.has(tool.name));
21
+ }
22
+ export function resolveAutopilotVisibleTools(tools, runtime) {
23
+ const hasRuntimePlan = Boolean(runtime?.plan);
24
+ return tools.filter((tool) => {
25
+ if (!isPhaseAllowed(tool, Phase.AUTOPILOT))
26
+ return false;
27
+ if (!tool.name.startsWith('plan.'))
28
+ return true;
29
+ if (!hasRuntimePlan)
30
+ return false;
31
+ return PLAN_RUNTIME_TOOL_NAMES.has(tool.name);
32
+ });
33
+ }
34
+ export function resolvePhaseVisibleTools(params) {
35
+ if (params.phase === Phase.PLAN) {
36
+ return resolvePlanVisibleTools(params.tools, params.runtime);
37
+ }
38
+ if (params.phase === Phase.PATCH) {
39
+ return resolvePatchVisibleTools(params.tools);
40
+ }
41
+ if (params.phase === Phase.AUTOPILOT) {
42
+ return resolveAutopilotVisibleTools(params.tools, params.runtime);
43
+ }
44
+ return params.tools;
45
+ }
46
+ export function resolveVisibleToolSpecs(params) {
47
+ if (!params.toolstack)
48
+ return [];
49
+ const allowedSpecs = params.toolstack.registry.listAll().filter((spec) => params.toolstack.policy.decide(params.phase, spec, {
50
+ worktreeRoot: params.worktreeRoot,
51
+ flowMode: params.flowMode,
52
+ }).allowed);
53
+ return resolvePhaseVisibleTools({
54
+ phase: params.phase,
55
+ tools: allowedSpecs,
56
+ runtime: params.runtime,
57
+ });
58
+ }
59
+ export function resolveVisibleToolNames(params) {
60
+ return resolveVisibleToolSpecs(params).map((spec) => spec.name);
61
+ }
62
+ //# sourceMappingURL=tool-visibility.js.map
@@ -1,2 +1,10 @@
1
- export const TOOL_INTENTS = ['READ', 'SEARCH', 'LIST', 'WRITE', 'INFRA', 'AGENT'];
1
+ export const TOOL_INTENTS = [
2
+ 'READ',
3
+ 'SEARCH',
4
+ 'LIST',
5
+ 'WRITE',
6
+ 'INFRA',
7
+ 'AGENT',
8
+ 'REPORT',
9
+ ];
2
10
  //# sourceMappingURL=types.js.map
@@ -30,6 +30,9 @@ export const Phase = {
30
30
  // SLASH is an out-of-band interactive phase used for adapter-level slash routing and skill expansion.
31
31
  // It is intentionally excluded from EXECUTION_PHASES to avoid impacting the main SalmonLoop flow.
32
32
  SLASH: 'SLASH',
33
+ // AUTOPILOT is introduced as a driver-owned business phase. It is intentionally excluded from
34
+ // EXECUTION_PHASES until the dedicated AutopilotFlow lands.
35
+ AUTOPILOT: 'AUTOPILOT',
33
36
  PREFLIGHT: 'PREFLIGHT',
34
37
  PREPARE_DEPS: 'PREPARE_DEPS',
35
38
  CONTEXT: 'CONTEXT',
@@ -47,6 +50,7 @@ export const Phase = {
47
50
  };
48
51
  export const ALL_VISIBLE_STEPS = [
49
52
  ...EXECUTION_PHASES,
53
+ 'AUTOPILOT',
50
54
  'REVIEW',
51
55
  'REPORT',
52
56
  'ANALYZE_ISSUES',
@@ -0,0 +1,8 @@
1
+ export const FLOW_MODES = ['patch', 'review', 'debug', 'research', 'answer', 'autopilot'];
2
+ export function parseFlowMode(raw) {
3
+ const value = String(raw ?? '')
4
+ .trim()
5
+ .toLowerCase();
6
+ return FLOW_MODES.includes(value) ? value : undefined;
7
+ }
8
+ //# sourceMappingURL=flow-mode.js.map
@@ -17,6 +17,16 @@ function shouldUseWin32PathSemantics(p) {
17
17
  // This enables correct behavior for inputs like "src\\components\\file.ts" on POSIX.
18
18
  return p.includes('\\');
19
19
  }
20
+ function selectPathImplForAbsolutePath(p) {
21
+ const normalized = normalizePath(p);
22
+ if (isWindowsAbsolutePath(normalized) || normalized.startsWith('//')) {
23
+ return path.win32;
24
+ }
25
+ if (normalized.startsWith('/')) {
26
+ return path.posix;
27
+ }
28
+ return path;
29
+ }
20
30
  /**
21
31
  * Normalize a path to use forward slashes, regardless of the operating system.
22
32
  * This ensures consistency across Windows and Linux/macOS.
@@ -30,6 +40,19 @@ export function normalizePath(p) {
30
40
  }
31
41
  return replaced.replace(/\/{2,}/g, '/');
32
42
  }
43
+ /**
44
+ * Normalize a path-like input into a stable repo-relative form.
45
+ *
46
+ * Intended for:
47
+ * - Permission rules matching (stable normalization across platforms)
48
+ * - Tool argument normalization and auditing
49
+ *
50
+ * This function does NOT validate safety (absolute/traversal); use isSafeRelativePath() or
51
+ * sandbox resolution checks for security-sensitive operations.
52
+ */
53
+ export function normalizeRepoRelativePath(input) {
54
+ return normalizePath(String(input ?? '').trim()).replace(/^(\.\/|\/)+/, '');
55
+ }
33
56
  /**
34
57
  * Join path segments and normalize the result to use forward slashes.
35
58
  */
@@ -56,6 +79,35 @@ export function safeDirname(p) {
56
79
  export function safeRelative(from, to) {
57
80
  return normalizePath(path.relative(from, to));
58
81
  }
82
+ /**
83
+ * Normalize an absolute or canonical path into a stable form for comparisons.
84
+ * Windows-style paths are compared case-insensitively; POSIX paths preserve case.
85
+ */
86
+ export function normalizeComparableAbsolutePath(input) {
87
+ const normalized = normalizePath(String(input ?? '').trim());
88
+ const impl = selectPathImplForAbsolutePath(normalized);
89
+ const resolved = normalizePath(impl.resolve(normalized));
90
+ return impl === path.win32 ? resolved.toLowerCase() : resolved;
91
+ }
92
+ /**
93
+ * Compare canonical paths using platform-appropriate semantics.
94
+ */
95
+ export function arePathsEquivalent(left, right) {
96
+ return normalizeComparableAbsolutePath(left) === normalizeComparableAbsolutePath(right);
97
+ }
98
+ /**
99
+ * Check whether a canonical target path is contained within a canonical root path.
100
+ * Inputs should already be resolved/canonicalized by the caller when used for security checks.
101
+ */
102
+ export function isCanonicalPathWithinDirectory(root, target, options = {}) {
103
+ const { allowEqual = true } = options;
104
+ const normalizedRoot = normalizeComparableAbsolutePath(root);
105
+ const normalizedTarget = normalizeComparableAbsolutePath(target);
106
+ if (normalizedTarget === normalizedRoot) {
107
+ return allowEqual;
108
+ }
109
+ return normalizedTarget.startsWith(`${normalizedRoot}/`);
110
+ }
59
111
  /**
60
112
  * Check whether a path is a safe relative path (no absolute paths or traversal).
61
113
  */
@@ -53,7 +53,10 @@ export function classifyError(output) {
53
53
  }
54
54
  if (lowerOutput.includes('eslint') ||
55
55
  lowerOutput.includes('prettier') ||
56
- lowerOutput.includes('prettier/prettier')) {
56
+ lowerOutput.includes('prettier/prettier') ||
57
+ lowerOutput.includes('oxfmt') ||
58
+ lowerOutput.includes('format issues found') ||
59
+ lowerOutput.includes('script "format:check" exited with code')) {
57
60
  return ErrorType.LINT;
58
61
  }
59
62
  if (lowerOutput.includes('fail ') ||
@@ -58,7 +58,10 @@ const commonDiagnostics = {
58
58
  // Lint error keywords
59
59
  if (lowerOutput.includes('eslint') ||
60
60
  lowerOutput.includes('prettier') ||
61
- lowerOutput.includes('stylelint')) {
61
+ lowerOutput.includes('stylelint') ||
62
+ lowerOutput.includes('oxfmt') ||
63
+ lowerOutput.includes('format issues found') ||
64
+ lowerOutput.includes('script "format:check" exited with code')) {
62
65
  return ErrorType.LINT;
63
66
  }
64
67
  return undefined;
@@ -88,6 +88,8 @@ export const en = {
88
88
  allowlistPathBlocked: 'Allowlist blocked the requested path.',
89
89
  // File system security errors
90
90
  pathOutsideRepo: 'Access denied: Path is outside of repository root.',
91
+ reservedPathPrefix: (prefix) => `Access denied: Reserved path prefix: ${prefix}`,
92
+ pathNotFound: (p) => `Path not found: ${p}`,
91
93
  // Worktree errors
92
94
  worktreePathMustBeUnderParityRoot: 'Worktree path must be under parity worktree root',
93
95
  worktreePathMustBeInTempDir: 'Worktree path must be in system temp directory',
@@ -151,6 +153,8 @@ export const en = {
151
153
  permissionPolicyAskDescription: 'Request user permission for side-effecting operations.',
152
154
  permissionPolicyDenyAllName: 'Deny all',
153
155
  permissionPolicyDenyAllDescription: 'Automatically deny side-effecting operations.',
156
+ permissionPolicyAllowAllName: 'Allow all',
157
+ permissionPolicyAllowAllDescription: 'Automatically allow side-effecting operations.',
154
158
  modeInteractiveDescription: 'Request permission before running side-effecting operations.',
155
159
  modeYoloDescription: 'Bypass permission prompts for side-effecting operations.',
156
160
  },
@@ -554,7 +558,12 @@ Please return the patch in PURE unified diff format:`;
554
558
  codeSearchDescription: 'Fast file pattern matching tool that works with any codebase size',
555
559
  fsReadDescription: 'Read the full content of a file from the repository',
556
560
  codeReadDescription: 'Read the full content of a source file from the repository',
557
- fsListDescription: 'List files and directories under a repository path',
561
+ fsListDescription: 'List directory entries under a repository path (legacy name). Prefer fs.list_directory for clarity.',
562
+ fsListDirectoryDescription: 'List directory entries (files and subdirectories) under a repository path',
563
+ fsListFilesDescription: 'List files (excluding subdirectories) under a repository path',
564
+ fsWriteFileDescription: 'Write a UTF-8 text file atomically (slash-only)',
565
+ fsCreateDirectoryDescription: 'Create a directory under the repository root (slash-only)',
566
+ fsDeleteFileDescription: 'Delete a file under the repository root (slash-only)',
558
567
  gitStatusDescription: 'Show the working tree status',
559
568
  gitCatDescription: 'Read file content from a specific git revision',
560
569
  codeAstDescription: 'Query AST definitions and references for symbols',
@@ -671,6 +680,30 @@ Please return the patch in PURE unified diff format:`;
671
680
  },
672
681
  skills: {
673
682
  maxRetriesExceeded: (id) => `Max retries exceeded for skill: ${id}. Possible circular dependency in dynamic data.`,
683
+ legacyRunnerForbidden: 'Legacy MicroTaskRunner is restricted to test context only. Use executeSkill() from SkillRunner.ts for production execution.',
684
+ missingFrontmatter: (filePath) => `Skill at ${filePath}: missing or malformed YAML frontmatter (expected --- delimiters).`,
685
+ invalidFrontmatter: (filePath, reason) => `Skill at ${filePath}: frontmatter validation failed — ${reason}`,
686
+ yamlParseError: (filePath, reason) => `Skill at ${filePath}: YAML parse error — ${reason}`,
687
+ nameDirMismatch: (filePath, expected, actual) => `Skill at ${filePath}: frontmatter name "${actual}" does not match parent directory "${expected}"`,
688
+ legacyDirectMdDeprecation: (filePath) => `Skill at ${filePath}: direct .md format is deprecated. Convert to subdirectory format: move to <skill-name>/SKILL.md`,
689
+ skillNotFoundInCatalog: (id) => `Skill "${id}" not found in catalog. Ensure loadCatalog() has been called and the skill exists.`,
690
+ skillActivated: (id) => `Skill "${id}" activated (Tier 2: full content loaded).`,
691
+ newSkillDiscovered: (id, location) => `New skill "${id}" discovered at ${location} during session.`,
692
+ conditionalSkillActivated: (id, matchedPattern) => `Conditional skill "${id}" activated: file matched pattern "${matchedPattern}".`,
693
+ permissionFileInvalidFormat: (filePath) => `Skill permissions file at ${filePath} has invalid format; starting with empty allowlist.`,
694
+ permissionFileLoadError: (filePath) => `Failed to load skill permissions from ${filePath}; starting with empty allowlist.`,
695
+ permissionFileSaveError: (filePath, reason) => `Failed to save skill permissions to ${filePath}: ${reason}`,
696
+ // Lenient validation warnings
697
+ nameTooLong: (filePath, name, len) => `Skill at ${filePath}: name "${name}" exceeds 64 characters (${len}); loading anyway`,
698
+ nameFormatWarning: (filePath, name) => `Skill at ${filePath}: name "${name}" does not match naming convention; loading anyway`,
699
+ descriptionTooLong: (filePath, len) => `Skill at ${filePath}: description exceeds 1024 characters (${len}); loading anyway`,
700
+ // YAML fallback
701
+ yamlFallbackApplied: (filePath, lines) => `Skill at ${filePath}: YAML fallback applied to fix common issues (${lines}); loading with corrected values`,
702
+ yamlFallbackFailed: (filePath) => `Skill at ${filePath}: YAML fallback recovery also failed; skipping skill`,
703
+ // Catalog disclosure
704
+ catalogDisclosurePreamble: 'The following skills provide specialized instructions for specific tasks. ' +
705
+ 'When a task matches a skill description, read the SKILL.md file at the listed location to load detailed instructions before proceeding. ' +
706
+ 'When a skill references relative paths, resolve them against the skill directory (the parent of SKILL.md).',
674
707
  },
675
708
  // Symbols for UI feedback
676
709
  symbols: {
@@ -723,7 +756,7 @@ Please return the patch in PURE unified diff format:`;
723
756
  missionFailedWithReason: (reason) => `Smallfry mission failed: ${reason}`,
724
757
  },
725
758
  ui: {
726
- spawnToolDescription: 'Deploy a specialized Smallfry (sub-agent) for autonomous task execution.',
759
+ spawnToolDescription: 'Delegate a concrete sub-task to a specialized sub-agent. This is not a no-argument action: always provide agent_ref and task. Use agent_ref="explorer" for read-only investigation, "reviewer" for audit, "surgeon" for implementation proposals, or "cleaner" for lint/format cleanup. Keep task self-contained with relevant files and the exact deliverable. Omit session_target unless shared context is explicitly required.',
727
760
  progressTitle: (id) => `[Smallfry: ${id}]`,
728
761
  },
729
762
  },
package/dist/utils/eol.js CHANGED
@@ -7,7 +7,7 @@ export class TextNormalizer {
7
7
  static read(content) {
8
8
  // 1. Count frequencies to handle mixed line endings
9
9
  const crlfCount = (content.match(/\r\n/g) || []).length;
10
- const lfCount = (content.match(/[^\r]\n/g) || []).length;
10
+ const lfCount = (content.match(/(?<!\r)\n/g) || []).length;
11
11
  // 2. Determine style (default to LF if LF >= CRLF)
12
12
  const eol = crlfCount > lfCount ? '\r\n' : '\n';
13
13
  // 3. Normalize to LF
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salmon-loop",
3
- "version": "0.2.13",
3
+ "version": "0.2.16",
4
4
  "description": "A chat-first coding agent CLI for safe, reviewable repository changes",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "dist/locales/**/*.js",
17
17
  "dist/utils/**/*.js",
18
18
  "dist/core/prompts/templates/**/*.hbs",
19
+ "scripts/fix-es-abstract-compat.js",
19
20
  "README.md",
20
21
  "README.zh-CN.md",
21
22
  "LICENSE"
@@ -29,12 +30,16 @@
29
30
  "t": "bun run test:unit",
30
31
  "test:unit": "bun scripts/run-bun-file-tests.ts tests/unit",
31
32
  "test:integration": "bun scripts/run-bun-file-tests.ts tests/integration",
33
+ "test:integration:network": "RUN_A2A_NETWORK_INTEGRATION=1 bun test --timeout 90000 --preload ./tests/setup-bun.ts tests/integration/a2a-sdk-server.test.ts",
32
34
  "test:e2e": "bun scripts/run-bun-file-tests.ts tests/e2e",
33
- "test:full": "bun run test:unit && bun run test:integration && bun run test:perf",
35
+ "test:full": "bun run test:unit && bun run test:integration",
36
+ "test:all": "bun run test:full && bun run test:perf",
34
37
  "test:runtime-boundary": "bun test --preload ./tests/setup-bun.ts tests/unit/scripts/target-runtime-boundary.test.ts tests/unit/scripts/bun-purity.test.ts",
35
38
  "test:worktree-smoke": "bun scripts/worktree-smoke.js",
36
39
  "test:headless-smoke": "bun scripts/headless-smoke.ts",
40
+ "test:contract-smoke": "bun test --timeout 15000 --preload ./tests/setup-bun.ts tests/unit/architecture/request-assembly-invariant.test.ts tests/unit/architecture/replacement-preview-boundary-invariant.test.ts tests/unit/architecture/sub-agent-prefix-consistency-invariant.test.ts tests/unit/architecture/tool-naming-contract.test.ts tests/unit/architecture/verify-contract-smoke-gate.test.ts tests/unit/tools/session-streaming.test.ts tests/unit/core/grizzco/steps/plan-patch-toolcalling.test.ts tests/unit/core/session/replacement-state.test.ts",
37
41
  "setup:hooks": "git config core.hooksPath .githooks",
42
+ "postinstall": "node scripts/fix-es-abstract-compat.js",
38
43
  "check:bun-purity": "bun scripts/check-bun-purity.ts",
39
44
  "check:bun-purity:staged": "bun scripts/check-bun-purity.ts --staged",
40
45
  "check:target-runtime-boundary": "bun scripts/check-target-runtime-hardcoding.ts",
@@ -51,17 +56,20 @@
51
56
  "check:test-runner-migration": "bun scripts/check-test-runner-migration.ts",
52
57
  "lint": "bun ./node_modules/eslint/bin/eslint.js .",
53
58
  "typecheck": "bun ./node_modules/typescript/bin/tsc --noEmit",
54
- "verify": "bun run check:bun-purity && bun run check:target-runtime-boundary && bun run check:unit-boundary && bun run check:fs-git-boundary && bun run check:bun-native-boundary && bun run check:test-runner-migration && bun run lint && bun run typecheck && bun run test:full",
59
+ "verify": "bun run check:bun-purity && bun run check:target-runtime-boundary && bun run check:unit-boundary && bun run check:fs-git-boundary && bun run check:bun-native-boundary && bun run check:test-runner-migration && bun run format:check && bun run lint && bun run typecheck && bun run test:contract-smoke && bun run test:full",
55
60
  "test:watch": "bun test --watch --preload ./tests/setup-bun.ts tests",
56
61
  "test:watch:unit": "bun test --watch --preload ./tests/setup-bun.ts tests/unit",
57
62
  "test:watch:integration": "bun test --watch --preload ./tests/setup-bun.ts tests/integration",
58
63
  "test:perf": "bun scripts/run-bun-file-tests.ts tests/perf",
64
+ "test:perf:network": "RUN_A2A_PERF_BENCHMARKS=1 bun test tests/perf/a2a-performance-benchmark.test.ts",
65
+ "test:perf:all": "RUN_A2A_PERF_BENCHMARKS=1 bun scripts/run-bun-file-tests.ts tests/perf",
59
66
  "test:watch:perf": "bun test --watch --preload ./tests/setup-bun.ts tests/perf",
60
67
  "test:coverage": "bun test --coverage --preload ./tests/setup-bun.ts tests",
61
68
  "test:report": "bun test --reporter=junit --reporter-outfile=test-report.xml --preload ./tests/setup-bun.ts tests",
62
69
  "test:ci": "bun run verify",
63
70
  "pack:dry": "npm pack --dry-run",
64
- "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
71
+ "format": "oxfmt --write \"src/**/*.ts\" \"tests/**/*.ts\"",
72
+ "format:check": "oxfmt --check \"src/**/*.ts\" \"tests/**/*.ts\"",
65
73
  "release": "bun scripts/release.ts cut",
66
74
  "release:cut": "bun scripts/release.ts cut",
67
75
  "release:publish": "bun scripts/release.ts publish"
@@ -106,10 +114,9 @@
106
114
  "eslint-config-prettier": "^10.1.8",
107
115
  "eslint-import-resolver-typescript": "^4.4.4",
108
116
  "eslint-plugin-import": "^2.32.0",
109
- "eslint-plugin-prettier": "^5.5.5",
110
117
  "fast-check": "^4.6.0",
111
118
  "jsdom": "^27.4.0",
112
- "prettier": "^3.4.2",
119
+ "oxfmt": "^0.42.0",
113
120
  "react-devtools-core": "^7.0.1",
114
121
  "simple-git": "^3.30.0",
115
122
  "sinon": "^21.0.1",
@@ -0,0 +1,77 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ async function fileExists(filePath) {
5
+ try {
6
+ await fs.access(filePath);
7
+ return true;
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+
13
+ async function listJsFiles(rootDir, baseDir = rootDir) {
14
+ const entries = await fs.readdir(rootDir, { withFileTypes: true });
15
+ const files = [];
16
+
17
+ for (const entry of entries) {
18
+ const full = path.join(rootDir, entry.name);
19
+ if (entry.isDirectory()) {
20
+ files.push(...(await listJsFiles(full, baseDir)));
21
+ continue;
22
+ }
23
+ if (entry.isFile() && entry.name.endsWith('.js')) {
24
+ files.push(path.relative(baseDir, full));
25
+ }
26
+ }
27
+
28
+ return files;
29
+ }
30
+
31
+ function toPosixPath(value) {
32
+ return value.split(path.sep).join('/');
33
+ }
34
+
35
+ async function writeCompatModule(dir2024, relativePath) {
36
+ const target = path.join(dir2024, relativePath);
37
+ const withoutExt = relativePath.replace(/\.js$/, '');
38
+ const requirePath = path.posix.join('..', '2025', toPosixPath(withoutExt));
39
+ const content = `'use strict';\n\nmodule.exports = require('${requirePath}');\n`;
40
+ await fs.mkdir(path.dirname(target), { recursive: true });
41
+ await fs.writeFile(target, content, 'utf8');
42
+ }
43
+
44
+ async function main() {
45
+ const root = path.resolve(process.cwd(), 'node_modules', 'es-abstract');
46
+ const dir2024 = path.join(root, '2024');
47
+ const dir2025 = path.join(root, '2025');
48
+
49
+ if (!(await fileExists(root)) || !(await fileExists(dir2025))) {
50
+ return;
51
+ }
52
+
53
+ await fs.mkdir(dir2024, { recursive: true });
54
+
55
+ const entries2025 = await listJsFiles(dir2025);
56
+ const missing = [];
57
+
58
+ for (const rel of entries2025) {
59
+ const target = path.join(dir2024, rel);
60
+ if (!(await fileExists(target))) {
61
+ missing.push(rel);
62
+ }
63
+ }
64
+
65
+ if (missing.length === 0) {
66
+ return;
67
+ }
68
+
69
+ for (const rel of missing) {
70
+ await writeCompatModule(dir2024, rel);
71
+ }
72
+ }
73
+
74
+ main().catch((error) => {
75
+ console.error(`Failed to apply es-abstract compatibility patch: ${error}`);
76
+ process.exitCode = 1;
77
+ });
@@ -1,26 +0,0 @@
1
- export function createFastifyServerBundle(deps) {
2
- const a2aServer = deps.createFastify();
3
- const sidecarServer = deps.createFastify();
4
- async function start() {
5
- if (deps.configureA2A) {
6
- await deps.configureA2A(a2aServer);
7
- }
8
- await a2aServer.register(deps.a2aPlugin);
9
- if (deps.configureSidecar) {
10
- await deps.configureSidecar(sidecarServer);
11
- }
12
- await sidecarServer.register(deps.sidecarPlugin);
13
- await a2aServer.listen(deps.a2aListen);
14
- await sidecarServer.listen(deps.sidecarListen);
15
- }
16
- async function close() {
17
- await Promise.all([a2aServer.close(), sidecarServer.close()]);
18
- }
19
- return {
20
- a2aServer,
21
- sidecarServer,
22
- start,
23
- close,
24
- };
25
- }
26
- //# sourceMappingURL=fastify-server-bundle.js.map
@@ -1,35 +0,0 @@
1
- import { buildFetchRequest, sendFetchResponse, } from './fastify-fetch-bridge.js';
2
- export function createSidecarFastifyPlugin(deps) {
3
- const baseUrl = deps.baseUrl ?? 'http://localhost';
4
- const allowConditional = deps.allowConditional ?? false;
5
- return async function sidecarFastifyPlugin(fastify) {
6
- for (const route of deps.routes) {
7
- if (route.exposure === 'forbidden')
8
- continue;
9
- if (route.scope !== 'both' && route.scope !== deps.scope)
10
- continue;
11
- if (!allowConditional && route.exposure === 'conditional')
12
- continue;
13
- const handler = async (request, reply) => {
14
- const fetchRequest = buildFetchRequest(request, baseUrl);
15
- if (deps.authorize) {
16
- const decision = await deps.authorize({
17
- request: fetchRequest,
18
- policyTag: route.policyTag,
19
- scope: deps.scope,
20
- });
21
- if (!decision.allowed) {
22
- await sendFetchResponse(reply, new Response(decision.message ?? 'Forbidden', {
23
- status: decision.status ?? 403,
24
- }));
25
- return;
26
- }
27
- }
28
- const response = await route.handler(fetchRequest);
29
- await sendFetchResponse(reply, response);
30
- };
31
- fastify.route({ method: route.method, url: route.path, handler });
32
- }
33
- };
34
- }
35
- //# sourceMappingURL=sidecar-fastify-plugin.js.map
@@ -1,47 +0,0 @@
1
- import os from 'node:os';
2
- import { defaultPathAdapter } from '../adapters/path/path-adapter.js';
3
- const DEFAULT_APP_NAME = 'SalmonLoop';
4
- export function resolveUserDataDir(options) {
5
- const platform = options?.platform ?? process.platform;
6
- const env = options?.env ?? process.env;
7
- const home = options?.homedir ?? os.homedir();
8
- const pathAdapter = options?.pathAdapter ?? defaultPathAdapter;
9
- const appName = options?.appName ?? DEFAULT_APP_NAME;
10
- if (platform === 'win32') {
11
- const base = env.APPDATA ?? env.LOCALAPPDATA ?? pathAdapter.join(home, 'AppData', 'Roaming');
12
- return pathAdapter.join(base, appName);
13
- }
14
- if (platform === 'darwin') {
15
- return pathAdapter.join(home, 'Library', 'Application Support', appName);
16
- }
17
- const xdg = env.XDG_DATA_HOME;
18
- const base = xdg && xdg.length > 0 ? xdg : pathAdapter.join(home, '.local', 'share');
19
- return pathAdapter.join(base, appName);
20
- }
21
- export function getSidecarSocketPath(options) {
22
- const platform = options?.platform ?? process.platform;
23
- const socketName = options?.socketName ?? 'agent-message.sock';
24
- if (platform === 'win32') {
25
- return '\\\\.\\pipe\\salmonloop-agent-message';
26
- }
27
- const dataDir = resolveUserDataDir(options);
28
- const pathAdapter = options?.pathAdapter ?? defaultPathAdapter;
29
- return pathAdapter.join(dataDir, 'sidecar', socketName);
30
- }
31
- export function isBunRuntime() {
32
- return typeof globalThis.Bun !== 'undefined';
33
- }
34
- export function createPipeListenOptions(path) {
35
- return { type: 'pipe', path };
36
- }
37
- export function createTcpListenOptions(port, host = '127.0.0.1') {
38
- return { type: 'tcp', port, host };
39
- }
40
- export function getSidecarListenOptions(options) {
41
- const platform = options?.platform ?? process.platform;
42
- if (platform === 'win32' && isBunRuntime()) {
43
- return { type: 'tcp', port: options?.sidecarPort ?? 7432, host: '127.0.0.1' };
44
- }
45
- return { type: 'pipe', path: getSidecarSocketPath(options) };
46
- }
47
- //# sourceMappingURL=sidecar-paths.js.map
@@ -1,103 +0,0 @@
1
- export const defaultSidecarRouteCatalog = [
2
- {
3
- id: 'health',
4
- method: 'GET',
5
- path: '/health',
6
- exposure: 'essential',
7
- scope: 'uds',
8
- policyTag: 'sidecar.health.read',
9
- },
10
- {
11
- id: 'status',
12
- method: 'GET',
13
- path: '/status',
14
- exposure: 'essential',
15
- scope: 'uds',
16
- policyTag: 'sidecar.status.read',
17
- },
18
- {
19
- id: 'info',
20
- method: 'GET',
21
- path: '/info',
22
- exposure: 'essential',
23
- scope: 'uds',
24
- policyTag: 'sidecar.info.read',
25
- },
26
- {
27
- id: 'abort',
28
- method: 'POST',
29
- path: '/abort',
30
- exposure: 'essential',
31
- scope: 'uds',
32
- policyTag: 'sidecar.task.abort',
33
- },
34
- {
35
- id: 'workspace_files',
36
- method: 'GET',
37
- path: '/workspace/files',
38
- exposure: 'conditional',
39
- scope: 'uds',
40
- policyTag: 'sidecar.workspace.files',
41
- },
42
- {
43
- id: 'logs_stream',
44
- method: 'GET',
45
- path: '/logs/stream',
46
- exposure: 'conditional',
47
- scope: 'uds',
48
- policyTag: 'sidecar.logs.stream',
49
- },
50
- {
51
- id: 'config_patch',
52
- method: 'PATCH',
53
- path: '/config',
54
- exposure: 'conditional',
55
- scope: 'uds',
56
- policyTag: 'sidecar.config.patch',
57
- },
58
- {
59
- id: 'shutdown',
60
- method: 'POST',
61
- path: '/shutdown',
62
- exposure: 'forbidden',
63
- scope: 'uds',
64
- policyTag: 'sidecar.shutdown',
65
- },
66
- {
67
- id: 'config_get',
68
- method: 'GET',
69
- path: '/config',
70
- exposure: 'forbidden',
71
- scope: 'uds',
72
- policyTag: 'sidecar.config.read',
73
- },
74
- {
75
- id: 'terminal_exec',
76
- method: 'POST',
77
- path: '/terminal/exec',
78
- exposure: 'forbidden',
79
- scope: 'uds',
80
- policyTag: 'sidecar.terminal.exec',
81
- },
82
- ];
83
- export function buildSidecarRouteDescriptors(input) {
84
- const catalog = input.catalog ?? defaultSidecarRouteCatalog;
85
- const descriptors = [];
86
- const missing = [];
87
- for (const route of catalog) {
88
- if (route.exposure === 'forbidden')
89
- continue;
90
- const handler = input.handlers[route.id];
91
- if (!handler) {
92
- missing.push(route.id);
93
- continue;
94
- }
95
- const { id: _id, ...descriptor } = route;
96
- descriptors.push({ ...descriptor, handler });
97
- }
98
- if (input.strict && missing.length > 0) {
99
- throw new Error(`Missing sidecar handler: ${missing[0]}`);
100
- }
101
- return descriptors;
102
- }
103
- //# sourceMappingURL=sidecar-route-catalog.js.map