salmon-loop 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/cli/authorization/non-interactive.js +9 -13
  2. package/dist/cli/authorization/provider.js +2 -10
  3. package/dist/cli/chat.js +12 -6
  4. package/dist/cli/commands/allowlist.js +1 -1
  5. package/dist/cli/commands/chat.js +13 -13
  6. package/dist/cli/commands/config.js +2 -2
  7. package/dist/cli/commands/mode.js +2 -2
  8. package/dist/cli/commands/parallel.js +1 -1
  9. package/dist/cli/commands/run/handler.js +9 -4
  10. package/dist/cli/commands/run/loop-params.js +2 -0
  11. package/dist/cli/commands/run/parse-options.js +14 -26
  12. package/dist/cli/commands/run/runtime-llm.js +15 -12
  13. package/dist/cli/commands/run/runtime-options.js +3 -1
  14. package/dist/cli/config.js +0 -8
  15. package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
  16. package/dist/cli/locales/en.js +2 -2
  17. package/dist/cli/reporters/standard.js +12 -3
  18. package/dist/cli/reporters/stream-json.js +2 -1
  19. package/dist/cli/slash/runtime.js +2 -2
  20. package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
  21. package/dist/cli/ui/hooks/useLoopState.js +1 -1
  22. package/dist/core/adapters/fs/file-adapter.js +3 -1
  23. package/dist/core/adapters/git/git-adapter.js +6 -3
  24. package/dist/core/adapters/git/git-runner.js +5 -2
  25. package/dist/core/adapters/git/lock-manager.js +7 -4
  26. package/dist/core/ast/parser.js +18 -9
  27. package/dist/core/checkpoint-domain/manifest-store.js +21 -13
  28. package/dist/core/checkpoint-domain/service.js +3 -1
  29. package/dist/core/config/limits.js +1 -1
  30. package/dist/core/config/model-pricing.js +61 -0
  31. package/dist/core/config/schema.js +738 -0
  32. package/dist/core/config/validate.js +11 -922
  33. package/dist/core/context/ast/skeleton-extractor.js +225 -0
  34. package/dist/core/context/ast/source-outline.js +24 -1
  35. package/dist/core/context/budget/dynamic-adjuster.js +20 -5
  36. package/dist/core/context/builder.js +7 -3
  37. package/dist/core/context/cache/store-factory.js +3 -1
  38. package/dist/core/context/dependencies.js +2 -1
  39. package/dist/core/context/effectiveness/persistence.js +50 -0
  40. package/dist/core/context/effectiveness/tracker.js +24 -0
  41. package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
  42. package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
  43. package/dist/core/context/gatherers/ast-gatherer.js +34 -40
  44. package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
  45. package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
  46. package/dist/core/context/gatherers/knowledge-gatherer.js +21 -2
  47. package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
  48. package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
  49. package/dist/core/context/service.js +12 -2
  50. package/dist/core/context/steps/context-gather.js +14 -3
  51. package/dist/core/context/steps/context-targets.js +1 -0
  52. package/dist/core/context/targeting/target-resolver.js +29 -11
  53. package/dist/core/context/token/cache.js +5 -2
  54. package/dist/core/context/token/encoding-registry.js +7 -6
  55. package/dist/core/context/truncation/strategies/json.js +5 -2
  56. package/dist/core/context/truncation/type-detector.js +3 -1
  57. package/dist/core/extensions/index.js +48 -3
  58. package/dist/core/extensions/load.js +3 -2
  59. package/dist/core/extensions/merge.js +5 -1
  60. package/dist/core/extensions/paths.js +8 -2
  61. package/dist/core/extensions/schemas.js +21 -0
  62. package/dist/core/facades/cli-authorization-provider.js +1 -0
  63. package/dist/core/facades/cli-command-chat.js +2 -0
  64. package/dist/core/facades/cli-run-handler.js +1 -0
  65. package/dist/core/facades/cli-utils-serialize.js +2 -0
  66. package/dist/core/feedback/parsers.js +290 -1
  67. package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
  68. package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
  69. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
  70. package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
  71. package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
  72. package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
  73. package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
  74. package/dist/core/grizzco/execution/RejectionManager.js +7 -5
  75. package/dist/core/grizzco/runtime/apply-back-runtime.js +5 -2
  76. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
  77. package/dist/core/grizzco/services/registry.js +18 -0
  78. package/dist/core/grizzco/steps/audit.js +20 -10
  79. package/dist/core/grizzco/steps/autopilot.js +21 -32
  80. package/dist/core/grizzco/steps/display-report.js +4 -11
  81. package/dist/core/grizzco/steps/explore.js +14 -4
  82. package/dist/core/grizzco/steps/generateReview.js +3 -1
  83. package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
  84. package/dist/core/grizzco/steps/patch.js +1 -0
  85. package/dist/core/grizzco/steps/plan.js +58 -49
  86. package/dist/core/grizzco/steps/research.js +3 -1
  87. package/dist/core/grizzco/steps/tool-runtime.js +3 -0
  88. package/dist/core/grizzco/steps/verify.js +7 -1
  89. package/dist/core/grizzco/validation/AstValidationService.js +3 -1
  90. package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
  91. package/dist/core/history/input-history.js +3 -1
  92. package/dist/core/intent/chat-intent.js +3 -1
  93. package/dist/core/llm/ai-sdk/message-mapper.js +37 -26
  94. package/dist/core/llm/ai-sdk/request-params.js +2 -6
  95. package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
  96. package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
  97. package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
  98. package/dist/core/llm/contracts/repair.js +16 -8
  99. package/dist/core/llm/errors.js +18 -14
  100. package/dist/core/llm/output-policy.js +8 -0
  101. package/dist/core/llm/redact.js +1 -3
  102. package/dist/core/llm/retry-utils.js +8 -2
  103. package/dist/core/llm/stream-utils.js +5 -3
  104. package/dist/core/llm/sub-agent-factory.js +51 -0
  105. package/dist/core/llm/tool-calling-stub.js +48 -0
  106. package/dist/core/llm/utils.js +17 -6
  107. package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
  108. package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
  109. package/dist/core/mcp/bridge/tool-bridge.js +5 -14
  110. package/dist/core/mcp/catalog/discovery.js +3 -1
  111. package/dist/core/mcp/client/connection-manager.js +7 -4
  112. package/dist/core/mcp/client/transport-factory.js +7 -3
  113. package/dist/core/mcp/host/sampling-provider.js +1 -1
  114. package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
  115. package/dist/core/memory/relevant-retrieval.js +6 -4
  116. package/dist/core/observability/audit-file.js +2 -1
  117. package/dist/core/observability/audit-trail.js +3 -1
  118. package/dist/core/observability/authorization-decisions.js +13 -12
  119. package/dist/core/observability/error-mapping.js +2 -1
  120. package/dist/core/observability/logger.js +2 -1
  121. package/dist/core/observability/monitor.js +24 -0
  122. package/dist/core/observability/run-outcome-reporter.js +1 -0
  123. package/dist/core/observability/token-usage.js +5 -4
  124. package/dist/core/permission-gate/default-gate.js +5 -8
  125. package/dist/core/plan/storage.js +7 -4
  126. package/dist/core/plugin/loader.js +8 -5
  127. package/dist/core/prompts/registry.js +12 -30
  128. package/dist/core/prompts/runtime.js +3 -1
  129. package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
  130. package/dist/core/protocols/a2a/sdk/executor.js +3 -1
  131. package/dist/core/protocols/a2a/sdk/server.js +5 -4
  132. package/dist/core/protocols/acp/acp-command-runner.js +7 -6
  133. package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
  134. package/dist/core/protocols/acp/formal-agent.js +13 -6
  135. package/dist/core/protocols/acp/permission-provider.js +3 -2
  136. package/dist/core/protocols/acp/stdio-server.js +6 -6
  137. package/dist/core/reflection/engine.js +114 -14
  138. package/dist/core/runtime/agent-server-runtime.js +3 -2
  139. package/dist/core/runtime/batch-runner.js +81 -0
  140. package/dist/core/runtime/initialize.js +71 -6
  141. package/dist/core/runtime/loop-finalize.js +3 -0
  142. package/dist/core/runtime/loop-session-runner.js +5 -0
  143. package/dist/core/runtime/loop.js +4 -0
  144. package/dist/core/runtime/paths.js +9 -6
  145. package/dist/core/runtime/spawn-interactive.js +5 -4
  146. package/dist/core/security/redaction.js +3 -2
  147. package/dist/core/session/compaction/index.js +4 -3
  148. package/dist/core/session/compression.js +3 -1
  149. package/dist/core/session/manager.js +26 -38
  150. package/dist/core/session/pruning-strategy.js +2 -1
  151. package/dist/core/session/token-tracker.js +27 -9
  152. package/dist/core/skills/parser.js +3 -2
  153. package/dist/core/skills/permissions.js +2 -2
  154. package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
  155. package/dist/core/skills/runtime/SkillRunner.js +5 -2
  156. package/dist/core/slash/steps/slash-execute.js +7 -5
  157. package/dist/core/slash/strategy.js +1 -1
  158. package/dist/core/strata/checkpoint/manager.js +16 -10
  159. package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
  160. package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
  161. package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
  162. package/dist/core/strata/interaction/file-system-provider.js +2 -1
  163. package/dist/core/strata/layers/file-state-resolver.js +9 -7
  164. package/dist/core/strata/layers/immutable-git-layer.js +3 -1
  165. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
  166. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
  167. package/dist/core/strata/layers/worktree.js +9 -10
  168. package/dist/core/strata/runtime/environment.js +2 -1
  169. package/dist/core/strata/runtime/synchronizer.js +28 -26
  170. package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
  171. package/dist/core/structured-output/json-extract.js +3 -1
  172. package/dist/core/structured-output/json-schema-validator.js +1 -13
  173. package/dist/core/sub-agent/artifacts/store.js +2 -1
  174. package/dist/core/sub-agent/context-snapshot.js +12 -6
  175. package/dist/core/sub-agent/controller.js +70 -1
  176. package/dist/core/sub-agent/core/loop.js +25 -3
  177. package/dist/core/sub-agent/core/manager.js +343 -117
  178. package/dist/core/sub-agent/registry-defaults.js +12 -0
  179. package/dist/core/sub-agent/registry.js +8 -0
  180. package/dist/core/sub-agent/summary.js +96 -0
  181. package/dist/core/sub-agent/team.js +98 -0
  182. package/dist/core/sub-agent/tools/task-await.js +109 -0
  183. package/dist/core/sub-agent/tools/task-spawn.js +52 -7
  184. package/dist/core/sub-agent/tools/team.js +92 -0
  185. package/dist/core/sub-agent/types.js +11 -2
  186. package/dist/core/target-runtime/profile.js +3 -1
  187. package/dist/core/tools/audit.js +3 -2
  188. package/dist/core/tools/budget.js +7 -12
  189. package/dist/core/tools/builtin/ast.js +144 -0
  190. package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
  191. package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
  192. package/dist/core/tools/builtin/code-search/executor.js +46 -43
  193. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
  194. package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
  195. package/dist/core/tools/builtin/fs.js +90 -7
  196. package/dist/core/tools/builtin/git.js +242 -0
  197. package/dist/core/tools/builtin/glob.js +79 -0
  198. package/dist/core/tools/builtin/index.js +53 -111
  199. package/dist/core/tools/builtin/interaction.js +13 -15
  200. package/dist/core/tools/builtin/knowledge.js +146 -4
  201. package/dist/core/tools/builtin/proposal.js +14 -3
  202. package/dist/core/tools/builtin/verify.js +35 -3
  203. package/dist/core/tools/capability/executor.js +5 -5
  204. package/dist/core/tools/headless-payload.js +1 -3
  205. package/dist/core/tools/mapper.js +8 -42
  206. package/dist/core/tools/parallel/persistence.js +17 -5
  207. package/dist/core/tools/parallel/scheduler.js +23 -21
  208. package/dist/core/tools/permissions/permission-rules.js +69 -115
  209. package/dist/core/tools/plugins/loader.js +4 -3
  210. package/dist/core/tools/router.js +112 -58
  211. package/dist/core/tools/session.js +64 -102
  212. package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
  213. package/dist/core/tools/tool-visibility.js +2 -1
  214. package/dist/core/tools/types.js +10 -0
  215. package/dist/core/types/batch.js +2 -0
  216. package/dist/core/utils/error.js +79 -0
  217. package/dist/core/utils/sanitizer.js +5 -2
  218. package/dist/core/utils/serialize.js +66 -0
  219. package/dist/core/utils/zod.js +29 -0
  220. package/dist/core/verification/detect-runner.js +86 -0
  221. package/dist/core/verification/runner.js +76 -0
  222. package/dist/core/version.js +3 -1
  223. package/dist/core/workspace/capabilities.js +3 -2
  224. package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
  225. package/dist/languages/python/index.js +154 -0
  226. package/dist/locales/en.js +8 -1
  227. package/package.json +2 -1
