salmon-loop 0.2.13 → 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 +160 -24
- package/dist/cli/commands/chat.js +14 -7
- package/dist/cli/commands/flow-mode.js +63 -0
- package/dist/cli/commands/registry.js +2 -0
- package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
- package/dist/cli/commands/run/early-errors.js +23 -0
- package/dist/cli/commands/run/handler.js +115 -27
- package/dist/cli/commands/run/headless-error-writer.js +8 -0
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/mode.js +2 -5
- package/dist/cli/commands/run/parse-options.js +16 -0
- 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/serve.js +91 -71
- 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 +30 -6
- package/dist/cli/program-bootstrap.js +8 -3
- package/dist/cli/program-commands.js +5 -1
- 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 +27 -12
- 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/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 +18 -11
- package/dist/core/config/resolve-llm.js +12 -0
- package/dist/core/config/resolvers/server.js +0 -6
- package/dist/core/config/validate.js +73 -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 +1 -0
- package/dist/core/facades/cli-command-tool-names.js +2 -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 +165 -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 -6
- 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/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 +73 -0
- 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 +2 -1
- package/dist/core/protocols/a2a/sdk/server.js +0 -1
- package/dist/core/protocols/acp/formal-agent.js +74 -51
- package/dist/core/protocols/acp/handlers.js +5 -1
- package/dist/core/protocols/acp/permission-provider.js +1 -1
- 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 +8 -0
- package/dist/core/session/manager.js +244 -8
- 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 +67 -10
- 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/languages/typescript/index.js +4 -1
- package/dist/locales/en.js +35 -2
- package/dist/utils/eol.js +1 -1
- package/package.json +13 -6
- 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
|
@@ -1,15 +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';
|
|
4
5
|
import { getLogger } from '../observability/logger.js';
|
|
6
|
+
import { parseFlowMode } from '../types/flow-mode.js';
|
|
7
|
+
import { mergeReplacementStateFromArtifactHints, mergeSessionArtifactState, normalizeSessionArtifactState, } from './artifact-state.js';
|
|
5
8
|
import { SessionCompressor, CompressedSessionStore } from './compression.js';
|
|
6
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
|
+
}
|
|
7
42
|
/**
|
|
8
43
|
* Manages chat session persistence and lifecycle.
|
|
9
44
|
* Storage: .salmonloop/chat-sessions/<id>.json
|
|
10
45
|
* Features: Auto-pruning, compression, intelligent cleanup
|
|
11
46
|
*/
|
|
12
47
|
export class ChatSessionManager {
|
|
48
|
+
repoPath;
|
|
13
49
|
storageDir;
|
|
14
50
|
currentSession = null;
|
|
15
51
|
fileAdapter = new FileAdapter();
|
|
@@ -17,6 +53,7 @@ export class ChatSessionManager {
|
|
|
17
53
|
compressor;
|
|
18
54
|
compressedStore;
|
|
19
55
|
constructor(repoPath, pruningStrategy) {
|
|
56
|
+
this.repoPath = repoPath;
|
|
20
57
|
this.storageDir = join(repoPath, '.salmonloop', 'chat-sessions');
|
|
21
58
|
this.pruningEngine = new SessionPruningEngine(pruningStrategy);
|
|
22
59
|
this.compressor = new SessionCompressor();
|
|
@@ -87,7 +124,11 @@ export class ChatSessionManager {
|
|
|
87
124
|
const filePath = join(this.storageDir, `${targetId}.json`);
|
|
88
125
|
try {
|
|
89
126
|
const data = await this.fileAdapter.readFile(filePath);
|
|
90
|
-
|
|
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;
|
|
91
132
|
return this.currentSession;
|
|
92
133
|
}
|
|
93
134
|
catch {
|
|
@@ -171,6 +212,9 @@ export class ChatSessionManager {
|
|
|
171
212
|
getSummaryState() {
|
|
172
213
|
return this.currentSession?.meta.summaryState;
|
|
173
214
|
}
|
|
215
|
+
getArtifactState() {
|
|
216
|
+
return normalizeSessionArtifactState(this.currentSession?.meta.artifactState);
|
|
217
|
+
}
|
|
174
218
|
/**
|
|
175
219
|
* Update summary state after summarization.
|
|
176
220
|
*/
|
|
@@ -179,6 +223,38 @@ export class ChatSessionManager {
|
|
|
179
223
|
throw new Error('No active session');
|
|
180
224
|
this.currentSession.meta.summaryState = state;
|
|
181
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
|
+
}
|
|
182
258
|
/**
|
|
183
259
|
* Clear summary state (e.g., on session reset).
|
|
184
260
|
*/
|
|
@@ -255,6 +331,9 @@ export class ChatSessionManager {
|
|
|
255
331
|
const filePath = join(this.storageDir, file);
|
|
256
332
|
const data = await this.fileAdapter.readFile(filePath);
|
|
257
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);
|
|
258
337
|
sessions.push(session);
|
|
259
338
|
}
|
|
260
339
|
catch (error) {
|
|
@@ -298,17 +377,174 @@ export class ChatSessionManager {
|
|
|
298
377
|
* List archived sessions
|
|
299
378
|
*/
|
|
300
379
|
async listArchivedSessions() {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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);
|
|
304
402
|
}
|
|
305
403
|
/**
|
|
306
404
|
* Restore session from archive
|
|
307
405
|
*/
|
|
308
|
-
async restoreFromArchive(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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;
|
|
312
548
|
}
|
|
313
549
|
}
|
|
314
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
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
export const TOOL_RESULT_REPLACEMENT_IDENTITY_VERSION = 'v1';
|
|
3
|
+
export const TOOL_RESULT_REPLACEMENT_HASH_ALGORITHM = 'sha256';
|
|
4
|
+
export const TOOL_RESULT_REPLACEMENT_STATE_SCHEMA_VERSION = 1;
|
|
5
|
+
const DEFAULT_MAX_ENTRIES = 256;
|
|
6
|
+
function normalizeNewlines(value) {
|
|
7
|
+
return value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
8
|
+
}
|
|
9
|
+
function canonicalize(value) {
|
|
10
|
+
if (value === null)
|
|
11
|
+
return 'null';
|
|
12
|
+
if (typeof value === 'string') {
|
|
13
|
+
return JSON.stringify(normalizeNewlines(value));
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'number') {
|
|
16
|
+
if (!Number.isFinite(value))
|
|
17
|
+
return JSON.stringify(String(value));
|
|
18
|
+
return JSON.stringify(Number(value));
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'boolean')
|
|
21
|
+
return value ? 'true' : 'false';
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return `[${value.map((item) => canonicalize(item)).join(',')}]`;
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === 'object') {
|
|
26
|
+
const record = value;
|
|
27
|
+
const keys = Object.keys(record).sort();
|
|
28
|
+
const pairs = keys.map((key) => `${JSON.stringify(key)}:${canonicalize(record[key])}`);
|
|
29
|
+
return `{${pairs.join(',')}}`;
|
|
30
|
+
}
|
|
31
|
+
return JSON.stringify(String(value));
|
|
32
|
+
}
|
|
33
|
+
export function createToolResultIdentity(params) {
|
|
34
|
+
const payloadBytes = canonicalize(params.payload);
|
|
35
|
+
const hash = createHash(TOOL_RESULT_REPLACEMENT_HASH_ALGORITHM);
|
|
36
|
+
hash.update(TOOL_RESULT_REPLACEMENT_IDENTITY_VERSION);
|
|
37
|
+
hash.update('\n');
|
|
38
|
+
hash.update(normalizeNewlines(params.canonicalToolCallIdentity).trim());
|
|
39
|
+
hash.update('\n');
|
|
40
|
+
hash.update(payloadBytes);
|
|
41
|
+
return hash.digest('hex');
|
|
42
|
+
}
|
|
43
|
+
function isValidEntry(value) {
|
|
44
|
+
if (!value || typeof value !== 'object')
|
|
45
|
+
return false;
|
|
46
|
+
const entry = value;
|
|
47
|
+
if (!entry.toolResultId || typeof entry.toolResultId !== 'string')
|
|
48
|
+
return false;
|
|
49
|
+
if (entry.decision !== 'kept' && entry.decision !== 'replaced')
|
|
50
|
+
return false;
|
|
51
|
+
if (typeof entry.preview !== 'string')
|
|
52
|
+
return false;
|
|
53
|
+
if (typeof entry.frozenAt !== 'number' || !Number.isFinite(entry.frozenAt))
|
|
54
|
+
return false;
|
|
55
|
+
if (entry.sourceArtifactHandle !== undefined && typeof entry.sourceArtifactHandle !== 'string') {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (entry.identityVersion !== TOOL_RESULT_REPLACEMENT_IDENTITY_VERSION)
|
|
59
|
+
return false;
|
|
60
|
+
if (entry.hashAlgorithm !== TOOL_RESULT_REPLACEMENT_HASH_ALGORITHM)
|
|
61
|
+
return false;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
export function normalizeToolResultReplacementState(state) {
|
|
65
|
+
if (!state || typeof state !== 'object')
|
|
66
|
+
return undefined;
|
|
67
|
+
if (state.schemaVersion !== TOOL_RESULT_REPLACEMENT_STATE_SCHEMA_VERSION)
|
|
68
|
+
return undefined;
|
|
69
|
+
if (!state.entries || typeof state.entries !== 'object')
|
|
70
|
+
return undefined;
|
|
71
|
+
const normalizedEntries = {};
|
|
72
|
+
for (const [key, value] of Object.entries(state.entries)) {
|
|
73
|
+
if (!isValidEntry(value))
|
|
74
|
+
continue;
|
|
75
|
+
if (value.toolResultId !== key)
|
|
76
|
+
continue;
|
|
77
|
+
normalizedEntries[key] = {
|
|
78
|
+
toolResultId: value.toolResultId,
|
|
79
|
+
decision: value.decision,
|
|
80
|
+
preview: value.preview,
|
|
81
|
+
frozenAt: value.frozenAt,
|
|
82
|
+
sourceArtifactHandle: value.sourceArtifactHandle,
|
|
83
|
+
identityVersion: value.identityVersion,
|
|
84
|
+
hashAlgorithm: value.hashAlgorithm,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (Object.keys(normalizedEntries).length === 0)
|
|
88
|
+
return undefined;
|
|
89
|
+
return {
|
|
90
|
+
schemaVersion: TOOL_RESULT_REPLACEMENT_STATE_SCHEMA_VERSION,
|
|
91
|
+
entries: normalizedEntries,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function createEmptyToolResultReplacementState() {
|
|
95
|
+
return {
|
|
96
|
+
schemaVersion: TOOL_RESULT_REPLACEMENT_STATE_SCHEMA_VERSION,
|
|
97
|
+
entries: {},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function freezeToolResultReplacementDecision(state, entry, options) {
|
|
101
|
+
const base = normalizeToolResultReplacementState(state) ?? createEmptyToolResultReplacementState();
|
|
102
|
+
const existing = base.entries[entry.toolResultId];
|
|
103
|
+
if (existing) {
|
|
104
|
+
return base;
|
|
105
|
+
}
|
|
106
|
+
const nextEntries = {
|
|
107
|
+
...base.entries,
|
|
108
|
+
[entry.toolResultId]: {
|
|
109
|
+
toolResultId: entry.toolResultId,
|
|
110
|
+
decision: entry.decision,
|
|
111
|
+
preview: entry.preview,
|
|
112
|
+
frozenAt: entry.frozenAt ?? Date.now(),
|
|
113
|
+
sourceArtifactHandle: entry.sourceArtifactHandle,
|
|
114
|
+
identityVersion: TOOL_RESULT_REPLACEMENT_IDENTITY_VERSION,
|
|
115
|
+
hashAlgorithm: TOOL_RESULT_REPLACEMENT_HASH_ALGORITHM,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
const maxEntries = Math.max(1, options?.maxEntries ?? DEFAULT_MAX_ENTRIES);
|
|
119
|
+
const keys = Object.keys(nextEntries);
|
|
120
|
+
if (keys.length > maxEntries) {
|
|
121
|
+
const sorted = keys.sort((a, b) => nextEntries[a].frozenAt - nextEntries[b].frozenAt);
|
|
122
|
+
for (const evict of sorted.slice(0, keys.length - maxEntries)) {
|
|
123
|
+
delete nextEntries[evict];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
schemaVersion: TOOL_RESULT_REPLACEMENT_STATE_SCHEMA_VERSION,
|
|
128
|
+
entries: nextEntries,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=replacement-state.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { loadRawArchiveStateStage } from './stages/load-raw-archive-state.js';
|
|
3
|
+
import { reattachRuntimeStateStage } from './stages/reattach-runtime-state.js';
|
|
4
|
+
import { recoverOrphanedBranchesStage } from './stages/recover-orphaned-branches.js';
|
|
5
|
+
import { relinkBoundaryAndTailStage } from './stages/relink-boundary-and-tail.js';
|
|
6
|
+
import { replayStartupHooksStage } from './stages/replay-startup-hooks.js';
|
|
7
|
+
import { rescueStaleMetadataStage } from './stages/rescue-stale-metadata.js';
|
|
8
|
+
export function createResumeRepairPipeline(params) {
|
|
9
|
+
const context = {
|
|
10
|
+
repoPath: params.repoPath,
|
|
11
|
+
now: params.now ?? (() => Date.now()),
|
|
12
|
+
nextId: params.nextId ?? (() => randomBytes(6).toString('hex')),
|
|
13
|
+
startupHooks: params.startupHooks ?? [],
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
async run(input) {
|
|
17
|
+
const compressed = await params.compressedStore.loadCompressed(input.filename);
|
|
18
|
+
if (!compressed) {
|
|
19
|
+
return {
|
|
20
|
+
warnings: [],
|
|
21
|
+
repairActions: [],
|
|
22
|
+
contractViolations: [
|
|
23
|
+
{
|
|
24
|
+
code: 'ARCHIVE_CORRUPTED',
|
|
25
|
+
message: `Archive "${input.archiveId}" cannot be loaded.`,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const partial = await params.compressor.decompressToSession(compressed);
|
|
31
|
+
const state = {
|
|
32
|
+
archiveId: input.archiveId,
|
|
33
|
+
filename: input.filename,
|
|
34
|
+
compressed,
|
|
35
|
+
partial,
|
|
36
|
+
session: {
|
|
37
|
+
meta: {
|
|
38
|
+
id: '',
|
|
39
|
+
name: '',
|
|
40
|
+
repoPath: context.repoPath,
|
|
41
|
+
createdAt: 0,
|
|
42
|
+
updatedAt: 0,
|
|
43
|
+
totalIterations: 0,
|
|
44
|
+
successfulIterations: 0,
|
|
45
|
+
totalTokens: { input: 0, output: 0 },
|
|
46
|
+
snapshots: [],
|
|
47
|
+
},
|
|
48
|
+
messages: [],
|
|
49
|
+
iterations: [],
|
|
50
|
+
},
|
|
51
|
+
warnings: [],
|
|
52
|
+
repairActions: [],
|
|
53
|
+
contractViolations: [],
|
|
54
|
+
};
|
|
55
|
+
const stages = [
|
|
56
|
+
loadRawArchiveStateStage,
|
|
57
|
+
rescueStaleMetadataStage,
|
|
58
|
+
relinkBoundaryAndTailStage,
|
|
59
|
+
recoverOrphanedBranchesStage,
|
|
60
|
+
reattachRuntimeStateStage,
|
|
61
|
+
replayStartupHooksStage,
|
|
62
|
+
];
|
|
63
|
+
for (const stage of stages) {
|
|
64
|
+
await stage(state, context);
|
|
65
|
+
if (state.contractViolations.length > 0) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
session: state.contractViolations.length > 0 ? undefined : state.session,
|
|
71
|
+
replacementState: state.replacementState,
|
|
72
|
+
warnings: state.warnings,
|
|
73
|
+
repairActions: state.repairActions,
|
|
74
|
+
contractViolations: state.contractViolations,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { parseFlowMode } from '../../../types/flow-mode.js';
|
|
2
|
+
import { normalizeSessionArtifactState } from '../../artifact-state.js';
|
|
3
|
+
import { normalizeToolResultReplacementState } from '../../replacement-state.js';
|
|
4
|
+
export const loadRawArchiveStateStage = async (state, context) => {
|
|
5
|
+
const partial = state.partial;
|
|
6
|
+
const flowMode = parseFlowMode(partial.meta.chatState?.flowMode);
|
|
7
|
+
const reconstructed = {
|
|
8
|
+
meta: {
|
|
9
|
+
id: partial.meta.id,
|
|
10
|
+
name: partial.meta.name,
|
|
11
|
+
repoPath: context.repoPath,
|
|
12
|
+
createdAt: partial.meta.createdAt,
|
|
13
|
+
updatedAt: context.now(),
|
|
14
|
+
totalIterations: partial.meta.totalIterations ?? partial.iterations.length,
|
|
15
|
+
successfulIterations: partial.meta.successfulIterations ?? 0,
|
|
16
|
+
totalTokens: partial.meta.totalTokens ?? { input: 0, output: 0 },
|
|
17
|
+
snapshots: [],
|
|
18
|
+
chatState: flowMode ? { flowMode } : undefined,
|
|
19
|
+
artifactState: normalizeSessionArtifactState(partial.meta.artifactState),
|
|
20
|
+
replacementState: normalizeToolResultReplacementState(partial.meta.replacementState),
|
|
21
|
+
},
|
|
22
|
+
messages: partial.messages.map((message, index) => ({
|
|
23
|
+
id: `restored-msg-${index}`,
|
|
24
|
+
role: message.role,
|
|
25
|
+
content: message.content,
|
|
26
|
+
timestamp: message.timestamp,
|
|
27
|
+
})),
|
|
28
|
+
iterations: partial.iterations.map((iteration, index) => ({
|
|
29
|
+
id: iteration.id || `restored-iter-${index + 1}`,
|
|
30
|
+
attempt: index + 1,
|
|
31
|
+
plan: null,
|
|
32
|
+
patch: null,
|
|
33
|
+
error: iteration.outcome === 'failure' ? iteration.summary : undefined,
|
|
34
|
+
contextSummary: iteration.summary,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
state.session = reconstructed;
|
|
38
|
+
state.replacementState = reconstructed.meta.replacementState;
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=load-raw-archive-state.js.map
|