claude-code-session-manager 0.104.1 → 0.105.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 +15 -3
- package/bin/cli.cjs +0 -5
- package/dist/assets/AgentLibrary-BDtr4oI-.js +2 -0
- package/dist/assets/Button-CuXBCQDp.js +1 -0
- package/dist/assets/DataModel-DmvRipT5.js +1 -0
- package/dist/assets/{History-BP6KKda_.js → History-B-v9pwwr.js} +2 -2
- package/dist/assets/Hooks-S_0oHHlr.js +3 -0
- package/dist/assets/{Library-BprXNJCG.js → Library-BcbGJxQ3.js} +5 -5
- package/dist/assets/ListDetail-DtqHqJHt.js +1 -0
- package/dist/assets/{MarkdownEditor-1KlkRAqT.js → MarkdownEditor-B56sPp_l.js} +1 -1
- package/dist/assets/{McpServers-FUDN7EnU.js → McpServers-D5VnmxAn.js} +2 -2
- package/dist/assets/{Memory-DryJweXu.js → Memory-0yGZLyRP.js} +6 -6
- package/dist/assets/Panel-NoAADJwx.js +1 -0
- package/dist/assets/Permissions-D2Dh-ee8.js +3 -0
- package/dist/assets/Plugins-kGoUgS9b.js +2 -0
- package/dist/assets/{ProvenanceBadge-6Hj42klQ.js → ProvenanceBadge-CHwDFTX7.js} +1 -1
- package/dist/assets/{SaveBar-xSfjUPiu.js → SaveBar-BtKNMIYK.js} +1 -1
- package/dist/assets/{Scheduler-Beh3Vj-Z.js → Scheduler-B0_kMMDs.js} +7 -7
- package/dist/assets/{ScopeSwitcher-9R1ANwns.js → ScopeSwitcher-BusohWZ5.js} +1 -1
- package/dist/assets/Settings-DuRJntaE.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CYLTGabR.js → SkillReferenceGraph-BHOdIpsy.js} +1 -1
- package/dist/assets/Skills-DSTLZimI.js +3 -0
- package/dist/assets/SystemPrompt-BG7wFWdb.js +1 -0
- package/dist/assets/TagLibrary-Bx27aE3_.js +1 -0
- package/dist/assets/{TiptapBody-l-BMtrPd.js → TiptapBody-j0O8J7YI.js} +1 -1
- package/dist/assets/{Toggle-Bj2Ia7AE.js → Toggle-DzVZ6rC7.js} +1 -1
- package/dist/assets/ViewTabs-BizjuynT.js +1 -0
- package/dist/assets/claude-settings-schema-COigsajx.js +3 -0
- package/dist/assets/{index-D89EUizB.css → index-BLVzrc4k.css} +1 -1
- package/dist/assets/index-CLIaYljt.js +3073 -0
- package/dist/assets/monaco-editor-BsIlgG7c.js +917 -0
- package/dist/assets/monaco-editor-Cu5D__1H.css +1 -0
- package/dist/assets/settingsSchema-7MRAxaJk.js +1 -0
- package/dist/assets/useAgentsDirWatch-BUwfU2Kq.js +1 -0
- package/dist/assets/useConfigDirWatch-ot0VYwQC.js +1 -0
- package/dist/index.html +4 -4
- package/package.json +12 -14
- package/plugins/session-manager-dev/skills/builder/3-publish/SKILL.md +12 -0
- package/plugins/session-manager-dev/skills/develop/SKILL.md +4 -2
- package/scripts/scheduler-mcp-server.cjs +7 -17
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +4 -4
- package/src/main/historyAggregator.cjs +7 -1
- package/src/main/index.cjs +34 -1
- package/src/main/lib/activeSessions.cjs +263 -71
- package/src/main/lib/agentPersonaSchema.cjs +8 -1
- package/src/main/lib/appRuntime.cjs +57 -0
- package/src/main/lib/childWithLog.cjs +10 -10
- package/src/main/lib/claudeBin.cjs +40 -16
- package/src/main/lib/cleanEnv.cjs +29 -6
- package/src/main/lib/defaultShell.cjs +36 -0
- package/src/main/lib/definitionOfDone.cjs +6 -2
- package/src/main/lib/delegationReadiness.cjs +13 -5
- package/src/main/lib/gitWorktree.cjs +6 -3
- package/src/main/lib/insideHome.cjs +15 -4
- package/src/main/lib/killTree.cjs +80 -0
- package/src/main/lib/loadGate.cjs +16 -0
- package/src/main/lib/loginShellPath.cjs +82 -0
- package/src/main/lib/machineProfile.cjs +4 -2
- package/src/main/lib/mcpStdioServer.cjs +127 -0
- package/src/main/lib/openExternalApp.cjs +62 -11
- package/src/main/lib/otlpTraceExporter.cjs +147 -0
- package/src/main/lib/prdMigration.cjs +64 -1
- package/src/main/lib/prereqs.cjs +145 -0
- package/src/main/lib/procIdentity.cjs +37 -2
- package/src/main/lib/queueHistory.cjs +97 -26
- package/src/main/lib/rcaReport.cjs +1 -0
- package/src/main/lib/reaperHelpers.cjs +5 -2
- package/src/main/lib/shippedPersonaSeeds.cjs +10 -0
- package/src/main/lib/timeoutShimScript.cjs +14 -2
- package/src/main/lib/updateCheck.cjs +41 -11
- package/src/main/lib/watchdogHelpers.cjs +23 -3
- package/src/main/lib/winSpawn.cjs +67 -0
- package/src/main/otel.cjs +18 -62
- package/src/main/pty.cjs +82 -12
- package/src/main/runVerify.cjs +42 -2
- package/src/main/scheduler.cjs +105 -20
- package/src/main/seedSchedulerMcp.cjs +11 -2
- package/src/main/usage.cjs +4 -1
- package/src/preload/api.d.ts +14 -0
- package/src/preload/index.cjs +2 -0
- package/src/seed/agents/validator.md +2 -2
- package/dist/assets/DataModel-BHcPa2nA.js +0 -1
- package/dist/assets/Hooks-CnJnddP6.js +0 -3
- package/dist/assets/Permissions-Df-LWvdk.js +0 -3
- package/dist/assets/Plugins-CIUOw9B2.js +0 -2
- package/dist/assets/Settings-gkp0WfEm.js +0 -3
- package/dist/assets/Skills-Dw5Am-f-.js +0 -3
- package/dist/assets/SystemPrompt-B3GwTLqh.js +0 -1
- package/dist/assets/TagLibrary-Dj46gOG2.js +0 -1
- package/dist/assets/ViewTabs-BQ_HbQFq.js +0 -1
- package/dist/assets/abap-DLDM7-KI.js +0 -1
- package/dist/assets/apex-DNDY2TF8.js +0 -1
- package/dist/assets/azcli-Y6nb8tq_.js +0 -1
- package/dist/assets/bat-BwHxbl9M.js +0 -1
- package/dist/assets/bicep-CFznDFnq.js +0 -2
- package/dist/assets/cameligo-Bf6VGUru.js +0 -1
- package/dist/assets/clojure-Dnu-v4kV.js +0 -1
- package/dist/assets/coffee-Bd8akH9Z.js +0 -1
- package/dist/assets/cpp-BbWJElDN.js +0 -1
- package/dist/assets/csharp-Co3qMtFm.js +0 -1
- package/dist/assets/csp-D-4FJmMZ.js +0 -1
- package/dist/assets/css-DdJfP1eB.js +0 -3
- package/dist/assets/css.worker-B4z49cGk.js +0 -93
- package/dist/assets/cssMode-DfqZGMQs.js +0 -1
- package/dist/assets/cypher-cTPe9QuQ.js +0 -1
- package/dist/assets/dart-BOtBlQCF.js +0 -1
- package/dist/assets/dockerfile-BG73LgW2.js +0 -1
- package/dist/assets/ecl-BEgZUVRK.js +0 -1
- package/dist/assets/elixir-BkW5O-1t.js +0 -1
- package/dist/assets/flow9-BeJ5waoc.js +0 -1
- package/dist/assets/freemarker2-XTPYh37h.js +0 -3
- package/dist/assets/fsharp-PahG7c26.js +0 -1
- package/dist/assets/go-acbASCJo.js +0 -1
- package/dist/assets/graphql-BxJiqAUM.js +0 -1
- package/dist/assets/handlebars-DKUF5VyH.js +0 -1
- package/dist/assets/hcl-DtV1sZF8.js +0 -1
- package/dist/assets/html-uqoqsIeI.js +0 -1
- package/dist/assets/html.worker-DtiGdgqp.js +0 -470
- package/dist/assets/htmlMode-aMTQs1su.js +0 -1
- package/dist/assets/index-DbR91Z5j.js +0 -3076
- package/dist/assets/ini-Kd9XrMLS.js +0 -1
- package/dist/assets/java-CXBNlu9o.js +0 -1
- package/dist/assets/javascript-BVxRZMds.js +0 -1
- package/dist/assets/jsonMode-D04xP2s5.js +0 -7
- package/dist/assets/julia-cl7-CwDS.js +0 -1
- package/dist/assets/kotlin-s7OhZKlX.js +0 -1
- package/dist/assets/less-9HpZscsL.js +0 -2
- package/dist/assets/lexon-OrD6JF1K.js +0 -1
- package/dist/assets/liquid-BkQHTH2P.js +0 -1
- package/dist/assets/lspLanguageFeatures-By9uLznH.js +0 -4
- package/dist/assets/lua-Cyyb5UIc.js +0 -1
- package/dist/assets/m3-B8OfTtLu.js +0 -1
- package/dist/assets/markdown-BFxVWTOG.js +0 -1
- package/dist/assets/mdx-Du1IlbjV.js +0 -1
- package/dist/assets/mips-CiqrrVzr.js +0 -1
- package/dist/assets/monaco-editor-BTnBOi8r.css +0 -1
- package/dist/assets/monaco-editor-BW5C4Iv1.js +0 -908
- package/dist/assets/msdax-DmeGPVcC.js +0 -1
- package/dist/assets/mysql-C_tMU-Nz.js +0 -1
- package/dist/assets/objective-c-BDtDVThU.js +0 -1
- package/dist/assets/pascal-vHIfCaH5.js +0 -1
- package/dist/assets/pascaligo-DtZ0uQbO.js +0 -1
- package/dist/assets/perl-Ub6l9XKa.js +0 -1
- package/dist/assets/pgsql-BlNEE0v7.js +0 -1
- package/dist/assets/php-BBUBE1dy.js +0 -1
- package/dist/assets/pla-DSh2-awV.js +0 -1
- package/dist/assets/postiats-CocnycG-.js +0 -1
- package/dist/assets/powerquery-tScXyioY.js +0 -1
- package/dist/assets/powershell-COWaemsV.js +0 -1
- package/dist/assets/protobuf-Brw8urJB.js +0 -2
- package/dist/assets/pug-8SOpv6rk.js +0 -1
- package/dist/assets/python-DSlImqXd.js +0 -1
- package/dist/assets/qsharp-Bw9ernYp.js +0 -1
- package/dist/assets/r-j7ic8hl3.js +0 -1
- package/dist/assets/razor-BmUVyvSK.js +0 -1
- package/dist/assets/redis-Bu5POkcn.js +0 -1
- package/dist/assets/redshift-Bs9aos_-.js +0 -1
- package/dist/assets/restructuredtext-CqXO7rUv.js +0 -1
- package/dist/assets/ruby-zBfavPgS.js +0 -1
- package/dist/assets/rust-BzKRNQWT.js +0 -1
- package/dist/assets/sb-BBc9UKZt.js +0 -1
- package/dist/assets/scala-D9hQfWCl.js +0 -1
- package/dist/assets/scheme-BPhDTwHR.js +0 -1
- package/dist/assets/scss-CBJaRo0y.js +0 -3
- package/dist/assets/settingsSchema-cR833AB2.js +0 -1
- package/dist/assets/shell-DiJ1NA_G.js +0 -1
- package/dist/assets/solidity-Db0IVjzk.js +0 -1
- package/dist/assets/sophia-CnS9iZB_.js +0 -1
- package/dist/assets/sparql-CJmd_6j2.js +0 -1
- package/dist/assets/sql-ClhHkBeG.js +0 -1
- package/dist/assets/st-CHwy0fLd.js +0 -1
- package/dist/assets/swift-Bqt4WxQ4.js +0 -3
- package/dist/assets/systemverilog-Bs9z6M-B.js +0 -1
- package/dist/assets/tcl-Dm6ycUr_.js +0 -1
- package/dist/assets/ts.worker-59MjiAqk.js +0 -67731
- package/dist/assets/tsMode-Btj0TTH7.js +0 -11
- package/dist/assets/twig-Csy3S7wG.js +0 -1
- package/dist/assets/typescript-Bzelq9vO.js +0 -1
- package/dist/assets/typespec-Btyra-wh.js +0 -1
- package/dist/assets/vb-Db0cS2oM.js +0 -1
- package/dist/assets/wgsl-BTesnYfV.js +0 -298
- package/dist/assets/xml-Whd9EaSd.js +0 -1
- package/dist/assets/yaml-QYf0-IN8.js +0 -1
- package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +0 -112
- package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +0 -106
- package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +0 -107
- package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
- package/scripts/postinstall.cjs +0 -52
- package/src/preload/__tests__/preload-surface.test.cjs +0 -68
|
@@ -236,11 +236,96 @@ async function readHistoryFile(target, cap) {
|
|
|
236
236
|
let historyCacheKey = null;
|
|
237
237
|
let historyCacheMap = null;
|
|
238
238
|
|
|
239
|
+
// Per-file byte-offset cache backing historyTerminalBySlug's incremental
|
|
240
|
+
// read: path -> { ino, size, offset, map }. `map` is this file's own
|
|
241
|
+
// slug -> record result as of `offset` bytes; `offset` always sits on a
|
|
242
|
+
// line boundary (the byte right after the last consumed '\n') so a partial
|
|
243
|
+
// trailing line never gets counted as consumed.
|
|
244
|
+
const perFileCache = new Map();
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* needs_review_entry/needs_review_resolution lines (needsReviewLedger.cjs)
|
|
248
|
+
* share this file but aren't a job's terminal record — skip them, or a
|
|
249
|
+
* needs_review episode logged for a slug AFTER its real terminal row (e.g.
|
|
250
|
+
* it re-parked on a later run) would overwrite a true 'completed' with a
|
|
251
|
+
* kind-less, status-less row read as "not terminal".
|
|
252
|
+
*/
|
|
253
|
+
function applyTerminalLines(text, map) {
|
|
254
|
+
if (!text) return;
|
|
255
|
+
for (const line of text.split('\n')) {
|
|
256
|
+
if (!line.trim()) continue;
|
|
257
|
+
try {
|
|
258
|
+
const j = JSON.parse(line);
|
|
259
|
+
if (j?.kind && j.kind !== 'terminal') continue;
|
|
260
|
+
if (j?.slug) map.set(j.slug, { status: j.status, finishedAt: j.finishedAt, landedCommit: j.landedCommit ?? null });
|
|
261
|
+
} catch {
|
|
262
|
+
// corrupt/partial line — ignore
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Returns this one file's slug -> record map, reading only the bytes
|
|
269
|
+
* appended since the last call when possible. Falls back to a full
|
|
270
|
+
* re-parse when the file is new to the cache, its inode changed, or its
|
|
271
|
+
* size went backwards (rotation/truncation).
|
|
272
|
+
*/
|
|
273
|
+
async function readFileTerminalMap(target, stat) {
|
|
274
|
+
const cached = perFileCache.get(target);
|
|
275
|
+
const { ino, size } = stat;
|
|
276
|
+
|
|
277
|
+
if (cached && cached.ino === ino && size >= cached.size) {
|
|
278
|
+
if (size === cached.size) return cached.map;
|
|
279
|
+
|
|
280
|
+
const len = size - cached.offset;
|
|
281
|
+
const fh = await fsp.open(target, 'r');
|
|
282
|
+
let buf;
|
|
283
|
+
try {
|
|
284
|
+
buf = Buffer.alloc(len);
|
|
285
|
+
await fh.read(buf, 0, len, cached.offset);
|
|
286
|
+
} finally {
|
|
287
|
+
await fh.close();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Stop at the last complete newline — a partial trailing line (still
|
|
291
|
+
// being written) is left unconsumed so the next pass re-reads it once
|
|
292
|
+
// it's whole, instead of permanently skipping or mis-parsing it.
|
|
293
|
+
const lastNl = buf.lastIndexOf(0x0a);
|
|
294
|
+
const map = new Map(cached.map);
|
|
295
|
+
let offset = cached.offset;
|
|
296
|
+
if (lastNl !== -1) {
|
|
297
|
+
applyTerminalLines(buf.subarray(0, lastNl + 1).toString('utf8'), map);
|
|
298
|
+
offset = cached.offset + lastNl + 1;
|
|
299
|
+
}
|
|
300
|
+
const updated = { ino, size, offset, map };
|
|
301
|
+
perFileCache.set(target, updated);
|
|
302
|
+
return map;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Full (re)parse: first time seeing this path, its inode changed, or it
|
|
306
|
+
// shrank (log rotation/truncation) — the existing offset is no longer
|
|
307
|
+
// trustworthy.
|
|
308
|
+
let text = '';
|
|
309
|
+
try {
|
|
310
|
+
text = await fsp.readFile(target, 'utf8');
|
|
311
|
+
} catch (e) {
|
|
312
|
+
if (e.code !== 'ENOENT') throw e;
|
|
313
|
+
}
|
|
314
|
+
const map = new Map();
|
|
315
|
+
applyTerminalLines(text, map);
|
|
316
|
+
const lastNl = Buffer.from(text, 'utf8').lastIndexOf(0x0a);
|
|
317
|
+
const offset = lastNl === -1 ? 0 : lastNl + 1;
|
|
318
|
+
perFileCache.set(target, { ino, size, offset, map });
|
|
319
|
+
return map;
|
|
320
|
+
}
|
|
321
|
+
|
|
239
322
|
/**
|
|
240
323
|
* Full-file scan of history.jsonl, returning slug -> { status, finishedAt }
|
|
241
324
|
* for the most recently appended record of each slug. O(H) where H is the
|
|
242
325
|
* line count of history.jsonl; cached by the file's mtime so a reconcile()
|
|
243
|
-
* pass with no new archives since the last call is O(1).
|
|
326
|
+
* pass with no new archives since the last call is O(1). When the mtime
|
|
327
|
+
* check misses, each file is re-read incrementally via readFileTerminalMap
|
|
328
|
+
* rather than re-parsed from byte zero.
|
|
244
329
|
*
|
|
245
330
|
* Exists so reconcile() can tell "this on-disk PRD's job row already left
|
|
246
331
|
* jobs[] into history — do not resurrect it as a fresh pending job" apart
|
|
@@ -248,36 +333,22 @@ let historyCacheMap = null;
|
|
|
248
333
|
*/
|
|
249
334
|
async function historyTerminalBySlug() {
|
|
250
335
|
const paths = historyPaths();
|
|
251
|
-
const
|
|
252
|
-
try { return
|
|
336
|
+
const stats = await Promise.all(paths.map(async (p2) => {
|
|
337
|
+
try { return await fsp.stat(p2); } catch { return null; }
|
|
253
338
|
}));
|
|
254
|
-
const cacheKey = paths.map((p2, i) => `${p2}:${
|
|
339
|
+
const cacheKey = paths.map((p2, i) => `${p2}:${stats[i] ? stats[i].mtimeMs : -1}`).join('|');
|
|
255
340
|
if (cacheKey === historyCacheKey && historyCacheMap) return historyCacheMap;
|
|
256
341
|
|
|
257
342
|
const map = new Map();
|
|
258
|
-
for (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
if (!text) continue;
|
|
266
|
-
for (const line of text.split('\n')) {
|
|
267
|
-
if (!line.trim()) continue;
|
|
268
|
-
try {
|
|
269
|
-
const j = JSON.parse(line);
|
|
270
|
-
// needs_review_entry/needs_review_resolution lines (needsReviewLedger.cjs)
|
|
271
|
-
// share this file but aren't a job's terminal record — skip them, or a
|
|
272
|
-
// needs_review episode logged for a slug AFTER its real terminal row
|
|
273
|
-
// (e.g. it re-parked on a later run) would overwrite a true 'completed'
|
|
274
|
-
// with a kind-less, status-less row read as "not terminal".
|
|
275
|
-
if (j?.kind && j.kind !== 'terminal') continue;
|
|
276
|
-
if (j?.slug) map.set(j.slug, { status: j.status, finishedAt: j.finishedAt, landedCommit: j.landedCommit ?? null });
|
|
277
|
-
} catch {
|
|
278
|
-
// corrupt/partial line — ignore
|
|
279
|
-
}
|
|
343
|
+
for (let i = 0; i < paths.length; i++) {
|
|
344
|
+
const p2 = paths[i];
|
|
345
|
+
const stat = stats[i];
|
|
346
|
+
if (!stat) {
|
|
347
|
+
perFileCache.delete(p2);
|
|
348
|
+
continue;
|
|
280
349
|
}
|
|
350
|
+
const fileMap = await readFileTerminalMap(p2, stat);
|
|
351
|
+
for (const [slug, rec] of fileMap) map.set(slug, rec);
|
|
281
352
|
}
|
|
282
353
|
historyCacheKey = cacheKey;
|
|
283
354
|
historyCacheMap = map;
|
|
@@ -50,6 +50,7 @@ const VERDICT_LABELS = {
|
|
|
50
50
|
no_verdict_sentinel: 'no commit or verdict sentinel',
|
|
51
51
|
abandoned_background_task: 'abandoned auto-backgrounded task',
|
|
52
52
|
pass_no_commit: 'PASS sentinel but no commit landed',
|
|
53
|
+
sentinel_fail: 'executor reported SCHEDULER_VERDICT: FAIL',
|
|
53
54
|
pass_no_commit_already_shipped: 'PASS with no commit — deliverables already shipped',
|
|
54
55
|
pass_no_commit_prior_run_verified: 'PASS with no commit — prior run of this slug already landed the work',
|
|
55
56
|
silent_no_op: 'no commit, clean tree — no evidence of work',
|
|
@@ -30,9 +30,11 @@ const { pidAlive } = require('./pidAlive.cjs');
|
|
|
30
30
|
* Conservative by design: a false negative (live process treated as dead) is
|
|
31
31
|
* far worse than a late reap.
|
|
32
32
|
*/
|
|
33
|
-
function claudePidAlive(pid) {
|
|
33
|
+
function claudePidAlive(pid, { platform = process.platform } = {}) {
|
|
34
34
|
if (!pid || typeof pid !== 'number' || pid <= 1) return false;
|
|
35
35
|
if (!pidAlive(pid)) return false;
|
|
36
|
+
// Windows has no /proc — cannot verify identity, so never call it dead.
|
|
37
|
+
if (platform === 'win32') return true;
|
|
36
38
|
try {
|
|
37
39
|
const cmd = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8').replace(/\0/g, ' ');
|
|
38
40
|
return /\bclaude\b/.test(cmd);
|
|
@@ -73,7 +75,8 @@ function claudePidAlive(pid) {
|
|
|
73
75
|
* — i.e. exactly today's behaviour (fail toward terminalizing), never a hang
|
|
74
76
|
* or a thrown error propagating to the caller.
|
|
75
77
|
*/
|
|
76
|
-
function findLiveProcessForJob(job, { worktreeDir, runCwd } = {}) {
|
|
78
|
+
function findLiveProcessForJob(job, { worktreeDir, runCwd, platform = process.platform } = {}) {
|
|
79
|
+
if (platform === 'win32') return null; // no /proc — skip every read
|
|
77
80
|
const slug = job?.slug;
|
|
78
81
|
let entries;
|
|
79
82
|
try {
|
|
@@ -207,6 +207,16 @@ const SHIPPED_PERSONA_SEEDS = deepFreeze({
|
|
|
207
207
|
tools: "Read, Grep, Glob, Bash",
|
|
208
208
|
},
|
|
209
209
|
},
|
|
210
|
+
{
|
|
211
|
+
commit: "<fill in>",
|
|
212
|
+
bodySha256: "ca159cd47ac4b618af842c6ccd59107fac4a34b0efcfa5dfe7d06875736156ed",
|
|
213
|
+
fm: {
|
|
214
|
+
description: "Validates a finished PLAN (the PRDs its validate PRD lists) once, after its last PRD lands — re-runs each PRD's gate, checks every acceptance criterion against the real tree, reviews the plan's combined diff, and reports one VERIFIED/REFUTED verdict per PRD via sentinel lines. Runs headless as a scheduled PRD (agentType: validator); never edits product code and never queues work.",
|
|
215
|
+
model: "sonnet",
|
|
216
|
+
title: "Engineering — Plan Validator",
|
|
217
|
+
tools: "Read, Grep, Glob, Bash",
|
|
218
|
+
},
|
|
219
|
+
},
|
|
210
220
|
],
|
|
211
221
|
"demo-video-builder": [
|
|
212
222
|
{
|
|
@@ -222,16 +222,28 @@ function main() {
|
|
|
222
222
|
// spawned, without detaching further, dies too); fall back to the direct
|
|
223
223
|
// pid only when the group signal itself fails (e.g. --foreground, where
|
|
224
224
|
// the child shares OUR group rather than leading its own).
|
|
225
|
+
// Inlined (not require('./killTree.cjs')): this script is copied to ~ and
|
|
226
|
+
// runs standalone, so it cannot resolve app-relative modules. win32 has no
|
|
227
|
+
// process groups, so taskkill /T /F stands in for the group signal.
|
|
225
228
|
function killGroup(sig) {
|
|
229
|
+
if (process.platform === 'win32') {
|
|
230
|
+
try {
|
|
231
|
+
require('node:child_process').spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
|
|
232
|
+
} catch {
|
|
233
|
+
try { process.kill(child.pid, sig); } catch { /* already gone */ }
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
226
237
|
try {
|
|
227
|
-
|
|
238
|
+
const groupPid = -child.pid; // negative pid = whole POSIX process group
|
|
239
|
+
process.kill(groupPid, sig);
|
|
228
240
|
} catch {
|
|
229
241
|
try { process.kill(child.pid, sig); } catch { /* already gone */ }
|
|
230
242
|
}
|
|
231
243
|
}
|
|
232
244
|
|
|
233
245
|
try {
|
|
234
|
-
child = spawn(command, commandArgs, { stdio: 'inherit', detached: !opts.foreground });
|
|
246
|
+
child = spawn(command, commandArgs, { stdio: 'inherit', detached: !opts.foreground, windowsHide: true });
|
|
235
247
|
} catch (err) {
|
|
236
248
|
finish(err && err.code === 'EACCES' ? 126 : 127);
|
|
237
249
|
return;
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* updateCheck.cjs — bounded, offline-safe, opt-out "is a newer build published?" probe.
|
|
5
5
|
*
|
|
6
|
-
* getUpdateStatus() → { current, latest, behind }
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* getUpdateStatus() → { current, latest, behind, channel, downloadUrl? } (installer builds compare
|
|
7
|
+
* against the latest GitHub Release; npx/dev builds against the npm registry). Never throws/rejects; any failure
|
|
8
|
+
* (offline, DNS, non-200, bad JSON, timeout) → { current, latest: null, behind: false, channel }.
|
|
9
|
+
* The request is a bare GET (only a static User-Agent; no cookies/query/body) so it carries nothing that
|
|
9
10
|
* identifies the user or machine. Disabled by SM_UPDATE_CHECK=0 or E2E mode (SM_E2E=1).
|
|
10
11
|
*/
|
|
11
12
|
|
|
@@ -16,11 +17,14 @@ const path = require('node:path');
|
|
|
16
17
|
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
17
18
|
const REQUEST_TIMEOUT_MS = 3000;
|
|
18
19
|
const CACHE_FILE = 'update-check.json';
|
|
20
|
+
const GITHUB_CACHE_FILE = 'update-check-github.json';
|
|
21
|
+
const GITHUB_RELEASE_URL = 'https://api.github.com/repos/StanislavBG/claude-code-session-manager/releases/latest';
|
|
19
22
|
|
|
20
23
|
/** Plain GET → parsed JSON. Rejects on non-200, bad JSON, or timeout. */
|
|
21
24
|
function httpGetJson(url, timeoutMs) {
|
|
22
25
|
return new Promise((resolve, reject) => {
|
|
23
|
-
|
|
26
|
+
// GitHub's API rejects requests without a User-Agent; it names the app only, not the user.
|
|
27
|
+
const req = https.get(url, { timeout: timeoutMs, headers: { 'User-Agent': 'claude-code-session-manager' } }, (res) => {
|
|
24
28
|
if (res.statusCode !== 200) {
|
|
25
29
|
res.resume();
|
|
26
30
|
reject(new Error(`HTTP ${res.statusCode}`));
|
|
@@ -58,15 +62,20 @@ function isBehind(current, latest) {
|
|
|
58
62
|
return false;
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
function
|
|
65
|
+
function readCacheEntry(file, now) {
|
|
62
66
|
try {
|
|
63
67
|
const c = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
64
68
|
if (c && typeof c.latest === 'string' && typeof c.checkedAt === 'number'
|
|
65
|
-
&& now - c.checkedAt >= 0 && now - c.checkedAt < CACHE_TTL_MS) return c
|
|
69
|
+
&& now - c.checkedAt >= 0 && now - c.checkedAt < CACHE_TTL_MS) return c;
|
|
66
70
|
} catch { /* missing/corrupt cache = miss */ }
|
|
67
71
|
return null;
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
function readCache(file, now) {
|
|
75
|
+
const c = readCacheEntry(file, now);
|
|
76
|
+
return c ? c.latest : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
70
79
|
function defaultPackageName() {
|
|
71
80
|
return require(path.join(__dirname, '..', '..', '..', 'package.json')).name;
|
|
72
81
|
}
|
|
@@ -74,13 +83,34 @@ function defaultPackageName() {
|
|
|
74
83
|
async function getUpdateStatus({ deps } = {}) {
|
|
75
84
|
const d = deps || {};
|
|
76
85
|
let current = '';
|
|
86
|
+
let channel = 'npm';
|
|
77
87
|
try {
|
|
78
88
|
const app = d.app || require('electron').app;
|
|
79
89
|
current = app.getVersion();
|
|
90
|
+
const packaged = (d.isPackaged || require('./appRuntime.cjs').isPackagedApp)();
|
|
91
|
+
channel = packaged ? 'installer' : 'npm';
|
|
80
92
|
const env = d.env || process.env;
|
|
81
|
-
if (env.SM_UPDATE_CHECK === '0' || env.SM_E2E === '1') return { current, latest: null, behind: false };
|
|
93
|
+
if (env.SM_UPDATE_CHECK === '0' || env.SM_E2E === '1') return { current, latest: null, behind: false, channel };
|
|
82
94
|
|
|
83
95
|
const now = (d.now || Date.now)();
|
|
96
|
+
const write = d.writeJson || require('../config.cjs').writeJson;
|
|
97
|
+
if (packaged) {
|
|
98
|
+
const file = path.join(app.getPath('userData'), GITHUB_CACHE_FILE);
|
|
99
|
+
let entry = readCacheEntry(file, now);
|
|
100
|
+
if (entry == null) {
|
|
101
|
+
const rel = await (d.fetchJson || httpGetJson)(GITHUB_RELEASE_URL, REQUEST_TIMEOUT_MS);
|
|
102
|
+
const tag = rel && typeof rel.tag_name === 'string' ? rel.tag_name.replace(/^v/, '') : '';
|
|
103
|
+
if (!tag) return { current, latest: null, behind: false, channel };
|
|
104
|
+
const downloadUrl = typeof rel.html_url === 'string' ? rel.html_url : null;
|
|
105
|
+
entry = { checkedAt: now, latest: tag, downloadUrl };
|
|
106
|
+
try { await write(file, entry); } catch { /* cache is best-effort */ }
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
current, latest: entry.latest, behind: isBehind(current, entry.latest), channel,
|
|
110
|
+
downloadUrl: typeof entry.downloadUrl === 'string' ? entry.downloadUrl : null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
84
114
|
const file = path.join(app.getPath('userData'), CACHE_FILE);
|
|
85
115
|
let latest = readCache(file, now);
|
|
86
116
|
if (latest == null) {
|
|
@@ -88,14 +118,14 @@ async function getUpdateStatus({ deps } = {}) {
|
|
|
88
118
|
const url = `https://registry.npmjs.org/-/package/${name}/dist-tags`;
|
|
89
119
|
const tags = await (d.fetchJson || httpGetJson)(url, REQUEST_TIMEOUT_MS);
|
|
90
120
|
latest = tags && typeof tags.latest === 'string' ? tags.latest : null;
|
|
91
|
-
if (latest == null) return { current, latest: null, behind: false };
|
|
121
|
+
if (latest == null) return { current, latest: null, behind: false, channel };
|
|
92
122
|
try {
|
|
93
|
-
await (
|
|
123
|
+
await write(file, { checkedAt: now, latest });
|
|
94
124
|
} catch { /* cache is best-effort */ }
|
|
95
125
|
}
|
|
96
|
-
return { current, latest, behind: isBehind(current, latest) };
|
|
126
|
+
return { current, latest, behind: isBehind(current, latest), channel };
|
|
97
127
|
} catch {
|
|
98
|
-
return { current, latest: null, behind: false };
|
|
128
|
+
return { current, latest: null, behind: false, channel };
|
|
99
129
|
}
|
|
100
130
|
}
|
|
101
131
|
|
|
@@ -9,6 +9,7 @@ const schedulerPaths = require('./schedulerPaths.cjs');
|
|
|
9
9
|
const { isRestartingMarkerActive } = require('./upgradeDrain.cjs');
|
|
10
10
|
const { pidAlive: pidAliveCore } = require('./pidAlive.cjs');
|
|
11
11
|
const atomicFs = require('./atomicFs.cjs');
|
|
12
|
+
const { isPackagedApp } = require('./appRuntime.cjs');
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
|
|
@@ -230,14 +231,32 @@ function logRelaunchLine(logPath, message) {
|
|
|
230
231
|
* (short-lived) process doesn't block on the launched app and the app
|
|
231
232
|
* survives the watchdog exiting.
|
|
232
233
|
*/
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
/**
|
|
235
|
+
* relaunchSpec(opts?) — what the watchdog spawns to bring the app back.
|
|
236
|
+
* Packaged installer builds have no npx: relaunch the app binary itself.
|
|
237
|
+
* @param {{ packaged?: boolean, execPath?: string }} [opts]
|
|
238
|
+
* @returns {{ command: string, args: string[], packaged: boolean }}
|
|
239
|
+
*/
|
|
240
|
+
function relaunchSpec(opts) {
|
|
241
|
+
if (isPackagedApp(opts)) {
|
|
242
|
+
return { command: (opts && opts.execPath) || process.execPath, args: [], packaged: true };
|
|
243
|
+
}
|
|
244
|
+
return { command: 'npx', args: ['claude-code-session-manager@latest'], packaged: false };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function defaultSpawnRelaunch({ logPath = schedulerPaths.watchdogRelaunchLogPath(), packaged, execPath, spawnFn } = {}) {
|
|
248
|
+
const spawn = spawnFn || require('node:child_process').spawn;
|
|
249
|
+
const spec = relaunchSpec({ packaged, execPath });
|
|
235
250
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
236
251
|
const fd = fs.openSync(logPath, 'a');
|
|
237
252
|
try {
|
|
238
|
-
const
|
|
253
|
+
const env = { ...process.env };
|
|
254
|
+
// The watchdog may itself run as node via the app binary; the relaunched app must not.
|
|
255
|
+
if (spec.packaged) delete env.ELECTRON_RUN_AS_NODE;
|
|
256
|
+
const child = spawn(spec.command, spec.args, {
|
|
239
257
|
detached: true,
|
|
240
258
|
stdio: ['ignore', fd, fd],
|
|
259
|
+
env,
|
|
241
260
|
});
|
|
242
261
|
child.on('error', (e) => {
|
|
243
262
|
logRelaunchLine(logPath, `spawn error: ${e?.message}`);
|
|
@@ -460,6 +479,7 @@ module.exports = {
|
|
|
460
479
|
readRelaunchState,
|
|
461
480
|
writeRelaunchState,
|
|
462
481
|
defaultSpawnRelaunch,
|
|
482
|
+
relaunchSpec,
|
|
463
483
|
maybeRelaunchApp,
|
|
464
484
|
DEFAULT_MAX_AGE_MS,
|
|
465
485
|
DEFAULT_DISPATCH_DEAD_MS,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* winSpawn.cjs — make spawn() work for Windows .cmd/.bat shims (npx, npm) without
|
|
3
|
+
* shell:true. Node refuses to spawn those directly (EINVAL), so they are wrapped in
|
|
4
|
+
* an explicit `cmd.exe /d /s /c "<line>"` argv with every cmd metacharacter escaped.
|
|
5
|
+
* Non-win32 input passes through untouched.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PATHEXT = '.COM;.EXE;.BAT;.CMD';
|
|
14
|
+
const META = /([()\][%!^"`<>&|;, *?])/g;
|
|
15
|
+
|
|
16
|
+
function envGet(env, name) {
|
|
17
|
+
if (env[name] !== undefined) return env[name];
|
|
18
|
+
const key = Object.keys(env).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
19
|
+
return key ? env[key] : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function findOnPath(command, env, exists) {
|
|
23
|
+
const w = path.win32;
|
|
24
|
+
const exts = (envGet(env, 'PATHEXT') || DEFAULT_PATHEXT)
|
|
25
|
+
.split(';').filter(Boolean).map((e) => e.toLowerCase());
|
|
26
|
+
const hasDir = /[\\/]/.test(command);
|
|
27
|
+
const dirs = hasDir ? [''] : (envGet(env, 'PATH') || '').split(';').filter(Boolean);
|
|
28
|
+
const hasExt = exts.includes(w.extname(command).toLowerCase());
|
|
29
|
+
for (const dir of dirs) {
|
|
30
|
+
const base = dir ? w.join(dir, command) : command;
|
|
31
|
+
const candidates = hasExt ? [base] : [];
|
|
32
|
+
for (const e of exts) candidates.push(base + e);
|
|
33
|
+
// PATHEXT order is by extension; keep the casing the filesystem probe accepts.
|
|
34
|
+
for (const cand of candidates) {
|
|
35
|
+
if (exists(cand)) return cand;
|
|
36
|
+
const upper = cand.slice(0, cand.length - w.extname(cand).length) + w.extname(cand).toUpperCase();
|
|
37
|
+
if (upper !== cand && exists(upper)) return upper;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Quote for CreateProcess argv parsing, then ^-escape every cmd metacharacter.
|
|
44
|
+
function quoteArg(arg) {
|
|
45
|
+
let s = String(arg);
|
|
46
|
+
s = s.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, '$1$1');
|
|
47
|
+
s = `"${s}"`;
|
|
48
|
+
return s.replace(META, '^$1');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveSpawn(command, args = [], { platform = process.platform, env = process.env, exists = fs.existsSync } = {}) {
|
|
52
|
+
if (platform !== 'win32') return { command, args };
|
|
53
|
+
const resolved = findOnPath(command, env, exists);
|
|
54
|
+
if (!resolved) return { command, args };
|
|
55
|
+
const ext = path.win32.extname(resolved).toLowerCase();
|
|
56
|
+
if (ext === '.cmd' || ext === '.bat') {
|
|
57
|
+
const line = [resolved, ...args].map(quoteArg).join(' ');
|
|
58
|
+
return {
|
|
59
|
+
command: envGet(env, 'ComSpec') || 'cmd.exe',
|
|
60
|
+
args: ['/d', '/s', '/c', `"${line}"`],
|
|
61
|
+
windowsVerbatimArguments: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { command: resolved, args };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { resolveSpawn };
|
package/src/main/otel.cjs
CHANGED
|
@@ -8,58 +8,36 @@
|
|
|
8
8
|
* plan text, and agent prompts are excluded unless `includeContent` is on
|
|
9
9
|
* (matches upstream OTEL_LOG_USER_PROMPTS opt-in).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Threading: the BatchSpanProcessor batches/sends spans on its own timer; we
|
|
16
|
-
* never block the transcript flush.
|
|
11
|
+
* Spans are POSTed as OTLP/HTTP JSON by lib/otlpTraceExporter.cjs (zero
|
|
12
|
+
* dependencies). recordSpan() only queues; the exporter batches/sends on its
|
|
13
|
+
* own unref()'d timer, so we never block the transcript flush. The app must
|
|
14
|
+
* keep working without telemetry: a failed export is surfaced, never thrown.
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
17
|
const otelSettings = require('./otelSettings.cjs');
|
|
18
|
+
const { createOtlpTraceExporter } = require('./lib/otlpTraceExporter.cjs');
|
|
20
19
|
|
|
21
|
-
let
|
|
22
|
-
let tracer = null;
|
|
20
|
+
let exporter = null;
|
|
23
21
|
let enabled = false;
|
|
24
22
|
let includeContent = false;
|
|
25
23
|
let lastError = null;
|
|
26
24
|
let initialized = false;
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
|
-
*
|
|
30
|
-
* We catch the per-require so a partial install doesn't half-init.
|
|
31
|
-
*/
|
|
32
|
-
function loadDeps() {
|
|
33
|
-
try {
|
|
34
|
-
const api = require('@opentelemetry/api');
|
|
35
|
-
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
|
|
36
|
-
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
|
|
37
|
-
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
38
|
-
const { resourceFromAttributes } = require('@opentelemetry/resources');
|
|
39
|
-
const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
|
|
40
|
-
return { api, NodeTracerProvider, BatchSpanProcessor, OTLPTraceExporter, resourceFromAttributes, ATTR_SERVICE_NAME };
|
|
41
|
-
} catch (err) {
|
|
42
|
-
return { error: err };
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Tear down any existing provider. Best-effort — flushes pending spans on a
|
|
27
|
+
* Tear down any existing exporter. Best-effort — flushes pending spans on a
|
|
48
28
|
* 2s ceiling so a wedged exporter doesn't block app shutdown / config reload.
|
|
49
29
|
*/
|
|
50
30
|
async function shutdown() {
|
|
51
|
-
if (!
|
|
31
|
+
if (!exporter) {
|
|
52
32
|
enabled = false;
|
|
53
|
-
tracer = null;
|
|
54
33
|
return;
|
|
55
34
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
tracer = null;
|
|
35
|
+
const e = exporter;
|
|
36
|
+
exporter = null;
|
|
59
37
|
enabled = false;
|
|
60
38
|
try {
|
|
61
39
|
await Promise.race([
|
|
62
|
-
|
|
40
|
+
e.shutdown(),
|
|
63
41
|
new Promise((resolve) => setTimeout(resolve, 2000)),
|
|
64
42
|
]);
|
|
65
43
|
} catch (err) {
|
|
@@ -68,7 +46,7 @@ async function shutdown() {
|
|
|
68
46
|
}
|
|
69
47
|
|
|
70
48
|
/**
|
|
71
|
-
* Configure the
|
|
49
|
+
* Configure the exporter. Tears down any prior exporter first so a
|
|
72
50
|
* settings change reliably re-points at the new endpoint.
|
|
73
51
|
*
|
|
74
52
|
* Returns { ok, error?: string } — the renderer surfaces `error` in the UI.
|
|
@@ -77,31 +55,13 @@ async function init({ endpoint, headers, serviceName, includeContent: ic } = {})
|
|
|
77
55
|
await shutdown();
|
|
78
56
|
initialized = true;
|
|
79
57
|
|
|
80
|
-
const deps = loadDeps();
|
|
81
|
-
if (deps.error) {
|
|
82
|
-
lastError = `OTEL packages unavailable: ${deps.error.message}`;
|
|
83
|
-
return { ok: false, error: lastError };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { NodeTracerProvider, BatchSpanProcessor, OTLPTraceExporter, resourceFromAttributes, ATTR_SERVICE_NAME } = deps;
|
|
87
|
-
|
|
88
58
|
try {
|
|
89
|
-
|
|
59
|
+
exporter = createOtlpTraceExporter({
|
|
90
60
|
url: String(endpoint || 'http://localhost:4318/v1/traces'),
|
|
91
61
|
headers: typeof headers === 'object' && headers ? headers : {},
|
|
62
|
+
serviceName: String(serviceName || 'session-manager'),
|
|
63
|
+
scopeName: 'session-manager-transcripts',
|
|
92
64
|
});
|
|
93
|
-
const tp = new NodeTracerProvider({
|
|
94
|
-
resource: resourceFromAttributes({
|
|
95
|
-
[ATTR_SERVICE_NAME]: String(serviceName || 'session-manager'),
|
|
96
|
-
}),
|
|
97
|
-
spanProcessors: [new BatchSpanProcessor(exporter)],
|
|
98
|
-
});
|
|
99
|
-
// Note: we deliberately do NOT call tp.register() — that installs a global
|
|
100
|
-
// tracer provider, which would intercept any other OTEL user in the
|
|
101
|
-
// process. We only want to emit our own spans, so we use the provider
|
|
102
|
-
// directly via getTracer().
|
|
103
|
-
provider = tp;
|
|
104
|
-
tracer = tp.getTracer('session-manager-transcripts');
|
|
105
65
|
includeContent = !!ic;
|
|
106
66
|
enabled = true;
|
|
107
67
|
lastError = null;
|
|
@@ -205,11 +165,11 @@ function pruneAttrs(obj) {
|
|
|
205
165
|
|
|
206
166
|
/**
|
|
207
167
|
* Emit a 0-duration span for one classified transcript event.
|
|
208
|
-
* Synchronous + non-blocking: the
|
|
168
|
+
* Synchronous + non-blocking: the exporter handles network I/O
|
|
209
169
|
* on its own schedule.
|
|
210
170
|
*/
|
|
211
171
|
function recordTranscriptEvent({ tabId, tabCwd, kind, data, ts }) {
|
|
212
|
-
if (!enabled || !
|
|
172
|
+
if (!enabled || !exporter) return;
|
|
213
173
|
try {
|
|
214
174
|
const startTime = typeof ts === 'number' ? ts : Date.now();
|
|
215
175
|
const attrs = pruneAttrs({
|
|
@@ -218,11 +178,7 @@ function recordTranscriptEvent({ tabId, tabCwd, kind, data, ts }) {
|
|
|
218
178
|
kind,
|
|
219
179
|
...attrsFor(kind, data),
|
|
220
180
|
});
|
|
221
|
-
|
|
222
|
-
startTime,
|
|
223
|
-
attributes: attrs,
|
|
224
|
-
});
|
|
225
|
-
span.end(startTime);
|
|
181
|
+
exporter.recordSpan({ name: `transcript.${kind}`, timeMs: startTime, attributes: attrs });
|
|
226
182
|
} catch (err) {
|
|
227
183
|
// A failure here must never break transcript ingestion. Log once-ish.
|
|
228
184
|
if (!recordTranscriptEvent._warned) {
|