@yugabytedb/meko-mcp 2.2.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -61
- package/assets/hooks-handlers/lib/capture-cleanup-cli.js +2134 -0
- package/assets/hooks-handlers/lib/capture-notices.js +224 -14
- package/assets/hooks-handlers/lib/capture-state.js +206 -1
- package/assets/hooks-handlers/lib/capture-status-cli.js +6 -2
- package/assets/hooks-handlers/lib/capture.js +1364 -139
- package/assets/hooks-handlers/lib/checkpoint-timer.js +445 -18
- package/assets/hooks-handlers/lib/journal-bridge.js +218 -0
- package/assets/hooks-handlers/lib/kiro-handler.js +26 -4
- package/assets/hooks-handlers/lib/opencode-handler.js +643 -0
- package/assets/hooks-handlers/lib/opencode-normalizer.js +145 -0
- package/assets/hooks-handlers/lib/terminal-handoff.js +10 -1
- package/assets/opencode-plugin/meko.js +213 -0
- package/assets/scripts/meko-statusline.sh +102 -19
- package/assets/skills/meko-mcp-tools/SKILL.md +59 -16
- package/assets/skills/meko-mcp-tools/references/_sections.md +2 -2
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +15 -7
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +275 -19
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +7 -4
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +39 -11
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +10 -6
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +44 -29
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +79 -14
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +47 -1
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +6 -39
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +57 -21
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +4 -4
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +8 -6
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +284 -38
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +32 -10
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +16 -12
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +46 -31
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +79 -14
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +69 -1
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +93 -14
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +44 -12
- package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +14 -10
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +17 -5
- package/bin/create-meko-setup.mjs +186 -30
- package/lib/autofix.mjs +100 -0
- package/lib/browser-login.mjs +495 -0
- package/lib/capture-cleanup.mjs +63 -0
- package/lib/clients/claude-code.mjs +1 -0
- package/lib/clients/codex.mjs +80 -35
- package/lib/clients/common.mjs +7 -0
- package/lib/clients/index.mjs +2 -0
- package/lib/clients/kiro.mjs +5 -5
- package/lib/clients/opencode.mjs +537 -0
- package/lib/config.mjs +68 -1
- package/lib/installer.mjs +18 -2
- package/lib/migrate/adapters/claude-code.mjs +11 -2
- package/lib/migrate/adapters/codex.mjs +9 -2
- package/lib/migrate/adapters/memory-md.mjs +27 -8
- package/lib/migrate/adapters/opencode.mjs +148 -0
- package/lib/migrate/claude-parser.mjs +153 -9
- package/lib/migrate/datapack.mjs +254 -16
- package/lib/migrate/id.mjs +195 -13
- package/lib/migrate/mcp-client.mjs +59 -1
- package/lib/migrate/opencode-normalizer.mjs +132 -0
- package/lib/migrate/orchestrator.mjs +294 -78
- package/lib/migrate/progress.mjs +239 -5
- package/lib/migrate/registry.mjs +72 -0
- package/lib/migrate.mjs +97 -9
- package/lib/validate.mjs +111 -22
- package/package.json +5 -5
- package/scripts/bundle-assets.mjs +4 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/** Fsynced OpenCode journal primitives; Kiro keeps a separate implementation. */
|
|
2
|
+
"use strict";
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const LOCK_RETRIES = 100;
|
|
5
|
+
const LOCK_RETRY_MS = 10;
|
|
6
|
+
function sleepSync(ms) {
|
|
7
|
+
const cell = new Int32Array(new SharedArrayBuffer(4));
|
|
8
|
+
Atomics.wait(cell, 0, 0, ms);
|
|
9
|
+
}
|
|
10
|
+
function pidAlive(pid) {
|
|
11
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
12
|
+
try {
|
|
13
|
+
process.kill(pid, 0);
|
|
14
|
+
return true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function ensureEventDir(eventDir) {
|
|
20
|
+
fs.mkdirSync(eventDir, { recursive: true, mode: 0o700 });
|
|
21
|
+
try { fs.chmodSync(eventDir, 0o700); } catch { /* best effort */ }
|
|
22
|
+
}
|
|
23
|
+
function acquireEventLock(lockPath, eventDir) {
|
|
24
|
+
ensureEventDir(eventDir);
|
|
25
|
+
for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
|
|
26
|
+
try {
|
|
27
|
+
const fd = fs.openSync(lockPath, "wx", 0o600);
|
|
28
|
+
try {
|
|
29
|
+
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, created_at: Date.now() }));
|
|
30
|
+
} finally {
|
|
31
|
+
fs.closeSync(fd);
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (!err || err.code !== "EEXIST") throw err;
|
|
36
|
+
let existing = null;
|
|
37
|
+
try { existing = JSON.parse(fs.readFileSync(lockPath, "utf8")); } catch {
|
|
38
|
+
try {
|
|
39
|
+
const st = fs.statSync(lockPath);
|
|
40
|
+
if (Date.now() - st.mtimeMs < 2000) {
|
|
41
|
+
sleepSync(LOCK_RETRY_MS);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
} catch { /* gone */ }
|
|
45
|
+
}
|
|
46
|
+
if (!existing || !pidAlive(Number(existing.pid))) {
|
|
47
|
+
try { fs.unlinkSync(lockPath); } catch { /* another process won */ }
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
sleepSync(LOCK_RETRY_MS);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`timed out acquiring event lock ${lockPath}`);
|
|
54
|
+
}
|
|
55
|
+
function releaseEventLock(lockPath) {
|
|
56
|
+
try {
|
|
57
|
+
const current = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
58
|
+
if (Number(current.pid) === process.pid) fs.unlinkSync(lockPath);
|
|
59
|
+
} catch { /* already gone */ }
|
|
60
|
+
}
|
|
61
|
+
function atomicWriteJson(filePath, value, eventDir) {
|
|
62
|
+
ensureEventDir(eventDir);
|
|
63
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
64
|
+
const fd = fs.openSync(tmp, "w", 0o600);
|
|
65
|
+
try {
|
|
66
|
+
fs.writeFileSync(fd, `${JSON.stringify(value)}\n`, "utf8");
|
|
67
|
+
fs.fsyncSync(fd);
|
|
68
|
+
} finally {
|
|
69
|
+
fs.closeSync(fd);
|
|
70
|
+
}
|
|
71
|
+
fs.renameSync(tmp, filePath);
|
|
72
|
+
try { fs.chmodSync(filePath, 0o600); } catch { /* best effort */ }
|
|
73
|
+
}
|
|
74
|
+
function appendJournal(journalPath, record, eventDir) {
|
|
75
|
+
ensureEventDir(eventDir);
|
|
76
|
+
let separator = "";
|
|
77
|
+
try {
|
|
78
|
+
const contents = fs.readFileSync(journalPath);
|
|
79
|
+
if (contents.length > 0 && contents[contents.length - 1] !== 0x0a) {
|
|
80
|
+
separator = "\n";
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (!err || err.code !== "ENOENT") throw err;
|
|
84
|
+
}
|
|
85
|
+
const data = Buffer.from(`${separator}${JSON.stringify(record)}\n`, "utf8");
|
|
86
|
+
const fd = fs.openSync(journalPath, "a", 0o600);
|
|
87
|
+
try {
|
|
88
|
+
let offset = 0;
|
|
89
|
+
while (offset < data.length) {
|
|
90
|
+
const written = fs.writeSync(fd, data, offset, data.length - offset, null);
|
|
91
|
+
if (!Number.isInteger(written) || written <= 0) {
|
|
92
|
+
throw new Error(`could not append complete journal record to ${journalPath}`);
|
|
93
|
+
}
|
|
94
|
+
offset += written;
|
|
95
|
+
}
|
|
96
|
+
fs.fsyncSync(fd);
|
|
97
|
+
} finally {
|
|
98
|
+
fs.closeSync(fd);
|
|
99
|
+
}
|
|
100
|
+
try { fs.chmodSync(journalPath, 0o600); } catch { /* best effort */ }
|
|
101
|
+
}
|
|
102
|
+
function journalEmpty(journalPath) {
|
|
103
|
+
try { return fs.statSync(journalPath).size === 0; } catch { return true; }
|
|
104
|
+
}
|
|
105
|
+
function emptyEventState(sessionId) {
|
|
106
|
+
return {
|
|
107
|
+
schema_version: 1,
|
|
108
|
+
session_id: sessionId,
|
|
109
|
+
turn_index: 0,
|
|
110
|
+
pending: null,
|
|
111
|
+
seen_source_ids: [],
|
|
112
|
+
updated_at: new Date().toISOString(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function rebuildEventState(journalPath, sessionId) {
|
|
116
|
+
const rebuilt = emptyEventState(sessionId);
|
|
117
|
+
let contents;
|
|
118
|
+
try {
|
|
119
|
+
contents = fs.readFileSync(journalPath, "utf8");
|
|
120
|
+
} catch {
|
|
121
|
+
return rebuilt;
|
|
122
|
+
}
|
|
123
|
+
for (const line of contents.split("\n")) {
|
|
124
|
+
if (!line.trim()) continue;
|
|
125
|
+
let record;
|
|
126
|
+
try {
|
|
127
|
+
record = JSON.parse(line);
|
|
128
|
+
} catch {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!record || record.session_id !== sessionId) continue;
|
|
132
|
+
if (record.type === "user") {
|
|
133
|
+
const content = record.message && record.message.content;
|
|
134
|
+
const text = Array.isArray(content) && content[0] && typeof content[0].text === "string"
|
|
135
|
+
? content[0].text
|
|
136
|
+
: "";
|
|
137
|
+
const uuid = String(record.uuid || "");
|
|
138
|
+
rebuilt.turn_index += 1;
|
|
139
|
+
const mekoObservedMs =
|
|
140
|
+
record.meko && Number.isFinite(record.meko.observed_at_ms)
|
|
141
|
+
? record.meko.observed_at_ms
|
|
142
|
+
: null;
|
|
143
|
+
const firstObservedAtMs = mekoObservedMs;
|
|
144
|
+
rebuilt.pending = {
|
|
145
|
+
user_uuid: uuid,
|
|
146
|
+
source_message_id: record.source_message_id || null,
|
|
147
|
+
turn_index: rebuilt.turn_index,
|
|
148
|
+
prompt_preview: text.slice(0, 80),
|
|
149
|
+
first_observed_at_ms: firstObservedAtMs,
|
|
150
|
+
};
|
|
151
|
+
if (record.source_message_id) rebuilt.seen_source_ids.push(record.source_message_id);
|
|
152
|
+
// Alias `${userId}:interrupted` so a later original id is already seen.
|
|
153
|
+
const legacySuffix = ":interrupted";
|
|
154
|
+
if (uuid.endsWith(legacySuffix)) {
|
|
155
|
+
const base = uuid.slice(0, -legacySuffix.length);
|
|
156
|
+
if (base) rebuilt.seen_source_ids.push(base);
|
|
157
|
+
}
|
|
158
|
+
} else if (record.type === "assistant" && rebuilt.pending) {
|
|
159
|
+
if (record.source_message_id) rebuilt.seen_source_ids.push(record.source_message_id);
|
|
160
|
+
rebuilt.pending = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return rebuilt;
|
|
164
|
+
}
|
|
165
|
+
function readEventState(statePath, journalPath, sessionId, eventDir) {
|
|
166
|
+
let persisted = null;
|
|
167
|
+
try {
|
|
168
|
+
const value = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
169
|
+
if (value && value.schema_version === 1 && value.session_id === sessionId) {
|
|
170
|
+
persisted = value;
|
|
171
|
+
}
|
|
172
|
+
} catch { /* fresh or corrupt rebuildable sidecar */ }
|
|
173
|
+
const rebuilt = rebuildEventState(journalPath, sessionId);
|
|
174
|
+
// MEKO-635: prefer the persisted pending's first_observed_at_ms over the
|
|
175
|
+
// rebuild value from record.meko.observed_at_ms. Fall back to rebuild when
|
|
176
|
+
// the sidecar is missing or names a different pending user.
|
|
177
|
+
let pending = rebuilt.pending;
|
|
178
|
+
const persistedPending = persisted && persisted.pending;
|
|
179
|
+
if (
|
|
180
|
+
pending &&
|
|
181
|
+
persistedPending &&
|
|
182
|
+
persistedPending.user_uuid === pending.user_uuid
|
|
183
|
+
) {
|
|
184
|
+
const persistedMs = persistedPending.first_observed_at_ms;
|
|
185
|
+
pending = {
|
|
186
|
+
...pending,
|
|
187
|
+
first_observed_at_ms: Number.isFinite(persistedMs)
|
|
188
|
+
? persistedMs
|
|
189
|
+
: pending.first_observed_at_ms,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
...(persisted || emptyEventState(sessionId)),
|
|
194
|
+
turn_index: rebuilt.turn_index,
|
|
195
|
+
pending,
|
|
196
|
+
seen_source_ids: rebuilt.seen_source_ids,
|
|
197
|
+
tentative_observation:
|
|
198
|
+
pending &&
|
|
199
|
+
persisted &&
|
|
200
|
+
persisted.pending &&
|
|
201
|
+
persisted.pending.user_uuid === pending.user_uuid
|
|
202
|
+
? persisted.tentative_observation || null
|
|
203
|
+
: null,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
module.exports = {
|
|
207
|
+
LOCK_RETRIES,
|
|
208
|
+
LOCK_RETRY_MS,
|
|
209
|
+
ensureEventDir,
|
|
210
|
+
acquireEventLock,
|
|
211
|
+
releaseEventLock,
|
|
212
|
+
atomicWriteJson,
|
|
213
|
+
appendJournal,
|
|
214
|
+
journalEmpty,
|
|
215
|
+
emptyEventState,
|
|
216
|
+
rebuildEventState,
|
|
217
|
+
readEventState,
|
|
218
|
+
};
|
|
@@ -535,20 +535,42 @@ function journalEvent(event) {
|
|
|
535
535
|
}
|
|
536
536
|
|
|
537
537
|
function ensureCaptureIntent(event, journalPath) {
|
|
538
|
-
const
|
|
538
|
+
const envOverride = (process.env.MEKO_AGENT_ID || "").trim();
|
|
539
|
+
const derivedAgentId = deriveAgentId({
|
|
539
540
|
client: "kiro",
|
|
540
541
|
cwd: event.cwd,
|
|
541
|
-
envOverride
|
|
542
|
+
envOverride,
|
|
542
543
|
});
|
|
544
|
+
const existing = captureState.readState(event.sessionId);
|
|
545
|
+
const existingAgentId = existing.ok && existing.state
|
|
546
|
+
? String(existing.state.agent_id || "").trim()
|
|
547
|
+
: "";
|
|
548
|
+
const existingSource = existing.ok && existing.state
|
|
549
|
+
? String(existing.state.agent_id_source || "").trim()
|
|
550
|
+
: "";
|
|
551
|
+
|
|
552
|
+
// Existing Kiro sessions keep their persisted owner. Changing it in place
|
|
553
|
+
// would orphan an already-created conversation and reset the capture intent.
|
|
554
|
+
// A live explicit override still wins, and stale pre-MEKO-385 client ids are
|
|
555
|
+
// allowed to upgrade to Kiro.
|
|
556
|
+
const preserveExisting = !envOverride && existingAgentId && (
|
|
557
|
+
existingSource === "explicit"
|
|
558
|
+
|| existingAgentId === "kiro"
|
|
559
|
+
|| existingAgentId.startsWith("kiro:")
|
|
560
|
+
);
|
|
561
|
+
const agentId = preserveExisting ? existingAgentId : derivedAgentId;
|
|
562
|
+
const agentIdSource = preserveExisting
|
|
563
|
+
? (existingSource || "derived")
|
|
564
|
+
: (envOverride ? "explicit" : "derived");
|
|
543
565
|
const intent = captureState.writeSessionIntent(event.sessionId, {
|
|
544
566
|
client: "kiro",
|
|
545
567
|
agent_id: agentId,
|
|
546
|
-
agent_id_source:
|
|
568
|
+
agent_id_source: agentIdSource,
|
|
547
569
|
transcript_path: journalPath,
|
|
548
570
|
datapack_id: process.env.MEKO_DATAPACK_ID || null,
|
|
549
571
|
datapack_name: process.env.MEKO_DATAPACK_NAME || null,
|
|
550
572
|
});
|
|
551
|
-
return { ...intent, agentId };
|
|
573
|
+
return { ...intent, agentId, derivedAgentId };
|
|
552
574
|
}
|
|
553
575
|
|
|
554
576
|
function launchCapture(mode, payload) {
|