@@ -1,15 +1,21 @@
1
1
  import { getLogger } from '../observability/logger.js';
2
2
  import { getPromptRegistry } from '../prompts/registry.js';
3
3
  import { executeUpdateKnowledge } from '../tools/builtin/knowledge.js';
4
+ // Frequency limiter for success reflections: at most once per N successes
5
+ let successReflectionCounter = 0;
6
+ const SUCCESS_REFLECTION_INTERVAL = 5;
4
7
  export class ReflectionEngine {
5
8
  llm;
6
9
  constructor(llm) {
7
10
  this.llm = llm;
8
11
  }
12
+ /**
13
+ * Main reflection: failures followed by success.
14
+ * Extracts "what went wrong and how it was fixed".
15
+ */
9
16
  async reflect(input, repoRoot) {
10
17
  const promptRegistry = getPromptRegistry();
11
18
  await promptRegistry.init();
12
- // Only reflect if there were failures and final success
13
19
  const failures = input.history.filter((h) => h.error);
14
20
  if (failures.length === 0 || !input.success) {
15
21
  return { lessons: [] };
@@ -21,19 +27,45 @@ export class ReflectionEngine {
21
27
  responseFormat: 'json_object',
22
28
  });
23
29
  const content = response.content;
24
- // Extract JSON from response (handle potential markdown markers)
25
30
  const jsonStr = content.match(/\{[\s\S]*\}/)?.[0] || content;
26
31
  const result = JSON.parse(jsonStr);
27
- // Persist suggested knowledge if any
28
- if (result.suggestedRules && result.suggestedRules.length > 0) {
29
- // Since we are in the engine, we can call the executor directly
30
- const mockCtx = { repoRoot };
31
- await executeUpdateKnowledge({
32
- category: 'project_rules',
33
- rules: result.suggestedRules,
34
- deprecated_rules: result.deprecatedRules,
35
- }, mockCtx);
36
- }
32
+ await this.persistKnowledge(result, repoRoot);
33
+ getLogger().debug(`[Reflection] Reflection completed with ${result.lessons?.length ?? 0} lessons.`);
34
+ return result;
35
+ }
36
+ catch (e) {
37
+ getLogger().warn(`[Reflection] Failed to perform reflection: ${e instanceof Error ? e.message : String(e)}`);
38
+ return { lessons: [] };
39
+ }
40
+ }
41
+ /**
42
+ * Reflect on first-try success (attempt === 1).
43
+ * Extracts "what was done right" as positive patterns.
44
+ * Frequency-limited: at most once every SUCCESS_REFLECTION_INTERVAL successes.
45
+ */
46
+ async reflectOnSuccess(input, repoRoot) {
47
+ successReflectionCounter++;
48
+ if (successReflectionCounter % SUCCESS_REFLECTION_INTERVAL !== 0) {
49
+ return { lessons: [] };
50
+ }
51
+ const promptRegistry = getPromptRegistry();
52
+ await promptRegistry.init();
53
+ getLogger().debug('[Reflection] Triggering success reflection (first-try success).');
54
+ const successPrompt = [
55
+ 'The task was completed successfully on the first attempt.',
56
+ `Instruction: ${input.instruction}`,
57
+ '',
58
+ 'Analyze what was done correctly. Extract 1-2 concise positive patterns that contributed to success.',
59
+ 'Return JSON: { "lessons": ["..."], "suggestedDecisions": ["..."] }',
60
+ 'Each decision should be a short, actionable pattern (10-200 chars).',
61
+ ].join('\n');
62
+ try {
63
+ const response = await this.llm.chat([{ role: 'user', content: successPrompt }], {
64
+ responseFormat: 'json_object',
65
+ });
66
+ const content = response.content;
67
+ const jsonStr = content.match(/\{[\s\S]*\}/)?.[0] || content;
68
+ const result = JSON.parse(jsonStr);
37
69
  if (result.suggestedDecisions && result.suggestedDecisions.length > 0) {
38
70
  const mockCtx = { repoRoot };
39
71
  for (const decision of result.suggestedDecisions) {
@@ -43,13 +75,81 @@ export class ReflectionEngine {
43
75
  }, mockCtx);
44
76
  }
45
77
  }
46
- getLogger().debug(`[Reflection] Reflection completed with ${result.lessons?.length ?? 0} lessons.`);
78
+ await this.persistLessons(result.lessons, repoRoot, 'success');
47
79
  return result;
48
80
  }
