elepha 0.1.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/LICENSE.md +375 -0
- package/README.md +135 -0
- package/bin/elepha.js +2 -0
- package/dist/adapters/base.js +600 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude-code.js +260 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.js +499 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/discriminators.js +39 -0
- package/dist/adapters/discriminators.js.map +1 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/internal-command.js +3 -0
- package/dist/adapters/internal-command.js.map +1 -0
- package/dist/cli/backup-wizard.js +97 -0
- package/dist/cli/backup-wizard.js.map +1 -0
- package/dist/cli/capture-service.js +80 -0
- package/dist/cli/capture-service.js.map +1 -0
- package/dist/cli/commands/backfills.js +336 -0
- package/dist/cli/commands/backfills.js.map +1 -0
- package/dist/cli/commands/backup.js +234 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/config.js +66 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/consent.js +237 -0
- package/dist/cli/commands/consent.js.map +1 -0
- package/dist/cli/commands/daemon-control.js +76 -0
- package/dist/cli/commands/daemon-control.js.map +1 -0
- package/dist/cli/commands/doctor.js +31 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/hook.js +24 -0
- package/dist/cli/commands/hook.js.map +1 -0
- package/dist/cli/commands/import.js +611 -0
- package/dist/cli/commands/import.js.map +1 -0
- package/dist/cli/commands/init.js +19 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/inspect.js +54 -0
- package/dist/cli/commands/inspect.js.map +1 -0
- package/dist/cli/commands/install.js +49 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/internal.js +16 -0
- package/dist/cli/commands/internal.js.map +1 -0
- package/dist/cli/commands/mcp.js +12 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/projects.js +26 -0
- package/dist/cli/commands/projects.js.map +1 -0
- package/dist/cli/commands/purge.js +216 -0
- package/dist/cli/commands/purge.js.map +1 -0
- package/dist/cli/commands/reingest.js +80 -0
- package/dist/cli/commands/reingest.js.map +1 -0
- package/dist/cli/commands/rekey.js +58 -0
- package/dist/cli/commands/rekey.js.map +1 -0
- package/dist/cli/commands/restore.js +376 -0
- package/dist/cli/commands/restore.js.map +1 -0
- package/dist/cli/commands/rollup.js +130 -0
- package/dist/cli/commands/rollup.js.map +1 -0
- package/dist/cli/commands/sanitize.js +72 -0
- package/dist/cli/commands/sanitize.js.map +1 -0
- package/dist/cli/commands/segment.js +153 -0
- package/dist/cli/commands/segment.js.map +1 -0
- package/dist/cli/commands/self-update.js +26 -0
- package/dist/cli/commands/self-update.js.map +1 -0
- package/dist/cli/commands/start.js +53 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/stats.js +42 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/status.js +73 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/uninstall.js +23 -0
- package/dist/cli/commands/uninstall.js.map +1 -0
- package/dist/cli/config-wizard.js +104 -0
- package/dist/cli/config-wizard.js.map +1 -0
- package/dist/cli/destructive-op.js +32 -0
- package/dist/cli/destructive-op.js.map +1 -0
- package/dist/cli/import-wizard.js +89 -0
- package/dist/cli/import-wizard.js.map +1 -0
- package/dist/cli/index.js +74 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-wizard.js +80 -0
- package/dist/cli/init-wizard.js.map +1 -0
- package/dist/cli/init.js +159 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/launcher-probe.js +71 -0
- package/dist/cli/launcher-probe.js.map +1 -0
- package/dist/cli/project-path.js +16 -0
- package/dist/cli/project-path.js.map +1 -0
- package/dist/cli/purge-wizard.js +200 -0
- package/dist/cli/purge-wizard.js.map +1 -0
- package/dist/cli/restore-wizard.js +101 -0
- package/dist/cli/restore-wizard.js.map +1 -0
- package/dist/cli/shared.js +128 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/status.js +22 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/wordmark.js +15 -0
- package/dist/cli/wordmark.js.map +1 -0
- package/dist/config/constants.js +115 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/env.js +168 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/memory-config.js +63 -0
- package/dist/config/memory-config.js.map +1 -0
- package/dist/config/paths.js +343 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/settings.js +132 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/config/version.js +12 -0
- package/dist/config/version.js.map +1 -0
- package/dist/daemon/failure-window.js +38 -0
- package/dist/daemon/failure-window.js.map +1 -0
- package/dist/daemon/heartbeat.js +40 -0
- package/dist/daemon/heartbeat.js.map +1 -0
- package/dist/daemon/index.js +873 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/log-rotation.js +27 -0
- package/dist/daemon/log-rotation.js.map +1 -0
- package/dist/daemon/readability-guard.js +89 -0
- package/dist/daemon/readability-guard.js.map +1 -0
- package/dist/daemon/rollup-service.js +239 -0
- package/dist/daemon/rollup-service.js.map +1 -0
- package/dist/daemon/update-check.js +87 -0
- package/dist/daemon/update-check.js.map +1 -0
- package/dist/daemon/work-queue.js +28 -0
- package/dist/daemon/work-queue.js.map +1 -0
- package/dist/discovery/session-projects.js +242 -0
- package/dist/discovery/session-projects.js.map +1 -0
- package/dist/hooks/codex-trust.js +80 -0
- package/dist/hooks/codex-trust.js.map +1 -0
- package/dist/hooks/common.js +81 -0
- package/dist/hooks/common.js.map +1 -0
- package/dist/hooks/hook-log.js +38 -0
- package/dist/hooks/hook-log.js.map +1 -0
- package/dist/hooks/installer.js +200 -0
- package/dist/hooks/installer.js.map +1 -0
- package/dist/hooks/session-start.js +321 -0
- package/dist/hooks/session-start.js.map +1 -0
- package/dist/hooks/user-prompt-submit.js +248 -0
- package/dist/hooks/user-prompt-submit.js.map +1 -0
- package/dist/install/binary.js +72 -0
- package/dist/install/binary.js.map +1 -0
- package/dist/install/config-file.js +119 -0
- package/dist/install/config-file.js.map +1 -0
- package/dist/install/daemon-health.js +48 -0
- package/dist/install/daemon-health.js.map +1 -0
- package/dist/install/daemon-service.js +309 -0
- package/dist/install/daemon-service.js.map +1 -0
- package/dist/install/doctor.js +180 -0
- package/dist/install/doctor.js.map +1 -0
- package/dist/install/health-checks.js +90 -0
- package/dist/install/health-checks.js.map +1 -0
- package/dist/install/installer.js +411 -0
- package/dist/install/installer.js.map +1 -0
- package/dist/install/launchd-backend.js +261 -0
- package/dist/install/launchd-backend.js.map +1 -0
- package/dist/install/launcher.js +224 -0
- package/dist/install/launcher.js.map +1 -0
- package/dist/install/markers.js +14 -0
- package/dist/install/markers.js.map +1 -0
- package/dist/install/platform.js +29 -0
- package/dist/install/platform.js.map +1 -0
- package/dist/install/present-tools.js +16 -0
- package/dist/install/present-tools.js.map +1 -0
- package/dist/install/self-update.js +120 -0
- package/dist/install/self-update.js.map +1 -0
- package/dist/install/service-backend.js +36 -0
- package/dist/install/service-backend.js.map +1 -0
- package/dist/install/service-manifest.js +36 -0
- package/dist/install/service-manifest.js.map +1 -0
- package/dist/install/status.js +58 -0
- package/dist/install/status.js.map +1 -0
- package/dist/install/systemd-backend.js +225 -0
- package/dist/install/systemd-backend.js.map +1 -0
- package/dist/markers.js +5 -0
- package/dist/markers.js.map +1 -0
- package/dist/mcp/installer.js +131 -0
- package/dist/mcp/installer.js.map +1 -0
- package/dist/mcp/server.js +81 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/get-context.js +3 -0
- package/dist/mcp/tools/get-context.js.map +1 -0
- package/dist/mcp/tools/list-projects.js +3 -0
- package/dist/mcp/tools/list-projects.js.map +1 -0
- package/dist/mcp/tools/save-memory.js +3 -0
- package/dist/mcp/tools/save-memory.js.map +1 -0
- package/dist/mcp/tools.js +288 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/rendering/raw-turn-renderer.js +85 -0
- package/dist/rendering/raw-turn-renderer.js.map +1 -0
- package/dist/security/file-permissions.js +15 -0
- package/dist/security/file-permissions.js.map +1 -0
- package/dist/security/provider-transcript.js +74 -0
- package/dist/security/provider-transcript.js.map +1 -0
- package/dist/security/sanitize.js +210 -0
- package/dist/security/sanitize.js.map +1 -0
- package/dist/security/self-ingestion.js +47 -0
- package/dist/security/self-ingestion.js.map +1 -0
- package/dist/security/sentinel.js +19 -0
- package/dist/security/sentinel.js.map +1 -0
- package/dist/security/subprocess-allowlist.js +421 -0
- package/dist/security/subprocess-allowlist.js.map +1 -0
- package/dist/serving/instructions.js +33 -0
- package/dist/serving/instructions.js.map +1 -0
- package/dist/serving/lexical-recall.js +249 -0
- package/dist/serving/lexical-recall.js.map +1 -0
- package/dist/serving/session-reader.js +302 -0
- package/dist/serving/session-reader.js.map +1 -0
- package/dist/storage/backfill-runner.js +34 -0
- package/dist/storage/backfill-runner.js.map +1 -0
- package/dist/storage/backup.js +46 -0
- package/dist/storage/backup.js.map +1 -0
- package/dist/storage/candidate-validator.js +77 -0
- package/dist/storage/candidate-validator.js.map +1 -0
- package/dist/storage/consent-store.js +198 -0
- package/dist/storage/consent-store.js.map +1 -0
- package/dist/storage/custom-title-backfill.js +68 -0
- package/dist/storage/custom-title-backfill.js.map +1 -0
- package/dist/storage/db.js +312 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/external-agent-import-purge.js +182 -0
- package/dist/storage/external-agent-import-purge.js.map +1 -0
- package/dist/storage/first-prompt-search-backfill.js +76 -0
- package/dist/storage/first-prompt-search-backfill.js.map +1 -0
- package/dist/storage/first-prompt-search.js +7 -0
- package/dist/storage/first-prompt-search.js.map +1 -0
- package/dist/storage/injection-store.js +37 -0
- package/dist/storage/injection-store.js.map +1 -0
- package/dist/storage/memory-store.js +428 -0
- package/dist/storage/memory-store.js.map +1 -0
- package/dist/storage/project-resolver.js +294 -0
- package/dist/storage/project-resolver.js.map +1 -0
- package/dist/storage/project-store.js +104 -0
- package/dist/storage/project-store.js.map +1 -0
- package/dist/storage/rendered-chars-backfill.js +85 -0
- package/dist/storage/rendered-chars-backfill.js.map +1 -0
- package/dist/storage/resegmentation.js +582 -0
- package/dist/storage/resegmentation.js.map +1 -0
- package/dist/storage/rollup-store.js +246 -0
- package/dist/storage/rollup-store.js.map +1 -0
- package/dist/storage/root-commit-backfill.js +32 -0
- package/dist/storage/root-commit-backfill.js.map +1 -0
- package/dist/storage/sanitize-backfill.js +184 -0
- package/dist/storage/sanitize-backfill.js.map +1 -0
- package/dist/storage/segmentation.js +87 -0
- package/dist/storage/segmentation.js.map +1 -0
- package/dist/storage/session-fields-backfill.js +196 -0
- package/dist/storage/session-fields-backfill.js.map +1 -0
- package/dist/storage/session-read-model.js +130 -0
- package/dist/storage/session-read-model.js.map +1 -0
- package/dist/storage/session-store.js +200 -0
- package/dist/storage/session-store.js.map +1 -0
- package/dist/storage/session-title-backfill.js +75 -0
- package/dist/storage/session-title-backfill.js.map +1 -0
- package/dist/storage/session-title.js +71 -0
- package/dist/storage/session-title.js.map +1 -0
- package/dist/storage/shown-session-list-store.js +33 -0
- package/dist/storage/shown-session-list-store.js.map +1 -0
- package/dist/storage/stats.js +30 -0
- package/dist/storage/stats.js.map +1 -0
- package/dist/storage/turn-store.js +202 -0
- package/dist/storage/turn-store.js.map +1 -0
- package/dist/storage/ulid.js +17 -0
- package/dist/storage/ulid.js.map +1 -0
- package/dist/summarizer/anthropic-call.js +76 -0
- package/dist/summarizer/anthropic-call.js.map +1 -0
- package/dist/summarizer/call-log.js +120 -0
- package/dist/summarizer/call-log.js.map +1 -0
- package/dist/summarizer/haiku-provider.js +136 -0
- package/dist/summarizer/haiku-provider.js.map +1 -0
- package/dist/summarizer/pricing.js +8 -0
- package/dist/summarizer/pricing.js.map +1 -0
- package/dist/summarizer/prompt-cache.js +6 -0
- package/dist/summarizer/prompt-cache.js.map +1 -0
- package/dist/summarizer/prompt.js +47 -0
- package/dist/summarizer/prompt.js.map +1 -0
- package/dist/summarizer/provider-config.js +25 -0
- package/dist/summarizer/provider-config.js.map +1 -0
- package/dist/summarizer/rollup-prompt.js +225 -0
- package/dist/summarizer/rollup-prompt.js.map +1 -0
- package/dist/summarizer/rollup-provider.js +224 -0
- package/dist/summarizer/rollup-provider.js.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/util/error.js +5 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/fs.js +97 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/relative-time.js +12 -0
- package/dist/util/relative-time.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-control.js","sourceRoot":"","sources":["../../../src/cli/commands/daemon-control.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { runDoctor } from '../../install/doctor.js';
|
|
2
|
+
import { ConsentStore } from '../../storage/consent-store.js';
|
|
3
|
+
import { openDb } from '../../storage/db.js';
|
|
4
|
+
export function registerDoctor(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('doctor')
|
|
7
|
+
.description('Diagnose capture recovery prerequisites and restart only a down or stuck daemon')
|
|
8
|
+
.action(() => {
|
|
9
|
+
let approvedRoots = 0;
|
|
10
|
+
let databaseError;
|
|
11
|
+
try {
|
|
12
|
+
approvedRoots = new ConsentStore(openDb()).countApproved();
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
databaseError = error;
|
|
16
|
+
}
|
|
17
|
+
const result = runDoctor({
|
|
18
|
+
approvedRoots,
|
|
19
|
+
inspectDatabase: databaseError
|
|
20
|
+
? () => {
|
|
21
|
+
throw databaseError;
|
|
22
|
+
}
|
|
23
|
+
: undefined,
|
|
24
|
+
});
|
|
25
|
+
for (const line of result.lines) {
|
|
26
|
+
console.log(line);
|
|
27
|
+
}
|
|
28
|
+
process.exitCode = result.exitCode;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC3C,OAAO;SACF,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,iFAAiF,CAAC;SAC9F,MAAM,CAAC,GAAG,EAAE;QACT,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,IAAI,aAAsB,CAAC;QAC3B,IAAI,CAAC;YACD,aAAa,GAAG,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,aAAa,GAAG,KAAK,CAAC;QAC1B,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC;YACrB,aAAa;YACb,eAAe,EAAE,aAAa;gBAC1B,CAAC,CAAC,GAAG,EAAE;oBACD,MAAM,aAAa,CAAC;gBACxB,CAAC;gBACH,CAAC,CAAC,SAAS;SAClB,CAAC,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACvC,CAAC,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { runSessionStartCli } from '../../hooks/session-start.js';
|
|
2
|
+
import { runUserPromptSubmitCli } from '../../hooks/user-prompt-submit.js';
|
|
3
|
+
export function registerHook(program) {
|
|
4
|
+
const hook = program.command('hook', { hidden: true }).description('Install and execute bounded elepha hooks');
|
|
5
|
+
hook.command('session-start')
|
|
6
|
+
.requiredOption('--tool <tool>', 'claude-code or codex')
|
|
7
|
+
.action(async (opts) => {
|
|
8
|
+
if (opts.tool !== 'claude-code' && opts.tool !== 'codex') {
|
|
9
|
+
process.exitCode = 0;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
await runSessionStartCli(opts.tool);
|
|
13
|
+
});
|
|
14
|
+
hook.command('user-prompt-submit')
|
|
15
|
+
.requiredOption('--tool <tool>', 'claude-code or codex')
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
if (opts.tool !== 'claude-code' && opts.tool !== 'codex') {
|
|
18
|
+
process.exitCode = 0;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
await runUserPromptSubmitCli(opts.tool);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../src/cli/commands/hook.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAG3E,MAAM,UAAU,YAAY,CAAC,OAAgB;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,CAAC,0CAA0C,CAAC,CAAC;IAC/G,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC;SACxB,cAAc,CAAC,eAAe,EAAE,sBAAsB,CAAC;SACvD,MAAM,CAAC,KAAK,EAAE,IAAwB,EAAE,EAAE;QACvC,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACvD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACX,CAAC;QACD,MAAM,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IACP,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC;SAC7B,cAAc,CAAC,eAAe,EAAE,sBAAsB,CAAC;SACvD,MAAM,CAAC,KAAK,EAAE,IAAwB,EAAE,EAAE;QACvC,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACvD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACX,CAAC;QACD,MAAM,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { canonicalizeExisting, isWithinProviderStore, normalizeForCompare } from '../../config/paths.js';
|
|
4
|
+
import { readSessionMetadata } from '../../discovery/session-projects.js';
|
|
5
|
+
import { daemonHealth as currentDaemonHealth } from '../../install/health-checks.js';
|
|
6
|
+
import { stripShellSyntax } from '../../security/sanitize.js';
|
|
7
|
+
import { writeBackup } from '../../storage/backup.js';
|
|
8
|
+
import { validateCandidateSemantics } from '../../storage/candidate-validator.js';
|
|
9
|
+
import { defaultDbPath } from '../../storage/db.js';
|
|
10
|
+
import { firstPromptSearch } from '../../storage/first-prompt-search.js';
|
|
11
|
+
import { MemoryStore } from '../../storage/memory-store.js';
|
|
12
|
+
import { ProjectResolver } from '../../storage/project-resolver.js';
|
|
13
|
+
import { sanitizeRollupDecisionsField, sanitizeRollupDisplayField, sanitizeRollupPendingItemsField, } from '../../storage/sanitize-backfill.js';
|
|
14
|
+
import { hydrateTurnDecisions, sanitizeTurnDecision } from '../../storage/turn-store.js';
|
|
15
|
+
import { errorMessage } from '../../util/error.js';
|
|
16
|
+
import { runImportWizard } from '../import-wizard.js';
|
|
17
|
+
import { confirmYesNo } from '../shared.js';
|
|
18
|
+
export const IMPORTED_TABLES = ['projects', 'sessions', 'memories', 'session_rollups'];
|
|
19
|
+
class ImportApplyError extends Error {
|
|
20
|
+
snapshotPath;
|
|
21
|
+
constructor(snapshotPath, cause) {
|
|
22
|
+
super(`Import failed after saving the pre-import snapshot to ${snapshotPath}: ${errorMessage(cause)}`);
|
|
23
|
+
this.snapshotPath = snapshotPath;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const REQUIRED_COLUMNS = {
|
|
27
|
+
projects: ['id', 'path', 'display_name', 'git_root', 'git_remote', 'git_root_commit', 'first_seen_at', 'last_seen_at'],
|
|
28
|
+
sessions: [
|
|
29
|
+
'id',
|
|
30
|
+
'tool',
|
|
31
|
+
'native_id',
|
|
32
|
+
'segment_index',
|
|
33
|
+
'project_id',
|
|
34
|
+
'source_path',
|
|
35
|
+
'cursor',
|
|
36
|
+
'started_at',
|
|
37
|
+
'last_ingested_at',
|
|
38
|
+
'surface',
|
|
39
|
+
'git_branch',
|
|
40
|
+
'kind',
|
|
41
|
+
'last_turn_at',
|
|
42
|
+
'trailing_branch',
|
|
43
|
+
'trailing_files',
|
|
44
|
+
'rendered_chars',
|
|
45
|
+
'rendered_turns',
|
|
46
|
+
'title',
|
|
47
|
+
'custom_title',
|
|
48
|
+
'git_commit_count',
|
|
49
|
+
],
|
|
50
|
+
memories: [
|
|
51
|
+
'id',
|
|
52
|
+
'project_id',
|
|
53
|
+
'session_id',
|
|
54
|
+
'turn_index',
|
|
55
|
+
'tool',
|
|
56
|
+
'turn_started_at',
|
|
57
|
+
'decisions',
|
|
58
|
+
'files_touched',
|
|
59
|
+
'pending_items',
|
|
60
|
+
'superseded_at',
|
|
61
|
+
'created_at',
|
|
62
|
+
'summarizer_status',
|
|
63
|
+
'reingested_at',
|
|
64
|
+
'has_external_content',
|
|
65
|
+
],
|
|
66
|
+
session_rollups: [
|
|
67
|
+
'session_id',
|
|
68
|
+
'project_id',
|
|
69
|
+
'tool',
|
|
70
|
+
'title',
|
|
71
|
+
'summary',
|
|
72
|
+
'decisions',
|
|
73
|
+
'pending_items',
|
|
74
|
+
'files_touched',
|
|
75
|
+
'turn_count',
|
|
76
|
+
'started_at',
|
|
77
|
+
'ended_at',
|
|
78
|
+
'kind',
|
|
79
|
+
'parent_session_id',
|
|
80
|
+
'summarizer_status',
|
|
81
|
+
'rollup_state',
|
|
82
|
+
'rolled_up_through_turn_index',
|
|
83
|
+
'computed_at',
|
|
84
|
+
'rollup_version',
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
const SESSION_WRITE_COLUMNS = [...REQUIRED_COLUMNS.sessions.filter((column) => column !== 'id'), 'first_prompt_search'];
|
|
88
|
+
const MEMORY_WRITE_COLUMNS = REQUIRED_COLUMNS.memories.filter((column) => column !== 'id');
|
|
89
|
+
const ROLLUP_WRITE_COLUMNS = REQUIRED_COLUMNS.session_rollups;
|
|
90
|
+
function tableNames(db) {
|
|
91
|
+
return new Set(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => row.name));
|
|
92
|
+
}
|
|
93
|
+
function tableColumns(db, table) {
|
|
94
|
+
return new Set(db.pragma(`table_info(${table})`).map((column) => column.name));
|
|
95
|
+
}
|
|
96
|
+
function validateCandidate(db) {
|
|
97
|
+
const tables = tableNames(db);
|
|
98
|
+
const missingTables = IMPORTED_TABLES.filter((table) => !tables.has(table));
|
|
99
|
+
if (missingTables.length > 0) {
|
|
100
|
+
throw new Error(`Backup is incomplete (missing required table(s): ${missingTables.join(', ')}).`);
|
|
101
|
+
}
|
|
102
|
+
for (const table of IMPORTED_TABLES) {
|
|
103
|
+
const columns = tableColumns(db, table);
|
|
104
|
+
const missingColumns = REQUIRED_COLUMNS[table].filter((column) => !columns.has(column));
|
|
105
|
+
if (missingColumns.length > 0) {
|
|
106
|
+
throw new Error(`Backup is incompatible (${table} is missing required column(s): ${missingColumns.join(', ')}).`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const semanticViolations = validateCandidateSemantics(db, ['sessions', 'memories', 'session_rollups']);
|
|
110
|
+
if (semanticViolations.length > 0) {
|
|
111
|
+
throw new Error(`Backup is semantically invalid: ${semanticViolations.join('; ')}`);
|
|
112
|
+
}
|
|
113
|
+
const integrity = db.pragma('integrity_check');
|
|
114
|
+
if (integrity.length !== 1 || integrity[0]?.integrity_check !== 'ok') {
|
|
115
|
+
throw new Error(`Backup failed integrity_check: ${integrity.map((row) => row.integrity_check).join('; ')}`);
|
|
116
|
+
}
|
|
117
|
+
const foreignKeys = db.pragma('foreign_key_check');
|
|
118
|
+
if (foreignKeys.length > 0) {
|
|
119
|
+
throw new Error(`Backup failed foreign_key_check with ${foreignKeys.length} violation(s).`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function openCandidate(candidatePath) {
|
|
123
|
+
let candidate;
|
|
124
|
+
try {
|
|
125
|
+
candidate = new Database(candidatePath, { readonly: true, fileMustExist: true });
|
|
126
|
+
candidate.pragma('query_only = ON');
|
|
127
|
+
candidate.exec('BEGIN');
|
|
128
|
+
validateCandidate(candidate);
|
|
129
|
+
return candidate;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
candidate?.close();
|
|
133
|
+
if (error instanceof Error && (error.message.startsWith('Backup is ') || error.message.startsWith('Backup failed '))) {
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`Not a valid SQLite backup at ${candidatePath}: ${errorMessage(error)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function identityCandidates(project) {
|
|
140
|
+
const candidates = [];
|
|
141
|
+
if (project.git_remote) {
|
|
142
|
+
candidates.push({ key: `remote:${project.git_remote}`, rank: 0 });
|
|
143
|
+
}
|
|
144
|
+
if (project.git_root_commit) {
|
|
145
|
+
candidates.push({ key: `commit:${project.git_root_commit}`, rank: 1 });
|
|
146
|
+
}
|
|
147
|
+
if (project.git_root) {
|
|
148
|
+
candidates.push({ key: `path:${normalizeForCompare(project.git_root)}`, rank: 2 });
|
|
149
|
+
}
|
|
150
|
+
candidates.push({ key: `path:${normalizeForCompare(project.path)}`, rank: 3 });
|
|
151
|
+
return candidates.filter((candidate, index) => candidates.findIndex((other) => other.key === candidate.key) === index);
|
|
152
|
+
}
|
|
153
|
+
function identityKeys(project) {
|
|
154
|
+
return identityCandidates(project).map((candidate) => candidate.key);
|
|
155
|
+
}
|
|
156
|
+
function isImportSourceWithinProviderStore(tool, sourcePath) {
|
|
157
|
+
if (typeof sourcePath !== 'string') {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return isWithinProviderStore(tool, sourcePath);
|
|
161
|
+
}
|
|
162
|
+
function groupBackupProjects(projects) {
|
|
163
|
+
const parent = new Map(projects.map((project) => [project.id, project.id]));
|
|
164
|
+
const ownerByIdentity = new Map();
|
|
165
|
+
const find = (id) => {
|
|
166
|
+
const owner = parent.get(id);
|
|
167
|
+
if (owner === undefined || owner === id) {
|
|
168
|
+
return id;
|
|
169
|
+
}
|
|
170
|
+
const root = find(owner);
|
|
171
|
+
parent.set(id, root);
|
|
172
|
+
return root;
|
|
173
|
+
};
|
|
174
|
+
const union = (left, right) => {
|
|
175
|
+
const leftRoot = find(left);
|
|
176
|
+
const rightRoot = find(right);
|
|
177
|
+
if (leftRoot !== rightRoot) {
|
|
178
|
+
parent.set(rightRoot, leftRoot);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
for (const project of projects) {
|
|
182
|
+
for (const key of identityKeys(project)) {
|
|
183
|
+
const owner = ownerByIdentity.get(key);
|
|
184
|
+
if (owner === undefined) {
|
|
185
|
+
ownerByIdentity.set(key, project.id);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
union(project.id, owner);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const groups = new Map();
|
|
193
|
+
for (const project of projects) {
|
|
194
|
+
const root = find(project.id);
|
|
195
|
+
const group = groups.get(root);
|
|
196
|
+
if (group) {
|
|
197
|
+
group.push(project);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
groups.set(root, [project]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return [...groups.values()];
|
|
204
|
+
}
|
|
205
|
+
function canonicalLocalIds(db) {
|
|
206
|
+
// Imported paths are untrusted database content, so Git resolution is disabled:
|
|
207
|
+
// identity comes only from stored fields and no backup-derived path can reach a subprocess.
|
|
208
|
+
const sets = new ProjectResolver(db, { resolveGitRoot: () => null }).list();
|
|
209
|
+
const canonicalById = new Map();
|
|
210
|
+
for (const set of sets) {
|
|
211
|
+
const canonical = set.projectIds[0];
|
|
212
|
+
if (canonical === undefined) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
for (const projectId of set.projectIds) {
|
|
216
|
+
canonicalById.set(projectId, canonical);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return canonicalById;
|
|
220
|
+
}
|
|
221
|
+
function planProjects(active, candidate) {
|
|
222
|
+
const localProjects = active.prepare('SELECT * FROM projects ORDER BY id').all();
|
|
223
|
+
const backupProjects = candidate.prepare('SELECT * FROM projects ORDER BY id').all();
|
|
224
|
+
const canonicalById = canonicalLocalIds(active);
|
|
225
|
+
const localByIdentity = new Map();
|
|
226
|
+
for (const project of localProjects) {
|
|
227
|
+
const canonical = canonicalById.get(project.id) ?? project.id;
|
|
228
|
+
for (const key of identityKeys(project)) {
|
|
229
|
+
if (!localByIdentity.has(key)) {
|
|
230
|
+
localByIdentity.set(key, canonical);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return groupBackupProjects(backupProjects).map((group) => {
|
|
235
|
+
const existingLocalId = group
|
|
236
|
+
.flatMap(identityCandidates)
|
|
237
|
+
.sort((a, b) => a.rank - b.rank)
|
|
238
|
+
.map((candidate) => localByIdentity.get(candidate.key))
|
|
239
|
+
.find((localId) => localId !== undefined);
|
|
240
|
+
const representative = [...group].sort((a, b) => Number(Boolean(b.git_remote)) - Number(Boolean(a.git_remote)) ||
|
|
241
|
+
Number(Boolean(b.git_root_commit)) - Number(Boolean(a.git_root_commit)) ||
|
|
242
|
+
Number(Boolean(b.git_root)) - Number(Boolean(a.git_root)) ||
|
|
243
|
+
a.id - b.id)[0];
|
|
244
|
+
if (!representative) {
|
|
245
|
+
throw new Error('Backup project group is empty.');
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
backupProjectIds: group.map((project) => project.id),
|
|
249
|
+
representative,
|
|
250
|
+
...(existingLocalId === undefined ? {} : { existingLocalId }),
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async function buildPlan(active, candidate) {
|
|
255
|
+
const projects = planProjects(active, candidate);
|
|
256
|
+
const activeStore = new MemoryStore(active);
|
|
257
|
+
const existing = active.prepare('SELECT id FROM sessions WHERE tool = ? AND native_id = ? AND segment_index = ?');
|
|
258
|
+
const purged = active.prepare('SELECT 1 FROM purged_transcripts WHERE tool = ? AND native_id = ?');
|
|
259
|
+
const sessions = [];
|
|
260
|
+
for (const row of candidate.prepare('SELECT * FROM sessions ORDER BY id').all()) {
|
|
261
|
+
if (!isImportSourceWithinProviderStore(row.tool, row.source_path)) {
|
|
262
|
+
sessions.push({ row, disposition: 'outside_store' });
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (purged.get(row.tool, row.native_id)) {
|
|
266
|
+
sessions.push({ row, disposition: 'purged' });
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (activeStore.isTranscriptIncognito(row.tool, row.native_id)) {
|
|
270
|
+
sessions.push({ row, disposition: 'incognito' });
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const sourcePath = typeof row.source_path === 'string' ? row.source_path : undefined;
|
|
274
|
+
const metadata = sourcePath === undefined ? undefined : await readSessionMetadata(sourcePath).catch(() => undefined);
|
|
275
|
+
const canonicalCwd = metadata === undefined ? undefined : canonicalizeExisting(metadata.cwd);
|
|
276
|
+
if (canonicalCwd === undefined || activeStore.consent.consentState(canonicalCwd) !== 'approved') {
|
|
277
|
+
sessions.push({ row, disposition: 'unconsented' });
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const local = existing.get(row.tool, row.native_id, row.segment_index);
|
|
281
|
+
sessions.push(local ? { row, disposition: 'existing', localSessionId: local.id, canonicalCwd } : { row, disposition: 'new', canonicalCwd });
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
projects,
|
|
285
|
+
sessions,
|
|
286
|
+
counts: {
|
|
287
|
+
new: sessions.filter((session) => session.disposition === 'new').length,
|
|
288
|
+
existing: sessions.filter((session) => session.disposition === 'existing').length,
|
|
289
|
+
purged: sessions.filter((session) => session.disposition === 'purged').length,
|
|
290
|
+
incognito: sessions.filter((session) => session.disposition === 'incognito').length,
|
|
291
|
+
unconsented: sessions.filter((session) => session.disposition === 'unconsented').length,
|
|
292
|
+
outsideStore: sessions.filter((session) => session.disposition === 'outside_store').length,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async function readPlan(dbPath, candidate) {
|
|
297
|
+
const active = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
298
|
+
try {
|
|
299
|
+
active.pragma('query_only = ON');
|
|
300
|
+
return await buildPlan(active, candidate);
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
active.close();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function printPreview(candidatePath, overwrite, plan) {
|
|
307
|
+
console.log(`Import preview: ${candidatePath}`);
|
|
308
|
+
if (overwrite) {
|
|
309
|
+
console.log(`${plan.counts.new} new, ${plan.counts.existing} overwritten, ${plan.counts.purged} skipped (purged), ${plan.counts.incognito} skipped (incognito), ${plan.counts.unconsented} skipped (unconsented)`);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
console.log(`${plan.counts.new} new, ${plan.counts.existing} skipped (already present), ${plan.counts.purged} skipped (purged), ${plan.counts.incognito} skipped (incognito), ${plan.counts.unconsented} skipped (unconsented)`);
|
|
313
|
+
}
|
|
314
|
+
if (plan.counts.outsideStore > 0) {
|
|
315
|
+
console.error(`${plan.counts.outsideStore} skipped (transcript outside provider store)`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function insertRow(db, table, columns, row, overrides) {
|
|
319
|
+
const values = columns.map((column) => (Object.hasOwn(overrides, column) ? overrides[column] : row[column]));
|
|
320
|
+
const result = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`).run(...values);
|
|
321
|
+
return Number(result.lastInsertRowid);
|
|
322
|
+
}
|
|
323
|
+
function sanitizedImportedTitle(value) {
|
|
324
|
+
return typeof value === 'string' ? stripShellSyntax(value) : value;
|
|
325
|
+
}
|
|
326
|
+
function sanitizedImportedFirstPromptSearch(value) {
|
|
327
|
+
return typeof value === 'string' ? firstPromptSearch(value) : null;
|
|
328
|
+
}
|
|
329
|
+
function sanitizedImportedDecisions(value) {
|
|
330
|
+
return typeof value === 'string' ? JSON.stringify(hydrateTurnDecisions(value).map(sanitizeTurnDecision)) : value;
|
|
331
|
+
}
|
|
332
|
+
function sanitizedImportedPendingItems(value) {
|
|
333
|
+
if (typeof value !== 'string') {
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const parsed = JSON.parse(value);
|
|
338
|
+
return JSON.stringify(Array.isArray(parsed) ? parsed.filter((item) => typeof item === 'string').map(stripShellSyntax) : []);
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
return '[]';
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function sanitizedImportedRollupText(value) {
|
|
345
|
+
return typeof value === 'string' ? sanitizeRollupDisplayField(value) : value;
|
|
346
|
+
}
|
|
347
|
+
function sanitizedImportedRollupDecisions(value) {
|
|
348
|
+
return typeof value === 'string' ? sanitizeRollupDecisionsField(value) : value;
|
|
349
|
+
}
|
|
350
|
+
function sanitizedImportedRollupPendingItems(value) {
|
|
351
|
+
return typeof value === 'string' ? sanitizeRollupPendingItemsField(value) : value;
|
|
352
|
+
}
|
|
353
|
+
function updateSession(db, row, localSessionId, projectId) {
|
|
354
|
+
const values = SESSION_WRITE_COLUMNS.map((column) => {
|
|
355
|
+
if (column === 'project_id') {
|
|
356
|
+
return projectId;
|
|
357
|
+
}
|
|
358
|
+
if (column === 'first_prompt_search') {
|
|
359
|
+
return sanitizedImportedFirstPromptSearch(row[column]);
|
|
360
|
+
}
|
|
361
|
+
return column === 'title' || column === 'custom_title' ? sanitizedImportedTitle(row[column]) : row[column];
|
|
362
|
+
});
|
|
363
|
+
db.prepare(`UPDATE sessions SET ${SESSION_WRITE_COLUMNS.map((column) => `${column} = ?`).join(', ')} WHERE id = ?`).run(...values, localSessionId);
|
|
364
|
+
}
|
|
365
|
+
function applyProjects(db, plan, overwrite) {
|
|
366
|
+
const sessionStore = new MemoryStore(db);
|
|
367
|
+
const localByCanonicalCwd = new Map();
|
|
368
|
+
const sessionProjectIds = new Map();
|
|
369
|
+
for (const session of plan.sessions) {
|
|
370
|
+
if (session.disposition !== 'new' && !(overwrite && session.disposition === 'existing')) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const canonicalCwd = session.canonicalCwd;
|
|
374
|
+
if (canonicalCwd === undefined) {
|
|
375
|
+
throw new Error(`Importable backup session ${session.row.id} has no canonical cwd.`);
|
|
376
|
+
}
|
|
377
|
+
const projectId = localByCanonicalCwd.get(canonicalCwd) ?? sessionStore.upsertProject(canonicalCwd).id;
|
|
378
|
+
localByCanonicalCwd.set(canonicalCwd, projectId);
|
|
379
|
+
sessionProjectIds.set(session.row.id, projectId);
|
|
380
|
+
}
|
|
381
|
+
return sessionProjectIds;
|
|
382
|
+
}
|
|
383
|
+
function requiredMapping(map, backupId, label) {
|
|
384
|
+
const localId = map.get(backupId);
|
|
385
|
+
if (localId === undefined) {
|
|
386
|
+
throw new Error(`Backup ${label} ${backupId} has no local identity mapping.`);
|
|
387
|
+
}
|
|
388
|
+
return localId;
|
|
389
|
+
}
|
|
390
|
+
function assertPlanStillAuthorized(db, plan) {
|
|
391
|
+
const activeStore = new MemoryStore(db);
|
|
392
|
+
const purged = db.prepare('SELECT 1 FROM purged_transcripts WHERE tool = ? AND native_id = ?');
|
|
393
|
+
const changed = [];
|
|
394
|
+
for (const session of plan.sessions) {
|
|
395
|
+
if (session.disposition !== 'new' && session.disposition !== 'existing') {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const reasons = [];
|
|
399
|
+
if (session.canonicalCwd === undefined) {
|
|
400
|
+
reasons.push('canonical cwd missing');
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
const consent = activeStore.consent.consentState(session.canonicalCwd);
|
|
404
|
+
if (consent !== 'approved') {
|
|
405
|
+
reasons.push(`consent ${consent}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (purged.get(session.row.tool, session.row.native_id)) {
|
|
409
|
+
reasons.push('purged');
|
|
410
|
+
}
|
|
411
|
+
if (activeStore.isTranscriptIncognito(session.row.tool, session.row.native_id)) {
|
|
412
|
+
reasons.push('incognito');
|
|
413
|
+
}
|
|
414
|
+
if (reasons.length > 0) {
|
|
415
|
+
changed.push(`${session.row.tool}:${session.row.native_id}#${session.row.segment_index} (${reasons.join(', ')})`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (changed.length > 0) {
|
|
419
|
+
throw new Error(`Import authorization changed after the preview for: ${changed.join('; ')}. Nothing was imported. Re-run elepha import for a fresh preview.`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function applyMerge(db, candidate, plan, overwrite) {
|
|
423
|
+
const sessionProjectIds = applyProjects(db, plan, overwrite);
|
|
424
|
+
const sessionIds = new Map();
|
|
425
|
+
const importedSessionIds = new Set();
|
|
426
|
+
for (const session of plan.sessions) {
|
|
427
|
+
if (session.disposition === 'purged' ||
|
|
428
|
+
session.disposition === 'incognito' ||
|
|
429
|
+
session.disposition === 'unconsented' ||
|
|
430
|
+
session.disposition === 'outside_store') {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (session.disposition === 'existing') {
|
|
434
|
+
const localSessionId = session.localSessionId;
|
|
435
|
+
if (localSessionId === undefined) {
|
|
436
|
+
throw new Error(`Existing backup session ${session.row.id} has no local mapping.`);
|
|
437
|
+
}
|
|
438
|
+
sessionIds.set(session.row.id, localSessionId);
|
|
439
|
+
if (!overwrite) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
const projectId = requiredMapping(sessionProjectIds, session.row.id, 'session project');
|
|
443
|
+
db.prepare('DELETE FROM memories WHERE session_id = ?').run(localSessionId);
|
|
444
|
+
db.prepare('DELETE FROM session_rollups WHERE session_id = ?').run(localSessionId);
|
|
445
|
+
updateSession(db, session.row, localSessionId, projectId);
|
|
446
|
+
importedSessionIds.add(session.row.id);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const projectId = requiredMapping(sessionProjectIds, session.row.id, 'session project');
|
|
450
|
+
const localSessionId = insertRow(db, 'sessions', SESSION_WRITE_COLUMNS, session.row, {
|
|
451
|
+
project_id: projectId,
|
|
452
|
+
title: sanitizedImportedTitle(session.row.title),
|
|
453
|
+
custom_title: sanitizedImportedTitle(session.row.custom_title),
|
|
454
|
+
first_prompt_search: sanitizedImportedFirstPromptSearch(session.row.first_prompt_search),
|
|
455
|
+
});
|
|
456
|
+
sessionIds.set(session.row.id, localSessionId);
|
|
457
|
+
importedSessionIds.add(session.row.id);
|
|
458
|
+
}
|
|
459
|
+
const memories = candidate.prepare('SELECT * FROM memories WHERE session_id = ? ORDER BY id');
|
|
460
|
+
const rollup = candidate.prepare('SELECT * FROM session_rollups WHERE session_id = ?');
|
|
461
|
+
for (const backupSessionId of importedSessionIds) {
|
|
462
|
+
const localSessionId = requiredMapping(sessionIds, backupSessionId, 'session');
|
|
463
|
+
const projectId = requiredMapping(sessionProjectIds, backupSessionId, 'session project');
|
|
464
|
+
for (const memory of memories.iterate(backupSessionId)) {
|
|
465
|
+
insertRow(db, 'memories', MEMORY_WRITE_COLUMNS, memory, {
|
|
466
|
+
project_id: projectId,
|
|
467
|
+
session_id: localSessionId,
|
|
468
|
+
decisions: sanitizedImportedDecisions(memory.decisions),
|
|
469
|
+
pending_items: sanitizedImportedPendingItems(memory.pending_items),
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
const backupRollup = rollup.get(backupSessionId);
|
|
473
|
+
if (backupRollup) {
|
|
474
|
+
// A vetoed parent has no local mapping by design. Detaching the child
|
|
475
|
+
// preserves its portable rollup without resurrecting the tombstoned session.
|
|
476
|
+
const parentSessionId = backupRollup.parent_session_id === null ? null : (sessionIds.get(backupRollup.parent_session_id) ?? null);
|
|
477
|
+
insertRow(db, 'session_rollups', ROLLUP_WRITE_COLUMNS, backupRollup, {
|
|
478
|
+
project_id: projectId,
|
|
479
|
+
session_id: localSessionId,
|
|
480
|
+
parent_session_id: parentSessionId,
|
|
481
|
+
title: sanitizedImportedRollupText(backupRollup.title),
|
|
482
|
+
summary: sanitizedImportedRollupText(backupRollup.summary),
|
|
483
|
+
decisions: sanitizedImportedRollupDecisions(backupRollup.decisions),
|
|
484
|
+
pending_items: sanitizedImportedRollupPendingItems(backupRollup.pending_items),
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function applyImport(dbPath, candidate, plan, overwrite, snapshotWriter, beforeVerify) {
|
|
490
|
+
const active = new Database(dbPath, { fileMustExist: true });
|
|
491
|
+
let snapshotPath;
|
|
492
|
+
try {
|
|
493
|
+
active.pragma('journal_mode = WAL');
|
|
494
|
+
active.pragma('foreign_keys = ON');
|
|
495
|
+
active.pragma('wal_checkpoint(TRUNCATE)');
|
|
496
|
+
snapshotPath = snapshotWriter(active, dbPath);
|
|
497
|
+
const merge = active.transaction(() => {
|
|
498
|
+
assertPlanStillAuthorized(active, plan);
|
|
499
|
+
applyMerge(active, candidate, plan, overwrite);
|
|
500
|
+
beforeVerify?.(active);
|
|
501
|
+
const foreignKeys = active.pragma('foreign_key_check');
|
|
502
|
+
if (foreignKeys.length > 0) {
|
|
503
|
+
throw new Error(`foreign_key_check found ${foreignKeys.length} violation(s)`);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
merge.immediate();
|
|
507
|
+
return snapshotPath;
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
if (snapshotPath !== undefined) {
|
|
511
|
+
throw new ImportApplyError(snapshotPath, error);
|
|
512
|
+
}
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
finally {
|
|
516
|
+
active.close();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/** A multi-table identity-remapped merge needs one transaction spanning project resolution, FK remapping, and verification, so it cannot use the generic destructive-operation runner. */
|
|
520
|
+
export async function runImportOperation(candidatePath, overwrite, runtime = {}) {
|
|
521
|
+
const dbPath = runtime.dbPath ?? defaultDbPath();
|
|
522
|
+
const candidate = openCandidate(candidatePath);
|
|
523
|
+
try {
|
|
524
|
+
const health = (runtime.daemonHealth ?? currentDaemonHealth)();
|
|
525
|
+
if (health.healthy) {
|
|
526
|
+
throw new Error(`Refusing import while the daemon is running (${health.state}). Run elepha pause first.`);
|
|
527
|
+
}
|
|
528
|
+
if (health.state.startsWith('STUCK')) {
|
|
529
|
+
console.error(`Daemon appears stuck (${health.state}); proceeding — it is not writing.`);
|
|
530
|
+
}
|
|
531
|
+
const plan = await readPlan(dbPath, candidate);
|
|
532
|
+
printPreview(candidatePath, overwrite, plan);
|
|
533
|
+
if (runtime.confirm && !(await runtime.confirm(plan))) {
|
|
534
|
+
return {
|
|
535
|
+
cancelled: true,
|
|
536
|
+
added: 0,
|
|
537
|
+
overwritten: 0,
|
|
538
|
+
skipped: plan.counts.existing + plan.counts.purged + plan.counts.incognito + plan.counts.unconsented + plan.counts.outsideStore,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const snapshotPath = applyImport(dbPath, candidate, plan, overwrite, runtime.writeBackup ?? writeBackup, runtime.beforeVerify);
|
|
542
|
+
const overwritten = overwrite ? plan.counts.existing : 0;
|
|
543
|
+
const skipped = plan.counts.outsideStore +
|
|
544
|
+
plan.counts.purged +
|
|
545
|
+
plan.counts.incognito +
|
|
546
|
+
plan.counts.unconsented +
|
|
547
|
+
(overwrite ? 0 : plan.counts.existing);
|
|
548
|
+
console.log(`Imported: ${plan.counts.new} added, ${overwritten} overwritten, ${skipped} skipped. ${plan.counts.unconsented} skipped (unconsented). Snapshot: ${snapshotPath}`);
|
|
549
|
+
return { cancelled: false, snapshotPath, added: plan.counts.new, overwritten, skipped };
|
|
550
|
+
}
|
|
551
|
+
finally {
|
|
552
|
+
candidate.close();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async function confirmImport(overwrite) {
|
|
556
|
+
const prompt = overwrite
|
|
557
|
+
? 'Import this backup and overwrite matching sessions? A snapshot is saved first. [y/N] '
|
|
558
|
+
: 'Import only new sessions from this backup? A snapshot is saved first. [y/N] ';
|
|
559
|
+
return confirmYesNo(prompt);
|
|
560
|
+
}
|
|
561
|
+
export function reportImportError(error) {
|
|
562
|
+
console.error(errorMessage(error));
|
|
563
|
+
process.exitCode = 1;
|
|
564
|
+
}
|
|
565
|
+
export function registerImport(program) {
|
|
566
|
+
program
|
|
567
|
+
.command('import [file]')
|
|
568
|
+
.description('Merge an elepha backup into the local database')
|
|
569
|
+
.option('--overwrite', "overwrite matching sessions with the backup's version")
|
|
570
|
+
.option('--skip-confirmation', 'import without an interactive confirmation')
|
|
571
|
+
.action(async (file, opts) => {
|
|
572
|
+
const importBackup = (candidate, overwrite, confirm) => runImportOperation(candidate, overwrite, {
|
|
573
|
+
dbPath: defaultDbPath(),
|
|
574
|
+
...(confirm === undefined ? {} : { confirm }),
|
|
575
|
+
});
|
|
576
|
+
if (file === undefined) {
|
|
577
|
+
if (!process.stdin.isTTY) {
|
|
578
|
+
console.error('Specify a backup file when not running interactively.');
|
|
579
|
+
process.exitCode = 1;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
process.exitCode = await runImportWizard({
|
|
584
|
+
skipConfirmation: opts.skipConfirmation,
|
|
585
|
+
importBackup,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
reportImportError(error);
|
|
590
|
+
}
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const confirm = opts.skipConfirmation
|
|
594
|
+
? undefined
|
|
595
|
+
: process.stdin.isTTY
|
|
596
|
+
? () => confirmImport(opts.overwrite)
|
|
597
|
+
: async () => {
|
|
598
|
+
throw new Error('Refusing to import without a TTY confirmation. Re-run interactively or use --skip-confirmation.');
|
|
599
|
+
};
|
|
600
|
+
try {
|
|
601
|
+
const result = await importBackup(path.resolve(file), opts.overwrite, confirm);
|
|
602
|
+
if (result.cancelled) {
|
|
603
|
+
console.log('Cancelled — no changes were made.');
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
reportImportError(error);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
//# sourceMappingURL=import.js.map
|