salmon-loop 0.4.1 → 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 (146) hide show
  1. package/dist/cli/authorization/provider.js +2 -10
  2. package/dist/cli/commands/config.js +2 -2
  3. package/dist/cli/commands/mode.js +2 -2
  4. package/dist/cli/commands/run/handler.js +3 -1
  5. package/dist/cli/commands/run/loop-params.js +1 -0
  6. package/dist/cli/commands/run/runtime-options.js +3 -1
  7. package/dist/cli/config.js +0 -8
  8. package/dist/cli/locales/en.js +2 -2
  9. package/dist/cli/reporters/standard.js +10 -0
  10. package/dist/core/adapters/fs/file-adapter.js +3 -1
  11. package/dist/core/adapters/git/git-adapter.js +6 -3
  12. package/dist/core/adapters/git/git-runner.js +5 -2
  13. package/dist/core/adapters/git/lock-manager.js +7 -4
  14. package/dist/core/checkpoint-domain/manifest-store.js +21 -13
  15. package/dist/core/checkpoint-domain/service.js +3 -1
  16. package/dist/core/config/limits.js +1 -1
  17. package/dist/core/config/model-pricing.js +61 -0
  18. package/dist/core/context/ast/skeleton-extractor.js +225 -0
  19. package/dist/core/context/ast/source-outline.js +24 -1
  20. package/dist/core/context/budget/dynamic-adjuster.js +20 -5
  21. package/dist/core/context/builder.js +7 -3
  22. package/dist/core/context/cache/store-factory.js +3 -1
  23. package/dist/core/context/dependencies.js +2 -1
  24. package/dist/core/context/effectiveness/persistence.js +50 -0
  25. package/dist/core/context/effectiveness/tracker.js +24 -0
  26. package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
  27. package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
  28. package/dist/core/context/gatherers/ast-gatherer.js +30 -28
  29. package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
  30. package/dist/core/context/gatherers/knowledge-gatherer.js +18 -2
  31. package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
  32. package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
  33. package/dist/core/context/service.js +4 -2
  34. package/dist/core/context/steps/context-gather.js +14 -3
  35. package/dist/core/context/steps/context-targets.js +1 -0
  36. package/dist/core/context/targeting/target-resolver.js +29 -11
  37. package/dist/core/context/token/cache.js +5 -2
  38. package/dist/core/context/truncation/strategies/json.js +5 -2
  39. package/dist/core/context/truncation/type-detector.js +3 -1
  40. package/dist/core/extensions/paths.js +2 -2
  41. package/dist/core/facades/cli-authorization-provider.js +1 -0
  42. package/dist/core/feedback/parsers.js +290 -1
  43. package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
  44. package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
  45. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
  46. package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
  47. package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
  48. package/dist/core/grizzco/execution/RejectionManager.js +7 -5
  49. package/dist/core/grizzco/runtime/apply-back-runtime.js +3 -1
  50. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
  51. package/dist/core/grizzco/steps/autopilot.js +21 -32
  52. package/dist/core/grizzco/steps/explore.js +5 -2
  53. package/dist/core/grizzco/steps/generateReview.js +3 -1
  54. package/dist/core/grizzco/steps/research.js +3 -1
  55. package/dist/core/grizzco/steps/verify.js +7 -1
  56. package/dist/core/grizzco/validation/AstValidationService.js +3 -1
  57. package/dist/core/history/input-history.js +3 -1
  58. package/dist/core/intent/chat-intent.js +3 -1
  59. package/dist/core/llm/ai-sdk/message-mapper.js +13 -8
  60. package/dist/core/llm/ai-sdk/request-params.js +1 -3
  61. package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
  62. package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
  63. package/dist/core/llm/errors.js +5 -4
  64. package/dist/core/llm/retry-utils.js +8 -2
  65. package/dist/core/llm/stream-utils.js +5 -3
  66. package/dist/core/llm/sub-agent-factory.js +3 -0
  67. package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
  68. package/dist/core/mcp/catalog/discovery.js +3 -1
  69. package/dist/core/mcp/client/connection-manager.js +4 -2
  70. package/dist/core/mcp/client/transport-factory.js +7 -3
  71. package/dist/core/observability/audit-file.js +2 -1
  72. package/dist/core/observability/audit-trail.js +3 -1
  73. package/dist/core/observability/logger.js +2 -1
  74. package/dist/core/observability/monitor.js +24 -0
  75. package/dist/core/observability/run-outcome-reporter.js +1 -0
  76. package/dist/core/permission-gate/default-gate.js +5 -8
  77. package/dist/core/plan/storage.js +7 -4
  78. package/dist/core/plugin/loader.js +3 -1
  79. package/dist/core/prompts/registry.js +1 -1
  80. package/dist/core/prompts/runtime.js +3 -1
  81. package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
  82. package/dist/core/protocols/a2a/sdk/executor.js +3 -1
  83. package/dist/core/protocols/a2a/sdk/server.js +3 -1
  84. package/dist/core/protocols/acp/acp-command-runner.js +7 -6
  85. package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
  86. package/dist/core/protocols/acp/formal-agent.js +3 -2
  87. package/dist/core/protocols/acp/permission-provider.js +3 -2
  88. package/dist/core/reflection/engine.js +114 -14
  89. package/dist/core/runtime/batch-runner.js +81 -0
  90. package/dist/core/runtime/initialize.js +2 -1
  91. package/dist/core/runtime/loop-finalize.js +3 -0
  92. package/dist/core/runtime/loop-session-runner.js +5 -0
  93. package/dist/core/runtime/loop.js +4 -0
  94. package/dist/core/runtime/paths.js +9 -6
  95. package/dist/core/runtime/spawn-interactive.js +5 -4
  96. package/dist/core/security/redaction.js +3 -2
  97. package/dist/core/session/compression.js +3 -1
  98. package/dist/core/session/manager.js +2 -1
  99. package/dist/core/session/pruning-strategy.js +2 -1
  100. package/dist/core/session/token-tracker.js +11 -4
  101. package/dist/core/skills/permissions.js +2 -2
  102. package/dist/core/strata/checkpoint/manager.js +16 -10
  103. package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
  104. package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
  105. package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
  106. package/dist/core/strata/interaction/file-system-provider.js +2 -1
  107. package/dist/core/strata/layers/file-state-resolver.js +9 -7
  108. package/dist/core/strata/layers/immutable-git-layer.js +3 -1
  109. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
  110. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
  111. package/dist/core/strata/layers/worktree.js +2 -1
  112. package/dist/core/strata/runtime/environment.js +2 -1
  113. package/dist/core/strata/runtime/synchronizer.js +18 -17
  114. package/dist/core/structured-output/json-extract.js +3 -1
  115. package/dist/core/sub-agent/artifacts/store.js +2 -1
  116. package/dist/core/sub-agent/core/manager.js +24 -1
  117. package/dist/core/sub-agent/registry-defaults.js +2 -2
  118. package/dist/core/sub-agent/summary.js +96 -0
  119. package/dist/core/sub-agent/tools/task-spawn.js +7 -4
  120. package/dist/core/target-runtime/profile.js +3 -1
  121. package/dist/core/tools/audit.js +3 -2
  122. package/dist/core/tools/budget.js +3 -1
  123. package/dist/core/tools/builtin/ast.js +144 -0
  124. package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
  125. package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
  126. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
  127. package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
  128. package/dist/core/tools/builtin/fs.js +76 -1
  129. package/dist/core/tools/builtin/git.js +242 -0
  130. package/dist/core/tools/builtin/glob.js +79 -0
  131. package/dist/core/tools/builtin/index.js +12 -4
  132. package/dist/core/tools/builtin/knowledge.js +146 -4
  133. package/dist/core/tools/builtin/proposal.js +3 -1
  134. package/dist/core/tools/builtin/verify.js +35 -3
  135. package/dist/core/tools/permissions/permission-rules.js +3 -1
  136. package/dist/core/tools/router.js +88 -5
  137. package/dist/core/tools/session.js +10 -5
  138. package/dist/core/types/batch.js +2 -0
  139. package/dist/core/utils/sanitizer.js +5 -2
  140. package/dist/core/utils/serialize.js +5 -2
  141. package/dist/core/verification/detect-runner.js +86 -0
  142. package/dist/core/verification/runner.js +76 -0
  143. package/dist/core/version.js +3 -1
  144. package/dist/languages/python/index.js +154 -0
  145. package/dist/locales/en.js +6 -0
  146. package/package.json +2 -1