49
81
  catch (e) {
50
- getLogger().warn(`[Reflection] Failed to perform reflection: ${e instanceof Error ? e.message : String(e)}`);
82
+ getLogger().warn(`[Reflection] Failed to perform success reflection: ${e instanceof Error ? e.message : String(e)}`);
83
+ return { lessons: [] };
84
+ }
85
+ }
86
+ /**
87
+ * Reflect on final failure (all retries exhausted).
88
+ * Extracts "why it failed" as deprecated approaches.
89
+ * No LLM call — structured extraction from failure history.
90
+ */
91
+ async reflectOnFailure(input, repoRoot) {
92
+ const failures = input.history.filter((h) => h.error);
93
+ if (failures.length === 0)
51
94
  return { lessons: [] };
95
+ getLogger().debug(`[Reflection] Extracting failure lessons from ${failures.length} failed attempts.`);
96
+ // Structured extraction: collect unique error patterns
97
+ const errorPatterns = new Set();
98
+ for (const entry of failures) {
99
+ if (!entry.error)
100
+ continue;
101
+ const summary = typeof entry.contextSummary === 'string'
102
+ ? entry.contextSummary.slice(0, 200)
103
+ : String(entry.error).slice(0, 200);
104
+ errorPatterns.add(summary);
52
105
  }
106
+ const lessons = Array.from(errorPatterns).map((pattern) => `Approach failed: ${pattern}`);
107
+ // Write deprecated rules
108
+ const deprecatedApproaches = Array.from(errorPatterns).map((pattern) => `Avoid: ${pattern.slice(0, 100)}`);
109
+ if (deprecatedApproaches.length > 0) {
110
+ const mockCtx = { repoRoot };
111
+ await executeUpdateKnowledge({
112
+ category: 'project_rules',
113
+ rules: [],
114
+ deprecated_rules: deprecatedApproaches,
115
+ }, mockCtx).catch((e) => {
116
+ getLogger().warn(`[Reflection] Failed to persist failure lessons: ${String(e)}`);
117
+ });
118
+ }
119
+ await this.persistLessons(lessons, repoRoot, 'failure');
120
+ return { lessons };
121
+ }
122
+ async persistKnowledge(result, repoRoot) {
123
+ if (result.suggestedRules && result.suggestedRules.length > 0) {
124
+ const mockCtx = { repoRoot };
125
+ await executeUpdateKnowledge({
126
+ category: 'project_rules',
127
+ rules: result.suggestedRules,
128
+ deprecated_rules: result.deprecatedRules,
129
+ }, mockCtx);
130
+ }
131
+ if (result.suggestedDecisions && result.suggestedDecisions.length > 0) {
132
+ const mockCtx = { repoRoot };
133
+ for (const decision of result.suggestedDecisions) {
134
+ await executeUpdateKnowledge({
135
+ category: 'architectural_decisions',
136
+ decision,
137
+ }, mockCtx);
138
+ }
139
+ }
140
+ await this.persistLessons(result.lessons, repoRoot, 'success');
141
+ }
142
+ async persistLessons(lessons, repoRoot, source) {
143
+ if (lessons.length === 0)
144
+ return;
145
+ const mockCtx = { repoRoot };
146
+ await executeUpdateKnowledge({
147
+ category: 'lessons_learned',
148
+ lessons,
149
+ source,
150
+ }, mockCtx).catch((e) => {
151
+ getLogger().warn(`[Reflection] Failed to persist lessons: ${String(e)}`);
152
+ });
53
153
  }
