claude-code-session-manager 0.38.4 → 0.38.6

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.
Files changed (29) hide show
  1. package/dist/assets/{TiptapBody-Dz0cPmSN.js → TiptapBody-CTotrCub.js} +1 -1
  2. package/dist/assets/index-BGTU12rl.css +32 -0
  3. package/dist/assets/index-C54zva1x.js +3178 -0
  4. package/dist/index.html +2 -2
  5. package/package.json +2 -1
  6. package/plugins/session-manager-dev/skills/blog-for-project-feature/2-storyboard/SKILL.md +35 -9
  7. package/plugins/session-manager-dev/skills/blog-for-project-feature/3-capture/SKILL.md +16 -7
  8. package/plugins/session-manager-dev/skills/blog-for-project-feature/6-compose/SKILL.md +8 -5
  9. package/plugins/session-manager-dev/skills/blog-for-project-feature/7-review/SKILL.md +7 -4
  10. package/plugins/session-manager-dev/skills/blog-for-project-feature/SKILL.md +7 -3
  11. package/plugins/session-manager-dev/skills/blog-for-project-feature/feature.config.yaml +23 -7
  12. package/src/main/__tests__/prdCreate.test.cjs +55 -1
  13. package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +65 -0
  14. package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +43 -0
  15. package/src/main/index.cjs +10 -10
  16. package/src/main/ipcSchemas.cjs +17 -16
  17. package/src/main/layoutStore.cjs +58 -0
  18. package/src/main/lib/prdCreate.cjs +66 -43
  19. package/src/main/pty.cjs +0 -5
  20. package/src/main/queueOps.cjs +58 -0
  21. package/src/main/scheduler.cjs +48 -3
  22. package/src/main/transcripts.cjs +2 -15
  23. package/src/preload/api.d.ts +39 -116
  24. package/src/preload/index.cjs +6 -27
  25. package/dist/assets/index-B45c4uKJ.js +0 -3504
  26. package/dist/assets/index-BZeC568j.css +0 -32
  27. package/src/main/hives.cjs +0 -287
  28. package/src/main/superagent.cjs +0 -212
  29. package/src/main/usageMatrix.cjs +0 -340
