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
|
@@ -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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
const instance = a2aServerInstance;
|
|
69
|
+
if (!instance) {
|
|
69
70
|
started = false;
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
73
|
await new Promise((resolve, reject) => {
|
|
73
|
-
|
|
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
|
|
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
|
|
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 (
|
|
49
|
+
if (isRecord(arg)) {
|
|
44
50
|
// Drop the object structure entirely for console output to prevent UI pollution
|
|
45
|
-
const code = arg.code
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
?
|
|
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
|
|
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
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
368
|
-
for (const result of
|
|
355
|
+
const chunkResults = await Promise.all(promises);
|
|
356
|
+
for (const result of chunkResults) {
|
|
369
357
|
if (result) {
|
|
370
|
-
|
|
358
|
+
results.push(result);
|
|
371
359
|
}
|
|
372
360
|
}
|
|
373
361
|
}
|
|
374
|
-
return
|
|
362
|
+
return results;
|
|
375
363
|
}
|
|
376
364
|
/**
|
|
377
365
|
* Delete a session file
|