claude-code-session-manager 0.38.5 → 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.
- package/dist/assets/{TiptapBody-D1ufJtTb.js → TiptapBody-CTotrCub.js} +1 -1
- package/dist/assets/index-BGTU12rl.css +32 -0
- package/dist/assets/index-C54zva1x.js +3178 -0
- package/dist/index.html +2 -2
- package/package.json +2 -1
- package/plugins/session-manager-dev/skills/blog-for-project-feature/2-storyboard/SKILL.md +35 -9
- package/plugins/session-manager-dev/skills/blog-for-project-feature/3-capture/SKILL.md +16 -7
- package/plugins/session-manager-dev/skills/blog-for-project-feature/6-compose/SKILL.md +8 -5
- package/plugins/session-manager-dev/skills/blog-for-project-feature/7-review/SKILL.md +7 -4
- package/plugins/session-manager-dev/skills/blog-for-project-feature/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/blog-for-project-feature/feature.config.yaml +11 -2
- package/src/main/__tests__/prdCreate.test.cjs +55 -1
- package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +65 -0
- package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +43 -0
- package/src/main/index.cjs +10 -10
- package/src/main/ipcSchemas.cjs +17 -16
- package/src/main/layoutStore.cjs +58 -0
- package/src/main/lib/prdCreate.cjs +66 -43
- package/src/main/pty.cjs +0 -5
- package/src/main/queueOps.cjs +58 -0
- package/src/main/scheduler.cjs +48 -3
- package/src/main/transcripts.cjs +2 -15
- package/src/preload/api.d.ts +39 -116
- package/src/preload/index.cjs +6 -27
- package/dist/assets/index-CZrfXQpq.css +0 -32
- package/dist/assets/index-DwjviHVo.js +0 -3501
- package/src/main/hives.cjs +0 -287
- package/src/main/superagent.cjs +0 -212
- package/src/main/usageMatrix.cjs +0 -340
package/src/main/hives.cjs
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hives.cjs — subagent recipe templates ("Hives").
|
|
3
|
-
*
|
|
4
|
-
* A Recipe is a named sequence of steps, each referencing an agent by name
|
|
5
|
-
* from `~/.claude/agents/<agentName>.md`. Shape:
|
|
6
|
-
* { slug, name, description, brief?, steps: [{ agentName, note? }] }
|
|
7
|
-
*
|
|
8
|
-
* Legacy hives (old shape: `roles: [{ label, prompt }]`) are migrated on
|
|
9
|
-
* first read: each role is materialized as an agent .md and replaced by a
|
|
10
|
-
* step referencing it. Migration is idempotent and permanent (re-saved).
|
|
11
|
-
*
|
|
12
|
-
* Storage: `~/.claude/session-manager/hives/<slug>.json`
|
|
13
|
-
* - slug must match SLUG_RE: /^[a-z0-9-_]{1,64}$/
|
|
14
|
-
* - up to 32 steps per recipe
|
|
15
|
-
* - per-field byte caps mirrored in the inline zod schemas below
|
|
16
|
-
*
|
|
17
|
-
* IPC namespace (channel names kept for backwards compat):
|
|
18
|
-
* - hives:list -> { hives: Recipe[], error: string | null }
|
|
19
|
-
* - hives:get -> { hive: Recipe | null, error: string | null }
|
|
20
|
-
* - hives:save -> { ok: boolean, error: string | null }
|
|
21
|
-
* - hives:delete -> { ok: boolean, error: string | null }
|
|
22
|
-
*
|
|
23
|
-
* All mutations go through config.cjs::writeJson / writeTextAtomic (atomic
|
|
24
|
-
* tmp+rename) and config.cjs::validatePath (allowedRoots = home dir).
|
|
25
|
-
* Never raw fs.writeFile.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
'use strict';
|
|
29
|
-
|
|
30
|
-
const { ipcMain } = require('electron');
|
|
31
|
-
const fsp = require('node:fs/promises');
|
|
32
|
-
const path = require('node:path');
|
|
33
|
-
const os = require('node:os');
|
|
34
|
-
const { z } = require('zod');
|
|
35
|
-
const config = require('./config.cjs');
|
|
36
|
-
const { kebabCase: sharedKebabCase } = require('./lib/kebabCase.cjs');
|
|
37
|
-
|
|
38
|
-
// ──────────────────────────────────────────── caps
|
|
39
|
-
const SLUG_RE = /^[a-z0-9-_]{1,64}$/;
|
|
40
|
-
const AGENT_NAME_RE = /^[a-z0-9-_]{1,64}$/;
|
|
41
|
-
const MAX_NAME_LEN = 128;
|
|
42
|
-
const MAX_DESC_LEN = 2048;
|
|
43
|
-
const MAX_BRIEF_LEN = 8 * 1024;
|
|
44
|
-
const MAX_NOTE_LEN = 2048;
|
|
45
|
-
const MAX_STEPS = 32;
|
|
46
|
-
|
|
47
|
-
// ──────────────────────────────────────────── inline zod schemas
|
|
48
|
-
const recipeStepSchema = z.object({
|
|
49
|
-
agentName: z.string().regex(AGENT_NAME_RE),
|
|
50
|
-
note: z.string().max(MAX_NOTE_LEN).optional(),
|
|
51
|
-
}).strict();
|
|
52
|
-
|
|
53
|
-
const recipeSchema = z.object({
|
|
54
|
-
slug: z.string().regex(SLUG_RE),
|
|
55
|
-
name: z.string().min(1).max(MAX_NAME_LEN),
|
|
56
|
-
description: z.string().max(MAX_DESC_LEN).default(''),
|
|
57
|
-
brief: z.string().max(MAX_BRIEF_LEN).optional(),
|
|
58
|
-
steps: z.array(recipeStepSchema).min(1).max(MAX_STEPS),
|
|
59
|
-
}).strict();
|
|
60
|
-
|
|
61
|
-
const slugPayload = z.object({
|
|
62
|
-
slug: z.string().regex(SLUG_RE),
|
|
63
|
-
}).strict();
|
|
64
|
-
|
|
65
|
-
// Field name "hive" kept for IPC channel backwards compatibility.
|
|
66
|
-
const savePayload = z.object({
|
|
67
|
-
slug: z.string().regex(SLUG_RE),
|
|
68
|
-
hive: recipeSchema,
|
|
69
|
-
}).strict();
|
|
70
|
-
|
|
71
|
-
// ──────────────────────────────────────────── paths
|
|
72
|
-
function rootDir() {
|
|
73
|
-
return path.join(os.homedir(), '.claude', 'session-manager', 'hives');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function agentsDir() {
|
|
77
|
-
return path.join(os.homedir(), '.claude', 'agents');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function hivePath(slug) {
|
|
81
|
-
if (!SLUG_RE.test(slug)) {
|
|
82
|
-
throw new Error(`invalid hive slug (must match ${SLUG_RE.source})`);
|
|
83
|
-
}
|
|
84
|
-
return path.join(rootDir(), `${slug}.json`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function ensureRoot() {
|
|
88
|
-
await fsp.mkdir(rootDir(), { recursive: true });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ──────────────────────────────────────────── helpers
|
|
92
|
-
function kebabCase(s) {
|
|
93
|
-
return sharedKebabCase(s, { maxLen: 64, fallback: 'agent' });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ──────────────────────────────────────────── legacy migration
|
|
97
|
-
async function migrateLegacyHive(slug, raw) {
|
|
98
|
-
const adir = agentsDir();
|
|
99
|
-
await fsp.mkdir(adir, { recursive: true });
|
|
100
|
-
|
|
101
|
-
const usedNames = new Set();
|
|
102
|
-
const steps = [];
|
|
103
|
-
|
|
104
|
-
for (const role of (raw.roles ?? [])) {
|
|
105
|
-
let base = kebabCase(role.label ?? 'agent');
|
|
106
|
-
let agentName = base;
|
|
107
|
-
let suffix = 2;
|
|
108
|
-
while (usedNames.has(agentName)) {
|
|
109
|
-
agentName = `${base.slice(0, 62)}-${suffix++}`;
|
|
110
|
-
}
|
|
111
|
-
usedNames.add(agentName);
|
|
112
|
-
|
|
113
|
-
const agentFilePath = path.join(adir, `${agentName}.md`);
|
|
114
|
-
// validatePath ensures we stay within allowedRoots (home dir).
|
|
115
|
-
const validPath = config.validatePath(agentFilePath);
|
|
116
|
-
|
|
117
|
-
let exists = false;
|
|
118
|
-
try { await fsp.stat(validPath); exists = true; } catch { /* ENOENT = not yet created */ }
|
|
119
|
-
|
|
120
|
-
if (!exists) {
|
|
121
|
-
const descLine = (role.prompt ?? '').replace(/[\r\n]+/g, ' ').slice(0, 200);
|
|
122
|
-
const content = `---\nname: ${role.label}\ndescription: ${descLine}\n---\n\n${role.prompt ?? ''}\n`;
|
|
123
|
-
await config.writeTextAtomic(validPath, content);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
steps.push({ agentName });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const recipe = {
|
|
130
|
-
slug,
|
|
131
|
-
name: raw.name,
|
|
132
|
-
description: raw.description ?? '',
|
|
133
|
-
steps,
|
|
134
|
-
...(raw.defaultPlan ? { brief: raw.defaultPlan } : {}),
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Persist migrated shape permanently so subsequent reads skip migration.
|
|
138
|
-
await saveHive(slug, recipe);
|
|
139
|
-
|
|
140
|
-
return { hive: recipe, error: null };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ──────────────────────────────────────────── core ops
|
|
144
|
-
async function listHives() {
|
|
145
|
-
try {
|
|
146
|
-
await ensureRoot();
|
|
147
|
-
const r = await config.listDir(rootDir(), { filesOnly: true });
|
|
148
|
-
if (!r.ok) return { hives: [], error: r.error };
|
|
149
|
-
const slugs = r.entries
|
|
150
|
-
.filter((e) => e.name.endsWith('.json'))
|
|
151
|
-
.map((e) => e.name.replace(/\.json$/, ''))
|
|
152
|
-
.filter((s) => SLUG_RE.test(s));
|
|
153
|
-
// Load each in parallel. Skip any that fail to parse cleanly.
|
|
154
|
-
const hives = await Promise.all(
|
|
155
|
-
slugs.map(async (slug) => {
|
|
156
|
-
try {
|
|
157
|
-
const got = await readHive(slug);
|
|
158
|
-
return got.hive;
|
|
159
|
-
} catch {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
}),
|
|
163
|
-
);
|
|
164
|
-
return {
|
|
165
|
-
hives: hives.filter((h) => h !== null).sort((a, b) => a.slug.localeCompare(b.slug)),
|
|
166
|
-
error: null,
|
|
167
|
-
};
|
|
168
|
-
} catch (e) {
|
|
169
|
-
return { hives: [], error: e.message };
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function readHive(slug) {
|
|
174
|
-
const abs = hivePath(slug);
|
|
175
|
-
const r = await config.readJson(abs);
|
|
176
|
-
if (!r || !r.exists) return { hive: null, error: null };
|
|
177
|
-
if (r.parseError) return { hive: null, error: `parse error: ${r.parseError}` };
|
|
178
|
-
|
|
179
|
-
const raw = r.data;
|
|
180
|
-
|
|
181
|
-
// Legacy migration: old shape has `roles` array instead of `steps`.
|
|
182
|
-
if (raw && Array.isArray(raw.roles) && !Array.isArray(raw.steps)) {
|
|
183
|
-
return migrateLegacyHive(slug, raw);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Re-validate stored shape so a hand-edited file can't smuggle in bad data.
|
|
187
|
-
const parsed = recipeSchema.safeParse(raw);
|
|
188
|
-
if (!parsed.success) {
|
|
189
|
-
return { hive: null, error: `invalid recipe on disk: ${parsed.error.issues[0]?.message ?? 'unknown'}` };
|
|
190
|
-
}
|
|
191
|
-
// Trust the file's own slug only if it matches the filename; filename wins.
|
|
192
|
-
const hive = { ...parsed.data, slug };
|
|
193
|
-
return { hive, error: null };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function getHive(slug) {
|
|
197
|
-
try {
|
|
198
|
-
return await readHive(slug);
|
|
199
|
-
} catch (e) {
|
|
200
|
-
return { hive: null, error: e.message };
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async function saveHive(slug, hive) {
|
|
205
|
-
try {
|
|
206
|
-
// Body slug must match path slug.
|
|
207
|
-
if (hive.slug !== slug) {
|
|
208
|
-
return { ok: false, error: `slug mismatch: payload slug "${hive.slug}" != path "${slug}"` };
|
|
209
|
-
}
|
|
210
|
-
await ensureRoot();
|
|
211
|
-
const abs = hivePath(slug);
|
|
212
|
-
const out = {
|
|
213
|
-
...hive,
|
|
214
|
-
description: hive.description ?? '',
|
|
215
|
-
};
|
|
216
|
-
if (out.brief === undefined || out.brief === '') delete out.brief;
|
|
217
|
-
const result = await config.writeJson(abs, out);
|
|
218
|
-
if (!result || !result.ok) {
|
|
219
|
-
return { ok: false, error: result?.error ?? 'write failed' };
|
|
220
|
-
}
|
|
221
|
-
return { ok: true, error: null };
|
|
222
|
-
} catch (e) {
|
|
223
|
-
return { ok: false, error: e.message };
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async function deleteHive(slug) {
|
|
228
|
-
try {
|
|
229
|
-
const abs = hivePath(slug);
|
|
230
|
-
let real;
|
|
231
|
-
try {
|
|
232
|
-
real = config.validatePath(abs);
|
|
233
|
-
if (typeof config.validateWrite === 'function') {
|
|
234
|
-
config.validateWrite(real);
|
|
235
|
-
}
|
|
236
|
-
} catch (e) {
|
|
237
|
-
return { ok: false, error: e.message };
|
|
238
|
-
}
|
|
239
|
-
try {
|
|
240
|
-
await fsp.unlink(real);
|
|
241
|
-
} catch (e) {
|
|
242
|
-
if (e.code === 'ENOENT') {
|
|
243
|
-
return { ok: true, error: null };
|
|
244
|
-
}
|
|
245
|
-
return { ok: false, error: e.message };
|
|
246
|
-
}
|
|
247
|
-
return { ok: true, error: null };
|
|
248
|
-
} catch (e) {
|
|
249
|
-
return { ok: false, error: e.message };
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ──────────────────────────────────────────── IPC
|
|
254
|
-
function validated(schema, handler) {
|
|
255
|
-
return (_event, payload) => {
|
|
256
|
-
const parsed = schema.parse(payload);
|
|
257
|
-
return handler(parsed);
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function registerHiveHandlers() {
|
|
262
|
-
ipcMain.handle('hives:list', () => listHives());
|
|
263
|
-
ipcMain.handle(
|
|
264
|
-
'hives:get',
|
|
265
|
-
validated(slugPayload, ({ slug }) => getHive(slug)),
|
|
266
|
-
);
|
|
267
|
-
ipcMain.handle(
|
|
268
|
-
'hives:save',
|
|
269
|
-
validated(savePayload, ({ slug, hive }) => saveHive(slug, hive)),
|
|
270
|
-
);
|
|
271
|
-
ipcMain.handle(
|
|
272
|
-
'hives:delete',
|
|
273
|
-
validated(slugPayload, ({ slug }) => deleteHive(slug)),
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
module.exports = {
|
|
278
|
-
registerHiveHandlers,
|
|
279
|
-
// exported for tests
|
|
280
|
-
rootDir,
|
|
281
|
-
hivePath,
|
|
282
|
-
SLUG_RE,
|
|
283
|
-
listHives,
|
|
284
|
-
getHive,
|
|
285
|
-
saveHive,
|
|
286
|
-
deleteHive,
|
|
287
|
-
};
|
package/src/main/superagent.cjs
DELETED
|
@@ -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
|
-
};
|