salmon-loop 0.2.3 → 0.2.16
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/argv/headless-detection.js +27 -0
- package/dist/cli/chat-flow.js +11 -0
- package/dist/cli/chat.js +161 -24
- package/dist/cli/commands/chat.js +30 -24
- package/dist/cli/commands/context.js +15 -3
- package/dist/cli/commands/flow-mode.js +63 -0
- package/dist/cli/commands/help-format.js +12 -0
- package/dist/cli/commands/registry.js +6 -7
- package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
- package/dist/cli/commands/run/config-resolution.js +30 -24
- package/dist/cli/commands/run/early-errors.js +23 -0
- package/dist/cli/commands/run/handler.js +131 -44
- package/dist/cli/commands/run/headless-error-writer.js +8 -0
- package/dist/cli/commands/run/loop-params.js +3 -0
- package/dist/cli/commands/run/mode.js +2 -5
- package/dist/cli/commands/run/parse-options.js +18 -2
- package/dist/cli/commands/run/persist-session.js +10 -1
- package/dist/cli/commands/run/preflight.js +10 -0
- package/dist/cli/commands/run/reporter-factory.js +4 -0
- package/dist/cli/commands/run/runtime-llm.js +38 -11
- package/dist/cli/commands/run/runtime-options.js +2 -2
- package/dist/cli/commands/run/validate-options.js +0 -5
- package/dist/cli/commands/run/verbose.js +2 -7
- package/dist/cli/commands/serve.js +117 -90
- package/dist/cli/commands/tool-names.js +78 -78
- package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/json-protocol.js +37 -0
- package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/protocol-metadata.js +22 -0
- package/dist/cli/headless/stream-json-protocol.js +34 -1
- package/dist/cli/index.js +6 -4
- package/dist/cli/locales/en.js +32 -6
- package/dist/cli/program-bootstrap.js +14 -4
- package/dist/cli/program-commands.js +9 -1
- package/dist/cli/program-options.js +1 -0
- package/dist/cli/reporters/anthropic-stream.js +7 -1
- package/dist/cli/reporters/json.js +4 -0
- package/dist/cli/reporters/stream-json.js +17 -2
- package/dist/cli/run-cli.js +5 -3
- package/dist/cli/slash/runtime.js +30 -15
- package/dist/cli/ui/components/CommandInput.js +7 -3
- package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
- package/dist/cli/utils/command-option-source.js +13 -0
- package/dist/cli/utils/output-format.js +6 -0
- package/dist/cli/utils/resolve-cli-config.js +98 -0
- package/dist/cli/utils/verbose-level.js +8 -0
- package/dist/cli/utils/verify-resolver.js +8 -4
- package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
- package/dist/core/adapters/fs/file-adapter.js +6 -0
- package/dist/core/adapters/fs/filesystem.js +2 -1
- package/dist/core/adapters/git/git-adapter.js +78 -1
- package/dist/core/benchmark/patch-artifact.js +124 -0
- package/dist/core/benchmark/swe-bench.js +25 -0
- package/dist/core/config/load.js +39 -18
- package/dist/core/config/merge.js +27 -0
- package/dist/core/config/paths.js +24 -5
- package/dist/core/config/resolve-llm.js +12 -0
- package/dist/core/config/resolve.js +7 -5
- package/dist/core/config/resolvers/server.js +0 -6
- package/dist/core/config/validate.js +94 -21
- package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
- package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
- package/dist/core/context/keywords.js +18 -4
- package/dist/core/context/service-deps.js +2 -2
- package/dist/core/context/service.js +8 -0
- package/dist/core/context/steps/context-gather.js +38 -0
- package/dist/core/context/summarization/summarizer.js +55 -12
- package/dist/core/context/targeting/target-resolver.js +4 -4
- package/dist/core/extensions/index.js +23 -5
- package/dist/core/extensions/paths.js +31 -0
- package/dist/core/extensions/schemas.js +8 -5
- package/dist/core/facades/cli-chat.js +6 -2
- package/dist/core/facades/cli-command-chat.js +2 -1
- package/dist/core/facades/cli-command-tool-names.js +2 -0
- package/dist/core/facades/cli-context.js +1 -0
- package/dist/core/facades/cli-observability.js +1 -1
- package/dist/core/facades/cli-run-handler.js +4 -2
- package/dist/core/facades/cli-run-persist-session.js +1 -0
- package/dist/core/facades/cli-serve.js +2 -4
- package/dist/core/facades/cli-utils-worktree.js +1 -1
- package/dist/core/failure/diagnostics.js +53 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
- package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
- package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +173 -7
- package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
- package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
- package/dist/core/grizzco/steps/answer.js +13 -14
- package/dist/core/grizzco/steps/autopilot.js +396 -0
- package/dist/core/grizzco/steps/cache-sharing.js +29 -0
- package/dist/core/grizzco/steps/explore.js +37 -21
- package/dist/core/grizzco/steps/generateReview.js +2 -5
- package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
- package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
- package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
- package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
- package/dist/core/grizzco/steps/patch.js +105 -146
- package/dist/core/grizzco/steps/plan.js +101 -25
- package/dist/core/grizzco/steps/preflight.js +5 -3
- package/dist/core/grizzco/steps/request-assembly.js +78 -0
- package/dist/core/grizzco/steps/research.js +39 -36
- package/dist/core/grizzco/steps/tool-runtime.js +47 -0
- package/dist/core/grizzco/steps/verify-shared.js +23 -0
- package/dist/core/grizzco/steps/verify.js +13 -21
- package/dist/core/intent/chat-intent.js +0 -4
- package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
- package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
- package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
- package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
- package/dist/core/llm/ai-sdk/request-params.js +74 -1
- package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
- package/dist/core/llm/ai-sdk.js +112 -27
- package/dist/core/llm/capabilities.js +12 -0
- package/dist/core/llm/contracts/repair.js +36 -30
- package/dist/core/llm/errors.js +83 -2
- package/dist/core/llm/message-composition.js +7 -22
- package/dist/core/llm/phase-router.js +29 -10
- package/dist/core/llm/redact.js +28 -3
- package/dist/core/llm/registry.js +2 -0
- package/dist/core/llm/request-augmentation.js +55 -0
- package/dist/core/llm/request-envelope.js +334 -0
- package/dist/core/llm/shared-request-assembly.js +35 -0
- package/dist/core/llm/stream-utils.js +13 -4
- package/dist/core/llm/utils.js +18 -29
- package/dist/core/memory/relevant-retrieval.js +144 -0
- package/dist/core/observability/logger.js +11 -2
- package/dist/core/patch/diff.js +1 -0
- package/dist/core/prompts/registry.js +39 -2
- package/dist/core/prompts/runtime.js +50 -12
- package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
- package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
- package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
- package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
- package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
- package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
- package/dist/core/prompts/templates/system/main_system.hbs +4 -16
- package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
- package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
- package/dist/core/prompts/templates/system/research_system.hbs +2 -0
- package/dist/core/protocols/a2a/agent-card.js +3 -2
- package/dist/core/protocols/a2a/sdk/executor.js +8 -6
- package/dist/core/protocols/a2a/sdk/server.js +0 -1
- package/dist/core/protocols/acp/formal-agent.js +221 -55
- package/dist/core/protocols/acp/handlers.js +5 -1
- package/dist/core/protocols/acp/permission-provider.js +21 -1
- package/dist/core/protocols/shared/execution-request.js +24 -0
- package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
- package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
- package/dist/core/public-capabilities/projections.js +29 -0
- package/dist/core/public-capabilities/registry.js +26 -0
- package/dist/core/public-capabilities/types.js +2 -0
- package/dist/core/runtime/agent-server-runtime.js +47 -43
- package/dist/core/runtime/execution-profile.js +67 -0
- package/dist/core/session/artifact-state.js +160 -0
- package/dist/core/session/compaction/index.js +183 -0
- package/dist/core/session/compaction/microcompact.js +78 -0
- package/dist/core/session/compaction/tracking.js +48 -0
- package/dist/core/session/compaction/types.js +11 -0
- package/dist/core/session/compression.js +12 -4
- package/dist/core/session/manager.js +247 -10
- package/dist/core/session/pruning-strategy.js +55 -9
- package/dist/core/session/replacement-preview-provider.js +24 -0
- package/dist/core/session/replacement-state.js +131 -0
- package/dist/core/session/resume-repair/pipeline.js +79 -0
- package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
- package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
- package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
- package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
- package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
- package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
- package/dist/core/session/resume-repair/types.js +2 -0
- package/dist/core/session/summary-sync.js +164 -13
- package/dist/core/session/token-tracker.js +6 -0
- package/dist/core/skills/audit.js +34 -0
- package/dist/core/skills/bridge.js +84 -7
- package/dist/core/skills/discovery.js +94 -0
- package/dist/core/skills/feature-flags.js +52 -0
- package/dist/core/skills/index.js +1 -1
- package/dist/core/skills/loader.js +195 -20
- package/dist/core/skills/parser.js +296 -24
- package/dist/core/skills/permissions.js +117 -0
- package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
- package/dist/core/skills/runtime/SkillRunner.js +240 -61
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
- package/dist/core/strata/layers/worktree.js +70 -13
- package/dist/core/strata/runtime/synchronizer.js +29 -2
- package/dist/core/streaming/stream-assembler.js +75 -31
- package/dist/core/sub-agent/context-snapshot.js +156 -0
- package/dist/core/sub-agent/core/loop.js +1 -1
- package/dist/core/sub-agent/core/manager.js +119 -20
- package/dist/core/sub-agent/dispatch-policy.js +29 -0
- package/dist/core/sub-agent/prefix-consistency.js +48 -0
- package/dist/core/sub-agent/registry-defaults.js +4 -0
- package/dist/core/sub-agent/tools/task-spawn.js +79 -2
- package/dist/core/sub-agent/types.js +134 -5
- package/dist/core/tools/audit.js +13 -4
- package/dist/core/tools/builtin/ast-grep.js +1 -1
- package/dist/core/tools/builtin/ast.js +1 -1
- package/dist/core/tools/builtin/benchmark.js +360 -0
- package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
- package/dist/core/tools/builtin/code-search/executor.js +6 -1
- package/dist/core/tools/builtin/code-search/spec.js +26 -2
- package/dist/core/tools/builtin/fs.js +256 -23
- package/dist/core/tools/builtin/git.js +2 -2
- package/dist/core/tools/builtin/index.js +51 -2
- package/dist/core/tools/builtin/interaction.js +8 -1
- package/dist/core/tools/builtin/plan.js +37 -15
- package/dist/core/tools/builtin/shell.js +1 -1
- package/dist/core/tools/loader.js +39 -16
- package/dist/core/tools/mapper.js +17 -3
- package/dist/core/tools/parallel/scheduler.js +35 -4
- package/dist/core/tools/permissions/permission-rules.js +5 -10
- package/dist/core/tools/policy.js +6 -1
- package/dist/core/tools/recoverable-tool-errors.js +10 -0
- package/dist/core/tools/router.js +24 -6
- package/dist/core/tools/session.js +458 -48
- package/dist/core/tools/tool-visibility.js +62 -0
- package/dist/core/tools/types.js +9 -1
- package/dist/core/types/execution.js +4 -0
- package/dist/core/types/flow-mode.js +8 -0
- package/dist/core/utils/path.js +52 -0
- package/dist/core/verification/runner.js +4 -1
- package/dist/interfaces/cli/task-runner.js +4 -3
- package/dist/languages/typescript/index.js +4 -1
- package/dist/locales/en.js +87 -2
- package/dist/utils/eol.js +1 -1
- package/package.json +15 -8
- package/scripts/fix-es-abstract-compat.js +77 -0
- package/dist/core/runtime/fastify-server-bundle.js +0 -26
- package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
- package/dist/core/runtime/sidecar-paths.js +0 -47
- package/dist/core/runtime/sidecar-route-catalog.js +0 -103
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getLogger } from '../../observability/logger.js';
|
|
2
|
+
import { DEFAULT_MICROCOMPACT_CONFIG } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Microcompact is a rule-based context reduction utility.
|
|
5
|
+
* It's idempotent, zero-LLM-cost, and operates on the "View" layer.
|
|
6
|
+
*
|
|
7
|
+
* Rules:
|
|
8
|
+
* - Only affects messages with role === 'assistant' that contain tool results.
|
|
9
|
+
* - Preserves the most recent `keepRecentTurns` rounds.
|
|
10
|
+
* - EXCLUDES "stateful" tools (e.g. cd, export) to avoid environment desync.
|
|
11
|
+
* - Preserves the assistant's thought process (the text part) before tools.
|
|
12
|
+
*/
|
|
13
|
+
export function microcompact(messages, config = {}) {
|
|
14
|
+
const mergedConfig = {
|
|
15
|
+
...DEFAULT_MICROCOMPACT_CONFIG,
|
|
16
|
+
...config,
|
|
17
|
+
};
|
|
18
|
+
const { keepRecentTurns, placeholder, statefulTools } = mergedConfig;
|
|
19
|
+
// 1. Identify cutoff turn (1 turn = user + assistant pair, usually)
|
|
20
|
+
// We'll keep the last N assistant messages as "recent"
|
|
21
|
+
let assistantCount = 0;
|
|
22
|
+
const cutoffIndex = [...messages].reverse().findIndex((msg) => {
|
|
23
|
+
if (msg.role === 'assistant') {
|
|
24
|
+
assistantCount++;
|
|
25
|
+
}
|
|
26
|
+
return assistantCount > keepRecentTurns;
|
|
27
|
+
});
|
|
28
|
+
// Calculate the absolute index in the original array
|
|
29
|
+
const absCutoffIndex = cutoffIndex === -1 ? -1 : messages.length - 1 - cutoffIndex;
|
|
30
|
+
let totalClearedCount = 0;
|
|
31
|
+
const result = messages.map((msg, index) => {
|
|
32
|
+
// Only process assistant messages BEFORE the cutoff
|
|
33
|
+
if (index > absCutoffIndex || msg.role !== 'assistant' || !msg.content) {
|
|
34
|
+
return msg;
|
|
35
|
+
}
|
|
36
|
+
const { content } = msg;
|
|
37
|
+
// Pattern to match tool results while capturing tool name and content
|
|
38
|
+
// Improved regex to handle attributes more robustly
|
|
39
|
+
const toolResultRegex = /<tool_result\b[^>]*?name="([^"]+)"[^>]*?>([\s\S]*?)<\/tool_result>/g;
|
|
40
|
+
let hasMatched = false;
|
|
41
|
+
const newContent = content.replace(toolResultRegex, (match, toolName, toolOutput) => {
|
|
42
|
+
// Rule: Skip stateful tools
|
|
43
|
+
if (statefulTools.includes(toolName)) {
|
|
44
|
+
return match;
|
|
45
|
+
}
|
|
46
|
+
// Rule: Skip if already cleared
|
|
47
|
+
if (toolOutput.trim() === placeholder) {
|
|
48
|
+
return match;
|
|
49
|
+
}
|
|
50
|
+
hasMatched = true;
|
|
51
|
+
totalClearedCount++;
|
|
52
|
+
// Extract original tag prefix (including attributes) to preserve them
|
|
53
|
+
const tagMatch = match.match(/<tool_result\b[^>]*?>/);
|
|
54
|
+
const tagPrefix = tagMatch ? tagMatch[0] : `<tool_result name="${toolName}">`;
|
|
55
|
+
return `${tagPrefix}${placeholder}</tool_result>`;
|
|
56
|
+
});
|
|
57
|
+
if (!hasMatched) {
|
|
58
|
+
return msg;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
...msg,
|
|
62
|
+
content: newContent,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
if (totalClearedCount > 0) {
|
|
66
|
+
getLogger().audit('COMPACTION_MICROCOMPACT', {
|
|
67
|
+
clearedCount: totalClearedCount,
|
|
68
|
+
keepRecentTurns,
|
|
69
|
+
}, {
|
|
70
|
+
source: 'session',
|
|
71
|
+
severity: 'low',
|
|
72
|
+
scope: 'session',
|
|
73
|
+
phase: 'COMPACTION',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=microcompact.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Initial compaction tracking state
|
|
4
|
+
*/
|
|
5
|
+
export function createInitialTracking() {
|
|
6
|
+
return {
|
|
7
|
+
compacted: false,
|
|
8
|
+
turnCounter: 0,
|
|
9
|
+
consecutiveFailures: 0,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Update state on successful compaction
|
|
14
|
+
*/
|
|
15
|
+
export function onCompactionSuccess(_prev) {
|
|
16
|
+
return {
|
|
17
|
+
compacted: true,
|
|
18
|
+
compactId: randomUUID(),
|
|
19
|
+
turnCounter: 0,
|
|
20
|
+
consecutiveFailures: 0,
|
|
21
|
+
lastCompactedAt: Date.now(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Update state on compaction failure (for circuit breaker)
|
|
26
|
+
*/
|
|
27
|
+
export function onCompactionFailure(prev) {
|
|
28
|
+
return {
|
|
29
|
+
...prev,
|
|
30
|
+
consecutiveFailures: prev.consecutiveFailures + 1,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Increment turn counter after successful execution cycle
|
|
35
|
+
*/
|
|
36
|
+
export function onNormalTurnComplete(prev) {
|
|
37
|
+
return {
|
|
38
|
+
...prev,
|
|
39
|
+
turnCounter: prev.turnCounter + 1,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if circuit breaker is tripped
|
|
44
|
+
*/
|
|
45
|
+
export function isCircuitBreakerTripped(tracking, maxFailures) {
|
|
46
|
+
return tracking.consecutiveFailures >= maxFailures;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=tracking.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DEFAULT_MICROCOMPACT_CONFIG = {
|
|
2
|
+
keepRecentTurns: 3, // Keep last 3 rounds (approx 6 messages)
|
|
3
|
+
placeholder: '[Previous tool output cleared for context efficiency]',
|
|
4
|
+
statefulTools: ['cd', 'export', 'env_set', 'enter_worktree', 'exit_worktree'],
|
|
5
|
+
};
|
|
6
|
+
export const DEFAULT_AUTOCOMPACT_CONFIG = {
|
|
7
|
+
tokenThreshold: 8000,
|
|
8
|
+
maxFailures: 3,
|
|
9
|
+
keepRecentMessages: 10,
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { promisify } from 'util';
|
|
2
2
|
import { gzip, gunzip } from 'zlib';
|
|
3
3
|
import { FileAdapter } from '../adapters/fs/index.js';
|
|
4
|
+
import { normalizeSessionArtifactState } from './artifact-state.js';
|
|
5
|
+
import { normalizeToolResultReplacementState, } from './replacement-state.js';
|
|
4
6
|
export const DEFAULT_COMPRESSION_CONFIG = {
|
|
5
7
|
maxKeyMessages: 20,
|
|
6
8
|
maxKeyIterations: 10,
|
|
@@ -53,6 +55,9 @@ export class SessionCompressor {
|
|
|
53
55
|
originalSize,
|
|
54
56
|
compressedSize: 0, // Will be updated after serialization
|
|
55
57
|
compressionRatio: 0, // Will be calculated after serialization
|
|
58
|
+
chatState: session.meta.chatState,
|
|
59
|
+
artifactState: normalizeSessionArtifactState(session.meta.artifactState),
|
|
60
|
+
replacementState: normalizeToolResultReplacementState(session.meta.replacementState),
|
|
56
61
|
},
|
|
57
62
|
compressed: {
|
|
58
63
|
summary: summary.text,
|
|
@@ -106,6 +111,9 @@ export class SessionCompressor {
|
|
|
106
111
|
successfulIterations: compressed.compressed.stats.successfulIterations,
|
|
107
112
|
totalTokens: compressed.compressed.stats.totalTokens,
|
|
108
113
|
snapshots: [], // Will be restored from full data
|
|
114
|
+
chatState: compressed.meta.chatState,
|
|
115
|
+
artifactState: normalizeSessionArtifactState(compressed.meta.artifactState),
|
|
116
|
+
replacementState: normalizeToolResultReplacementState(compressed.meta.replacementState),
|
|
109
117
|
},
|
|
110
118
|
messages: compressed.compressed.keyMessages.map((msg) => ({
|
|
111
119
|
role: msg.role,
|
|
@@ -178,7 +186,7 @@ export class SessionCompressor {
|
|
|
178
186
|
return stats;
|
|
179
187
|
}
|
|
180
188
|
determineOutcome(iteration) {
|
|
181
|
-
//
|
|
189
|
+
// Simplified result determination logic
|
|
182
190
|
if (iteration.result?.success === true)
|
|
183
191
|
return 'success';
|
|
184
192
|
if (iteration.result?.success === false)
|
|
@@ -186,7 +194,7 @@ export class SessionCompressor {
|
|
|
186
194
|
return 'partial';
|
|
187
195
|
}
|
|
188
196
|
generateIterationSummary(iteration) {
|
|
189
|
-
//
|
|
197
|
+
// Generate brief iteration summary
|
|
190
198
|
const result = iteration.result;
|
|
191
199
|
if (!result)
|
|
192
200
|
return 'No result data';
|
|
@@ -198,7 +206,7 @@ export class SessionCompressor {
|
|
|
198
206
|
}
|
|
199
207
|
}
|
|
200
208
|
countErrors(iteration) {
|
|
201
|
-
//
|
|
209
|
+
// Count errors in iteration
|
|
202
210
|
const result = iteration.result;
|
|
203
211
|
if (!result)
|
|
204
212
|
return 0;
|
|
@@ -313,7 +321,7 @@ export class CompressedSessionStore {
|
|
|
313
321
|
}
|
|
314
322
|
async readFile(path) {
|
|
315
323
|
const data = await this.fileAdapter.readFile(path);
|
|
316
|
-
// FileAdapter
|
|
324
|
+
// FileAdapter returns base64 string, need to decode to Uint8Array
|
|
317
325
|
if (typeof data === 'string') {
|
|
318
326
|
return new Uint8Array(Buffer.from(data, 'base64'));
|
|
319
327
|
}
|
|
@@ -1,14 +1,51 @@
|
|
|
1
1
|
import { randomBytes } from 'crypto';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { FileAdapter } from '../adapters/fs/index.js';
|
|
4
|
+
import { recordAuditEvent } from '../observability/audit-trail.js';
|
|
5
|
+
import { getLogger } from '../observability/logger.js';
|
|
6
|
+
import { parseFlowMode } from '../types/flow-mode.js';
|
|
7
|
+
import { mergeReplacementStateFromArtifactHints, mergeSessionArtifactState, normalizeSessionArtifactState, } from './artifact-state.js';
|
|
4
8
|
import { SessionCompressor, CompressedSessionStore } from './compression.js';
|
|
5
9
|
import { SessionPruningEngine } from './pruning-strategy.js';
|
|
10
|
+
import { freezeToolResultReplacementDecision, normalizeToolResultReplacementState, } from './replacement-state.js';
|
|
11
|
+
import { createResumeRepairPipeline } from './resume-repair/pipeline.js';
|
|
12
|
+
const RESUME_REPAIR_V1_FLAG = 'SALMONLOOP_RESUME_REPAIR_V1';
|
|
13
|
+
function resolveResumeRepairV1Enabled() {
|
|
14
|
+
const raw = process.env[RESUME_REPAIR_V1_FLAG];
|
|
15
|
+
if (!raw || !raw.trim())
|
|
16
|
+
return true;
|
|
17
|
+
const normalized = raw.trim().toLowerCase();
|
|
18
|
+
if (normalized === '0' || normalized === 'false' || normalized === 'off' || normalized === 'no') {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function recordResumeRepairMetrics(details) {
|
|
24
|
+
recordAuditEvent('session.resume_repair.completed', {
|
|
25
|
+
mode: details.mode,
|
|
26
|
+
success: details.success,
|
|
27
|
+
metric: 'repair_violation_rate',
|
|
28
|
+
repairViolationCount: details.repairViolationCount,
|
|
29
|
+
replacementReuseMetric: 'replacement_reuse_hit_rate',
|
|
30
|
+
replacementReuseHitCount: details.replacementReuseHitCount,
|
|
31
|
+
contractViolationCodes: details.contractViolationCodes ?? [],
|
|
32
|
+
}, {
|
|
33
|
+
source: 'session',
|
|
34
|
+
severity: details.success ? 'low' : 'medium',
|
|
35
|
+
scope: 'session',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function normalizeChatState(chatState) {
|
|
39
|
+
const flowMode = parseFlowMode(chatState?.flowMode);
|
|
40
|
+
return flowMode ? { flowMode } : undefined;
|
|
41
|
+
}
|
|
6
42
|
/**
|
|
7
43
|
* Manages chat session persistence and lifecycle.
|
|
8
44
|
* Storage: .salmonloop/chat-sessions/<id>.json
|
|
9
45
|
* Features: Auto-pruning, compression, intelligent cleanup
|
|
10
46
|
*/
|
|
11
47
|
export class ChatSessionManager {
|
|
48
|
+
repoPath;
|
|
12
49
|
storageDir;
|
|
13
50
|
currentSession = null;
|
|
14
51
|
fileAdapter = new FileAdapter();
|
|
@@ -16,6 +53,7 @@ export class ChatSessionManager {
|
|
|
16
53
|
compressor;
|
|
17
54
|
compressedStore;
|
|
18
55
|
constructor(repoPath, pruningStrategy) {
|
|
56
|
+
this.repoPath = repoPath;
|
|
19
57
|
this.storageDir = join(repoPath, '.salmonloop', 'chat-sessions');
|
|
20
58
|
this.pruningEngine = new SessionPruningEngine(pruningStrategy);
|
|
21
59
|
this.compressor = new SessionCompressor();
|
|
@@ -86,7 +124,11 @@ export class ChatSessionManager {
|
|
|
86
124
|
const filePath = join(this.storageDir, `${targetId}.json`);
|
|
87
125
|
try {
|
|
88
126
|
const data = await this.fileAdapter.readFile(filePath);
|
|
89
|
-
|
|
127
|
+
const parsed = JSON.parse(data);
|
|
128
|
+
parsed.meta.chatState = normalizeChatState(parsed.meta.chatState);
|
|
129
|
+
parsed.meta.artifactState = normalizeSessionArtifactState(parsed.meta.artifactState);
|
|
130
|
+
parsed.meta.replacementState = normalizeToolResultReplacementState(parsed.meta.replacementState);
|
|
131
|
+
this.currentSession = parsed;
|
|
90
132
|
return this.currentSession;
|
|
91
133
|
}
|
|
92
134
|
catch {
|
|
@@ -170,6 +212,9 @@ export class ChatSessionManager {
|
|
|
170
212
|
getSummaryState() {
|
|
171
213
|
return this.currentSession?.meta.summaryState;
|
|
172
214
|
}
|
|
215
|
+
getArtifactState() {
|
|
216
|
+
return normalizeSessionArtifactState(this.currentSession?.meta.artifactState);
|
|
217
|
+
}
|
|
173
218
|
/**
|
|
174
219
|
* Update summary state after summarization.
|
|
175
220
|
*/
|
|
@@ -178,6 +223,38 @@ export class ChatSessionManager {
|
|
|
178
223
|
throw new Error('No active session');
|
|
179
224
|
this.currentSession.meta.summaryState = state;
|
|
180
225
|
}
|
|
226
|
+
updateArtifactState(state) {
|
|
227
|
+
if (!this.currentSession)
|
|
228
|
+
throw new Error('No active session');
|
|
229
|
+
this.currentSession.meta.artifactState = normalizeSessionArtifactState(state);
|
|
230
|
+
}
|
|
231
|
+
mergeArtifactState(state) {
|
|
232
|
+
if (!this.currentSession)
|
|
233
|
+
throw new Error('No active session');
|
|
234
|
+
this.currentSession.meta.artifactState = mergeSessionArtifactState(this.currentSession.meta.artifactState, state);
|
|
235
|
+
this.currentSession.meta.replacementState = mergeReplacementStateFromArtifactHints(this.currentSession.meta.replacementState, state);
|
|
236
|
+
}
|
|
237
|
+
getReplacementState() {
|
|
238
|
+
return normalizeToolResultReplacementState(this.currentSession?.meta.replacementState);
|
|
239
|
+
}
|
|
240
|
+
updateReplacementState(state) {
|
|
241
|
+
if (!this.currentSession)
|
|
242
|
+
throw new Error('No active session');
|
|
243
|
+
this.currentSession.meta.replacementState = normalizeToolResultReplacementState(state);
|
|
244
|
+
}
|
|
245
|
+
getChatFlowMode() {
|
|
246
|
+
return this.currentSession?.meta.chatState?.flowMode;
|
|
247
|
+
}
|
|
248
|
+
updateChatFlowMode(mode) {
|
|
249
|
+
if (!this.currentSession)
|
|
250
|
+
throw new Error('No active session');
|
|
251
|
+
this.currentSession.meta.chatState = normalizeChatState(mode === undefined ? undefined : { flowMode: mode });
|
|
252
|
+
}
|
|
253
|
+
freezeReplacementDecision(entry, options) {
|
|
254
|
+
if (!this.currentSession)
|
|
255
|
+
throw new Error('No active session');
|
|
256
|
+
this.currentSession.meta.replacementState = freezeToolResultReplacementDecision(this.currentSession.meta.replacementState, entry, options);
|
|
257
|
+
}
|
|
181
258
|
/**
|
|
182
259
|
* Clear summary state (e.g., on session reset).
|
|
183
260
|
*/
|
|
@@ -254,11 +331,14 @@ export class ChatSessionManager {
|
|
|
254
331
|
const filePath = join(this.storageDir, file);
|
|
255
332
|
const data = await this.fileAdapter.readFile(filePath);
|
|
256
333
|
const session = JSON.parse(data);
|
|
334
|
+
session.meta.chatState = normalizeChatState(session.meta.chatState);
|
|
335
|
+
session.meta.artifactState = normalizeSessionArtifactState(session.meta.artifactState);
|
|
336
|
+
session.meta.replacementState = normalizeToolResultReplacementState(session.meta.replacementState);
|
|
257
337
|
sessions.push(session);
|
|
258
338
|
}
|
|
259
339
|
catch (error) {
|
|
260
340
|
// Skip corrupted session files
|
|
261
|
-
|
|
341
|
+
getLogger().warn(`Failed to load session file ${file}: ${error}`);
|
|
262
342
|
}
|
|
263
343
|
}
|
|
264
344
|
return sessions;
|
|
@@ -272,7 +352,7 @@ export class ChatSessionManager {
|
|
|
272
352
|
await this.fileAdapter.deleteFile(filePath);
|
|
273
353
|
}
|
|
274
354
|
catch (error) {
|
|
275
|
-
|
|
355
|
+
getLogger().warn(`Failed to delete session ${sessionId}: ${error}`);
|
|
276
356
|
}
|
|
277
357
|
}
|
|
278
358
|
/**
|
|
@@ -297,17 +377,174 @@ export class ChatSessionManager {
|
|
|
297
377
|
* List archived sessions
|
|
298
378
|
*/
|
|
299
379
|
async listArchivedSessions() {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
380
|
+
const archiveDir = this.getArchiveStorageDir();
|
|
381
|
+
const files = await this.fileAdapter.readdir(archiveDir).catch(() => []);
|
|
382
|
+
const archived = [];
|
|
383
|
+
for (const file of files) {
|
|
384
|
+
if (!file.endsWith('.mpack.gz'))
|
|
385
|
+
continue;
|
|
386
|
+
try {
|
|
387
|
+
const compressed = await this.compressedStore.loadCompressed(file);
|
|
388
|
+
if (!compressed)
|
|
389
|
+
continue;
|
|
390
|
+
const stats = await this.fileAdapter.stat(join(archiveDir, file));
|
|
391
|
+
archived.push({
|
|
392
|
+
id: compressed.meta.id,
|
|
393
|
+
name: compressed.meta.name,
|
|
394
|
+
archivedAt: stats.mtime.getTime(),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
getLogger().warn(`Failed to load archived session ${file}: ${error}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return archived.sort((a, b) => b.archivedAt - a.archivedAt);
|
|
303
402
|
}
|
|
304
403
|
/**
|
|
305
404
|
* Restore session from archive
|
|
306
405
|
*/
|
|
307
|
-
async restoreFromArchive(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
406
|
+
async restoreFromArchive(archiveId) {
|
|
407
|
+
const filename = await this.resolveArchiveFilename(archiveId);
|
|
408
|
+
if (!filename)
|
|
409
|
+
return null;
|
|
410
|
+
const resumeRepairV1Enabled = resolveResumeRepairV1Enabled();
|
|
411
|
+
try {
|
|
412
|
+
if (!resumeRepairV1Enabled) {
|
|
413
|
+
const restored = await this.restoreFromArchiveLegacy(filename);
|
|
414
|
+
if (!restored) {
|
|
415
|
+
recordResumeRepairMetrics({
|
|
416
|
+
mode: 'legacy',
|
|
417
|
+
success: false,
|
|
418
|
+
repairViolationCount: 1,
|
|
419
|
+
replacementReuseHitCount: 0,
|
|
420
|
+
contractViolationCodes: ['LEGACY_RESTORE_FAILED'],
|
|
421
|
+
});
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
recordResumeRepairMetrics({
|
|
425
|
+
mode: 'legacy',
|
|
426
|
+
success: true,
|
|
427
|
+
repairViolationCount: 0,
|
|
428
|
+
replacementReuseHitCount: Object.keys(restored.meta.replacementState?.entries ?? {})
|
|
429
|
+
.length,
|
|
430
|
+
});
|
|
431
|
+
this.currentSession = restored;
|
|
432
|
+
await this.save();
|
|
433
|
+
return restored;
|
|
434
|
+
}
|
|
435
|
+
const pipeline = createResumeRepairPipeline({
|
|
436
|
+
compressedStore: this.compressedStore,
|
|
437
|
+
compressor: this.compressor,
|
|
438
|
+
repoPath: this.repoPath,
|
|
439
|
+
});
|
|
440
|
+
const repaired = await pipeline.run({ archiveId, filename });
|
|
441
|
+
if (!repaired.session) {
|
|
442
|
+
recordResumeRepairMetrics({
|
|
443
|
+
mode: 'repair_v1',
|
|
444
|
+
success: false,
|
|
445
|
+
repairViolationCount: repaired.contractViolations.length,
|
|
446
|
+
replacementReuseHitCount: 0,
|
|
447
|
+
contractViolationCodes: repaired.contractViolations.map((entry) => entry.code),
|
|
448
|
+
});
|
|
449
|
+
const violationText = repaired.contractViolations.map((entry) => entry.message).join('; ');
|
|
450
|
+
getLogger().warn(`Failed to restore archived session ${archiveId}: ${violationText || 'repair pipeline rejected archive'}`);
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
repaired.session.meta.resumeRepairState = {
|
|
454
|
+
schemaVersion: 1,
|
|
455
|
+
lastRunAt: Date.now(),
|
|
456
|
+
warnings: repaired.warnings.map((entry) => `${entry.code}: ${entry.message}`),
|
|
457
|
+
repairActions: repaired.repairActions.map((entry) => `${entry.code}: ${entry.detail}`),
|
|
458
|
+
contractViolations: repaired.contractViolations.map((entry) => `${entry.code}: ${entry.message}`),
|
|
459
|
+
};
|
|
460
|
+
repaired.session.meta.replacementState = normalizeToolResultReplacementState(repaired.replacementState);
|
|
461
|
+
recordResumeRepairMetrics({
|
|
462
|
+
mode: 'repair_v1',
|
|
463
|
+
success: true,
|
|
464
|
+
repairViolationCount: repaired.contractViolations.length,
|
|
465
|
+
replacementReuseHitCount: Object.keys(repaired.replacementState?.entries ?? {}).length,
|
|
466
|
+
contractViolationCodes: repaired.contractViolations.map((entry) => entry.code),
|
|
467
|
+
});
|
|
468
|
+
this.currentSession = repaired.session;
|
|
469
|
+
await this.save();
|
|
470
|
+
return repaired.session;
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
recordResumeRepairMetrics({
|
|
474
|
+
mode: resumeRepairV1Enabled ? 'repair_v1' : 'legacy',
|
|
475
|
+
success: false,
|
|
476
|
+
repairViolationCount: 1,
|
|
477
|
+
replacementReuseHitCount: 0,
|
|
478
|
+
contractViolationCodes: ['RESTORE_EXCEPTION'],
|
|
479
|
+
});
|
|
480
|
+
getLogger().warn(`Failed to restore archived session ${archiveId}: ${error}`);
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
async restoreFromArchiveLegacy(filename) {
|
|
485
|
+
const compressed = await this.compressedStore.loadCompressed(filename);
|
|
486
|
+
if (!compressed)
|
|
487
|
+
return null;
|
|
488
|
+
const partial = await this.compressor.decompressToSession(compressed);
|
|
489
|
+
if (!partial?.meta?.id || !partial?.meta?.name)
|
|
490
|
+
return null;
|
|
491
|
+
return {
|
|
492
|
+
meta: {
|
|
493
|
+
id: partial.meta.id,
|
|
494
|
+
name: partial.meta.name,
|
|
495
|
+
repoPath: this.repoPath,
|
|
496
|
+
createdAt: partial.meta.createdAt,
|
|
497
|
+
updatedAt: Date.now(),
|
|
498
|
+
totalIterations: partial.meta.totalIterations ?? partial.iterations.length,
|
|
499
|
+
successfulIterations: partial.meta.successfulIterations ?? 0,
|
|
500
|
+
totalTokens: partial.meta.totalTokens ?? { input: 0, output: 0 },
|
|
501
|
+
snapshots: [],
|
|
502
|
+
chatState: normalizeChatState(partial.meta.chatState),
|
|
503
|
+
artifactState: normalizeSessionArtifactState(partial.meta.artifactState),
|
|
504
|
+
replacementState: normalizeToolResultReplacementState(partial.meta.replacementState),
|
|
505
|
+
},
|
|
506
|
+
messages: partial.messages.map((message, index) => ({
|
|
507
|
+
id: `restored-msg-${index}`,
|
|
508
|
+
role: message.role,
|
|
509
|
+
content: message.content,
|
|
510
|
+
timestamp: message.timestamp,
|
|
511
|
+
})),
|
|
512
|
+
iterations: partial.iterations.map((iteration, index) => ({
|
|
513
|
+
id: iteration.id || `restored-iter-${index + 1}`,
|
|
514
|
+
attempt: index + 1,
|
|
515
|
+
plan: null,
|
|
516
|
+
patch: null,
|
|
517
|
+
error: iteration.outcome === 'failure' ? iteration.summary : undefined,
|
|
518
|
+
contextSummary: iteration.summary,
|
|
519
|
+
})),
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
getArchiveStorageDir() {
|
|
523
|
+
return join(this.repoPath, '.salmonloop', 'compressed-sessions');
|
|
524
|
+
}
|
|
525
|
+
async resolveArchiveFilename(archiveId) {
|
|
526
|
+
const archiveDir = this.getArchiveStorageDir();
|
|
527
|
+
const files = (await this.fileAdapter.readdir(archiveDir).catch(() => [])).filter((file) => file.endsWith('.mpack.gz'));
|
|
528
|
+
if (files.length === 0)
|
|
529
|
+
return null;
|
|
530
|
+
if (archiveId.endsWith('.mpack.gz') && files.includes(archiveId)) {
|
|
531
|
+
return archiveId;
|
|
532
|
+
}
|
|
533
|
+
const exactFilename = `${archiveId}.mpack.gz`;
|
|
534
|
+
if (files.includes(exactFilename)) {
|
|
535
|
+
return exactFilename;
|
|
536
|
+
}
|
|
537
|
+
const prefixMatches = files.filter((file) => file.startsWith(archiveId));
|
|
538
|
+
if (prefixMatches.length === 0)
|
|
539
|
+
return null;
|
|
540
|
+
if (prefixMatches.length === 1)
|
|
541
|
+
return prefixMatches[0];
|
|
542
|
+
const withMtime = await Promise.all(prefixMatches.map(async (file) => {
|
|
543
|
+
const stats = await this.fileAdapter.stat(join(archiveDir, file));
|
|
544
|
+
return { file, mtime: stats.mtime.getTime() };
|
|
545
|
+
}));
|
|
546
|
+
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
547
|
+
return withMtime[0]?.file ?? null;
|
|
311
548
|
}
|
|
312
549
|
}
|
|
313
550
|
//# sourceMappingURL=manager.js.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { FileAdapter } from '../adapters/fs/index.js';
|
|
2
|
+
import { getLogger } from '../observability/logger.js';
|
|
3
|
+
import { normalizeSessionArtifactState } from './artifact-state.js';
|
|
4
|
+
import { SessionCompressor } from './compression.js';
|
|
1
5
|
/**
|
|
2
6
|
* Default memory pruning strategy configuration
|
|
3
7
|
*/
|
|
@@ -113,8 +117,14 @@ export class SessionPruningEngine {
|
|
|
113
117
|
*/
|
|
114
118
|
export class SessionArchiver {
|
|
115
119
|
archiveDir;
|
|
120
|
+
baseDir;
|
|
121
|
+
fileAdapter;
|
|
122
|
+
compressor;
|
|
116
123
|
constructor(baseDir) {
|
|
124
|
+
this.baseDir = baseDir;
|
|
117
125
|
this.archiveDir = `${baseDir}/.salmonloop/chat-archives`;
|
|
126
|
+
this.fileAdapter = new FileAdapter();
|
|
127
|
+
this.compressor = new SessionCompressor();
|
|
118
128
|
}
|
|
119
129
|
/**
|
|
120
130
|
* Create session archive
|
|
@@ -131,23 +141,59 @@ export class SessionArchiver {
|
|
|
131
141
|
/**
|
|
132
142
|
* Restore session from archive
|
|
133
143
|
*/
|
|
134
|
-
async restoreFromArchive(
|
|
144
|
+
async restoreFromArchive(archiveId) {
|
|
135
145
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
const archiveFile = archiveId.endsWith('.mpack.gz') ? archiveId : `${archiveId}.mpack.gz`;
|
|
147
|
+
const archivePath = `${this.archiveDir}/${archiveFile}`;
|
|
148
|
+
const encoded = await this.fileAdapter.readFile(archivePath);
|
|
149
|
+
const binary = new Uint8Array(Buffer.from(encoded, 'base64'));
|
|
150
|
+
const compressed = await this.compressor.decompressFromBinary(binary);
|
|
151
|
+
const partial = await this.compressor.decompressToSession(compressed);
|
|
152
|
+
return {
|
|
153
|
+
meta: {
|
|
154
|
+
id: partial.meta.id,
|
|
155
|
+
name: partial.meta.name,
|
|
156
|
+
repoPath: this.baseDir,
|
|
157
|
+
createdAt: partial.meta.createdAt,
|
|
158
|
+
updatedAt: Date.now(),
|
|
159
|
+
totalIterations: partial.meta.totalIterations ?? partial.iterations.length,
|
|
160
|
+
successfulIterations: partial.meta.successfulIterations ?? 0,
|
|
161
|
+
totalTokens: partial.meta.totalTokens ?? { input: 0, output: 0 },
|
|
162
|
+
snapshots: [],
|
|
163
|
+
artifactState: normalizeSessionArtifactState(partial.meta.artifactState),
|
|
164
|
+
},
|
|
165
|
+
messages: partial.messages.map((message, index) => ({
|
|
166
|
+
id: `archived-msg-${index}`,
|
|
167
|
+
role: message.role,
|
|
168
|
+
content: message.content,
|
|
169
|
+
timestamp: message.timestamp,
|
|
170
|
+
})),
|
|
171
|
+
iterations: partial.iterations.map((iter, index) => ({
|
|
172
|
+
id: iter.id || `archived-iter-${index + 1}`,
|
|
173
|
+
attempt: index + 1,
|
|
174
|
+
plan: null,
|
|
175
|
+
patch: null,
|
|
176
|
+
error: iter.outcome === 'failure' ? iter.summary : undefined,
|
|
177
|
+
contextSummary: iter.summary,
|
|
178
|
+
})),
|
|
179
|
+
};
|
|
139
180
|
}
|
|
140
181
|
catch {
|
|
141
182
|
return null;
|
|
142
183
|
}
|
|
143
184
|
}
|
|
144
185
|
async ensureArchiveDir() {
|
|
145
|
-
|
|
146
|
-
// The SessionArchiver is currently a placeholder for future implementation
|
|
186
|
+
await this.fileAdapter.mkdir(this.archiveDir);
|
|
147
187
|
}
|
|
148
|
-
async writeCompressedData(
|
|
149
|
-
|
|
150
|
-
|
|
188
|
+
async writeCompressedData(archivePath, compressedData) {
|
|
189
|
+
const encoded = Buffer.from(compressedData).toString('base64');
|
|
190
|
+
try {
|
|
191
|
+
await this.fileAdapter.writeFile(archivePath, encoded);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
getLogger().warn(`Failed to write session archive ${archivePath}: ${error}`);
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
151
197
|
}
|
|
152
198
|
}
|
|
153
199
|
//# sourceMappingURL=pruning-strategy.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class SessionReplacementPreviewProvider {
|
|
2
|
+
state;
|
|
3
|
+
constructor(state) {
|
|
4
|
+
this.state = state;
|
|
5
|
+
}
|
|
6
|
+
getPreviewHints() {
|
|
7
|
+
if (!this.state)
|
|
8
|
+
return undefined;
|
|
9
|
+
const out = Object.values(this.state.entries)
|
|
10
|
+
.filter((entry) => entry.decision === 'replaced' && entry.sourceArtifactHandle)
|
|
11
|
+
.sort((a, b) => a.frozenAt - b.frozenAt)
|
|
12
|
+
.map((entry) => ({
|
|
13
|
+
label: `Tool result preview: ${entry.toolResultId}`,
|
|
14
|
+
artifact: {
|
|
15
|
+
handle: entry.sourceArtifactHandle,
|
|
16
|
+
mimeType: 'application/json',
|
|
17
|
+
sha256: entry.toolResultId,
|
|
18
|
+
size: entry.preview.length,
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
return out.length > 0 ? out : undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=replacement-preview-provider.js.map
|