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.
- package/dist/cli/authorization/non-interactive.js +9 -13
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +9 -4
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +12 -3
- package/dist/cli/reporters/stream-json.js +2 -1
- package/dist/cli/slash/runtime.js +2 -2
- package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
- package/dist/cli/ui/hooks/useLoopState.js +1 -1
- 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/ast/parser.js +18 -9
- 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/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- 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 +34 -40
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +21 -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 +12 -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/token/encoding-registry.js +7 -6
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/index.js +48 -3
- package/dist/core/extensions/load.js +3 -2
- package/dist/core/extensions/merge.js +5 -1
- package/dist/core/extensions/paths.js +8 -2
- package/dist/core/extensions/schemas.js +21 -0
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/facades/cli-command-chat.js +2 -0
- package/dist/core/facades/cli-run-handler.js +1 -0
- package/dist/core/facades/cli-utils-serialize.js +2 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- 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 +5 -2
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/services/registry.js +18 -0
- package/dist/core/grizzco/steps/audit.js +20 -10
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +14 -4
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
- package/dist/core/grizzco/steps/patch.js +1 -0
- package/dist/core/grizzco/steps/plan.js +58 -49
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/tool-runtime.js +3 -0
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -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 +37 -26
- package/dist/core/llm/ai-sdk/request-params.js +2 -6
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +18 -14
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- 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 +51 -0
- package/dist/core/llm/tool-calling-stub.js +48 -0
- package/dist/core/llm/utils.js +17 -6
- package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/bridge/tool-bridge.js +5 -14
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +7 -4
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/mcp/host/sampling-provider.js +1 -1
- package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
- package/dist/core/memory/relevant-retrieval.js +6 -4
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -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/observability/token-usage.js +5 -4
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +8 -5
- package/dist/core/prompts/registry.js +12 -30
- 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 +5 -4
- 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 +13 -6
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +71 -6
- 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/compaction/index.js +4 -3
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +26 -38
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +27 -9
- package/dist/core/skills/parser.js +3 -2
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
- package/dist/core/skills/runtime/SkillRunner.js +5 -2
- package/dist/core/slash/steps/slash-execute.js +7 -5
- package/dist/core/slash/strategy.js +1 -1
- 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 +9 -10
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +28 -26
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/context-snapshot.js +12 -6
- package/dist/core/sub-agent/controller.js +70 -1
- package/dist/core/sub-agent/core/loop.js +25 -3
- package/dist/core/sub-agent/core/manager.js +343 -117
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -0
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/team.js +98 -0
- package/dist/core/sub-agent/tools/task-await.js +109 -0
- package/dist/core/sub-agent/tools/task-spawn.js +52 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +7 -12
- 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/executor.js +46 -43
- 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 +90 -7
- 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 +53 -111
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +14 -3
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/capability/executor.js +5 -5
- package/dist/core/tools/headless-payload.js +1 -3
- package/dist/core/tools/mapper.js +8 -42
- package/dist/core/tools/parallel/persistence.js +17 -5
- package/dist/core/tools/parallel/scheduler.js +23 -21
- package/dist/core/tools/permissions/permission-rules.js +69 -115
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +112 -58
- package/dist/core/tools/session.js +64 -102
- package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
- package/dist/core/tools/tool-visibility.js +2 -1
- package/dist/core/tools/types.js +10 -0
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/error.js +79 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +66 -0
- package/dist/core/utils/zod.js +29 -0
- 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/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +8 -1
- package/package.json +2 -1
|
@@ -8,6 +8,7 @@ import { GitAdapter } from '../../adapters/git/git-adapter.js';
|
|
|
8
8
|
import { logIgnoredError } from '../../observability/ignored-error.js';
|
|
9
9
|
import { getLogger } from '../../observability/logger.js';
|
|
10
10
|
import { getMonitor } from '../../observability/monitor.js';
|
|
11
|
+
import { errorMessage } from '../../utils/error.js';
|
|
11
12
|
import { isCanonicalPathWithinDirectory } from '../../utils/path.js';
|
|
12
13
|
import { detectDependencyPaths } from '../layers/shadow-driver/strategy.js';
|
|
13
14
|
const SECURITY_BLOCKLIST = [
|
|
@@ -56,7 +57,8 @@ export class WorkspaceSynchronizer {
|
|
|
56
57
|
try {
|
|
57
58
|
return await realpath(value);
|
|
58
59
|
}
|
|
59
|
-
catch {
|
|
60
|
+
catch (error) {
|
|
61
|
+
getLogger().debug(`[Synchronizer] realpath failed for ${value}: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
62
|
return null;
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -87,7 +89,8 @@ export class WorkspaceSynchronizer {
|
|
|
87
89
|
try {
|
|
88
90
|
entries = (await readdir(tempRoot, { withFileTypes: true }));
|
|
89
91
|
}
|
|
90
|
-
catch {
|
|
92
|
+
catch (error) {
|
|
93
|
+
getLogger().debug(`[Synchronizer] Failed to read tmpdir for backup pruning: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
94
|
return;
|
|
92
95
|
}
|
|
93
96
|
const cutoffTs = Date.now() - retentionMs;
|
|
@@ -100,8 +103,8 @@ export class WorkspaceSynchronizer {
|
|
|
100
103
|
await rm(backupPath, { recursive: true, force: true });
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
|
-
catch {
|
|
104
|
-
|
|
106
|
+
catch (error) {
|
|
107
|
+
getLogger().debug(`[Synchronizer] Failed to prune stale backup ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
105
108
|
}
|
|
106
109
|
}));
|
|
107
110
|
}
|
|
@@ -135,7 +138,7 @@ export class WorkspaceSynchronizer {
|
|
|
135
138
|
detectedDependencyPaths = await detectDependencyPaths(repoPath);
|
|
136
139
|
}
|
|
137
140
|
catch (error) {
|
|
138
|
-
getLogger().debug(`[checkpoint] Failed to detect dependency paths: ${
|
|
141
|
+
getLogger().debug(`[checkpoint] Failed to detect dependency paths: ${errorMessage(error)}`);
|
|
139
142
|
}
|
|
140
143
|
const candidates = new Set([
|
|
141
144
|
...DEFAULT_DEPENDENCY_ROOT_CANDIDATES,
|
|
@@ -159,8 +162,8 @@ export class WorkspaceSynchronizer {
|
|
|
159
162
|
symlinkedRoots.add(normalizedCandidate);
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
|
-
catch {
|
|
163
|
-
|
|
165
|
+
catch (error) {
|
|
166
|
+
getLogger().debug(`[Synchronizer] Dependency root probe failed for ${normalizedCandidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
164
167
|
}
|
|
165
168
|
}
|
|
166
169
|
return symlinkedRoots;
|
|
@@ -364,10 +367,8 @@ export class WorkspaceSynchronizer {
|
|
|
364
367
|
try {
|
|
365
368
|
oursContent = await readFile(mainAbsPath);
|
|
366
369
|
}
|
|
367
|
-
catch {
|
|
368
|
-
|
|
369
|
-
// Since we filtered for 'M', it implies it existed in Base. If missing in Main, User deleted it.
|
|
370
|
-
// 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)}`);
|
|
371
372
|
conflicts.push(relativePath);
|
|
372
373
|
continue;
|
|
373
374
|
}
|
|
@@ -445,7 +446,7 @@ export class WorkspaceSynchronizer {
|
|
|
445
446
|
});
|
|
446
447
|
}
|
|
447
448
|
catch (error) {
|
|
448
|
-
throw new Error(`Apply-back completed with conflicts (Atomic Patch). Rejection files (.rej) have been generated. Original error: ${
|
|
449
|
+
throw new Error(`Apply-back completed with conflicts (Atomic Patch). Rejection files (.rej) have been generated. Original error: ${errorMessage(error)}`);
|
|
449
450
|
}
|
|
450
451
|
}
|
|
451
452
|
parseStatusEntries(statusPorcelainZ) {
|
|
@@ -545,7 +546,8 @@ export class WorkspaceSynchronizer {
|
|
|
545
546
|
const content = await readFile(path.join(mainRepoPath, ...file.split('/')));
|
|
546
547
|
entries.push(`${file}:${hashContent(content)}`);
|
|
547
548
|
}
|
|
548
|
-
catch {
|
|
549
|
+
catch (error) {
|
|
550
|
+
getLogger().debug(`[Synchronizer] Fingerprint read failed for untracked file ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
549
551
|
entries.push(`${file}:missing`);
|
|
550
552
|
}
|
|
551
553
|
}
|
|
@@ -594,8 +596,8 @@ export class WorkspaceSynchronizer {
|
|
|
594
596
|
try {
|
|
595
597
|
await copyFile(src, dst);
|
|
596
598
|
}
|
|
597
|
-
catch {
|
|
598
|
-
|
|
599
|
+
catch (error) {
|
|
600
|
+
getLogger().debug(`[Synchronizer] Failed to backup dirty file ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
599
601
|
}
|
|
600
602
|
}
|
|
601
603
|
}
|
|
@@ -624,7 +626,7 @@ export class WorkspaceSynchronizer {
|
|
|
624
626
|
stagedPatchPath,
|
|
625
627
|
};
|
|
626
628
|
};
|
|
627
|
-
dirtyBackup =
|
|
629
|
+
dirtyBackup = await createDirtyBackup();
|
|
628
630
|
getLogger().info(text.loop.applyBackCheckpointCreated());
|
|
629
631
|
getLogger().info(text.loop.applyBackCheckpointLocation(dirtyBackup?.dir || ''));
|
|
630
632
|
if (telemetry) {
|
|
@@ -673,7 +675,7 @@ export class WorkspaceSynchronizer {
|
|
|
673
675
|
}
|
|
674
676
|
}
|
|
675
677
|
catch (error) {
|
|
676
|
-
const err = error instanceof Error ? error : new Error(
|
|
678
|
+
const err = error instanceof Error ? error : new Error(errorMessage(error));
|
|
677
679
|
if (telemetry) {
|
|
678
680
|
telemetry.error = err.message;
|
|
679
681
|
}
|
|
@@ -693,8 +695,8 @@ export class WorkspaceSynchronizer {
|
|
|
693
695
|
current.working !== originalFingerprint.working ||
|
|
694
696
|
current.untracked !== originalFingerprint.untracked;
|
|
695
697
|
}
|
|
696
|
-
catch {
|
|
697
|
-
|
|
698
|
+
catch (error) {
|
|
699
|
+
getLogger().debug(`[Synchronizer] Workspace fingerprint comparison failed, assuming changed: ${error instanceof Error ? error.message : String(error)}`);
|
|
698
700
|
workspaceChanged = true;
|
|
699
701
|
}
|
|
700
702
|
}
|
|
@@ -737,7 +739,7 @@ export class WorkspaceSynchronizer {
|
|
|
737
739
|
await copyFile(path.join(trackedDir, ...file.split('/')), path.join(mainRepoPath, ...file.split('/')));
|
|
738
740
|
}
|
|
739
741
|
catch (e) {
|
|
740
|
-
getLogger().error(`[applyBack] Failed to restore tracked file ${file}: ${
|
|
742
|
+
getLogger().error(`[applyBack] Failed to restore tracked file ${file}: ${errorMessage(e)}`);
|
|
741
743
|
}
|
|
742
744
|
}
|
|
743
745
|
}
|
|
@@ -751,8 +753,8 @@ export class WorkspaceSynchronizer {
|
|
|
751
753
|
});
|
|
752
754
|
await copyFile(path.join(untrackedDir, ...file.split('/')), path.join(mainRepoPath, ...file.split('/')));
|
|
753
755
|
}
|
|
754
|
-
catch {
|
|
755
|
-
|
|
756
|
+
catch (error) {
|
|
757
|
+
getLogger().debug(`[Synchronizer] Failed to restore untracked file ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
756
758
|
}
|
|
757
759
|
}
|
|
758
760
|
}
|
|
@@ -769,7 +771,7 @@ export class WorkspaceSynchronizer {
|
|
|
769
771
|
}
|
|
770
772
|
}
|
|
771
773
|
catch (e) {
|
|
772
|
-
const patchError =
|
|
774
|
+
const patchError = errorMessage(e);
|
|
773
775
|
getLogger().error(`[applyBack] Failed to restore staged state from patch. ${patchError}. ` +
|
|
774
776
|
`Falling back to read-tree restore.`);
|
|
775
777
|
try {
|
|
@@ -780,7 +782,7 @@ export class WorkspaceSynchronizer {
|
|
|
780
782
|
}
|
|
781
783
|
}
|
|
782
784
|
catch (fallbackError) {
|
|
783
|
-
const fallbackMessage =
|
|
785
|
+
const fallbackMessage = errorMessage(fallbackError);
|
|
784
786
|
if (telemetry) {
|
|
785
787
|
telemetry.stagedRestoreSucceeded = false;
|
|
786
788
|
telemetry.stagedRestoreError = `${patchError}; fallback read-tree failed: ${fallbackMessage}`;
|
|
@@ -808,7 +810,7 @@ export class WorkspaceSynchronizer {
|
|
|
808
810
|
catch (snapshotRestoreError) {
|
|
809
811
|
getLogger().error(`[applyBack] Snapshot restore failed during clean rollback. ` +
|
|
810
812
|
`baseRef=${checkpointRef.baseRef}; ` +
|
|
811
|
-
`error=${
|
|
813
|
+
`error=${errorMessage(snapshotRestoreError)}. ` +
|
|
812
814
|
`Falling back to clean reset.`);
|
|
813
815
|
}
|
|
814
816
|
if (!restoredFromSnapshot) {
|
|
@@ -830,7 +832,7 @@ export class WorkspaceSynchronizer {
|
|
|
830
832
|
await rm(dirtyBackup.dir, { recursive: true, force: true });
|
|
831
833
|
}
|
|
832
834
|
catch (cleanupError) {
|
|
833
|
-
getLogger().debug(`[applyBack] Failed to cleanup dirty backup ${dirtyBackup.dir}: ${
|
|
835
|
+
getLogger().debug(`[applyBack] Failed to cleanup dirty backup ${dirtyBackup.dir}: ${errorMessage(cleanupError)}`);
|
|
834
836
|
}
|
|
835
837
|
}
|
|
836
838
|
if (telemetry) {
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
-
}
|
|
4
|
-
function getString(record, key) {
|
|
5
|
-
const value = record[key];
|
|
6
|
-
return typeof value === 'string' ? value : null;
|
|
7
|
-
}
|
|
8
|
-
function getRecord(record, key) {
|
|
9
|
-
const value = record[key];
|
|
10
|
-
return isRecord(value) ? value : null;
|
|
11
|
-
}
|
|
1
|
+
import { isRecord, getString, getRecord } from '../../utils/serialize.js';
|
|
12
2
|
/**
|
|
13
3
|
* Best-effort conversion from our provider-agnostic `LLMStreamChunk` into
|
|
14
4
|
* provider-agnostic canonical stream parts.
|
|
@@ -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
|
}
|
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
import { Ajv } from 'ajv';
|
|
2
|
-
|
|
3
|
-
try {
|
|
4
|
-
return JSON.stringify(value);
|
|
5
|
-
}
|
|
6
|
-
catch {
|
|
7
|
-
try {
|
|
8
|
-
return String(value);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return '[Unserializable]';
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
2
|
+
import { safeStringify } from '../utils/serialize.js';
|
|
15
3
|
function toSchemaKey(schema) {
|
|
16
4
|
if (!schema || typeof schema !== 'object')
|
|
17
5
|
return `non_object:${typeof schema}`;
|
|
@@ -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() {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { normalizeToolResultReplacementState, } from '../session/replacement-state.js';
|
|
2
2
|
import { SUB_AGENT_CONTEXT_SNAPSHOT_VERSION, SUB_AGENT_CONTEXT_SNAPSHOT_FIELD_SEMANTICS, } from './types.js';
|
|
3
|
+
const SUPPORTED_SNAPSHOT_FIELDS = new Set([
|
|
4
|
+
'version',
|
|
5
|
+
...Object.keys(SUB_AGENT_CONTEXT_SNAPSHOT_FIELD_SEMANTICS),
|
|
6
|
+
]);
|
|
3
7
|
function deepClone(value) {
|
|
4
8
|
if (typeof structuredClone === 'function') {
|
|
5
9
|
return structuredClone(value);
|
|
@@ -39,7 +43,13 @@ function cloneConversationContext(messages) {
|
|
|
39
43
|
function cloneToolCallingAudit(entries) {
|
|
40
44
|
if (!Array.isArray(entries) || entries.length === 0)
|
|
41
45
|
return undefined;
|
|
42
|
-
return entries.map((entry) =>
|
|
46
|
+
return entries.map((entry) => ({
|
|
47
|
+
...entry,
|
|
48
|
+
toolResultPatchArtifact: cloneArtifactHandle(entry.toolResultPatchArtifact),
|
|
49
|
+
toolResultAuditArtifact: cloneArtifactHandle(entry.toolResultAuditArtifact),
|
|
50
|
+
toolResultReadArtifact: cloneArtifactHandle(entry.toolResultReadArtifact),
|
|
51
|
+
toolResultPreviewArtifact: cloneArtifactHandle(entry.toolResultPreviewArtifact),
|
|
52
|
+
}));
|
|
43
53
|
}
|
|
44
54
|
function cloneArtifactHints(hints) {
|
|
45
55
|
if (!hints)
|
|
@@ -106,11 +116,7 @@ function normalizeSnapshotVersion(snapshot) {
|
|
|
106
116
|
return version;
|
|
107
117
|
}
|
|
108
118
|
function assertSupportedSnapshotFields(snapshot) {
|
|
109
|
-
const
|
|
110
|
-
'version',
|
|
111
|
-
...Object.keys(SUB_AGENT_CONTEXT_SNAPSHOT_FIELD_SEMANTICS),
|
|
112
|
-
]);
|
|
113
|
-
const unknownFields = Object.keys(snapshot).filter((key) => !supportedFields.has(key));
|
|
119
|
+
const unknownFields = Object.keys(snapshot).filter((key) => !SUPPORTED_SNAPSHOT_FIELDS.has(key));
|
|
114
120
|
if (unknownFields.length > 0) {
|
|
115
121
|
throw new Error(`Unsupported sub-agent context snapshot fields: ${unknownFields.sort().join(', ')}`);
|
|
116
122
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
const LOG_HISTORY_LIMIT =
|
|
1
|
+
const LOG_HISTORY_LIMIT = 200;
|
|
2
2
|
export class InMemorySubAgentController {
|
|
3
3
|
agents = new Map();
|
|
4
|
+
toolCallListeners = new Set();
|
|
5
|
+
results = new Map();
|
|
6
|
+
waiters = new Map();
|
|
4
7
|
registerAgent(id, profile, status) {
|
|
5
8
|
const existing = this.agents.get(id);
|
|
6
9
|
if (existing) {
|
|
@@ -16,6 +19,8 @@ export class InMemorySubAgentController {
|
|
|
16
19
|
updatedAt: new Date(),
|
|
17
20
|
stopRequested: false,
|
|
18
21
|
logs: [],
|
|
22
|
+
tokenUsage: 0,
|
|
23
|
+
toolCallCount: 0,
|
|
19
24
|
});
|
|
20
25
|
}
|
|
21
26
|
updateStatus(id, status, summary) {
|
|
@@ -37,6 +42,35 @@ export class InMemorySubAgentController {
|
|
|
37
42
|
agent.logs.splice(0, agent.logs.length - LOG_HISTORY_LIMIT);
|
|
38
43
|
}
|
|
39
44
|
}
|
|
45
|
+
addTokenUsage(id, tokens) {
|
|
46
|
+
const agent = this.agents.get(id);
|
|
47
|
+
if (!agent)
|
|
48
|
+
return;
|
|
49
|
+
agent.tokenUsage += tokens;
|
|
50
|
+
}
|
|
51
|
+
recordToolCall(id, toolName, durationMs, success) {
|
|
52
|
+
const agent = this.agents.get(id);
|
|
53
|
+
if (!agent)
|
|
54
|
+
return;
|
|
55
|
+
agent.toolCallCount++;
|
|
56
|
+
const event = {
|
|
57
|
+
type: 'tool.call.end',
|
|
58
|
+
agentId: id,
|
|
59
|
+
toolName,
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
durationMs,
|
|
62
|
+
success,
|
|
63
|
+
};
|
|
64
|
+
for (const listener of this.toolCallListeners) {
|
|
65
|
+
listener(event);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
onToolCall(listener) {
|
|
69
|
+
this.toolCallListeners.add(listener);
|
|
70
|
+
return () => {
|
|
71
|
+
this.toolCallListeners.delete(listener);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
40
74
|
listAgents() {
|
|
41
75
|
return Array.from(this.agents.values());
|
|
42
76
|
}
|
|
@@ -62,6 +96,41 @@ export class InMemorySubAgentController {
|
|
|
62
96
|
isStopRequested(id) {
|
|
63
97
|
return this.agents.get(id)?.stopRequested ?? false;
|
|
64
98
|
}
|
|
99
|
+
setResult(id, result) {
|
|
100
|
+
this.results.set(id, result);
|
|
101
|
+
const waiters = this.waiters.get(id);
|
|
102
|
+
if (waiters) {
|
|
103
|
+
for (const resolve of waiters) {
|
|
104
|
+
resolve(result);
|
|
105
|
+
}
|
|
106
|
+
this.waiters.delete(id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async awaitResult(id, timeoutMs = 300_000) {
|
|
110
|
+
// Check if result is already available
|
|
111
|
+
const existing = this.results.get(id);
|
|
112
|
+
if (existing)
|
|
113
|
+
return existing;
|
|
114
|
+
// Wait for the result with timeout
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
const timer = setTimeout(() => {
|
|
117
|
+
// Remove this waiter on timeout
|
|
118
|
+
const waiters = this.waiters.get(id);
|
|
119
|
+
if (waiters) {
|
|
120
|
+
const idx = waiters.indexOf(resolve);
|
|
121
|
+
if (idx >= 0)
|
|
122
|
+
waiters.splice(idx, 1);
|
|
123
|
+
}
|
|
124
|
+
resolve(undefined);
|
|
125
|
+
}, timeoutMs);
|
|
126
|
+
const waiters = this.waiters.get(id) ?? [];
|
|
127
|
+
waiters.push((result) => {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
resolve(result);
|
|
130
|
+
});
|
|
131
|
+
this.waiters.set(id, waiters);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
65
134
|
}
|
|
66
135
|
export function createSubAgentController() {
|
|
67
136
|
return new InMemorySubAgentController();
|
|
@@ -27,12 +27,34 @@ export class SmallfryLoop {
|
|
|
27
27
|
async execute(initCtx) {
|
|
28
28
|
getLogger().debug(`[SmallfryLoop] ${text.smallfry.status.working} (${this.profile.name})`);
|
|
29
29
|
let pipeline = Pipeline.of(initCtx);
|
|
30
|
+
let turnCount = 0;
|
|
31
|
+
const maxTurns = this.profile.maxTurns;
|
|
30
32
|
// Dynamic Phase Injection based on Stratagem
|
|
33
|
+
// PREFLIGHT is deterministic (no LLM call), so it doesn't count as a turn
|
|
31
34
|
pipeline = pipeline.step('PREFLIGHT', runPreflight);
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
// Each subsequent step makes at least one LLM call
|
|
36
|
+
if (maxTurns !== undefined && turnCount >= maxTurns) {
|
|
37
|
+
getLogger().warn(`[SmallfryLoop] maxTurns (${maxTurns}) reached before CONTEXT — stopping early`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
pipeline = pipeline.step('CONTEXT', buildContext);
|
|
41
|
+
turnCount++;
|
|
42
|
+
}
|
|
43
|
+
if (maxTurns !== undefined && turnCount >= maxTurns) {
|
|
44
|
+
getLogger().warn(`[SmallfryLoop] maxTurns (${maxTurns}) reached before PLAN — stopping early`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
pipeline = pipeline.step('PLAN', generatePlan);
|
|
48
|
+
turnCount++;
|
|
49
|
+
}
|
|
34
50
|
if (this.profile.stratagem === 'surgeon') {
|
|
35
|
-
|
|
51
|
+
if (maxTurns !== undefined && turnCount >= maxTurns) {
|
|
52
|
+
getLogger().warn(`[SmallfryLoop] maxTurns (${maxTurns}) reached before PATCH — stopping early`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
pipeline = pipeline.step('PATCH', generatePatch);
|
|
56
|
+
turnCount++;
|
|
57
|
+
}
|
|
36
58
|
}
|
|
37
59
|
const report = await pipeline.execute();
|
|
38
60
|
report.auditPath = await saveAudit(report, initCtx.options);
|