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
|
@@ -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
|
|
@@ -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
|
|
@@ -143,7 +143,8 @@ function loadUserAgentProfiles(registry) {
|
|
|
143
143
|
const content = readFileSync(filePath, 'utf-8');
|
|
144
144
|
return AgentsConfigSchema.parse(JSON.parse(content));
|
|
145
145
|
}
|
|
146
|
-
catch {
|
|
146
|
+
catch (error) {
|
|
147
|
+
getLogger().debug(`[InitializeRuntime] Failed to load agent config from ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
147
148
|
return null;
|
|
148
149
|
}
|
|
149
150
|
};
|
|
@@ -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 { 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
|
}
|
|
@@ -178,7 +178,8 @@ export class SessionArchiver {
|
|
|
178
178
|
})),
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
|
-
catch {
|
|
181
|
+
catch (error) {
|
|
182
|
+
getLogger().debug(`[SessionPruning] Failed to restore session from archive: ${error instanceof Error ? error.message : String(error)}`);
|
|
182
183
|
return null;
|
|
183
184
|
}
|
|
184
185
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { FileAdapter } from '../adapters/fs/index.js';
|
|
3
|
+
import { estimateCost } from '../config/model-pricing.js';
|
|
3
4
|
import { logIgnoredError } from '../observability/ignored-error.js';
|
|
5
|
+
import { getLogger } from '../observability/logger.js';
|
|
4
6
|
import { isRecord } from '../utils/serialize.js';
|
|
5
7
|
/**
|
|
6
8
|
* Token usage tracker for chat sessions.
|
|
@@ -39,8 +41,8 @@ export class TokenTracker {
|
|
|
39
41
|
try {
|
|
40
42
|
events.push(JSON.parse(line));
|
|
41
43
|
}
|
|
42
|
-
catch {
|
|
43
|
-
|
|
44
|
+
catch (error) {
|
|
45
|
+
getLogger().debug(`[TokenTracker] Skipping malformed JSON line in events file: ${error instanceof Error ? error.message : String(error)}`);
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
let inputTokens = 0;
|
|
@@ -76,11 +78,16 @@ export class TokenTracker {
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
79
|
-
* Accumulate tokens into session metadata
|
|
81
|
+
* Accumulate tokens into session metadata.
|
|
82
|
+
* Computes estimated cost if model pricing is available.
|
|
80
83
|
*/
|
|
81
|
-
static accumulate(session, usage) {
|
|
84
|
+
static accumulate(session, usage, modelId) {
|
|
82
85
|
session.meta.totalTokens.input += usage.inputTokens;
|
|
83
86
|
session.meta.totalTokens.output += usage.outputTokens;
|
|
87
|
+
const cost = usage.estimatedCost ?? estimateCost(usage.inputTokens, usage.outputTokens, modelId);
|
|
88
|
+
if (cost !== undefined) {
|
|
89
|
+
session.meta.totalTokens.estimatedCost = (session.meta.totalTokens.estimatedCost ?? 0) + cost;
|
|
90
|
+
}
|
|
84
91
|
}
|
|
85
92
|
/**
|
|
86
93
|
* Estimate token count based on text length
|
|
@@ -89,9 +89,9 @@ export class SkillPermissionManager {
|
|
|
89
89
|
this.policies = [];
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
catch {
|
|
92
|
+
catch (error) {
|
|
93
93
|
const logger = tryGetLogger();
|
|
94
|
-
logger?.warn(text.skills.permissionFileLoadError(this.filePath));
|
|
94
|
+
logger?.warn(`${text.skills.permissionFileLoadError(this.filePath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
95
|
this.policies = [];
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -63,7 +63,8 @@ export class CheckpointManager {
|
|
|
63
63
|
try {
|
|
64
64
|
writeTreeProbe = await probeWriteTreeFailure(git);
|
|
65
65
|
}
|
|
66
|
-
catch {
|
|
66
|
+
catch (probeError) {
|
|
67
|
+
getLogger().debug(`[CheckpointManager] probeWriteTreeFailure failed: ${probeError instanceof Error ? probeError.message : String(probeError)}`);
|
|
67
68
|
writeTreeProbe = {};
|
|
68
69
|
}
|
|
69
70
|
}
|
|
@@ -129,7 +130,8 @@ export class CheckpointManager {
|
|
|
129
130
|
try {
|
|
130
131
|
meta = JSON.parse(msg);
|
|
131
132
|
}
|
|
132
|
-
catch {
|
|
133
|
+
catch (error) {
|
|
134
|
+
getLogger().debug(`[CheckpointManager] Invalid snapshot metadata for ${snapshotHash}: ${error instanceof Error ? error.message : String(error)}`);
|
|
133
135
|
throw new Error(`Invalid snapshot metadata for ${snapshotHash}`);
|
|
134
136
|
}
|
|
135
137
|
if (!meta.staged) {
|
|
@@ -268,8 +270,8 @@ export class CheckpointManager {
|
|
|
268
270
|
try {
|
|
269
271
|
await rm(tempIndexFile, { force: true });
|
|
270
272
|
}
|
|
271
|
-
catch {
|
|
272
|
-
|
|
273
|
+
catch (error) {
|
|
274
|
+
getLogger().debug(`[CheckpointManager] Failed to cleanup temp index: ${error instanceof Error ? error.message : String(error)}`);
|
|
273
275
|
}
|
|
274
276
|
}
|
|
275
277
|
}
|
|
@@ -292,7 +294,8 @@ export class CheckpointManager {
|
|
|
292
294
|
try {
|
|
293
295
|
meta = JSON.parse(msg);
|
|
294
296
|
}
|
|
295
|
-
catch {
|
|
297
|
+
catch (error) {
|
|
298
|
+
getLogger().debug(`[CheckpointManager] Invalid snapshot metadata for ${snapshotHash}: ${error instanceof Error ? error.message : String(error)}`);
|
|
296
299
|
throw new Error(`Invalid snapshot metadata for ${snapshotHash}`);
|
|
297
300
|
}
|
|
298
301
|
// 3. Get Parent Commit (Original HEAD)
|
|
@@ -339,7 +342,8 @@ export class CheckpointManager {
|
|
|
339
342
|
return { hash, timestamp, message, ref };
|
|
340
343
|
});
|
|
341
344
|
}
|
|
342
|
-
catch {
|
|
345
|
+
catch (error) {
|
|
346
|
+
getLogger().debug(`[CheckpointManager] Failed to list snapshots: ${error instanceof Error ? error.message : String(error)}`);
|
|
343
347
|
return [];
|
|
344
348
|
}
|
|
345
349
|
}
|
|
@@ -354,8 +358,8 @@ export class CheckpointManager {
|
|
|
354
358
|
await git.exec(['update-ref', '-d', `refs/s8p/snapshots/${snapshotHash}`]);
|
|
355
359
|
return;
|
|
356
360
|
}
|
|
357
|
-
catch {
|
|
358
|
-
|
|
361
|
+
catch (error) {
|
|
362
|
+
getLogger().debug(`[CheckpointManager] Direct snapshot deletion failed, trying ref search: ${error instanceof Error ? error.message : String(error)}`);
|
|
359
363
|
}
|
|
360
364
|
// Fallback: find the ref pointing to this hash
|
|
361
365
|
// Note: This is expensive if there are many snapshots, but safe.
|
|
@@ -392,7 +396,8 @@ export class CheckpointManager {
|
|
|
392
396
|
try {
|
|
393
397
|
meta = JSON.parse(msg);
|
|
394
398
|
}
|
|
395
|
-
catch {
|
|
399
|
+
catch (error) {
|
|
400
|
+
getLogger().debug(`[CheckpointManager] Invalid snapshot metadata for ${snapshotHash}: ${error instanceof Error ? error.message : String(error)}`);
|
|
396
401
|
throw new Error(`Invalid snapshot metadata for ${snapshotHash}`);
|
|
397
402
|
}
|
|
398
403
|
// Staged files: Diff between Parent (HEAD at time of snapshot) and Staged Tree
|
|
@@ -475,7 +480,8 @@ export class CheckpointManager {
|
|
|
475
480
|
try {
|
|
476
481
|
meta = JSON.parse(msg);
|
|
477
482
|
}
|
|
478
|
-
catch {
|
|
483
|
+
catch (error) {
|
|
484
|
+
getLogger().debug(`[CheckpointManager] Invalid backup metadata for ${backupHash}: ${error instanceof Error ? error.message : String(error)}`);
|
|
479
485
|
throw new Error(`Invalid backup metadata for ${backupHash}`);
|
|
480
486
|
}
|
|
481
487
|
// 2. Restore Worktree and Index to the T1 state
|
|
@@ -2,6 +2,7 @@ import { randomBytes } from 'crypto';
|
|
|
2
2
|
import { tmpdir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { rm } from '../../adapters/fs/node-fs.js';
|
|
5
|
+
import { getLogger } from '../../observability/logger.js';
|
|
5
6
|
import { normalizePath } from '../../utils/path.js';
|
|
6
7
|
async function getSafeUntrackedFiles(git) {
|
|
7
8
|
// --exclude-standard: Respect .gitignore
|
|
@@ -50,8 +51,8 @@ export async function createSnapshotCommitFromStagedTree(input) {
|
|
|
50
51
|
await git.exec(['add', '--', file], { env });
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
|
-
catch {
|
|
54
|
-
|
|
54
|
+
catch (error) {
|
|
55
|
+
getLogger().debug(`[SnapshotCreate] Failed to add file ${file} to snapshot index: ${error instanceof Error ? error.message : String(error)}`);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -71,8 +72,8 @@ export async function createSnapshotCommitFromStagedTree(input) {
|
|
|
71
72
|
try {
|
|
72
73
|
await rm(tempIndexFile, { force: true });
|
|
73
74
|
}
|
|
74
|
-
catch {
|
|
75
|
-
|
|
75
|
+
catch (error) {
|
|
76
|
+
getLogger().debug(`[SnapshotCreate] Failed to clean up temp index file: ${error instanceof Error ? error.message : String(error)}`);
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { stat } from '../../adapters/fs/node-fs.js';
|
|
3
|
+
import { getLogger } from '../../observability/logger.js';
|
|
3
4
|
import { classifyGitFailureHint } from './snapshot-audit.js';
|
|
4
5
|
export async function tryWriteTreeWithRetry(git, retryDelaysMs) {
|
|
5
6
|
let attempts = 0;
|
|
@@ -28,7 +29,8 @@ export async function probeWriteTreeFailure(git) {
|
|
|
28
29
|
details.indexLockPresent = true;
|
|
29
30
|
details.indexLockAgeMs = Math.max(0, Math.floor(Date.now() - lockStat.mtimeMs));
|
|
30
31
|
}
|
|
31
|
-
catch {
|
|
32
|
+
catch (error) {
|
|
33
|
+
getLogger().debug(`[SnapshotWriteTree] Index lock not present or unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
32
34
|
details.indexLockPresent = false;
|
|
33
35
|
}
|
|
34
36
|
try {
|
|
@@ -39,7 +41,8 @@ export async function probeWriteTreeFailure(git) {
|
|
|
39
41
|
.filter(Boolean).length;
|
|
40
42
|
details.unmergedCount = count;
|
|
41
43
|
}
|
|
42
|
-
catch {
|
|
44
|
+
catch (error) {
|
|
45
|
+
getLogger().debug(`[SnapshotWriteTree] Failed to list unmerged files: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
46
|
details.unmergedCount = undefined;
|
|
44
47
|
}
|
|
45
48
|
try {
|
|
@@ -64,7 +67,8 @@ export async function probeWriteTreeFailure(git) {
|
|
|
64
67
|
});
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
|
-
catch {
|
|
70
|
+
catch (error) {
|
|
71
|
+
getLogger().debug(`[SnapshotWriteTree] Failed to probe work tree status: ${error instanceof Error ? error.message : String(error)}`);
|
|
68
72
|
details.isInsideWorkTree = undefined;
|
|
69
73
|
}
|
|
70
74
|
return details;
|
|
@@ -269,7 +269,8 @@ export class ShadowMergeEngine {
|
|
|
269
269
|
return false;
|
|
270
270
|
return this.guardian.inspect(buffer).isBinary;
|
|
271
271
|
}
|
|
272
|
-
catch {
|
|
272
|
+
catch (error) {
|
|
273
|
+
getLogger().debug(`[ShadowMergeEngine] Binary detection failed for ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
273
274
|
return false;
|
|
274
275
|
}
|
|
275
276
|
}
|
|
@@ -282,7 +283,8 @@ export class ShadowMergeEngine {
|
|
|
282
283
|
try {
|
|
283
284
|
return await git.show(ref, relativePath);
|
|
284
285
|
}
|
|
285
|
-
catch {
|
|
286
|
+
catch (error) {
|
|
287
|
+
getLogger().debug(`[ShadowMergeEngine] git show failed for ${relativePath}@${ref}: ${error instanceof Error ? error.message : String(error)}`);
|
|
286
288
|
return null;
|
|
287
289
|
}
|
|
288
290
|
}
|