@@ -81,7 +81,8 @@ export class StrataFileSystemProvider {
81
81
  const buffer = await fs.readFile(safePath);
82
82
  return this.guardian.inspect(buffer).isBinary;
83
83
  }
84
- catch {
84
+ catch (error) {
85
+ getLogger().debug(`[StrataFileSystem] Binary detection failed for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
85
86
  return false;
86
87
  }
87
88
  }
@@ -3,6 +3,7 @@ import * as path from 'path';
3
3
  import * as fs from '../../adapters/fs/node-fs.js';
4
4
  import { LIMITS } from '../../config/limits.js';
5
5
  import { FileStatus } from '../../grizzco/domain/grizzco-types.js';
6
+ import { getLogger } from '../../observability/logger.js';
6
7
  /**
7
8
  * FileStateResolver
8
9
  * Responsibilities: Accurately scan workspace and index state, implementing the data foundation for Zero Index Access.
@@ -43,9 +44,8 @@ export class FileStateResolver {
43
44
  state.stagedContent = await this.git.show(':0', normalizedPath);
44
45
  state.workingContent = await fs.readFile(absolutePath);
45
46
  }
46
- catch {
47
- // Fallback: If we can't capture content, we might be in a race condition.
48
- // But proceed with what we have; strategy layer will handle missing content if needed.
47
+ catch (error) {
48
+ getLogger().debug(`[FileStateResolver] Failed to capture MM content for ${normalizedPath}: ${error instanceof Error ? error.message : String(error)}`);
49
49
  }
50
50
  }
51
51
  return state;
@@ -124,8 +124,8 @@ export class FileStateResolver {
124
124
  await fd.close();
125
125
  }
126
126
  }
127
- catch {
128
- // If file doesn't exist or can't be read, assume non-binary (safe default for new files)
127
+ catch (error) {
128
+ getLogger().debug(`[FileStateResolver] Binary detection failed for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
129
129
  return false;
130
130
  }
131
131
  }
@@ -137,7 +137,8 @@ export class FileStateResolver {
137
137
  const stats = await fs.lstat(filePath);
138
138
  return stats.isSymbolicLink();
139
139
  }
140
- catch {
140
+ catch (error) {
141
+ getLogger().debug(`[FileStateResolver] Symlink detection failed for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
141
142
  return false;
142
143
  }
143
144
  }
@@ -149,7 +150,8 @@ export class FileStateResolver {
149
150
  const stats = await fs.stat(filePath);
150
151
  return stats.size;
151
152
  }
152
- catch {
153
+ catch (error) {
154
+ getLogger().debug(`[FileStateResolver] File size check failed for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
153
155
  return 0;
154
156
  }
155
157
  }
@@ -4,6 +4,7 @@
4
4
  * Wraps existing CheckpointManager to provide StrataSystem interface
5
5
  * for L1 Git snapshot and worktree operations.
6
6
  */
7
+ import { getLogger } from '../../observability/logger.js';
7
8
  import { CheckpointManager } from '../checkpoint/manager.js';
8
9
  /**
9
10
  * ImmutableGitLayer Implementation
@@ -34,7 +35,8 @@ export class ImmutableGitLayerImpl {
34
35
  const content = await this.checkpointManager.getSnapshotFileContent(process.cwd(), 'HEAD', path);
35
36
  return Buffer.from(content);
36
37
  }
37
- catch {
38
+ catch (error) {
39
+ getLogger().debug(`[ImmutableGitLayer] Failed to get file from snapshot: ${error instanceof Error ? error.message : String(error)}`);
38
40
  return null;
39
41
  }
40
42
  }
@@ -129,7 +129,8 @@ async function removeLockByToken(lockPath, expectedToken) {
129
129
  try {
130
130
  await rename(lockPath, swapPath);
131
131
  }
132
- catch {
132
+ catch (error) {
133
+ getLogger().debug(`[ReadonlyLock] Failed to rename lock file for atomic swap: ${error instanceof Error ? error.message : String(error)}`);
133
134
  return false;
134
135
  }
135
136
  try {
@@ -141,18 +142,19 @@ async function removeLockByToken(lockPath, expectedToken) {
141
142
  try {
142
143
  await rename(swapPath, lockPath);
143
144
  }
144
- catch {
145
- // Best-effort rollback when concurrent updates happen.
145
+ catch (rollbackError) {
146
+ getLogger().debug(`[ReadonlyLock] Failed to rollback lock swap (token mismatch): ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
146
147
  await unlink(swapPath).catch(() => null);
147
148
  }
148
149
  return false;
149
150
  }
150
- catch {
151
+ catch (error) {
152
+ getLogger().debug(`[ReadonlyLock] Failed to verify lock token after swap: ${error instanceof Error ? error.message : String(error)}`);
151
153
  try {
152
154
  await rename(swapPath, lockPath);
153
155
  }
154
- catch {
155
- // Best-effort rollback when concurrent updates happen.
156
+ catch (rollbackError) {
157
+ getLogger().debug(`[ReadonlyLock] Failed to rollback lock swap (verification error): ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
156
158
  await unlink(swapPath).catch(() => null);
157
159
  }
158
160
  return false;
@@ -36,7 +36,8 @@ async function pointsToExpectedDependency(sourcePath, targetPath) {
36
36
  ]);
37
37
  return arePathsEquivalent(resolvedSourcePath, resolvedTargetPath);
38
38
  }
39
- catch {
39
+ catch (error) {
40
+ getLogger().debug(`[ShadowDriver] Failed to verify dependency path equivalence: ${error instanceof Error ? error.message : String(error)}`);
40
41
  return false;
41
42
  }
42
43
  }
@@ -48,7 +48,8 @@ async function tryRealpath(value) {
48
48
  try {
49
49
  return await realpath(value);
50
50
  }
51
- catch {
51
+ catch (error) {
52
+ getLogger().debug(`[Worktree] Failed to resolve realpath for ${value}: ${error instanceof Error ? error.message : String(error)}`);
52
53
  return null;
53
54
  }
54
55
  }
@@ -20,7 +20,8 @@ async function pathExists(target) {
20
20
  await stat(target);
21
21
  return true;
22
22
  }
23
- catch {
23
+ catch (error) {
24
+ getLogger().debug(`[RuntimeEnvironment] pathExists check failed for ${target}: ${error instanceof Error ? error.message : String(error)}`);
24
25
  return false;
25
26
  }
26
27
  }
@@ -57,7 +57,8 @@ export class WorkspaceSynchronizer {
57
57
  try {
58
58
  return await realpath(value);
59
59
  }
60
- catch {
60
+ catch (error) {
61
+ getLogger().debug(`[Synchronizer] realpath failed for ${value}: ${error instanceof Error ? error.message : String(error)}`);
61
62
  return null;
62
63
  }
63
64
  }
@@ -88,7 +89,8 @@ export class WorkspaceSynchronizer {
88
89
  try {
89
90
  entries = (await readdir(tempRoot, { withFileTypes: true }));
90
91
  }
91
- catch {
92
+ catch (error) {
93
+ getLogger().debug(`[Synchronizer] Failed to read tmpdir for backup pruning: ${error instanceof Error ? error.message : String(error)}`);
92
94
  return;
93
95
  }
94
96
  const cutoffTs = Date.now() - retentionMs;
@@ -101,8 +103,8 @@ export class WorkspaceSynchronizer {
101
103
  await rm(backupPath, { recursive: true, force: true });
102
104
  }
103
105
  }
104
- catch {
105
- // Ignore stale cleanup failures; cleanup is best-effort.
106
+ catch (error) {
107
+ getLogger().debug(`[Synchronizer] Failed to prune stale backup ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
106
108
  }
107
109
  }));
108
110
  }
@@ -160,8 +162,8 @@ export class WorkspaceSynchronizer {
160
162
  symlinkedRoots.add(normalizedCandidate);
161
163
  }
162
164
  }
163
- catch {
164
- // Ignore non-existent dependency roots.
165
+ catch (error) {
166
+ getLogger().debug(`[Synchronizer] Dependency root probe failed for ${normalizedCandidate}: ${error instanceof Error ? error.message : String(error)}`);
165
167
  }
166
168
  }
167
169
  return symlinkedRoots;
@@ -365,10 +367,8 @@ export class WorkspaceSynchronizer {
365
367
  try {
366
368
  oursContent = await readFile(mainAbsPath);
367
369
  }
368
- catch {
369
- // If file missing in main but modify in shadow -> conflict or re-create?
370
- // Since we filtered for 'M', it implies it existed in Base. If missing in Main, User deleted it.
371
- // Merge Modified vs Deleted -> Conflict.
370
+ catch (error) {
371
+ getLogger().debug(`[ExplicitMerge] Ours file missing in main workspace, treating as conflict: ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
372
372
  conflicts.push(relativePath);
373
373
  continue;
374
374
  }
@@ -546,7 +546,8 @@ export class WorkspaceSynchronizer {
546
546
  const content = await readFile(path.join(mainRepoPath, ...file.split('/')));
547
547
  entries.push(`${file}:${hashContent(content)}`);
548
548
  }
549
- catch {
549
+ catch (error) {
550
+ getLogger().debug(`[Synchronizer] Fingerprint read failed for untracked file ${file}: ${error instanceof Error ? error.message : String(error)}`);
550
551
  entries.push(`${file}:missing`);
551
552
  }
552
553
  }
@@ -595,8 +596,8 @@ export class WorkspaceSynchronizer {
595
596
  try {
596
597
  await copyFile(src, dst);
597
598
  }
598
- catch {
599
- // Ignore backup failure for deleted files
599
+ catch (error) {
600
+ getLogger().debug(`[Synchronizer] Failed to backup dirty file ${file}: ${error instanceof Error ? error.message : String(error)}`);
600
601
  }
601
602
  }
602
603
  }
@@ -694,8 +695,8 @@ export class WorkspaceSynchronizer {
694
695
  current.working !== originalFingerprint.working ||
695
696
  current.untracked !== originalFingerprint.untracked;
696
697
  }
697
- catch {
698
- // If fingerprinting fails, assume changed to be safe.
698
+ catch (error) {
699
+ getLogger().debug(`[Synchronizer] Workspace fingerprint comparison failed, assuming changed: ${error instanceof Error ? error.message : String(error)}`);
699
700
  workspaceChanged = true;
700
701
  }
701
702
  }
@@ -752,8 +753,8 @@ export class WorkspaceSynchronizer {
752
753
  });
753
754
  await copyFile(path.join(untrackedDir, ...file.split('/')), path.join(mainRepoPath, ...file.split('/')));
754
755
  }
755
- catch {
756
- // Ignore restore errors for untracked files
756
+ catch (error) {
757
+ getLogger().debug(`[Synchronizer] Failed to restore untracked file ${file}: ${error instanceof Error ? error.message : String(error)}`);
757
758
  }
758
759
  }
759
760
  }
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../observability/logger.js';
1
2
  function findFirstJsonStart(text) {
2
3
  const obj = text.indexOf('{');
3
4
  const arr = text.indexOf('[');
@@ -63,7 +64,8 @@ export function extractFirstJsonValueFromText(text) {
63
64
  try {
64
65
  return JSON.parse(slice);
65
66
  }
66
- catch {
67
+ catch (error) {
68
+ getLogger().debug(`[StructuredOutput] Failed to parse extracted JSON: ${error instanceof Error ? error.message : String(error)}`);
67
69
  return null;
68
70
  }
69
71
  }
@@ -125,8 +125,9 @@ export class ArtifactStore {
125
125
  getLogger().debug(`[ArtifactStore] GC removed ${result.removedFiles} files (${result.removedBytes} bytes)`);
126
126
  }
127
127
  }
128
- catch {
128
+ catch (error) {
129
129
  // Best-effort only; never fail the caller.
130
+ getLogger().debug(`[ArtifactStore] GC failed: ${error instanceof Error ? error.message : String(error)}`);
130
131
  }
131
132
  }
132
133
  static ensureGcLoop() {
@@ -15,6 +15,7 @@ import { cloneSubAgentContextSnapshot } from '../context-snapshot.js';
15
15
  import { isReadOnlySubAgentContext, resolveSubAgentDryRun } from '../dispatch-policy.js';
16
16
  import { validateSharedPrefixConsistency } from '../prefix-consistency.js';
17
17
  import { getSubAgentRegistry } from '../registry.js';
18
+ import { generateSubAgentSummary, formatSubAgentSummary } from '../summary.js';
18
19
  import { SmallfryLoop } from './loop.js';
19
20
  /**
20
21
  * SubAgentManager coordinates the lifecycle of Smallfrys.
@@ -24,6 +25,7 @@ export class SubAgentManager {
24
25
  ctx;
25
26
  controller;
26
27
  activeAgents = new Map();
28
+ completedResults = [];
27
29
  deps;
28
30
  constructor(ctx, controller, deps) {
29
31
  this.ctx = ctx;
@@ -98,6 +100,24 @@ export class SubAgentManager {
98
100
  });
99
101
  });
100
102
  }
103
+ /**
104
+ * Get a summary of all completed sub-agent results.
105
+ * Includes conflict detection across patches.
106
+ */
107
+ getSummary() {
108
+ if (this.completedResults.length === 0)
109
+ return null;
110
+ return generateSubAgentSummary(this.completedResults);
111
+ }
112
+ /**
113
+ * Get a formatted summary string for LLM context injection.
114
+ */
115
+ getFormattedSummary() {
116
+ const summary = this.getSummary();
117
+ if (!summary)
118
+ return null;
119
+ return formatSubAgentSummary(summary);
120
+ }
101
121
  normalizeRequest(request) {
102
122
  // Fork mode: no prefix consistency validation needed (it's a clone, not a shared session)
103
123
  if (request.session_target === 'fork')
@@ -153,6 +173,8 @@ export class SubAgentManager {
153
173
  taskId,
154
174
  state: result.success ? 'completed' : 'failed',
155
175
  });
176
+ // Track for summary generation
177
+ this.completedResults.push({ agentId, result });
156
178
  // Notify completion listener (for background auto-notify)
157
179
  this.deps.onSubAgentComplete?.(agentId, result);
158
180
  })