54
154
  }
55
155
  //# sourceMappingURL=engine.js.map
@@ -65,12 +65,13 @@ export function createAgentServerRuntime(deps) {
65
65
  }
66
66
  }
67
67
  async function close() {
68
- if (!a2aServerInstance) {
68
+ const instance = a2aServerInstance;
69
+ if (!instance) {
69
70
  started = false;
70
71
  return;
71
72
  }
72
73
  await new Promise((resolve, reject) => {
73
- a2aServerInstance.close((error) => {
74
+ instance.close((error) => {
74
75
  if (error) {
75
76
  reject(error);
76
77
  return;
@@ -0,0 +1,81 @@
1
+ import { createRunSalmonLoop } from './loop.js';
2
+ export class BatchRunner {
3
+ runLoop;
4
+ constructor(deps) {
5
+ this.runLoop = createRunSalmonLoop({ semaphore: deps?.semaphore });
6
+ }
7
+ async run(tasks, batchOptions) {
8
+ const { rateLimitMs = 0, filter, signal } = batchOptions ?? {};
9
+ const filtered = filter ? tasks.filter(filter) : tasks;
10
+ const results = [];
11
+ for (let i = 0; i < filtered.length; i++) {
12
+ if (signal?.aborted)
13
+ break;
14
+ if (i > 0 && rateLimitMs > 0) {
15
+ await new Promise((r) => setTimeout(r, rateLimitMs));
16
+ }
17
+ const result = await this.runLoop(filtered[i]);
18
+ results.push(result);
19
+ }
20
+ return buildBatchRunReport(results);
21
+ }
22
+ }
23
+ function aggregateUsage(results) {
24
+ const total = { inputTokens: 0, outputTokens: 0, totalTokens: 0, estimatedCost: 0 };
25
+ for (const r of results) {
26
+ if (!r.usage)
27
+ continue;
28
+ total.inputTokens += r.usage.inputTokens;
29
+ total.outputTokens += r.usage.outputTokens;
30
+ total.totalTokens += r.usage.totalTokens;
31
+ if (r.usage.estimatedCost)
32
+ total.estimatedCost += r.usage.estimatedCost;
33
+ }
34
+ return total;
35
+ }
36
+ export function buildBatchRunReport(results) {
37
+ const completed = results.filter((r) => r.success).length;
38
+ const failed = results.length - completed;
39
+ const totalDuration = results.reduce((s, r) => s + (r.durationMs ?? 0), 0);
40
+ return {
41
+ totalRuns: results.length,
42
+ completedRuns: completed,
43
+ failedRuns: failed,
44
+ successRate: results.length > 0 ? completed / results.length : 0,
45
+ avgDurationMs: results.length > 0 ? totalDuration / results.length : 0,
46
+ results,
47
+ totalUsage: aggregateUsage(results),
48
+ byDimension: (dimension) => buildDimensionMap(results, dimension),
49
+ };
50
+ }
51
+ function buildDimensionMap(results, dimension) {
52
+ const map = {};
53
+ for (const r of results) {
54
+ const value = String(r.providerMeta?.[dimension] ?? 'unknown');
55
+ map[value] ??= {
56
+ total: 0,
57
+ success: 0,
58
+ failed: 0,
59
+ avgDurationMs: 0,
60
+ totalUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
61
+ };
62
+ const bucket = map[value];
63
+ bucket.total++;
64
+ if (r.success)
65
+ bucket.success++;
66
+ else
67
+ bucket.failed++;
68
+ if (r.durationMs)
69
+ bucket.avgDurationMs += r.durationMs;
70
+ if (r.usage) {
71
+ bucket.totalUsage.inputTokens += r.usage.inputTokens;
72
+ bucket.totalUsage.outputTokens += r.usage.outputTokens;
73
+ bucket.totalUsage.totalTokens += r.usage.totalTokens;
74
+ }
75
+ }
76
+ for (const bucket of Object.values(map)) {
77
+ bucket.avgDurationMs = bucket.total > 0 ? bucket.avgDurationMs / bucket.total : 0;
78
+ }
79
+ return map;
80
+ }
81
+ //# sourceMappingURL=batch-runner.js.map
@@ -1,19 +1,24 @@
1
+ import { readFileSync } from '../adapters/fs/node-fs.js';
1
2
  import { initializeDefaultCalculator } from '../context/policies/pack-until-full.js';
3
+ import { getRepoAgentsConfigPath, getUserAgentsConfigPath } from '../extensions/paths.js';
4
+ import { AgentsConfigSchema } from '../extensions/schemas.js';
2
5
  import { createLogger, getLogger, setLogger, tryGetLogger } from '../observability/logger.js';
3
6
  import { createMonitor, setMonitor, tryGetMonitor } from '../observability/monitor.js';
4
7
  import { registerDefaultSubAgentProfiles } from '../sub-agent/registry-defaults.js';
5
8
  import { createSubAgentRegistry, setSubAgentRegistry, tryGetSubAgentRegistry, } from '../sub-agent/registry.js';
9
+ import { isRecord } from '../utils/serialize.js';
6
10
  /**
7
11
  * Initializes the Core safety runtime.
8
12
  * Mounts global error handlers and ensures environment safety.
9
13
  */
14
+ const GLOBAL_FLAG = '__SALMON_RUNTIME_INITIALIZED__';
10
15
  export function initializeRuntime() {
11
16
  // Prevent duplicate initialization
12
- if (globalThis.__SALMON_RUNTIME_INITIALIZED__)
17
+ if (globalThis[GLOBAL_FLAG])
13
18
  return;
14
19
  // Bypass interception in debug mode to allow raw console/stream output
15
20
  if (process.env.SALMONLOOP_DEBUG === 'true') {
16
- globalThis.__SALMON_RUNTIME_INITIALIZED__ = true;
21
+ globalThis[GLOBAL_FLAG] = true;
17
22
  return;
18
23
  }
19
24
  // Preload token calculator in background (non-blocking)
@@ -32,6 +37,7 @@ export function initializeRuntime() {
32
37
  if (!tryGetSubAgentRegistry()) {
33
38
  const registry = createSubAgentRegistry();
34
39
  registerDefaultSubAgentProfiles(registry);
40
+ loadUserAgentProfiles(registry);
35
41
  setSubAgentRegistry(registry);
36
42
  }
37
43
  const isGui = process.argv.includes('--gui');
@@ -40,10 +46,12 @@ export function initializeRuntime() {
40
46
  const originalConsoleError = console.error;
41
47
  console.error = (...args) => {
42
48
  const sanitizedArgs = args.map((arg) => {
43
- if (typeof arg === 'object' && arg !== null) {
49
+ if (isRecord(arg)) {
44
50
  // Drop the object structure entirely for console output to prevent UI pollution
45
- const code = arg.code || arg.llmCode || 'TECHNICAL_ERROR';
46
- const msg = arg.message || 'No detail provided';
51
+ const code = (typeof arg.code === 'string' ? arg.code : undefined) ||
52
+ (typeof arg.llmCode === 'string' ? arg.llmCode : undefined) ||
53
+ 'TECHNICAL_ERROR';
54
+ const msg = (typeof arg.message === 'string' ? arg.message : undefined) || 'No detail provided';
47
55
  return `[${code}] ${msg}`;
48
56
  }
49
57
  return arg;
@@ -127,6 +135,63 @@ export function initializeRuntime() {
127
135
  process.on('uncaughtException', (error) => {
128
136
  getLogger().error('Uncaught Exception detected in Core runtime', error, true);
129
137
  });
130
- globalThis.__SALMON_RUNTIME_INITIALIZED__ = true;
138
+ globalThis[GLOBAL_FLAG] = true;
139
+ }
140
+ function loadUserAgentProfiles(registry) {
141
+ const tryLoadSync = (filePath) => {
142
+ try {
143
+ const content = readFileSync(filePath, 'utf-8');
144
+ return AgentsConfigSchema.parse(JSON.parse(content));
145
+ }
146
+ catch (error) {
147
+ getLogger().debug(`[InitializeRuntime] Failed to load agent config from ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
148
+ return null;
149
+ }
150
+ };
151
+ // Load from repo and user scopes; repo takes priority
152
+ const repoRoot = process.cwd();
153
+ const userConfig = tryLoadSync(getUserAgentsConfigPath());
154
+ const repoConfig = tryLoadSync(getRepoAgentsConfigPath(repoRoot));
155
+ const toProfile = (raw) => ({
156
+ id: raw.id,
157
+ name: raw.name,
158
+ role: raw.role,
159
+ description: raw.description,
160
+ allowedTools: raw.allowedTools ?? ['code.search', 'fs.read'],
161
+ readOnly: raw.readOnly ?? false,
162
+ stratagem: raw.stratagem ?? 'investigator',
163
+ toolInheritance: raw.toolInheritance,
164
+ permissionMode: raw.permissionMode,
165
+ systemPrompt: raw.systemPrompt,
166
+ maxTokens: raw.maxTokens,
167
+ maxAttempts: raw.maxAttempts,
168
+ timeoutMs: raw.timeoutMs,
169
+ });
170
+ // User profiles first (lower priority)
171
+ if (userConfig) {
172
+ for (const agent of userConfig.agents) {
173
+ if (agent.enabled === false)
174
+ continue;
175
+ // Don't override built-in profiles
176
+ if (registry.has(agent.id)) {
177
+ tryGetLogger()?.debug(`[initializeRuntime] Skipping user agent '${agent.id}': conflicts with built-in profile`);
178
+ continue;
179
+ }
180
+ registry.register(toProfile(agent));
181
+ }
182
+ }
183
+ // Repo profiles override user (higher priority)
184
+ if (repoConfig) {
185
+ for (const agent of repoConfig.agents) {
186
+ if (agent.enabled === false)
187
+ continue;
188
+ // Don't override built-in profiles
189
+ if (registry.has(agent.id)) {
190
+ tryGetLogger()?.debug(`[initializeRuntime] Skipping repo agent '${agent.id}': conflicts with built-in profile`);
191
+ continue;
192
+ }
193
+ registry.register(toProfile(agent));
194
+ }
195
+ }
131
196
  }
132
197
  //# sourceMappingURL=initialize.js.map
@@ -1,4 +1,5 @@
1
1
  import { text } from '../../locales/index.js';
2
+ import { persistEffectiveness } from '../context/effectiveness/persistence.js';
2
3
  import { appendAuditTrailToAuditFile } from '../observability/audit-file.js';
3
4
  import { clearAuditContext, drainAuditDropStats, recordAuditEvent, } from '../observability/audit-trail.js';
4
5
  import { getLogger } from '../observability/logger.js';
@@ -64,6 +65,8 @@ export async function finalizeLoopRun(params) {
64
65
  if (finalResult)
65
66
  finalResult.auditPath = appendedPath;
66
67
  }
68
+ // Persist effectiveness data for cross-session learning
69
+ await persistEffectiveness(options.repoPath).catch(() => { });
67
70
  clearAuditContext();
68
71
  }
69
72
  return { latestAuditPath, finalResult };
@@ -1,5 +1,10 @@
1
+ import { restoreEffectiveness } from '../context/effectiveness/persistence.js';
2
+ import { getEffectivenessTracker } from '../context/effectiveness/tracker.js';
1
3
  import { buildFlowTransactionRunner, runFlowSession } from '../grizzco/engine/transaction/index.js';
2
4
  export async function executeLoopSession(params) {
5
+ // Restore effectiveness data from previous sessions
6
+ await restoreEffectiveness(params.options.repoPath).catch(() => { });
7
+ getEffectivenessTracker().startSession();
3
8
  const hostContext = await params.lifecycle.hostRunner.boot();
4
9
  const runner = buildFlowTransactionRunner({
5
10
  flowMode: hostContext.flowMode,
@@ -25,6 +25,7 @@ export class SalmonLoop {
25
25
  this.config = config;
26
26
  }
27
27
  async run(options) {
28
+ const runStartedAt = Date.now();
28
29
  const lifecycle = initializeLoopLifecycle(options);
29
30
  let latestAuditPath;
30
31
  let finalResult;
@@ -78,6 +79,9 @@ export class SalmonLoop {
78
79
  });
79
80
  latestAuditPath = finalized.latestAuditPath;
80
81
  finalResult = finalized.finalResult;
82
+ if (finalResult) {
83
+ finalResult.durationMs = Date.now() - runStartedAt;
84
+ }
81
85
  }
82
86
  }
83
87
  }
@@ -2,12 +2,14 @@ import { createHash } from 'crypto';
2
2
  import * as os from 'os';
3
3
  import path from 'path';
4
4
  import { mkdir, readdir, rename, rm, stat } from '../adapters/fs/node-fs.js';
5
+ import { getLogger } from '../observability/logger.js';
5
6
  async function pathExists(target) {
6
7
  try {
7
8
  await stat(target);
8
9
  return true;
9
10
  }
10
- catch {
11
+ catch (error) {
12
+ getLogger().debug(`[RuntimePaths] pathExists check failed for ${target}: ${error instanceof Error ? error.message : String(error)}`);
11
13
  return false;
12
14
  }
13
15
  }
@@ -54,7 +56,8 @@ export async function migrateLegacyRuntime(repoRoot) {
54
56
  await rename(legacyRoot, runtimeRoot);
55
57
  return;
56
58
  }
57
- catch {
59
+ catch (error) {
60
+ getLogger().debug(`[RuntimePaths] Failed to rename legacy runtime root: ${error instanceof Error ? error.message : String(error)}`);
58
61
  await mkdir(runtimeRoot, { recursive: true });
59
62
  }
60
63
  }
@@ -67,8 +70,8 @@ export async function migrateLegacyRuntime(repoRoot) {
67
70
  try {
68
71
  await rename(from, to);
69
72
  }
70
- catch {
71
- // Best-effort migration; keep legacy data if move fails.
73
+ catch (error) {
74
+ getLogger().debug(`[RuntimePaths] Failed to migrate legacy entry ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
72
75
  }
73
76
  }
74
77
  try {
@@ -77,8 +80,8 @@ export async function migrateLegacyRuntime(repoRoot) {
77
80
  await rm(legacyRoot, { recursive: true, force: true });
78
81
  }
79
82
  }
80
- catch {
81
- // Ignore cleanup failures; legacy folder can be removed manually.
83
+ catch (error) {
84
+ getLogger().debug(`[RuntimePaths] Failed to clean up legacy runtime folder: ${error instanceof Error ? error.message : String(error)}`);
82
85
  }
83
86
  }
84
87
  //# sourceMappingURL=paths.js.map
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { EventEmitter } from 'events';
3
+ import { getLogger } from '../observability/logger.js';
3
4
  import { getBunRuntime, normalizeSignal, toNodeReadableStream } from './bun-runtime.js';
4
5
  export function spawnInteractiveProcess(input) {
5
6
  const bun = getBunRuntime();
@@ -37,8 +38,8 @@ export function spawnInteractiveProcess(input) {
37
38
  try {
38
39
  subprocess.kill(signal);
39
40
  }
40
- catch {
41
- // Ignore kill errors.
41
+ catch (error) {
42
+ getLogger().debug(`[SpawnInteractive] Failed to kill bun subprocess: ${error instanceof Error ? error.message : String(error)}`);
42
43
  }
43
44
  },
44
45
  on: (event, listener) => {
@@ -75,8 +76,8 @@ export function spawnInteractiveProcess(input) {
75
76
  try {
76
77
  child.kill(signal);
77
78
  }
78
- catch {
79
- // Ignore kill errors.
79
+ catch (error) {
80
+ getLogger().debug(`[SpawnInteractive] Failed to kill child process: ${error instanceof Error ? error.message : String(error)}`);
80
81
  }
81
82
  },
82
83
  on: (event, listener) => {
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../observability/logger.js';
1
2
  const DEFAULT_REDACTION_MARK = '[REDACTED]';
2
3
  const DEFAULT_LIMITS = {
3
4
  maxDepth: 6,
@@ -34,8 +35,8 @@ export function setRedactionConfig(options) {
34
35
  try {
35
36
  compiledPatterns.push(new RegExp(pattern, 'g'));
36
37
  }
37
- catch {
38
- // Ignore invalid patterns to avoid breaking runtime.
38
+ catch (error) {
39
+ getLogger().debug(`[SecurityRedaction] Skipping invalid redaction pattern "${pattern}": ${error instanceof Error ? error.message : String(error)}`);
39
40
  }
40
41
  }
41
42
  }
@@ -1,6 +1,7 @@
1
1
  import { getModelRecommendedBudget } from '../../context/token/adaptive-budget.js';
2
2
  import { LlmError } from '../../llm/errors.js';
3
3
  import { getLogger } from '../../observability/logger.js';
4
+ import { isRecord } from '../../utils/serialize.js';
4
5
  import { refreshSessionSummary } from '../summary-sync.js';
5
6
  import { TokenTracker } from '../token-tracker.js';
6
7
  import { isCircuitBreakerTripped, onCompactionFailure, onCompactionSuccess } from './tracking.js';
@@ -11,8 +12,8 @@ function isContextOverflowLike(error) {
11
12
  }
12
13
  const message = error instanceof Error
13
14
  ? error.message
14
- : error && typeof error === 'object' && typeof error.message === 'string'
15
- ? String(error.message)
15
+ : isRecord(error) && typeof error.message === 'string'
16
+ ? error.message
16
17
  : '';
17
18
  if (!message)
18
19
  return false;
@@ -136,7 +137,7 @@ export async function autocompact(params) {
136
137
  performed: true,
137
138
  tracking: onCompactionSuccess(tracking),
138
139
  preTokens: totalTokens,
139
- trigger: trigger,
140
+ trigger,
140
141
  };
141
142
  }
142
143
  catch (error) {
@@ -1,6 +1,7 @@
1
1
  import { promisify } from 'util';
2
2
  import { gzip, gunzip } from 'zlib';
3
3
  import { FileAdapter } from '../adapters/fs/index.js';
4
+ import { getLogger } from '../observability/logger.js';
4
5
  import { normalizeSessionArtifactState } from './artifact-state.js';
5
6
  import { normalizeToolResultReplacementState, } from './replacement-state.js';
6
7
  export const DEFAULT_COMPRESSION_CONFIG = {
@@ -307,7 +308,8 @@ export class CompressedSessionStore {
307
308
  const data = await this.readFile(filepath);
308
309
  return await this.compressor.decompressFromBinary(data);
309
310
  }
310
- catch {
311
+ catch (error) {
312
+ getLogger().debug(`[SessionCompression] Failed to load compressed session ${filename}: ${error instanceof Error ? error.message : String(error)}`);
311
313
  return null;
312
314
  }
313
315
  }
@@ -132,7 +132,8 @@ export class ChatSessionManager {
132
132
  this.currentSession = parsed;
133
133
  return this.currentSession;
134
134
  }
135
- catch {
135
+ catch (error) {
136
+ getLogger().warn(`[SessionManager] Failed to load session ${targetId}: ${error instanceof Error ? error.message : String(error)}`);
136
137
  return null;
137
138
  }
138
139
  }
@@ -268,34 +269,11 @@ export class ChatSessionManager {
268
269
  * List all sessions (sorted by update time)
269
270
  */
270
271
  async listSessions() {
271
- const files = await this.fileAdapter.readdir(this.storageDir).catch(() => []);
272
- const jsonFiles = files.filter((f) => f.endsWith('.json'));
273
- const sessions = [];
274
- for (let i = 0; i < jsonFiles.length; i += ChatSessionManager.FILE_READ_CHUNK_SIZE) {
275
- const chunk = jsonFiles.slice(i, i + ChatSessionManager.FILE_READ_CHUNK_SIZE);
276
- const promises = chunk.map(async (file) => {
277
- try {
278
- const filePath = join(this.storageDir, file);
279
- const data = await this.fileAdapter.readFile(filePath);
280
- const session = JSON.parse(data);
281
- return {
282
- id: session.meta.id,
283
- name: session.meta.name,
284
- updatedAt: session.meta.updatedAt,
285
- };
286
- }
287
- catch (error) {
288
- getLogger().warn(`Failed to list session file ${file}: ${error}`);
289
- return null;
290
- }
291
- });
292
- const results = await Promise.all(promises);
293
- for (const result of results) {
294
- if (result) {
295
- sessions.push(result);
296
- }
297
- }
298
- }
272
+ const sessions = await this.scanSessionFiles((session) => ({
273
+ id: session.meta.id,
274
+ name: session.meta.name,
275
+ updatedAt: session.meta.updatedAt,
276
+ }));
299
277
  return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
300
278
  }
301
279
  /**
@@ -344,9 +322,22 @@ export class ChatSessionManager {
344
322
  * Load all sessions from storage
345
323
  */
346
324
  async loadAllSessions() {
325
+ return this.scanSessionFiles((session) => {
326
+ session.meta.chatState = normalizeChatState(session.meta.chatState);
327
+ session.meta.artifactState = normalizeSessionArtifactState(session.meta.artifactState);
328
+ session.meta.replacementState = normalizeToolResultReplacementState(session.meta.replacementState);
329
+ return session;
330
+ });
331
+ }
332
+ /**
333
+ * Shared scan-and-parse for session files.
334
+ * Reads JSON files from storageDir in chunks, parses each, and maps via the provided callback.
335
+ * Silently skips files that fail to parse.
336
+ */
337
+ async scanSessionFiles(mapFn) {
347
338
  const files = await this.fileAdapter.readdir(this.storageDir).catch(() => []);
348
339
  const jsonFiles = files.filter((f) => f.endsWith('.json'));
349
- const sessions = [];
340
+ const results = [];
350
341
  for (let i = 0; i < jsonFiles.length; i += ChatSessionManager.FILE_READ_CHUNK_SIZE) {
351
342
  const chunk = jsonFiles.slice(i, i + ChatSessionManager.FILE_READ_CHUNK_SIZE);
352
343
  const promises = chunk.map(async (file) => {
@@ -354,24 +345,21 @@ export class ChatSessionManager {
354
345
  const filePath = join(this.storageDir, file);
355
346
  const data = await this.fileAdapter.readFile(filePath);
356
347
  const session = JSON.parse(data);
357
- session.meta.chatState = normalizeChatState(session.meta.chatState);
358
- session.meta.artifactState = normalizeSessionArtifactState(session.meta.artifactState);
359
- session.meta.replacementState = normalizeToolResultReplacementState(session.meta.replacementState);
360
- return session;
348
+ return mapFn(session);
361
349
  }
362
350
  catch (error) {
363
351
  getLogger().warn(`Failed to load session file ${file}: ${error}`);
364
352
  return null;
365
353
  }
366
354
  });
367
- const results = await Promise.all(promises);
368
- for (const result of results) {
355
+ const chunkResults = await Promise.all(promises);
356
+ for (const result of chunkResults) {
369
357
  if (result) {
370
- sessions.push(result);
358
+ results.push(result);
371
359
  }
372
360
  }
373
361
  }
374
- return sessions;
362
+ return results;
375
363
  }
376
364
  /**
377
365
  * Delete a session file