@yugabytedb/meko-mcp 2.2.0 → 2.3.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 +106 -38
- package/assets/hooks-handlers/lib/capture-cleanup-cli.js +1473 -0
- package/assets/hooks-handlers/lib/capture-notices.js +131 -14
- package/assets/hooks-handlers/lib/capture-state.js +95 -1
- package/assets/hooks-handlers/lib/capture-status-cli.js +6 -2
- package/assets/hooks-handlers/lib/capture.js +1237 -137
- package/assets/hooks-handlers/lib/checkpoint-timer.js +51 -10
- 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 +620 -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 +85 -18
- package/assets/skills/meko-mcp-tools/SKILL.md +57 -14
- 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 +86 -14
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +1 -1
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +29 -6
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +8 -4
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +32 -22
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +14 -8
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +45 -1
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +6 -39
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +54 -18
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +1 -1
- 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 +95 -33
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +22 -5
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +14 -10
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +35 -25
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +14 -8
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +67 -1
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +4 -4
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +5 -5
- package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +7 -5
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +4 -4
- package/bin/create-meko-setup.mjs +70 -7
- 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 +532 -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/id.mjs +195 -13
- package/lib/migrate/mcp-client.mjs +25 -1
- package/lib/migrate/opencode-normalizer.mjs +132 -0
- package/lib/migrate/orchestrator.mjs +293 -77
- package/lib/migrate/progress.mjs +239 -5
- package/lib/migrate/registry.mjs +72 -0
- package/lib/migrate.mjs +96 -8
- package/package.json +1 -1
- package/scripts/bundle-assets.mjs +4 -0
|
@@ -31,11 +31,14 @@ const {
|
|
|
31
31
|
timerPidPath,
|
|
32
32
|
watermarkDir,
|
|
33
33
|
scanStates,
|
|
34
|
+
scanCompleteForDropClamp,
|
|
34
35
|
sortOldestFirst,
|
|
35
36
|
computeAggregateHealth,
|
|
36
37
|
writeHealthCache,
|
|
37
38
|
importLegacyCaptureErrors,
|
|
38
39
|
legacyErrorsSafeToRemove,
|
|
40
|
+
isResumableState,
|
|
41
|
+
readState,
|
|
39
42
|
} = require("./capture-state");
|
|
40
43
|
|
|
41
44
|
const INTERVAL_SECS = Math.max(1, checkpointInterval());
|
|
@@ -185,11 +188,27 @@ function runCaptureChild(mode, payload, timeoutMs) {
|
|
|
185
188
|
});
|
|
186
189
|
}
|
|
187
190
|
|
|
191
|
+
// MEKO-635: settle OpenCode pending turns past the 30-minute default.
|
|
192
|
+
function sweepOpenCodeSettles() {
|
|
193
|
+
try {
|
|
194
|
+
const opencode = require("./opencode-handler");
|
|
195
|
+
if (typeof opencode.sweepExpiredSettles === "function") {
|
|
196
|
+
opencode.sweepExpiredSettles();
|
|
197
|
+
}
|
|
198
|
+
} catch (err) {
|
|
199
|
+
process.stderr.write(
|
|
200
|
+
`[meko-timer] OpenCode settle sweep failed: ${err.message}\n`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
188
205
|
function rebuildHealth() {
|
|
189
206
|
try {
|
|
190
207
|
const scan = scanStates({ includeBlocked: true });
|
|
191
208
|
const health = computeAggregateHealth(scan);
|
|
192
|
-
writeHealthCache(health
|
|
209
|
+
writeHealthCache(health, {
|
|
210
|
+
clampDropped: scanCompleteForDropClamp(scan),
|
|
211
|
+
});
|
|
193
212
|
} catch (err) {
|
|
194
213
|
process.stderr.write(
|
|
195
214
|
`[meko-timer] Health rebuild failed: ${err.message}\n`,
|
|
@@ -250,6 +269,10 @@ async function runRecoveryCycle() {
|
|
|
250
269
|
const forcePersistentRetry = startupRecoveryPending;
|
|
251
270
|
let moreWork = false;
|
|
252
271
|
try {
|
|
272
|
+
// Finalize any resumable session whose settling window has expired before
|
|
273
|
+
// the scan snapshots eligibility. Otherwise recovery keeps draining a
|
|
274
|
+
// pending user (queued_exchanges === 1) whose assistant will never arrive.
|
|
275
|
+
sweepOpenCodeSettles();
|
|
253
276
|
const scan = scanStates({
|
|
254
277
|
includeBlocked: true,
|
|
255
278
|
forcePersistentRetry,
|
|
@@ -292,11 +315,12 @@ async function runRecoveryCycle() {
|
|
|
292
315
|
}
|
|
293
316
|
|
|
294
317
|
const state = entry.state || {};
|
|
318
|
+
const resumable = isResumableState(state);
|
|
295
319
|
const payload = {
|
|
296
320
|
session_id: entry.session_id,
|
|
297
321
|
transcript_path: state.transcript_path || undefined,
|
|
298
|
-
lifecycle:
|
|
299
|
-
confirmed_dead:
|
|
322
|
+
lifecycle: resumable ? "active" : "closing",
|
|
323
|
+
confirmed_dead: !resumable,
|
|
300
324
|
recorded_owner_only: true,
|
|
301
325
|
max_exchanges: Math.min(budget, maxCaptureBatchSize()),
|
|
302
326
|
force_retry: Boolean(
|
|
@@ -321,7 +345,10 @@ async function runRecoveryCycle() {
|
|
|
321
345
|
const captured = Number((result && result.captured) || 0);
|
|
322
346
|
budget -= captured;
|
|
323
347
|
const queued = Number((result && result.queued_remaining) || 0);
|
|
324
|
-
if (
|
|
348
|
+
if (
|
|
349
|
+
(queued > 0 || (result && result.status === "retry_wait")) &&
|
|
350
|
+
!(resumable && captured === 0 && queued <= 1 && result.status !== "retry_wait")
|
|
351
|
+
) {
|
|
325
352
|
moreWork = true;
|
|
326
353
|
}
|
|
327
354
|
if (result && (result.status === "partial" || result.status === "retry_wait" || result.status === "blocked")) {
|
|
@@ -346,7 +373,10 @@ async function runRecoveryCycle() {
|
|
|
346
373
|
e.eligibility &&
|
|
347
374
|
e.session_id !== sessionId &&
|
|
348
375
|
Number(e.eligibility.queued_exchanges || 0) > 0 &&
|
|
349
|
-
e.eligibility.retry_due === false
|
|
376
|
+
e.eligibility.retry_due === false &&
|
|
377
|
+
!(isResumableState(e.state) &&
|
|
378
|
+
Number(e.eligibility.queued_exchanges || 0) <= 1 &&
|
|
379
|
+
e.state && e.state.delivery === "pending"),
|
|
350
380
|
);
|
|
351
381
|
}
|
|
352
382
|
|
|
@@ -361,6 +391,14 @@ async function runRecoveryCycle() {
|
|
|
361
391
|
return moreWork;
|
|
362
392
|
}
|
|
363
393
|
|
|
394
|
+
// MEKO-635: for the daemon's own session, look up whether its captured intent
|
|
395
|
+
// declared a resumable pending-turn policy. If so, timer-idle and
|
|
396
|
+
// transcript-gone paths must drain without finalizing the session as dead.
|
|
397
|
+
function selfIsResumable() {
|
|
398
|
+
const read = readState(sessionId);
|
|
399
|
+
return read.ok ? isResumableState(read.state) : false;
|
|
400
|
+
}
|
|
401
|
+
|
|
364
402
|
async function tick() {
|
|
365
403
|
if (tickInFlight) return;
|
|
366
404
|
tickInFlight = true;
|
|
@@ -382,14 +420,16 @@ async function tick() {
|
|
|
382
420
|
await runRecoveryCycle();
|
|
383
421
|
return;
|
|
384
422
|
}
|
|
385
|
-
// Transcript gone after grace
|
|
423
|
+
// Transcript gone after grace. For a resumable session drain without
|
|
424
|
+
// finalizing so a later snapshot can complete its pending turn.
|
|
425
|
+
const resumable = selfIsResumable();
|
|
386
426
|
await runCaptureChild(
|
|
387
427
|
"drain",
|
|
388
428
|
{
|
|
389
429
|
session_id: sessionId,
|
|
390
430
|
transcript_path: transcriptPath,
|
|
391
|
-
lifecycle: "closing",
|
|
392
|
-
confirmed_dead:
|
|
431
|
+
lifecycle: resumable ? "active" : "closing",
|
|
432
|
+
confirmed_dead: !resumable,
|
|
393
433
|
recorded_owner_only: true,
|
|
394
434
|
},
|
|
395
435
|
CAPTURE_TIMEOUT_MS,
|
|
@@ -403,13 +443,14 @@ async function tick() {
|
|
|
403
443
|
process.stderr.write(
|
|
404
444
|
`[meko-timer] Transcript idle > ${IDLE_EXIT_SECS}s; final drain then exit.\n`,
|
|
405
445
|
);
|
|
446
|
+
const resumable = selfIsResumable();
|
|
406
447
|
await runCaptureChild(
|
|
407
448
|
"drain",
|
|
408
449
|
{
|
|
409
450
|
session_id: sessionId,
|
|
410
451
|
transcript_path: transcriptPath,
|
|
411
|
-
lifecycle: "closing",
|
|
412
|
-
confirmed_dead:
|
|
452
|
+
lifecycle: resumable ? "active" : "closing",
|
|
453
|
+
confirmed_dead: !resumable,
|
|
413
454
|
recorded_owner_only: true,
|
|
414
455
|
},
|
|
415
456
|
CAPTURE_TIMEOUT_MS,
|
|
@@ -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) {
|