@@ -540,7 +562,8 @@ export class SubAgentManager {
540
562
  fileExt: 'json',
541
563
  });
542
564
  }
543
- catch {
565
+ catch (error) {
566
+ getLogger().debug(`[SubAgentManager] Failed to persist audit artifact: ${error instanceof Error ? error.message : String(error)}`);
544
567
  return undefined;
545
568
  }
546
569
  }
@@ -12,7 +12,7 @@ const DEFAULT_SUB_AGENT_PROFILES = [
12
12
  maxAttempts: 3,
13
13
  timeoutMs: 60_000,
14
14
  maxTurns: 20,
15
- model: 'haiku',
15
+ model: 'inherit',
16
16
  },
17
17
  {
18
18
  id: 'surgeon',
@@ -42,7 +42,7 @@ const DEFAULT_SUB_AGENT_PROFILES = [
42
42
  maxAttempts: 2,
43
43
  timeoutMs: 60_000,
44
44
  maxTurns: 15,
45
- model: 'haiku',
45
+ model: 'inherit',
46
46
  },
47
47
  {
48
48
  id: 'cleaner',
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Sub-agent result synthesis.
3
+ *
4
+ * Detects file-level conflicts between sub-agent patches
5
+ * and generates structured summaries for parent injection.
6
+ */
7
+ /**
8
+ * Extract file paths touched by a unified diff.
9
+ * Parses diff headers (lines starting with "--- a/" and "+++ b/").
10
+ */
11
+ export function extractDiffFiles(patch) {
12
+ const files = new Set();
13
+ const lines = patch.split('\n');
14
+ for (const line of lines) {
15
+ if (line.startsWith('+++ b/')) {
16
+ files.add(line.slice(6));
17
+ }
18
+ }
19
+ return Array.from(files);
20
+ }
21
+ /**
22
+ * Detect file-level conflicts across multiple sub-agent results.
23
+ * Two agents conflict if they both modify the same file.
24
+ */
25
+ export function detectConflicts(results) {
26
+ const fileToAgents = new Map();
27
+ for (const { agentId, result } of results) {
28
+ if (!result.finalPatch || typeof result.finalPatch !== 'string')
29
+ continue;
30
+ const files = extractDiffFiles(result.finalPatch);
31
+ for (const file of files) {
32
+ const agents = fileToAgents.get(file) ?? [];
33
+ agents.push(agentId);
34
+ fileToAgents.set(file, agents);
35
+ }
36
+ }
37
+ const conflicts = [];
38
+ for (const [file, agents] of fileToAgents) {
39
+ if (agents.length > 1) {
40
+ conflicts.push({ file, agents });
41
+ }
42
+ }
43
+ return conflicts;
44
+ }
45
+ /**
46
+ * Generate a structured summary of all sub-agent results.
47
+ */
48
+ export function generateSubAgentSummary(results) {
49
+ const conflicts = detectConflicts(results);
50
+ const changedFiles = new Set();
51
+ let succeeded = 0;
52
+ let failed = 0;
53
+ let totalTokens = 0;
54
+ for (const { result } of results) {
55
+ if (result.success)
56
+ succeeded++;
57
+ else
58
+ failed++;
59
+ totalTokens += result.tokenUsage ?? 0;
60
+ if (result.finalPatch && typeof result.finalPatch === 'string') {
61
+ for (const file of extractDiffFiles(result.finalPatch)) {
62
+ changedFiles.add(file);
63
+ }
64
+ }
65
+ }
66
+ return {
67
+ totalAgents: results.length,
68
+ succeeded,
69
+ failed,
70
+ conflicts,
71
+ totalTokens,
72
+ changedFiles: Array.from(changedFiles),
73
+ };
74
+ }
75
+ /**
76
+ * Format a sub-agent summary as a human-readable string for LLM context injection.
77
+ */
78
+ export function formatSubAgentSummary(summary) {
79
+ const lines = [
80
+ `## Sub-Agent Summary`,
81
+ `- Total: ${summary.totalAgents} | Succeeded: ${summary.succeeded} | Failed: ${summary.failed}`,
82
+ `- Total tokens: ${summary.totalTokens.toLocaleString()}`,
83
+ `- Changed files: ${summary.changedFiles.length}`,
84
+ ];
85
+ if (summary.changedFiles.length > 0) {
86
+ lines.push(`- Files: ${summary.changedFiles.join(', ')}`);
87
+ }
88
+ if (summary.conflicts.length > 0) {
89
+ lines.push(`\n### Conflicts Detected`);
90
+ for (const conflict of summary.conflicts) {
91
+ lines.push(`- \`${conflict.file}\`: modified by ${conflict.agents.join(', ')}`);
92
+ }
93
+ }
94
+ return lines.join('\n');
95
+ }
96
+ //# sourceMappingURL=summary.js.map
@@ -123,10 +123,13 @@ export const subAgentTaskSpec = {
123
123
  ],
124
124
  executor: async (input, ctx) => {
125
125
  const parsed = SubAgentRequestSchema.parse(input);
126
- const manager = new SubAgentManager(ctx, ctx.subAgentController ?? createSubAgentController(), {
127
- llmFactory: ctx.llmFactory,
128
- onSubAgentComplete: ctx.onSubAgentComplete,
129
- });
126
+ const controller = ctx.subAgentController ?? createSubAgentController();
127
+ const manager = ctx.subAgentManagerFactory
128
+ ? ctx.subAgentManagerFactory(ctx, controller)
129
+ : new SubAgentManager(ctx, controller, {
130
+ llmFactory: ctx.llmFactory,
131
+ onSubAgentComplete: ctx.onSubAgentComplete,
132
+ });
130
133
  const request = normalizeDispatchRequest(parsed, ctx);
131
134
  try {
132
135
  return await manager.execute(request);
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from '../adapters/fs/node-fs.js';
2
2
  import { readFile } from '../adapters/fs/node-fs.js';
3
+ import { getLogger } from '../observability/logger.js';
3
4
  import { ensureInSandbox, safeJoin } from '../utils/path.js';
4
5
  const LOCKFILE_HINTS = [
5
6
  { file: 'bun.lock', manager: 'bun' },
@@ -59,7 +60,8 @@ export async function detectNodeRuntimeProfile(repoPath) {
59
60
  try {
60
61
  parsed = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
61
62
  }
62
- catch {
63
+ catch (error) {
64
+ getLogger().debug(`[TargetRuntime] Failed to parse package.json: ${error instanceof Error ? error.message : String(error)}`);
63
65
  return undefined;
64
66
  }
65
67
  const { packageManager, source } = detectPackageManager(repoPath, parsed);
@@ -1,5 +1,5 @@
1
1
  import { text } from '../../locales/index.js';
2
- import { tryGetLogger } from '../observability/logger.js';
2
+ import { getLogger, tryGetLogger } from '../observability/logger.js';
3
3
  import { Phase } from '../types/runtime.js';
4
4
  import { sanitizeErrorMessage } from '../utils/sanitizer.js';
5
5
  export class ToolAuditLogger {
@@ -112,7 +112,8 @@ export class ToolAuditLogger {
112
112
  const str = JSON.stringify(data);
113
113
  return str.length > 200 ? str.substring(0, 200) + '...' : str;
114
114
  }
115
- catch {
115
+ catch (error) {
116
+ getLogger().debug(`[ToolAudit] Failed to summarize data: ${error instanceof Error ? error.message : String(error)}`);
116
117
  return '[Circular/Unserializable]';
117
118
  }
118
119
  }
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../observability/logger.js';
1
2
  /**
2
3
  * Default budget limits to ensure system stability.
3
4
  */
@@ -103,7 +104,8 @@ export class BudgetGuard {
103
104
  // This is expensive for large objects, but safe for checking limits
104
105
  return JSON.stringify(obj).length;
105
106
  }
106
- catch {
107
+ catch (error) {
108
+ getLogger().debug(`[Budget] Failed to estimate size: ${error instanceof Error ? error.message : String(error)}`);
107
109
  return 0; // Circular structure or otherwise un-stringifiable
108
110
  }
109
111
  }