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
|
@@ -49,11 +49,11 @@ function findCommand(name) {
|
|
|
49
49
|
*
|
|
50
50
|
* @returns {Promise<{ ok: true, opener: string } | { ok: false, error: string }>}
|
|
51
51
|
*/
|
|
52
|
-
function spawnDetached(cmd, args, opener = cmd) {
|
|
52
|
+
function spawnDetached(cmd, args, opener = cmd, spawnFn = spawn, extraOpts = {}) {
|
|
53
53
|
return new Promise((resolve) => {
|
|
54
54
|
let child;
|
|
55
55
|
try {
|
|
56
|
-
child =
|
|
56
|
+
child = spawnFn(cmd, args, { detached: true, stdio: 'ignore', env: cleanChildEnv(), ...extraOpts });
|
|
57
57
|
} catch (e) {
|
|
58
58
|
resolve({ ok: false, error: `failed to launch ${opener}: ${e?.message ?? e}` });
|
|
59
59
|
return;
|
|
@@ -116,12 +116,16 @@ async function openFileInEditor({ path: abs, line, col, editor }) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* Open a directory in the OS file manager (Finder on macOS, Nautilus etc on Linux
|
|
119
|
+
* Open a directory in the OS file manager (Finder on macOS, Nautilus etc on Linux,
|
|
120
|
+
* explorer.exe on Windows).
|
|
120
121
|
*
|
|
121
122
|
* @param {{ cwd: string }} opts
|
|
123
|
+
* @param {{ platform?: string, spawn?: Function }} [deps]
|
|
122
124
|
* @returns {Promise<{ ok: true, opener: string } | { ok: false, error: string }>}
|
|
123
125
|
*/
|
|
124
|
-
async function openInFinder({ cwd }) {
|
|
126
|
+
async function openInFinder({ cwd }, deps = {}) {
|
|
127
|
+
const { platform = process.platform, spawn: spawnFn = spawn } = deps;
|
|
128
|
+
if (platform === 'win32') return spawnDetached('explorer.exe', [cwd], 'explorer', spawnFn);
|
|
125
129
|
const errStr = await shell.openPath(cwd);
|
|
126
130
|
if (errStr) return { ok: false, error: errStr };
|
|
127
131
|
return { ok: true, opener: 'shell' };
|
|
@@ -132,28 +136,75 @@ async function openInFinder({ cwd }) {
|
|
|
132
136
|
*
|
|
133
137
|
* Linux: tries gnome-terminal → konsole → xfce4-terminal → xterm.
|
|
134
138
|
* macOS: delegates to Terminal.app via `open -a`.
|
|
139
|
+
* Windows: `wt.exe -d <cwd>` when on PATH, else powershell.exe -NoExit in a new
|
|
140
|
+
* console via `cmd.exe /d /c start`.
|
|
135
141
|
*
|
|
136
142
|
* Arg shapes per terminal are preserved verbatim from index.cjs (AC impl note):
|
|
137
143
|
* gnome-terminal → ['--working-directory=<cwd>']
|
|
138
144
|
* others → ['-e', "bash -c \"cd '<cwd>' && exec bash\""]
|
|
139
145
|
*
|
|
140
146
|
* @param {{ cwd: string }} opts
|
|
147
|
+
* @param {{ platform?: string, spawn?: Function, findCommand?: (n: string) => string | null }} [deps]
|
|
141
148
|
* @returns {Promise<{ ok: true, opener: string } | { ok: false, error: string }>}
|
|
142
149
|
*/
|
|
143
|
-
async function openInTerminal({ cwd }) {
|
|
144
|
-
|
|
150
|
+
async function openInTerminal({ cwd }, deps = {}) {
|
|
151
|
+
const { platform = process.platform, spawn: spawnFn = spawn, findCommand: find = findCommand } = deps;
|
|
152
|
+
if (platform === 'linux') {
|
|
145
153
|
const terms = ['gnome-terminal', 'konsole', 'xfce4-terminal', 'xterm'];
|
|
146
154
|
for (const t of terms) {
|
|
147
|
-
if (!
|
|
155
|
+
if (!find(t)) continue;
|
|
148
156
|
const args = t === 'gnome-terminal'
|
|
149
157
|
? ['--working-directory=' + cwd]
|
|
150
158
|
: ['-e', `bash -c "cd '${cwd.replace(/'/g, "'\\''")}' && exec bash"`];
|
|
151
|
-
return spawnDetached(t, args);
|
|
159
|
+
return spawnDetached(t, args, t, spawnFn);
|
|
152
160
|
}
|
|
153
|
-
} else if (
|
|
154
|
-
return spawnDetached('open', ['-a', 'Terminal', cwd], 'Terminal.app');
|
|
161
|
+
} else if (platform === 'darwin') {
|
|
162
|
+
return spawnDetached('open', ['-a', 'Terminal', cwd], 'Terminal.app', spawnFn);
|
|
163
|
+
} else if (platform === 'win32') {
|
|
164
|
+
if (find('wt.exe')) return spawnDetached('wt.exe', ['-d', cwd], 'wt', spawnFn);
|
|
165
|
+
const setLoc = `Set-Location -LiteralPath '${cwd.replace(/'/g, "''")}'`;
|
|
166
|
+
return spawnDetached('cmd.exe',
|
|
167
|
+
['/d', '/c', 'start', '', 'powershell.exe', '-NoExit', '-Command', setLoc],
|
|
168
|
+
'powershell', spawnFn, { windowsHide: true });
|
|
155
169
|
}
|
|
156
170
|
return { ok: false, error: 'no terminal found' };
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
|
|
173
|
+
/** Escape a string for use inside an AppleScript double-quoted literal. */
|
|
174
|
+
const appleScriptQuote = (s) => `"${String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Run a command in a visible OS terminal so the user sees and controls it
|
|
178
|
+
* (password prompts, installer dialogs). `command` MUST come from main-side
|
|
179
|
+
* code, never from the renderer. argv arrays only — never shell:true.
|
|
180
|
+
*
|
|
181
|
+
* @param {{ command: string, shell?: 'sh' | 'powershell' }} opts
|
|
182
|
+
* @param {{ platform?: string, spawn?: Function, find?: (n: string) => string | null }} [deps]
|
|
183
|
+
* @returns {Promise<{ ok: true, opener: string } | { ok: false, error: string }>}
|
|
184
|
+
*/
|
|
185
|
+
async function runInTerminal({ command, shell: shellKind }, deps = {}) {
|
|
186
|
+
const { platform = process.platform, spawn: spawnFn = spawn, find = findCommand } = deps;
|
|
187
|
+
if (typeof command !== 'string' || !command.trim()) return { ok: false, error: 'empty command' };
|
|
188
|
+
if (platform === 'darwin') {
|
|
189
|
+
const script = `tell application "Terminal"\nactivate\ndo script ${appleScriptQuote(command)}\nend tell`;
|
|
190
|
+
return spawnDetached('osascript', ['-e', script], 'Terminal.app', spawnFn);
|
|
191
|
+
}
|
|
192
|
+
if (platform === 'win32') {
|
|
193
|
+
return spawnDetached('cmd.exe',
|
|
194
|
+
['/d', '/c', 'start', '', 'powershell.exe', '-NoExit', '-Command', command],
|
|
195
|
+
'powershell', spawnFn);
|
|
196
|
+
}
|
|
197
|
+
if (platform === 'linux') {
|
|
198
|
+
const inner = `${command}; exec bash`;
|
|
199
|
+
for (const t of ['gnome-terminal', 'konsole', 'xfce4-terminal', 'xterm']) {
|
|
200
|
+
if (!find(t)) continue;
|
|
201
|
+
const args = t === 'gnome-terminal'
|
|
202
|
+
? ['--', 'bash', '-lc', inner]
|
|
203
|
+
: ['-e', 'bash', '-lc', inner];
|
|
204
|
+
return spawnDetached(t, args, t, spawnFn);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { ok: false, error: 'no terminal found' };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = { spawnDetached, findCommand, openInEditor, openFileInEditor, openInFinder, openInTerminal, runInTerminal };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* otlpTraceExporter — zero-dependency batching exporter that POSTs 0-duration
|
|
3
|
+
* spans to an OTLP/HTTP endpoint as JSON (ExportTraceServiceRequest).
|
|
4
|
+
*
|
|
5
|
+
* Replaces the @opentelemetry/* SDK for the one thing otel.cjs does: emit
|
|
6
|
+
* "event" spans. recordSpan() only queues (no I/O, never throws); a timer
|
|
7
|
+
* unref()'d so it can't hold the process open drains the queue in batches.
|
|
8
|
+
* A failed POST drops that batch and is surfaced via stats() — the app must
|
|
9
|
+
* keep working without telemetry. Field names mirror otlp-transformer's JSON.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const crypto = require('node:crypto');
|
|
13
|
+
|
|
14
|
+
function toAnyValue(v) {
|
|
15
|
+
switch (typeof v) {
|
|
16
|
+
case 'string': return { stringValue: v };
|
|
17
|
+
case 'boolean': return { boolValue: v };
|
|
18
|
+
case 'bigint': return { intValue: v.toString() };
|
|
19
|
+
case 'number':
|
|
20
|
+
if (!Number.isFinite(v)) return { stringValue: String(v) };
|
|
21
|
+
return Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v };
|
|
22
|
+
default:
|
|
23
|
+
try { return { stringValue: JSON.stringify(v) ?? String(v) }; } catch { return { stringValue: String(v) }; }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toAttributes(obj) {
|
|
28
|
+
const out = [];
|
|
29
|
+
if (!obj || typeof obj !== 'object') return out;
|
|
30
|
+
for (const [key, v] of Object.entries(obj)) {
|
|
31
|
+
if (v === undefined || v === null) continue;
|
|
32
|
+
out.push({ key, value: toAnyValue(v) });
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createOtlpTraceExporter({
|
|
38
|
+
url,
|
|
39
|
+
headers,
|
|
40
|
+
serviceName,
|
|
41
|
+
scopeName,
|
|
42
|
+
maxQueue = 2048,
|
|
43
|
+
maxBatch = 512,
|
|
44
|
+
flushIntervalMs = 5000,
|
|
45
|
+
timeoutMs = 10000,
|
|
46
|
+
} = {}) {
|
|
47
|
+
let queue = [];
|
|
48
|
+
let timer = null;
|
|
49
|
+
let closed = false;
|
|
50
|
+
let inFlight = Promise.resolve();
|
|
51
|
+
const counts = { exported: 0, dropped: 0, failed: 0, lastError: null };
|
|
52
|
+
|
|
53
|
+
function ensureTimer() {
|
|
54
|
+
if (timer || closed) return;
|
|
55
|
+
timer = setInterval(() => { flush(); }, flushIntervalMs);
|
|
56
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function recordSpan({ name, timeMs, attributes } = {}) {
|
|
60
|
+
try {
|
|
61
|
+
if (closed) return;
|
|
62
|
+
const ms = typeof timeMs === 'number' && Number.isFinite(timeMs) ? timeMs : Date.now();
|
|
63
|
+
const nanos = (BigInt(Math.round(ms)) * 1000000n).toString();
|
|
64
|
+
queue.push({
|
|
65
|
+
traceId: crypto.randomBytes(16).toString('hex'),
|
|
66
|
+
spanId: crypto.randomBytes(8).toString('hex'),
|
|
67
|
+
name: String(name),
|
|
68
|
+
kind: 1,
|
|
69
|
+
startTimeUnixNano: nanos,
|
|
70
|
+
endTimeUnixNano: nanos,
|
|
71
|
+
attributes: toAttributes(attributes),
|
|
72
|
+
});
|
|
73
|
+
if (queue.length > maxQueue) {
|
|
74
|
+
const over = queue.length - maxQueue;
|
|
75
|
+
queue.splice(0, over);
|
|
76
|
+
counts.dropped += over;
|
|
77
|
+
}
|
|
78
|
+
ensureTimer();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
counts.lastError = err?.message || String(err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function post(spans) {
|
|
85
|
+
const body = JSON.stringify({
|
|
86
|
+
resourceSpans: [{
|
|
87
|
+
resource: { attributes: [{ key: 'service.name', value: { stringValue: String(serviceName) } }] },
|
|
88
|
+
scopeSpans: [{ scope: { name: String(scopeName) }, spans }],
|
|
89
|
+
}],
|
|
90
|
+
});
|
|
91
|
+
const res = await fetch(url, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: { ...(headers || {}), 'content-type': 'application/json' },
|
|
94
|
+
body,
|
|
95
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
96
|
+
});
|
|
97
|
+
try { await res.arrayBuffer(); } catch { /* body drain is best-effort */ }
|
|
98
|
+
if (!res.ok) throw new Error(`OTLP endpoint responded ${res.status}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function drain() {
|
|
102
|
+
while (queue.length > 0) {
|
|
103
|
+
const batch = queue.splice(0, maxBatch);
|
|
104
|
+
try {
|
|
105
|
+
await post(batch);
|
|
106
|
+
counts.exported += batch.length;
|
|
107
|
+
counts.lastError = null;
|
|
108
|
+
} catch (err) {
|
|
109
|
+
counts.failed += 1;
|
|
110
|
+
counts.lastError = err?.message || String(err);
|
|
111
|
+
return; // drop this batch; the next tick retries whatever is left
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function flush() {
|
|
117
|
+
const next = inFlight.then(drain, drain);
|
|
118
|
+
inFlight = next.catch(() => {});
|
|
119
|
+
return next.catch(() => {});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function shutdown() {
|
|
123
|
+
closed = true;
|
|
124
|
+
if (timer) { clearInterval(timer); timer = null; }
|
|
125
|
+
let guard;
|
|
126
|
+
await Promise.race([
|
|
127
|
+
flush(),
|
|
128
|
+
new Promise((resolve) => { guard = setTimeout(resolve, timeoutMs); }),
|
|
129
|
+
]);
|
|
130
|
+
clearTimeout(guard);
|
|
131
|
+
queue = [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function stats() {
|
|
135
|
+
return {
|
|
136
|
+
queued: queue.length,
|
|
137
|
+
exported: counts.exported,
|
|
138
|
+
dropped: counts.dropped,
|
|
139
|
+
failed: counts.failed,
|
|
140
|
+
lastError: counts.lastError,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { recordSpan, flush, shutdown, stats };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { createOtlpTraceExporter };
|
|
@@ -166,13 +166,55 @@ function slugForPrdFile(name) {
|
|
|
166
166
|
return name.slice(0, -3);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Per-cwd cache of the flat prds/ dir's last-swept state, keyed by cwd.
|
|
171
|
+
* {exists, mtimeMs, failed, skipped} — failed/skipped are the RESIDUAL
|
|
172
|
+
* (still-in-place) files from the last real sweep, so a cache hit can report
|
|
173
|
+
* them again without re-reading anything. `moved` is deliberately NOT cached:
|
|
174
|
+
* it is a count of actions taken during a pass, and a pass that does no work
|
|
175
|
+
* (cache hit) always reports moved: 0, never replays a stale move count.
|
|
176
|
+
*
|
|
177
|
+
* Perf win (PRD: flatPrdSweep phase, p50 1.3s/max 5.7s over up to 500 cwds
|
|
178
|
+
* per reconcile): a cwd whose flat dir is absent or whose mtime hasn't
|
|
179
|
+
* changed since the last successful sweep skips the readdir AND the
|
|
180
|
+
* liveSlugsForCwd() queue.json parse entirely — only a cheap stat runs.
|
|
181
|
+
*/
|
|
182
|
+
const flatDirSweepCache = new Map();
|
|
183
|
+
|
|
184
|
+
/** Test-only: clear the per-cwd flat-dir sweep cache between test cases. */
|
|
185
|
+
function resetFlatDirSweepCacheForTests() {
|
|
186
|
+
flatDirSweepCache.clear();
|
|
187
|
+
}
|
|
188
|
+
|
|
169
189
|
async function consolidateFlatPrds(cwd, opts = {}) {
|
|
170
190
|
const flatDir = resolvePrdWriteDir(cwd);
|
|
171
191
|
const archiveDir = path.join(path.dirname(flatDir), 'prds-archived');
|
|
192
|
+
|
|
193
|
+
let mtimeMs;
|
|
194
|
+
try {
|
|
195
|
+
mtimeMs = (await fsp.stat(flatDir)).mtimeMs;
|
|
196
|
+
} catch (e) {
|
|
197
|
+
if (e?.code !== 'ENOENT') throw e;
|
|
198
|
+
mtimeMs = null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (mtimeMs === null) {
|
|
202
|
+
flatDirSweepCache.set(cwd, { exists: false, mtimeMs: null, failed: [], skipped: [] });
|
|
203
|
+
return { moved: 0, failed: [], skipped: [] };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const cached = flatDirSweepCache.get(cwd);
|
|
207
|
+
if (cached && cached.exists && cached.mtimeMs === mtimeMs) {
|
|
208
|
+
// Unchanged since the last successful sweep — no readdir, no queue.json
|
|
209
|
+
// parse. Report the same residual (still-blocked/still-failed) files.
|
|
210
|
+
return { moved: 0, failed: cached.failed, skipped: cached.skipped };
|
|
211
|
+
}
|
|
212
|
+
|
|
172
213
|
let entries;
|
|
173
214
|
try {
|
|
174
215
|
entries = await fsp.readdir(flatDir);
|
|
175
216
|
} catch {
|
|
217
|
+
flatDirSweepCache.set(cwd, { exists: false, mtimeMs: null, failed: [], skipped: [] });
|
|
176
218
|
return { moved: 0, failed: [], skipped: [] };
|
|
177
219
|
}
|
|
178
220
|
|
|
@@ -188,6 +230,8 @@ async function consolidateFlatPrds(cwd, opts = {}) {
|
|
|
188
230
|
if (!name.endsWith('.md') || name.startsWith('.')) continue;
|
|
189
231
|
skipped.push({ file: name, reason: 'queue state unreadable — cannot prove no live job' });
|
|
190
232
|
}
|
|
233
|
+
// Never cache a fail-closed read as settled — queue state may become
|
|
234
|
+
// readable on the very next pass and must be re-parsed, not replayed.
|
|
191
235
|
return { moved: 0, failed, skipped };
|
|
192
236
|
}
|
|
193
237
|
|
|
@@ -211,6 +255,19 @@ async function consolidateFlatPrds(cwd, opts = {}) {
|
|
|
211
255
|
failed.push({ file: name, reason: e?.message ?? 'move failed' });
|
|
212
256
|
}
|
|
213
257
|
}
|
|
258
|
+
|
|
259
|
+
// Re-stat after the sweep: renames just mutated the dir's own mtime, so the
|
|
260
|
+
// cached value must reflect the post-sweep state, not the one we read in.
|
|
261
|
+
let postMtimeMs = null;
|
|
262
|
+
try {
|
|
263
|
+
postMtimeMs = (await fsp.stat(flatDir)).mtimeMs;
|
|
264
|
+
} catch { /* removed concurrently — leave uncached, next pass re-checks */ }
|
|
265
|
+
if (postMtimeMs !== null) {
|
|
266
|
+
flatDirSweepCache.set(cwd, { exists: true, mtimeMs: postMtimeMs, failed, skipped });
|
|
267
|
+
} else {
|
|
268
|
+
flatDirSweepCache.delete(cwd);
|
|
269
|
+
}
|
|
270
|
+
|
|
214
271
|
return { moved, failed, skipped };
|
|
215
272
|
}
|
|
216
273
|
|
|
@@ -275,4 +332,10 @@ async function legacyAdoptExistingPrds() {
|
|
|
275
332
|
return { stamped, failed };
|
|
276
333
|
}
|
|
277
334
|
|
|
278
|
-
module.exports = {
|
|
335
|
+
module.exports = {
|
|
336
|
+
migratePrds,
|
|
337
|
+
consolidateFlatPrds,
|
|
338
|
+
legacyAdoptExistingPrds,
|
|
339
|
+
LIVE_JOB_STATUSES,
|
|
340
|
+
resetFlatDirSweepCacheForTests,
|
|
341
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prereqs.cjs — first-run prerequisite checklist (git, Claude Code CLI,
|
|
3
|
+
* Claude sign-in, and Git Bash on Windows). Each missing item carries the
|
|
4
|
+
* OFFICIAL one-line install command so a UI can offer one-click installs.
|
|
5
|
+
* Detection only — never runs an installer. Probes are injectable
|
|
6
|
+
* (opts.execFile / hasCredentials / exists) so tests spawn nothing.
|
|
7
|
+
*/
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const { promisify } = require('node:util');
|
|
13
|
+
const childProcess = require('node:child_process');
|
|
14
|
+
const { pathWithUserBins } = require('./cleanEnv.cjs');
|
|
15
|
+
const { resolveClaudeBin } = require('./claudeBin.cjs');
|
|
16
|
+
const { readCredentials } = require('./credentials.cjs');
|
|
17
|
+
|
|
18
|
+
const PROBE_TIMEOUT_MS = 5000;
|
|
19
|
+
const GIT_URL = 'https://git-scm.com/downloads';
|
|
20
|
+
const CLAUDE_SETUP_URL = 'https://docs.claude.com/en/docs/claude-code/setup';
|
|
21
|
+
const WINGET_GIT = 'winget install --id Git.Git -e --source winget';
|
|
22
|
+
|
|
23
|
+
const execFileP = promisify(childProcess.execFile);
|
|
24
|
+
|
|
25
|
+
/** Default probe: no shell, 5 s timeout, PATH widened with the user bin dirs. */
|
|
26
|
+
function defaultExecFile(cmd, args) {
|
|
27
|
+
return execFileP(cmd, args, {
|
|
28
|
+
env: { ...process.env, PATH: pathWithUserBins() },
|
|
29
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
30
|
+
windowsHide: true,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function defaultHasCredentials() {
|
|
35
|
+
const r = await readCredentials();
|
|
36
|
+
return r.kind === 'ok';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function errText(e) {
|
|
40
|
+
const msg = (e && (e.message || e.code)) || String(e);
|
|
41
|
+
return String(msg).split('\n')[0].slice(0, 200);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Run a probe; resolves {ok, out} and never rejects (timeout/ENOENT → ok:false). */
|
|
45
|
+
async function probe(execFile, cmd, args) {
|
|
46
|
+
try {
|
|
47
|
+
const r = await execFile(cmd, args);
|
|
48
|
+
const out = typeof r === 'string' ? r : (r && r.stdout) || '';
|
|
49
|
+
return { ok: true, out: String(out).trim(), error: null };
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return { ok: false, out: '', error: errText(e) };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function item(id, label, ok, version, detail, fix) {
|
|
56
|
+
return { id, label, ok, version: ok ? version : null, detail: ok ? null : detail, fix: ok ? null : fix };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function gitFix(platform) {
|
|
60
|
+
if (platform === 'darwin') return { command: 'xcode-select --install', shell: 'sh', url: GIT_URL };
|
|
61
|
+
if (platform === 'win32') return { command: WINGET_GIT, shell: 'powershell', url: GIT_URL };
|
|
62
|
+
// linux: distro-specific — point at the package-manager docs instead of a command.
|
|
63
|
+
return { command: '', shell: 'sh', url: GIT_URL };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function checkGit(platform, execFile) {
|
|
67
|
+
const ver = await probe(execFile, 'git', ['--version']);
|
|
68
|
+
let ok = ver.ok;
|
|
69
|
+
let detail = ver.error;
|
|
70
|
+
if (ok && platform === 'darwin') {
|
|
71
|
+
// /usr/bin/git on macOS is a stub that prompts for the Command Line Tools.
|
|
72
|
+
const xc = await probe(execFile, 'xcode-select', ['-p']);
|
|
73
|
+
if (!xc.ok) {
|
|
74
|
+
const which = await probe(execFile, 'which', ['git']);
|
|
75
|
+
if (!which.ok || which.out === '/usr/bin/git' || !which.out) {
|
|
76
|
+
ok = false;
|
|
77
|
+
detail = 'Xcode Command Line Tools are not installed';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const version = ver.out.replace(/^git version\s+/, '').split(/\s/)[0] || null;
|
|
82
|
+
const fix = gitFix(platform);
|
|
83
|
+
const missing = platform === 'linux'
|
|
84
|
+
? `${detail || 'git not found'} — install it with your distro's package manager`
|
|
85
|
+
: (detail || 'git not found');
|
|
86
|
+
return item('git', 'Git', ok, version, missing, fix);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function checkClaudeCli(platform, execFile, claudeBin) {
|
|
90
|
+
const bin = claudeBin || resolveClaudeBin();
|
|
91
|
+
const r = await probe(execFile, bin, ['--version']);
|
|
92
|
+
const win = platform === 'win32';
|
|
93
|
+
return item('claude-cli', 'Claude Code CLI', r.ok, r.out.split('\n')[0] || null,
|
|
94
|
+
r.error || 'claude not found', {
|
|
95
|
+
command: win ? 'irm https://claude.ai/install.ps1 | iex' : 'curl -fsSL https://claude.ai/install.sh | bash',
|
|
96
|
+
shell: win ? 'powershell' : 'sh',
|
|
97
|
+
url: CLAUDE_SETUP_URL,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function checkClaudeAuth(platform, hasCredentials) {
|
|
102
|
+
let ok = false;
|
|
103
|
+
try { ok = !!(await hasCredentials()); } catch { ok = false; }
|
|
104
|
+
return item('claude-auth', 'Claude account', ok, null,
|
|
105
|
+
'Not signed in — run `claude` and sign in to your Claude account', {
|
|
106
|
+
command: 'claude',
|
|
107
|
+
shell: platform === 'win32' ? 'powershell' : 'sh',
|
|
108
|
+
url: CLAUDE_SETUP_URL,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function checkGitBash(exists) {
|
|
113
|
+
const candidates = [];
|
|
114
|
+
if (process.env.CLAUDE_CODE_GIT_BASH_PATH) candidates.push(process.env.CLAUDE_CODE_GIT_BASH_PATH);
|
|
115
|
+
candidates.push(path.win32.join(process.env.ProgramFiles || 'C:\\Program Files', 'Git', 'bin', 'bash.exe'));
|
|
116
|
+
let found = null;
|
|
117
|
+
for (const c of candidates) {
|
|
118
|
+
try { if (exists(c)) { found = c; break; } } catch { /* treat as absent */ }
|
|
119
|
+
}
|
|
120
|
+
return item('git-bash', 'Git Bash', !!found, found,
|
|
121
|
+
'Claude Code on Windows needs Git for Windows (bash.exe)',
|
|
122
|
+
{ command: WINGET_GIT, shell: 'powershell', url: GIT_URL });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {{platform?: string, execFile?: Function, hasCredentials?: Function,
|
|
127
|
+
* claudeBin?: string, exists?: (p: string) => boolean}} [opts]
|
|
128
|
+
* @returns {Promise<Array<{id:string,label:string,ok:boolean,version:string|null,detail:string|null,
|
|
129
|
+
* fix:{command:string,shell:'sh'|'powershell',url?:string}|null}>>}
|
|
130
|
+
*/
|
|
131
|
+
async function checkPrereqs(opts = {}) {
|
|
132
|
+
const {
|
|
133
|
+
platform = process.platform,
|
|
134
|
+
execFile = defaultExecFile,
|
|
135
|
+
hasCredentials = defaultHasCredentials,
|
|
136
|
+
claudeBin,
|
|
137
|
+
exists = fs.existsSync,
|
|
138
|
+
} = opts;
|
|
139
|
+
const jobs = [checkGit(platform, execFile)];
|
|
140
|
+
if (platform === 'win32') jobs.push(Promise.resolve(checkGitBash(exists)));
|
|
141
|
+
jobs.push(checkClaudeCli(platform, execFile, claudeBin), checkClaudeAuth(platform, hasCredentials));
|
|
142
|
+
return Promise.all(jobs);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { checkPrereqs };
|
|
@@ -97,12 +97,47 @@ function identityFromPs(pid) {
|
|
|
97
97
|
return { pid, startTicks, cmdline, complete: startTicks !== null && cmdline !== null };
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
/**
|
|
101
|
-
function
|
|
100
|
+
/** PowerShell serialises DateTime as "\/Date(ms)\/" (5.1) or ISO-8601 (7+). */
|
|
101
|
+
function parseWinStartTime(v) {
|
|
102
|
+
if (typeof v === 'string') {
|
|
103
|
+
const m = /^\/Date\((-?\d+)\)\/$/.exec(v.trim());
|
|
104
|
+
if (m) return Number(m[1]);
|
|
105
|
+
const ms = Date.parse(v);
|
|
106
|
+
return Number.isFinite(ms) ? ms : null;
|
|
107
|
+
}
|
|
108
|
+
if (v && typeof v === 'object' && typeof v.value === 'string') return parseWinStartTime(v.value);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function identityFromPowerShell(pid, execFile) {
|
|
113
|
+
const unknown = { pid, startTicks: null, cmdline: null, complete: false };
|
|
114
|
+
let out;
|
|
115
|
+
try {
|
|
116
|
+
out = execFile(
|
|
117
|
+
'powershell.exe',
|
|
118
|
+
['-NoProfile', '-Command', `Get-Process -Id ${pid} | Select-Object StartTime,ProcessName | ConvertTo-Json`],
|
|
119
|
+
{ encoding: 'utf8', windowsHide: true, timeout: 5000 },
|
|
120
|
+
);
|
|
121
|
+
const parsed = JSON.parse(String(out));
|
|
122
|
+
const startTicks = parseWinStartTime(parsed && parsed.StartTime);
|
|
123
|
+
const name = parsed && typeof parsed.ProcessName === 'string' ? parsed.ProcessName.trim() : '';
|
|
124
|
+
const cmdline = name || null;
|
|
125
|
+
return { pid, startTicks, cmdline, complete: startTicks !== null && cmdline !== null };
|
|
126
|
+
} catch {
|
|
127
|
+
return unknown; // missing process, timeout, or unparseable output
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* identity(pid, opts?) → {pid, startTicks, cmdline, complete}. Never throws.
|
|
133
|
+
* opts.platform / opts.execFile (sync, execFileSync-shaped) are injectable for tests.
|
|
134
|
+
*/
|
|
135
|
+
function identity(pid, { platform = process.platform, execFile = execFileSync } = {}) {
|
|
102
136
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
103
137
|
return { pid, startTicks: null, cmdline: null, complete: false };
|
|
104
138
|
}
|
|
105
139
|
try {
|
|
140
|
+
if (platform === 'win32') return identityFromPowerShell(pid, execFile);
|
|
106
141
|
return hasProc() ? identityFromProc(pid) : identityFromPs(pid);
|
|
107
142
|
} catch {
|
|
108
143
|
return { pid, startTicks: null, cmdline: null, complete: false };
|