@@ -1,212 +0,0 @@
1
- /**
2
- * SuperAgent — "boss" runner that dispatches specialist subagents.
3
- *
4
- * Ported (in MVP form) from ClaudeCodeUnleashed's SuperAgent feature.
5
- * Differs significantly from upstream:
6
- * - We don't call Groq/OpenAI. The boss IS Claude — we send a structured
7
- * prompt to the active PTY tab asking Claude to act as the boss, pick
8
- * specialists, and report progress in a parseable format.
9
- * - No autonomous output-parsing / fast-path / WAIT logic. State machine
10
- * is renderer-driven via transcript events (see live.ts pattern).
11
- * - Per-tab run state lives here so the renderer can poll status across
12
- * navigation, and so a future supervisor probe can act on stuck runs.
13
- *
14
- * Channels:
15
- * superagent:start({ tabId, prompt, specialistCount, depth })
16
- * → { ok, error? } and broadcasts the boss prompt to the tab's PTY.
17
- * superagent:status(tabId) → SuperAgentRunState | null
18
- * superagent:stop(tabId) → { ok }
19
- *
20
- * Broadcasts:
21
- * superagent:state-changed → { tabId, state } whenever a run starts/stops.
22
- *
23
- * The PTY write is intentionally done via the existing ptyManager.write
24
- * indirection, NOT a fresh spawn — SuperAgent runs inside the user's current
25
- * Claude session.
26
- */
27
-
28
- const { ipcMain } = require('electron');
29
- const { schemas } = require('./ipcSchemas.cjs');
30
- const { manager: ptyManager } = require('./pty.cjs');
31
- const logs = require('./logs.cjs');
32
-
33
- // Per-tab run state. Single live run per tab — starting a new run on a tab
34
- // that's already running stops the existing one first.
35
- //
36
- // Shape (also exposed on the renderer via api.d.ts SuperAgentRunState):
37
- // { status: 'idle' | 'running' | 'done' | 'error',
38
- // prompt, specialistCount, depth,
39
- // startedAt: number | null, finishedAt: number | null,
40
- // error?: string }
41
- const runs = new Map();
42
-
43
- let mainWindow = null;
44
-
45
- function attachWindow(win) {
46
- mainWindow = win;
47
- }
48
-
49
- function broadcast(tabId) {
50
- if (!mainWindow || mainWindow.isDestroyed()) return;
51
- const state = runs.get(tabId) ?? null;
52
- try {
53
- mainWindow.webContents.send('superagent:state-changed', { tabId, state });
54
- } catch { /* renderer gone */ }
55
- }
56
-
57
- /**
58
- * Build the prompt we send to Claude to act as the boss. Structured so the
59
- * renderer can (in a follow-up) pattern-match progress markers off the
60
- * transcript without us having to parse PTY output ourselves.
61
- *
62
- * O(1) string concat — the prompt is bounded by the zod max(8KiB).
63
- */
64
- function buildBossPrompt({ prompt, specialistCount, depth }) {
65
- const depthLine = depth === 'quick'
66
- ? 'Quick pass — surface-level analysis, single-shot specialist work.'
67
- : depth === 'deep'
68
- ? 'Deep pass — multi-step plans per specialist, recurse on findings.'
69
- : 'Standard pass — moderate detail, one or two iterations per specialist.';
70
-
71
- return [
72
- '## SuperAgent boss mode',
73
- '',
74
- `You are coordinating ${specialistCount} specialist subagent(s) for the user's task.`,
75
- depthLine,
76
- '',
77
- 'Workflow:',
78
- ` 1. Pick ${specialistCount} specialist subagent type(s) that best match the task.`,
79
- ' Report them as: [SUPERAGENT] specialists: <type1>, <type2>, ...',
80
- ' 2. Dispatch them via the Task tool, one at a time or in parallel as appropriate.',
81
- ' 3. After each specialist returns, report: [SUPERAGENT] progress: <specialist> done',
82
- ' 4. When the task is complete, summarize results and report: [SUPERAGENT] complete',
83
- '',
84
- 'User task:',
85
- prompt,
86
- ].join('\n');
87
- }
88
-
89
- function startRun(payload) {
90
- const { tabId, prompt, specialistCount, depth } = payload;
91
-
92
- // Stop any in-flight run on this tab — single run-per-tab invariant.
93
- const existing = runs.get(tabId);
94
- if (existing && existing.status === 'running') {
95
- runs.set(tabId, {
96
- ...existing,
97
- status: 'done',
98
- finishedAt: Date.now(),
99
- });
100
- }
101
-
102
- const fullPrompt = buildBossPrompt({ prompt, specialistCount, depth });
103
-
104
- // PTY write — exactly the pattern docstring'd at the top of the task brief.
105
- // The trailing carriage-return submits the prompt in Claude's TUI.
106
- let writeErr = null;
107
- try {
108
- ptyManager.write({ tabId, data: fullPrompt + '\r' });
109
- } catch (e) {
110
- writeErr = e?.message ?? String(e);
111
- }
112
-
113
- if (writeErr) {
114
- const errState = {
115
- status: 'error',
116
- prompt,
117
- specialistCount,
118
- depth,
119
- startedAt: Date.now(),
120
- finishedAt: Date.now(),
121
- error: writeErr,
122
- };
123
- runs.set(tabId, errState);
124
- broadcast(tabId);
125
- logs.writeLine({
126
- scope: 'superagent',
127
- level: 'error',
128
- message: 'start failed',
129
- meta: { tabId, error: writeErr },
130
- });
131
- return { ok: false, error: writeErr };
132
- }
133
-
134
- const state = {
135
- status: 'running',
136
- prompt,
137
- specialistCount,
138
- depth,
139
- startedAt: Date.now(),
140
- finishedAt: null,
141
- };
142
- runs.set(tabId, state);
143
- broadcast(tabId);
144
-
145
- logs.writeLine({
146
- scope: 'superagent',
147
- level: 'info',
148
- message: 'start',
149
- meta: { tabId, specialistCount, depth, promptBytes: prompt.length },
150
- });
151
-
152
- return { ok: true };
153
- }
154
-
155
- function stopRun(tabId) {
156
- const existing = runs.get(tabId);
157
- if (!existing || existing.status !== 'running') {
158
- return { ok: true };
159
- }
160
- runs.set(tabId, {
161
- ...existing,
162
- status: 'done',
163
- finishedAt: Date.now(),
164
- });
165
- broadcast(tabId);
166
- logs.writeLine({
167
- scope: 'superagent',
168
- level: 'info',
169
- message: 'stop',
170
- meta: { tabId },
171
- });
172
- return { ok: true };
173
- }
174
-
175
- function getStatus(tabId) {
176
- return runs.get(tabId) ?? null;
177
- }
178
-
179
- function dropTab(tabId) {
180
- if (runs.has(tabId)) {
181
- runs.delete(tabId);
182
- if (process.env.SM_DEBUG_LEAKS === '1') {
183
- console.log('[superagent] dropTab', tabId, 'runs.size=', runs.size);
184
- }
185
- }
186
- }
187
-
188
- function registerSuperAgentHandlers() {
189
- ipcMain.handle('superagent:start', (_e, payload) => {
190
- const parsed = schemas.superagentStart.parse(payload);
191
- return startRun(parsed);
192
- });
193
-
194
- ipcMain.handle('superagent:status', (_e, payload) => {
195
- const { tabId } = schemas.superagentTabId.parse(payload);
196
- return getStatus(tabId);
197
- });
198
-
199
- ipcMain.handle('superagent:stop', (_e, payload) => {
200
- const { tabId } = schemas.superagentTabId.parse(payload);
201
- return stopRun(tabId);
202
- });
203
- }
204
-
205
- module.exports = {
206
- attachWindow,
207
- registerSuperAgentHandlers,
208
- dropTab,
209
- // Exposed for tests.
210
- buildBossPrompt,
211
- _runs: runs,
212
- };
@@ -1,340 +0,0 @@
1
- /**
2
- * usageMatrix.cjs — per-tab agentic-ops aggregator.
3
- *
4
- * Watches the same classified transcript events that transcripts.cjs streams
5
- * to the renderer and maintains a small rolling state per tab: turn count,
6
- * token velocity (5-min window), cache age, subagent fan-out, and behavioral
7
- * flags (geometric context growth, cache cold-start risk, long session).
8
- *
9
- * Lives in main rather than the renderer so a renderer reload does not lose
10
- * the rolling counters — multi-hour sessions are the primary use case and
11
- * losing the velocity series at a reload would make the dashboard useless
12
- * exactly when it matters most.
13
- *
14
- * Broadcasts `usage:matrix:tick` on a 5-second cadence (and once on demand
15
- * via the `usage:matrix:snapshot` handler at mount).
16
- */
17
-
18
- 'use strict';
19
-
20
- const path = require('node:path');
21
- const { ipcMain } = require('electron');
22
- const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
23
-
24
- const TICK_MS = 5_000;
25
- const TOKEN_WINDOW_MS = 5 * 60_000; // 5 min rolling window for tokens/min
26
- const TURN_RING = 20; // per-turn input-token series cap
27
- const TOOL_RING = 100; // recent tool-name series cap
28
- const CACHE_TTL_MS = 60 * 60_000; // Anthropic prefix cache ~60 min
29
- const CACHE_WARN_MS = 50 * 60_000; // start warning at 50 min
30
-
31
- const BURN_LOW = 5_000; // tokens/min
32
- const BURN_CRITICAL = 30_000; // tokens/min
33
- const LONG_SESSION_TURNS = 70;
34
- const GEOMETRIC_MIN_TOKENS = 50_000; // don't flag tiny series
35
-
36
- /** Map<tabId, TabState> */
37
- const tabs = new Map();
38
- let window = null;
39
- let tickTimer = null;
40
- let dirty = false;
41
-
42
- function attachWindow(w) {
43
- window = w;
44
- if (!tickTimer) {
45
- tickTimer = setInterval(broadcastIfChanged, TICK_MS);
46
- }
47
- }
48
-
49
- function workspaceName(cwd) {
50
- if (!cwd) return '(unknown)';
51
- return path.basename(cwd) || cwd;
52
- }
53
-
54
- function blankTab(tabId, cwd, sessionUuid) {
55
- return {
56
- tabId,
57
- cwd,
58
- sessionUuid,
59
- workspace: workspaceName(cwd),
60
- startedAt: Date.now(),
61
- lastEventAt: 0,
62
- lastAssistantAt: 0,
63
- lastUserAt: 0,
64
- lastPlanAt: 0,
65
- turns: 0,
66
- cumulativeUsage: { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 },
67
- perTurnInputTokens: [],
68
- tokenWindow: [], // [{ts, tokens}]
69
- toolSeries: [], // [{ts, name}]
70
- agentSpawns: 0,
71
- agentsActive: new Map(), // toolUseId -> {at}
72
- };
73
- }
74
-
75
- function ensureTab(tabId, cwd, sessionUuid) {
76
- let t = tabs.get(tabId);
77
- if (!t) {
78
- t = blankTab(tabId, cwd, sessionUuid);
79
- tabs.set(tabId, t);
80
- }
81
- return t;
82
- }
83
-
84
- /**
85
- * Feed one classified transcript event into the per-tab aggregator. Called
86
- * from transcripts.cjs for every event, both during replay and live.
87
- */
88
- function recordEvent({ tabId, cwd, sessionUuid, ev, replay = false }) {
89
- if (!tabId || !ev) return;
90
- const t = ensureTab(tabId, cwd, sessionUuid);
91
- const now = Date.now();
92
- t.lastEventAt = now;
93
-
94
- switch (ev.kind) {
95
- case 'usage': {
96
- // Each usage rollup = one assistant turn in Claude Code's transcript.
97
- const u = ev.data || {};
98
- const inTok = (u.input_tokens || 0)
99
- + (u.cache_creation_input_tokens || 0)
100
- + (u.cache_read_input_tokens || 0);
101
- const outTok = u.output_tokens || 0;
102
- t.cumulativeUsage.input += u.input_tokens || 0;
103
- t.cumulativeUsage.output += outTok;
104
- t.cumulativeUsage.cacheRead += u.cache_read_input_tokens || 0;
105
- t.cumulativeUsage.cacheCreate += u.cache_creation_input_tokens || 0;
106
- t.turns += 1;
107
- t.lastAssistantAt = now;
108
-
109
- t.perTurnInputTokens.push(inTok);
110
- if (t.perTurnInputTokens.length > TURN_RING) t.perTurnInputTokens.shift();
111
-
112
- // Historical events must not enter the 5-min sliding window — doing so
113
- // would make tokensPerMin spike to "critical" on every tab switch.
114
- if (!replay) {
115
- t.tokenWindow.push({ ts: now, tokens: inTok + outTok });
116
- pruneWindow(t.tokenWindow, now);
117
- }
118
- dirty = true;
119
- break;
120
- }
121
- case 'tool_use': {
122
- const name = ev.data?.name;
123
- if (name) {
124
- t.toolSeries.push({ ts: now, name });
125
- if (t.toolSeries.length > TOOL_RING) t.toolSeries.shift();
126
- }
127
- dirty = true;
128
- break;
129
- }
130
- case 'agent_spawn': {
131
- t.agentSpawns += 1;
132
- const id = ev.data?.toolUseId;
133
- if (id) t.agentsActive.set(id, { at: now });
134
- dirty = true;
135
- break;
136
- }
137
- case 'tool_result': {
138
- const id = ev.data?.toolUseId;
139
- if (id && t.agentsActive.has(id)) {
140
- t.agentsActive.delete(id);
141
- dirty = true;
142
- }
143
- break;
144
- }
145
- case 'plan': {
146
- t.lastPlanAt = now;
147
- dirty = true;
148
- break;
149
- }
150
- default:
151
- break;
152
- }
153
- }
154
-
155
- function pruneWindow(arr, now) {
156
- const cutoff = now - TOKEN_WINDOW_MS;
157
- while (arr.length && arr[0].ts < cutoff) arr.shift();
158
- }
159
-
160
- function removeTab(tabId) {
161
- if (tabs.delete(tabId)) dirty = true;
162
- }
163
-
164
- /**
165
- * Build a derived snapshot. Heavy work happens here, not on the hot
166
- * recordEvent path — recordEvent only appends to ring buffers and flips a
167
- * dirty bit.
168
- */
169
- function buildSnapshot() {
170
- const now = Date.now();
171
- const out = [];
172
-
173
- let totalSubagentsActive = 0;
174
- let totalSubagentsSpawned = 0;
175
- let totalTokensToday = 0;
176
- let combinedTokensPerMin = 0;
177
-
178
- for (const t of tabs.values()) {
179
- pruneWindow(t.tokenWindow, now);
180
- const windowTokens = t.tokenWindow.reduce((s, e) => s + e.tokens, 0);
181
- const windowMin = Math.min(TOKEN_WINDOW_MS, now - (t.tokenWindow[0]?.ts ?? now)) / 60_000;
182
- const tokensPerMin = windowMin > 0.1 ? Math.round(windowTokens / windowMin) : 0;
183
- combinedTokensPerMin += tokensPerMin;
184
-
185
- const intensity = tokensPerMin >= BURN_CRITICAL
186
- ? 'critical'
187
- : tokensPerMin >= BURN_LOW
188
- ? 'medium'
189
- : tokensPerMin > 0
190
- ? 'low'
191
- : 'idle';
192
-
193
- const cacheAgeMs = t.lastAssistantAt ? now - t.lastAssistantAt : null;
194
- const cacheState = cacheAgeMs == null
195
- ? 'cold'
196
- : cacheAgeMs > CACHE_TTL_MS
197
- ? 'cold'
198
- : cacheAgeMs > CACHE_WARN_MS
199
- ? 'expiring'
200
- : 'warm';
201
-
202
- const avgPerTurn = t.perTurnInputTokens.length > 0
203
- ? Math.round(t.perTurnInputTokens.reduce((s, n) => s + n, 0) / t.perTurnInputTokens.length)
204
- : 0;
205
-
206
- const geometricGrowth = detectGeometricGrowth(t.perTurnInputTokens);
207
-
208
- // State derivation. Order matters — earlier branches win.
209
- let state;
210
- if (t.lastPlanAt > 0 && (now - t.lastPlanAt) < 60_000 && t.lastPlanAt >= t.lastAssistantAt) {
211
- state = 'plan';
212
- } else if (t.agentsActive.size > 0 && (now - t.lastAssistantAt) > 10_000) {
213
- state = 'background';
214
- } else if (t.lastEventAt > 0 && (now - t.lastEventAt) < 30_000) {
215
- state = 'executing';
216
- } else {
217
- state = 'idle';
218
- }
219
-
220
- const subagentsActive = t.agentsActive.size;
221
- totalSubagentsActive += subagentsActive;
222
- totalSubagentsSpawned += t.agentSpawns;
223
- totalTokensToday += t.cumulativeUsage.input + t.cumulativeUsage.output;
224
-
225
- const alerts = [];
226
- if (geometricGrowth) {
227
- alerts.push({
228
- level: 'warn',
229
- code: 'geometric-growth',
230
- message: `Per-turn input grew geometrically (avg ${(avgPerTurn / 1000).toFixed(0)}k). Run /compact.`,
231
- });
232
- }
233
- if (t.turns >= LONG_SESSION_TURNS) {
234
- alerts.push({
235
- level: 'warn',
236
- code: 'long-session',
237
- message: `Session is at turn ${t.turns}. Context resubmissions cost ${(avgPerTurn / 1000).toFixed(0)}k tokens each.`,
238
- });
239
- }
240
- if (cacheState === 'expiring' && state === 'idle') {
241
- const minLeft = Math.max(0, Math.round((CACHE_TTL_MS - (cacheAgeMs ?? 0)) / 60_000));
242
- alerts.push({
243
- level: 'info',
244
- code: 'cache-cold-start',
245
- message: `Cache cold-start in ${minLeft}m. Next command will incur full cache_creation cost.`,
246
- });
247
- }
248
- if (cacheState === 'cold' && t.lastAssistantAt > 0) {
249
- alerts.push({
250
- level: 'info',
251
- code: 'cache-cold',
252
- message: 'Cache expired. Next command pays full cache_creation pricing.',
253
- });
254
- }
255
- if (subagentsActive >= 4) {
256
- alerts.push({
257
- level: 'info',
258
- code: 'subagent-fanout',
259
- message: `${subagentsActive} subagents in flight. Consider waiting before fanning out more.`,
260
- });
261
- }
262
-
263
- out.push({
264
- tabId: t.tabId,
265
- workspace: t.workspace,
266
- cwd: t.cwd,
267
- state,
268
- turns: t.turns,
269
- lastEventAt: t.lastEventAt,
270
- lastAssistantAt: t.lastAssistantAt,
271
- cacheAgeMs,
272
- cacheState,
273
- tokensPerMin,
274
- intensity,
275
- avgTokensPerTurn: avgPerTurn,
276
- cumulativeUsage: { ...t.cumulativeUsage },
277
- subagentsActive,
278
- subagentsSpawned: t.agentSpawns,
279
- geometricGrowth,
280
- alerts,
281
- });
282
- }
283
-
284
- // Most active first.
285
- out.sort((a, b) => b.tokensPerMin - a.tokensPerMin || b.lastEventAt - a.lastEventAt);
286
-
287
- return {
288
- generatedAt: now,
289
- tabs: out,
290
- totals: {
291
- activeSessions: out.length,
292
- subagentsActive: totalSubagentsActive,
293
- subagentsSpawned: totalSubagentsSpawned,
294
- tokensTotal: totalTokensToday,
295
- combinedTokensPerMin,
296
- },
297
- };
298
- }
299
-
300
- /**
301
- * Geometric growth: avg of last 3 turns > 1.8 × avg of prior 3 turns,
302
- * and last-3 avg above GEOMETRIC_MIN_TOKENS so trivial sessions don't flag.
303
- */
304
- function detectGeometricGrowth(series) {
305
- if (series.length < 6) return false;
306
- const tail = series.slice(-3);
307
- const prev = series.slice(-6, -3);
308
- const tailAvg = tail.reduce((s, n) => s + n, 0) / 3;
309
- const prevAvg = prev.reduce((s, n) => s + n, 0) / 3;
310
- if (tailAvg < GEOMETRIC_MIN_TOKENS) return false;
311
- if (prevAvg <= 0) return false;
312
- return tailAvg / prevAvg >= 1.8;
313
- }
314
-
315
- function broadcastIfChanged() {
316
- if (!dirty) return;
317
- dirty = false;
318
- if (!window) return;
319
- sendIfAlive(window, 'usage:matrix:tick', buildSnapshot());
320
- }
321
-
322
- function registerHandlers() {
323
- ipcMain.handle('usage:matrix:snapshot', () => buildSnapshot());
324
- }
325
-
326
- function closeAll() {
327
- if (tickTimer) {
328
- clearInterval(tickTimer);
329
- tickTimer = null;
330
- }
331
- tabs.clear();
332
- }
333
-
334
- module.exports = {
335
- attachWindow,
336
- recordEvent,
337
- removeTab,
338
- registerHandlers,
339
- closeAll,
340
- };