This repository was archived by the owner on Jun 8, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathopencode-import.js
More file actions
131 lines (118 loc) · 4.39 KB
/
Copy pathopencode-import.js
File metadata and controls
131 lines (118 loc) · 4.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* @file opencode-import.js
* @description Bootstrap importer for OpenCode sessions. Parses the canonical
* `opencode.db` session/message store into the shared normalized session shape
* and reuses importSession().
*/
const fs = require("fs");
const path = require("path");
const { loadSessionsFromDb } = require("./opencode-parser");
const { getOpenCodeHome, getOpenCodeDbWatchFiles } = require("./opencode-home");
const { importSession } = require("../../scripts/import-history");
const { reactivateImportedSession } = require("../agent-monitor-shared/import-session-utils");
const { ingestStateDir } = require("../agent-monitor-shared/ingest-paths");
// See FEA-1316: skip the full DB load when neither opencode.db nor its
// WAL/SHM siblings have changed since the last catchup tick. FEA-1334
// persists that fingerprint to disk so a fresh process also skips the load
// on the cold-start boot import when the DB is untouched.
function fingerprintFilePath() {
return path.join(ingestStateDir(), "ingest-opencode-fingerprint.txt");
}
function loadPersistedFingerprint() {
try {
return fs.readFileSync(fingerprintFilePath(), "utf8");
} catch {
return null;
}
}
function persistFingerprint(fingerprint) {
try {
fs.mkdirSync(ingestStateDir(), { recursive: true });
fs.writeFileSync(fingerprintFilePath(), fingerprint);
} catch {
/* best-effort — an unwritable state dir just costs one extra load */
}
}
let lastDbFingerprint = loadPersistedFingerprint();
function fingerprintDbFiles() {
const home = getOpenCodeHome();
const parts = [];
for (const name of getOpenCodeDbWatchFiles()) {
try {
const stat = fs.statSync(path.join(home, name));
parts.push(`${name}:${stat.mtimeMs}:${stat.size}`);
} catch {
parts.push(`${name}:missing`);
}
}
return parts.join("|");
}
function importOpenCodeSession(dbModule, session) {
const result = importSession(dbModule, session);
try {
dbModule.stmts.setSessionHarness.run("opencode", session.sessionId, "opencode");
} catch { /* non-fatal */ }
// FEA-1434: OpenCode is hosted on a flat subscription model — no per-token
// API cost surface. Stamp the mode so the UI ledger split counts these as
// subscription-covered.
//
// FEA-1434 (round-3 review follow-up): pass the protected-mode exclusion
// list ('api', 'claude_max', 'claude_pro') so the importer can never
// demote a row that the desktop main process has deliberately marked.
// See the prepared-statement comment in `build-agent-monitor.mjs`.
try {
dbModule.stmts.setSessionBillingMode.run(
"opencode",
session.sessionId,
"opencode",
"api",
"claude_max",
"claude_pro",
);
} catch { /* non-fatal */ }
const reactivated = reactivateImportedSession(dbModule, session);
return { sessionId: session.sessionId, result, reactivated };
}
/**
* Parse + import every OpenCode session from opencode.db. Idempotent.
*
* @param {any} dbModule
* @param {{ signal?: AbortSignal, onBegin?: (total: number) => void,
* onProgress?: () => void }} [opts] - ingest-orchestrator progress
* hooks (FEA-1334). The watcher catchup tick calls this with no opts.
* OpenCode is a single DB read, so it counts as one unit of progress.
*/
async function importAllOpenCodeSessions(dbModule, opts = {}) {
const onBegin = typeof opts.onBegin === "function" ? opts.onBegin : null;
const onProgress = typeof opts.onProgress === "function" ? opts.onProgress : null;
let imported = 0;
let skipped = 0;
let errors = 0;
// OpenCode is a single batch DB load rather than a per-file walk.
if (onBegin) onBegin(1);
const importBatch = dbModule.db.transaction((sessions) => {
for (const session of sessions) {
const { result, reactivated } = importOpenCodeSession(dbModule, session);
if (result && result.skipped && !reactivated) skipped++;
else imported++;
}
});
const fingerprint = fingerprintDbFiles();
if (fingerprint === lastDbFingerprint) {
if (onProgress) onProgress();
return { imported, skipped, errors };
}
try {
const sessions = loadSessionsFromDb();
if (sessions.length > 0) {
importBatch(sessions);
}
lastDbFingerprint = fingerprint;
persistFingerprint(fingerprint);
} catch {
errors++;
}
if (onProgress) onProgress();
return { imported, skipped, errors };
}
module.exports = { importAllOpenCodeSessions, importOpenCodeSession };