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.
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/run/handler.js +3 -1
- package/dist/cli/commands/run/loop-params.js +1 -0
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +10 -0
- package/dist/core/adapters/fs/file-adapter.js +3 -1
- package/dist/core/adapters/git/git-adapter.js +6 -3
- package/dist/core/adapters/git/git-runner.js +5 -2
- package/dist/core/adapters/git/lock-manager.js +7 -4
- package/dist/core/checkpoint-domain/manifest-store.js +21 -13
- package/dist/core/checkpoint-domain/service.js +3 -1
- package/dist/core/config/limits.js +1 -1
- package/dist/core/config/model-pricing.js +61 -0
- package/dist/core/context/ast/skeleton-extractor.js +225 -0
- package/dist/core/context/ast/source-outline.js +24 -1
- package/dist/core/context/budget/dynamic-adjuster.js +20 -5
- package/dist/core/context/builder.js +7 -3
- package/dist/core/context/cache/store-factory.js +3 -1
- package/dist/core/context/dependencies.js +2 -1
- package/dist/core/context/effectiveness/persistence.js +50 -0
- package/dist/core/context/effectiveness/tracker.js +24 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
- package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
- package/dist/core/context/gatherers/ast-gatherer.js +30 -28
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +18 -2
- package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
- package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
- package/dist/core/context/service.js +4 -2
- package/dist/core/context/steps/context-gather.js +14 -3
- package/dist/core/context/steps/context-targets.js +1 -0
- package/dist/core/context/targeting/target-resolver.js +29 -11
- package/dist/core/context/token/cache.js +5 -2
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/paths.js +2 -2
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
- package/dist/core/grizzco/execution/RejectionManager.js +7 -5
- package/dist/core/grizzco/runtime/apply-back-runtime.js +3 -1
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/explore.js +5 -2
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/history/input-history.js +3 -1
- package/dist/core/intent/chat-intent.js +3 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +13 -8
- package/dist/core/llm/ai-sdk/request-params.js +1 -3
- package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/errors.js +5 -4
- package/dist/core/llm/retry-utils.js +8 -2
- package/dist/core/llm/stream-utils.js +5 -3
- package/dist/core/llm/sub-agent-factory.js +3 -0
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +4 -2
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/logger.js +2 -1
- package/dist/core/observability/monitor.js +24 -0
- package/dist/core/observability/run-outcome-reporter.js +1 -0
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +3 -1
- package/dist/core/prompts/registry.js +1 -1
- package/dist/core/prompts/runtime.js +3 -1
- package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
- package/dist/core/protocols/a2a/sdk/executor.js +3 -1
- package/dist/core/protocols/a2a/sdk/server.js +3 -1
- package/dist/core/protocols/acp/acp-command-runner.js +7 -6
- package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
- package/dist/core/protocols/acp/formal-agent.js +3 -2
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +2 -1
- package/dist/core/runtime/loop-finalize.js +3 -0
- package/dist/core/runtime/loop-session-runner.js +5 -0
- package/dist/core/runtime/loop.js +4 -0
- package/dist/core/runtime/paths.js +9 -6
- package/dist/core/runtime/spawn-interactive.js +5 -4
- package/dist/core/security/redaction.js +3 -2
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +2 -1
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +11 -4
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/strata/checkpoint/manager.js +16 -10
- package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
- package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
- package/dist/core/strata/interaction/file-system-provider.js +2 -1
- package/dist/core/strata/layers/file-state-resolver.js +9 -7
- package/dist/core/strata/layers/immutable-git-layer.js +3 -1
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
- package/dist/core/strata/layers/worktree.js +2 -1
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +18 -17
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/core/manager.js +24 -1
- package/dist/core/sub-agent/registry-defaults.js +2 -2
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/tools/task-spawn.js +7 -4
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +3 -1
- package/dist/core/tools/builtin/ast.js +144 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
- package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
- package/dist/core/tools/builtin/fs.js +76 -1
- package/dist/core/tools/builtin/git.js +242 -0
- package/dist/core/tools/builtin/glob.js +79 -0
- package/dist/core/tools/builtin/index.js +12 -4
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +3 -1
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/permissions/permission-rules.js +3 -1
- package/dist/core/tools/router.js +88 -5
- package/dist/core/tools/session.js +10 -5
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +5 -2
- package/dist/core/verification/detect-runner.js +86 -0
- package/dist/core/verification/runner.js +76 -0
- package/dist/core/version.js +3 -1
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +6 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
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
|
|
127
|
-
|
|
128
|
-
|
|
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);
|
package/dist/core/tools/audit.js
CHANGED
|
@@ -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
|
}
|