claude-code-session-manager 0.37.1 → 0.38.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/bin/cli.cjs +12 -1
- package/dist/assets/{TiptapBody-DXQDiOUx.js → TiptapBody-GMk5LS9Y.js} +1 -1
- package/dist/assets/index-CD_LuJZF.css +32 -0
- package/dist/assets/{index--a8m5_ET.js → index-CQqSHpIq.js} +444 -436
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/develop/SKILL.md +134 -36
- package/plugins/session-manager-dev/skills/develop/standards.md +25 -0
- package/plugins/session-manager-dev/skills/issue-address/0-select/0-fetch-pool/SKILL.md +38 -0
- package/plugins/session-manager-dev/skills/issue-address/0-select/1-reject-fixed/SKILL.md +64 -0
- package/plugins/session-manager-dev/skills/issue-address/0-select/2-reject-claimed/SKILL.md +71 -0
- package/plugins/session-manager-dev/skills/issue-address/0-select/3-rank-impact/SKILL.md +39 -0
- package/plugins/session-manager-dev/skills/issue-address/0-select/SKILL.md +75 -0
- package/plugins/session-manager-dev/skills/issue-address/1-confirm-open/SKILL.md +39 -0
- package/plugins/session-manager-dev/skills/issue-address/2-claim/SKILL.md +57 -0
- package/plugins/session-manager-dev/skills/issue-address/3-reproduce/SKILL.md +40 -0
- package/plugins/session-manager-dev/skills/issue-address/4-fix/SKILL.md +33 -0
- package/plugins/session-manager-dev/skills/issue-address/5-verify/SKILL.md +40 -0
- package/plugins/session-manager-dev/skills/issue-address/SKILL.md +120 -0
- package/plugins/session-manager-dev/skills/pr-review-sweep/0-fetch/SKILL.md +41 -0
- package/plugins/session-manager-dev/skills/pr-review-sweep/1-classify/SKILL.md +45 -0
- package/plugins/session-manager-dev/skills/pr-review-sweep/2-check-fixed/SKILL.md +49 -0
- package/plugins/session-manager-dev/skills/pr-review-sweep/3-queue/SKILL.md +50 -0
- package/plugins/session-manager-dev/skills/pr-review-sweep/4-land-and-resolve/SKILL.md +60 -0
- package/plugins/session-manager-dev/skills/pr-review-sweep/SKILL.md +124 -0
- package/plugins/session-manager-dev/skills/pr-signal/SKILL.md +67 -0
- package/plugins/session-manager-dev/skills/requesting-code-review/SKILL.md +20 -17
- package/scripts/lib/watchdogHelpers.cjs +296 -166
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +31 -0
- package/src/main/__tests__/chat-exit-close-race.test.cjs +140 -0
- package/src/main/__tests__/classifyTranscriptLine.test.cjs +90 -0
- package/src/main/__tests__/docEdit.test.cjs +164 -2
- package/src/main/__tests__/prdCreate.test.cjs +265 -25
- package/src/main/__tests__/scheduler-admin-routes.test.cjs +150 -0
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +172 -0
- package/src/main/__tests__/scheduler-committed-in-window.test.cjs +64 -0
- package/src/main/browserAgentServer.cjs +11 -10
- package/src/main/chatRunner.cjs +58 -14
- package/src/main/docEdit.cjs +125 -5
- package/src/main/health.cjs +15 -0
- package/src/main/index.cjs +12 -6
- package/src/main/ipcSchemas.cjs +16 -3
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +120 -0
- package/src/main/lib/classifyTranscriptLine.cjs +89 -0
- package/src/main/lib/localAdminHttp.cjs +157 -0
- package/src/main/lib/personaImportHealth.cjs +161 -0
- package/src/main/lib/prdCreate.cjs +107 -17
- package/src/main/lib/rcaFeedbackHook.cjs +2 -2
- package/src/main/lib/singleInstanceGuard.cjs +20 -0
- package/src/main/scheduler.cjs +223 -56
- package/src/main/sessionsStore.cjs +4 -5
- package/src/main/templates/PRD_AUTHORING.md +4 -4
- package/src/main/transcripts.cjs +1 -85
- package/src/main/webRemote.cjs +3 -3
- package/src/preload/api.d.ts +16 -6
- package/src/preload/index.cjs +9 -2
- package/dist/assets/index-CTTjT08J.css +0 -32
- package/plugins/session-manager-dev/skills/security-review/SKILL.md +0 -105
- package/src/main/__tests__/adminServer.test.cjs +0 -380
- package/src/main/adminServer.cjs +0 -242
package/src/main/adminServer.cjs
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* adminServer.cjs — loopback-only HTTP admin API for the scheduler.
|
|
3
|
-
*
|
|
4
|
-
* Foundation for PRD 449 (an MCP server wraps this HTTP API). Exposes a
|
|
5
|
-
* narrow, token-authed surface so a same-machine script/tool can reset a
|
|
6
|
-
* stuck scheduler job or list jobs without hand-editing queue.json from
|
|
7
|
-
* outside the running app (which would race the in-process mutate() queue
|
|
8
|
-
* — see scheduler.cjs's serialized-mutation comment).
|
|
9
|
-
*
|
|
10
|
-
* Security posture:
|
|
11
|
-
* - Binds 127.0.0.1 only, OS-assigned ephemeral port. Never reachable
|
|
12
|
-
* off-box.
|
|
13
|
-
* - Bearer token, regenerated every app boot, written to
|
|
14
|
-
* ~/.claude/session-manager/admin-api.json with 0600 perms.
|
|
15
|
-
* - Token compared with crypto.timingSafeEqual to avoid a timing
|
|
16
|
-
* side-channel on the comparison itself.
|
|
17
|
-
* - Three routes: reset-job (flips fields on an already-vetted job),
|
|
18
|
-
* jobs (read-only list), and create-prd (PRD 549 — authors a BRAND-NEW
|
|
19
|
-
* file that the scheduler will later run as `claude -p
|
|
20
|
-
* --dangerously-skip-permissions` in a caller-chosen, home-dir-bounded
|
|
21
|
-
* cwd; a materially larger blast radius than reset-job, justified only
|
|
22
|
-
* because it unblocks external automation with no other queueing path).
|
|
23
|
-
* cwd is validated via config.cjs's validatePath; title/cwd are
|
|
24
|
-
* newline-blocked at the zod boundary (ipcSchemas.cjs) to prevent
|
|
25
|
-
* frontmatter injection into the hand-rolled PRD serializer
|
|
26
|
-
* (prdCreate.cjs). pause/resume/cancel/update remain intentionally NOT
|
|
27
|
-
* exposed here.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
'use strict';
|
|
31
|
-
|
|
32
|
-
const http = require('node:http');
|
|
33
|
-
const os = require('node:os');
|
|
34
|
-
const path = require('node:path');
|
|
35
|
-
const crypto = require('node:crypto');
|
|
36
|
-
const fsp = require('node:fs/promises');
|
|
37
|
-
const config = require('./config.cjs');
|
|
38
|
-
const { expandHome } = require('./lib/expandHome.cjs');
|
|
39
|
-
const { schemas } = require('./ipcSchemas.cjs');
|
|
40
|
-
const prdCreate = require('./lib/prdCreate.cjs');
|
|
41
|
-
|
|
42
|
-
const TOKEN_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.json');
|
|
43
|
-
|
|
44
|
-
function timingSafeEqualStrings(a, b) {
|
|
45
|
-
const bufA = Buffer.from(String(a ?? ''));
|
|
46
|
-
const bufB = Buffer.from(String(b ?? ''));
|
|
47
|
-
if (bufA.length !== bufB.length) {
|
|
48
|
-
// Compare against a same-length buffer anyway so the failure path still
|
|
49
|
-
// takes constant time relative to the (fixed) token length, rather than
|
|
50
|
-
// returning early on a length mismatch.
|
|
51
|
-
crypto.timingSafeEqual(bufA, bufA);
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
return crypto.timingSafeEqual(bufA, bufB);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function readBody(req, maxBytes = 1024 * 1024) {
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
let total = 0;
|
|
60
|
-
const chunks = [];
|
|
61
|
-
req.on('data', (chunk) => {
|
|
62
|
-
total += chunk.length;
|
|
63
|
-
if (total > maxBytes) {
|
|
64
|
-
reject(new Error('body too large'));
|
|
65
|
-
req.destroy();
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
chunks.push(chunk);
|
|
69
|
-
});
|
|
70
|
-
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
71
|
-
req.on('error', reject);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function sendJson(res, status, obj) {
|
|
76
|
-
const body = JSON.stringify(obj);
|
|
77
|
-
res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
|
|
78
|
-
res.end(body);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create the admin HTTP server. `remote` is scheduler.cjs's remote object
|
|
83
|
-
* (injected, not required directly, so this module stays testable without
|
|
84
|
-
* booting Electron).
|
|
85
|
-
*/
|
|
86
|
-
function createAdminServer(remote) {
|
|
87
|
-
let server = null;
|
|
88
|
-
let token = null;
|
|
89
|
-
|
|
90
|
-
async function ensureToken() {
|
|
91
|
-
token = crypto.randomBytes(32).toString('hex');
|
|
92
|
-
await config.writeJson(TOKEN_PATH, { port: null, token });
|
|
93
|
-
try { await fsp.chmod(TOKEN_PATH, 0o600); } catch { /* best-effort on platforms without POSIX perms */ }
|
|
94
|
-
return token;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function persistPort(port) {
|
|
98
|
-
await config.writeJson(TOKEN_PATH, { port, token });
|
|
99
|
-
try { await fsp.chmod(TOKEN_PATH, 0o600); } catch { /* */ }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function authorized(req) {
|
|
103
|
-
const header = req.headers.authorization || '';
|
|
104
|
-
const match = /^Bearer (.+)$/.exec(header);
|
|
105
|
-
if (!match) return false;
|
|
106
|
-
return timingSafeEqualStrings(match[1], token);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function handleRequest(req, res) {
|
|
110
|
-
if (!authorized(req)) {
|
|
111
|
-
sendJson(res, 401, { ok: false, error: 'unauthorized' });
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
if (req.method === 'GET' && req.url === '/admin/scheduler/jobs') {
|
|
116
|
-
const jobs = await remote.listJobs();
|
|
117
|
-
sendJson(res, 200, jobs);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (req.method === 'POST' && req.url === '/admin/scheduler/reset-job') {
|
|
121
|
-
const raw = await readBody(req);
|
|
122
|
-
let parsed;
|
|
123
|
-
try {
|
|
124
|
-
parsed = raw ? JSON.parse(raw) : {};
|
|
125
|
-
} catch {
|
|
126
|
-
sendJson(res, 400, { ok: false, error: 'invalid JSON body' });
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const slug = typeof parsed.slug === 'string' ? parsed.slug : null;
|
|
130
|
-
if (!slug) {
|
|
131
|
-
sendJson(res, 400, { ok: false, error: 'missing slug' });
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const result = await remote.resetJob(slug);
|
|
135
|
-
sendJson(res, 200, result);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (req.method === 'POST' && req.url === '/admin/scheduler/create-prd') {
|
|
139
|
-
const raw = await readBody(req);
|
|
140
|
-
let parsed;
|
|
141
|
-
try {
|
|
142
|
-
parsed = raw ? JSON.parse(raw) : {};
|
|
143
|
-
} catch {
|
|
144
|
-
sendJson(res, 400, { ok: false, error: 'invalid JSON body' });
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let input;
|
|
149
|
-
try {
|
|
150
|
-
input = schemas.schedulerCreatePrd.parse(parsed);
|
|
151
|
-
} catch (e) {
|
|
152
|
-
sendJson(res, 400, { ok: false, error: 'invalid PRD payload', details: e?.issues ?? e?.message });
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// cwd is untrusted: this is the first *creating* mutation on a
|
|
157
|
-
// token-authed API whose product is "a command that will later run
|
|
158
|
-
// with --dangerously-skip-permissions in a chosen cwd". Route it
|
|
159
|
-
// through config.cjs's validatePath (allowedRoots = home dir) —
|
|
160
|
-
// same boundary every other fs-touching IPC handler uses — never a
|
|
161
|
-
// bespoke check here.
|
|
162
|
-
try {
|
|
163
|
-
config.validatePath(expandHome(input.cwd));
|
|
164
|
-
} catch (e) {
|
|
165
|
-
sendJson(res, 400, { ok: false, error: `cwd rejected: ${e?.message ?? 'outside allowed roots'}` });
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const slug = input.slug || prdCreate.deriveSlugFromTitle(input.title);
|
|
170
|
-
if (!slug || !prdCreate.PRD_CREATE_SLUG_RE.test(slug)) {
|
|
171
|
-
sendJson(res, 400, { ok: false, error: 'could not derive a valid kebab-case slug from title; supply "slug" explicitly' });
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// NN allocation is delegated to allocateParallelGroup() (PRD 548) via
|
|
176
|
-
// the injected remote — never re-derived here — unless the caller
|
|
177
|
-
// opted into an existing group explicitly.
|
|
178
|
-
const nn = input.parallelGroup ?? await remote.allocateParallelGroup();
|
|
179
|
-
const filenameSlug = `${nn}-${slug}`;
|
|
180
|
-
|
|
181
|
-
// An explicit `parallelGroup` bypasses allocateParallelGroup()'s
|
|
182
|
-
// collision-proof reservation, so re-check for an existing file at
|
|
183
|
-
// this exact destination before writing — remote.writePrd itself has
|
|
184
|
-
// no existence guard (by design, it doubles as the edit-in-place
|
|
185
|
-
// path for Scheduler UI PRD edits), so "create" must not silently
|
|
186
|
-
// clobber an existing job's PRD.
|
|
187
|
-
const existing = await remote.readPrd(filenameSlug);
|
|
188
|
-
if (existing?.ok) {
|
|
189
|
-
sendJson(res, 409, { ok: false, error: `PRD already exists: ${filenameSlug}.md` });
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
let standardsText;
|
|
194
|
-
try {
|
|
195
|
-
standardsText = await prdCreate.readStandards();
|
|
196
|
-
} catch (e) {
|
|
197
|
-
sendJson(res, 500, { ok: false, error: `could not read engineering standards: ${e?.message}` });
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const body = prdCreate.buildPrdBody(input, standardsText);
|
|
202
|
-
const writeResult = await remote.writePrd(filenameSlug, body);
|
|
203
|
-
if (!writeResult?.ok) {
|
|
204
|
-
sendJson(res, 500, { ok: false, error: writeResult?.error ?? 'write failed' });
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
sendJson(res, 200, { nn, filename: `${filenameSlug}.md`, status: 'queued' });
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
sendJson(res, 404, { ok: false, error: 'not found' });
|
|
212
|
-
} catch (e) {
|
|
213
|
-
sendJson(res, 500, { ok: false, error: e?.message ?? 'internal error' });
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async function start() {
|
|
218
|
-
await ensureToken();
|
|
219
|
-
server = http.createServer((req, res) => {
|
|
220
|
-
handleRequest(req, res).catch(() => {
|
|
221
|
-
try { sendJson(res, 500, { ok: false, error: 'internal error' }); } catch { /* */ }
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
await new Promise((resolve, reject) => {
|
|
225
|
-
server.once('error', reject);
|
|
226
|
-
server.listen(0, '127.0.0.1', resolve);
|
|
227
|
-
});
|
|
228
|
-
const { port } = server.address();
|
|
229
|
-
await persistPort(port);
|
|
230
|
-
return { port, token };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function stop() {
|
|
234
|
-
if (!server) return;
|
|
235
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
236
|
-
server = null;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return { start, stop, get token() { return token; }, get server() { return server; } };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
module.exports = { createAdminServer, TOKEN_PATH };
|