@ra3orblade/swarm 0.4.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/dist/swarmd.js ADDED
@@ -0,0 +1,4554 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // packages/client/src/daemon.ts
5
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { join } from "path";
8
+ function swarmHome() {
9
+ return process.env.SWARM_HOME ?? join(homedir(), ".swarm");
10
+ }
11
+ var DEFAULT_PORT = Number(process.env.SWARM_PORT ?? 7777);
12
+ var infoFile = () => join(swarmHome(), "daemon.json");
13
+ function writeDaemonInfo(info) {
14
+ const home = swarmHome();
15
+ mkdirSync(home, { recursive: true });
16
+ const full = { ...info, url: `http://127.0.0.1:${info.port}` };
17
+ writeFileSync(join(home, "daemon.json"), `${JSON.stringify(full, null, 2)}
18
+ `);
19
+ return full;
20
+ }
21
+ function clearDaemonInfo() {
22
+ rmSync(infoFile(), { force: true });
23
+ }
24
+ // packages/core/src/adapters/claude-code/transcript.ts
25
+ function parseTranscriptChunk(chunk) {
26
+ const out = {
27
+ turns: [],
28
+ title: null,
29
+ branch: null,
30
+ version: null,
31
+ turnDurationsMs: []
32
+ };
33
+ const byId = new Map;
34
+ for (const line of chunk.split(`
35
+ `)) {
36
+ if (!line.trim())
37
+ continue;
38
+ let d;
39
+ try {
40
+ d = JSON.parse(line);
41
+ } catch {
42
+ continue;
43
+ }
44
+ if (d.type === "ai-title" && d.aiTitle)
45
+ out.title = d.aiTitle;
46
+ if (d.gitBranch && d.gitBranch !== "HEAD")
47
+ out.branch = d.gitBranch;
48
+ if (d.version)
49
+ out.version = d.version;
50
+ if (d.type === "system" && d.subtype === "turn_duration" && typeof d.durationMs === "number")
51
+ out.turnDurationsMs.push(d.durationMs);
52
+ if (d.type !== "assistant" || !d.message)
53
+ continue;
54
+ const m = d.message;
55
+ const u = m.usage;
56
+ const id = m.id ?? d.uuid ?? "";
57
+ if (!id)
58
+ continue;
59
+ const content = Array.isArray(m.content) ? m.content : [];
60
+ const prev = byId.get(id);
61
+ const turn = prev ?? {
62
+ id,
63
+ ts: d.timestamp ?? new Date().toISOString(),
64
+ model: m.model ?? "unknown",
65
+ usage: { input: 0, output: 0, cacheWrite: 0, cacheWrite1h: 0, cacheRead: 0, thinking: 0 },
66
+ text: "",
67
+ tools: [],
68
+ effort: d.effort ?? null,
69
+ sidechain: Boolean(d.isSidechain)
70
+ };
71
+ if (u) {
72
+ turn.usage = {
73
+ input: u.input_tokens ?? 0,
74
+ output: u.output_tokens ?? 0,
75
+ cacheWrite: u.cache_creation_input_tokens ?? 0,
76
+ cacheWrite1h: u.cache_creation?.ephemeral_1h_input_tokens ?? 0,
77
+ cacheRead: u.cache_read_input_tokens ?? 0,
78
+ thinking: u.output_tokens_details?.thinking_tokens ?? 0
79
+ };
80
+ }
81
+ for (const c of content) {
82
+ if (c.type === "text" && c.text && turn.text.length < 400)
83
+ turn.text = `${turn.text}${turn.text ? `
84
+ ` : ""}${c.text}`.slice(0, 400);
85
+ if (c.type === "tool_use" && c.name)
86
+ turn.tools.push(c.name);
87
+ }
88
+ if (!prev) {
89
+ byId.set(id, turn);
90
+ out.turns.push(turn);
91
+ }
92
+ }
93
+ return out;
94
+ }
95
+
96
+ // packages/core/src/adapters/codex/rollout.ts
97
+ function parseCodexRollout(chunk) {
98
+ const out = { turns: [], sessionId: null, cwd: null, model: null, title: null };
99
+ let n = 0;
100
+ let text = "";
101
+ let tools = [];
102
+ let lastTs = "";
103
+ for (const raw of chunk.split(`
104
+ `)) {
105
+ if (!raw.trim())
106
+ continue;
107
+ let d;
108
+ try {
109
+ d = JSON.parse(raw);
110
+ } catch {
111
+ continue;
112
+ }
113
+ const p = d.payload ?? {};
114
+ if (d.type === "session_meta") {
115
+ out.sessionId = p.session_id ?? out.sessionId ?? null;
116
+ out.cwd = p.cwd ?? out.cwd ?? null;
117
+ } else if (d.type === "turn_context") {
118
+ if (p.model)
119
+ out.model = p.model;
120
+ } else if (d.type === "response_item") {
121
+ if (p.type === "function_call" && p.name)
122
+ tools.push(p.name);
123
+ else if (p.type === "custom_tool_call" && p.name)
124
+ tools.push(p.name);
125
+ } else if (d.type === "event_msg") {
126
+ if (p.type === "agent_message") {
127
+ const t = p.message ?? p.text ?? "";
128
+ if (t && text.length < 400)
129
+ text = `${text}${text ? `
130
+ ` : ""}${t}`.slice(0, 400);
131
+ lastTs = d.timestamp ?? lastTs;
132
+ } else if (p.type === "token_count") {
133
+ const u = p.info?.last_token_usage;
134
+ if (!u)
135
+ continue;
136
+ const cacheRead = u.cached_input_tokens ?? 0;
137
+ const turn = {
138
+ id: `${out.sessionId ?? "codex"}-t${n}`,
139
+ ts: d.timestamp ?? (lastTs || new Date(0).toISOString()),
140
+ model: out.model ?? "gpt-5",
141
+ usage: {
142
+ input: Math.max(0, (u.input_tokens ?? 0) - cacheRead),
143
+ output: u.output_tokens ?? 0,
144
+ cacheWrite: 0,
145
+ cacheWrite1h: 0,
146
+ cacheRead,
147
+ thinking: u.reasoning_output_tokens ?? 0
148
+ },
149
+ text,
150
+ tools,
151
+ effort: null,
152
+ sidechain: false
153
+ };
154
+ out.turns.push(turn);
155
+ n++;
156
+ text = "";
157
+ tools = [];
158
+ }
159
+ }
160
+ }
161
+ return out;
162
+ }
163
+
164
+ // packages/core/src/adapters/grok/updates.ts
165
+ function parseGrokUpdates(chunk) {
166
+ const out = { turns: [], sessionId: null, model: null, cwd: null, title: null };
167
+ let n = 0;
168
+ let text = "";
169
+ let tools = [];
170
+ let ts = "";
171
+ for (const raw of chunk.split(`
172
+ `)) {
173
+ if (!raw.trim())
174
+ continue;
175
+ let d = null;
176
+ try {
177
+ d = JSON.parse(raw);
178
+ } catch {
179
+ continue;
180
+ }
181
+ if (typeof d.timestamp === "number")
182
+ ts = new Date(d.timestamp * 1000).toISOString();
183
+ const p = d.params ?? {};
184
+ if (p.sessionId)
185
+ out.sessionId = p.sessionId;
186
+ const u = p.update;
187
+ if (!u)
188
+ continue;
189
+ const model = u._meta?.modelId;
190
+ if (model)
191
+ out.model = model;
192
+ switch (u.sessionUpdate) {
193
+ case "agent_message_chunk":
194
+ if (u.content?.text && text.length < 400)
195
+ text = `${text}${u.content.text}`.slice(0, 400);
196
+ break;
197
+ case "tool_call":
198
+ if (u.title)
199
+ tools.push(u.title);
200
+ break;
201
+ case "turn_completed": {
202
+ const usage = u.usage;
203
+ const cacheRead = usage?.cachedReadTokens ?? 0;
204
+ const turn = {
205
+ id: `${out.sessionId ?? "grok"}-t${n}`,
206
+ ts: ts || new Date(0).toISOString(),
207
+ model: out.model ?? "grok-4",
208
+ usage: {
209
+ input: Math.max(0, (usage?.inputTokens ?? 0) - cacheRead),
210
+ output: usage?.outputTokens ?? 0,
211
+ cacheWrite: 0,
212
+ cacheWrite1h: 0,
213
+ cacheRead,
214
+ thinking: usage?.reasoningTokens ?? 0
215
+ },
216
+ text,
217
+ tools,
218
+ effort: null,
219
+ sidechain: false
220
+ };
221
+ out.turns.push(turn);
222
+ n++;
223
+ text = "";
224
+ tools = [];
225
+ break;
226
+ }
227
+ }
228
+ }
229
+ return out;
230
+ }
231
+ // packages/core/src/adapters/claude-code/hooks.ts
232
+ var MAP = {
233
+ SessionStart: "session.started",
234
+ UserPromptSubmit: "prompt.submitted",
235
+ PreToolUse: "tool.requested",
236
+ PostToolUse: "tool.completed",
237
+ SubagentStart: "subagent.started",
238
+ SubagentStop: "subagent.stopped",
239
+ Stop: "agent.text",
240
+ SessionEnd: "session.ended",
241
+ Notification: "session.notification",
242
+ PreCompact: "agent.text"
243
+ };
244
+ function summarizeToolInput(tool, input) {
245
+ const i = input ?? {};
246
+ const s = (v) => typeof v === "string" ? v : JSON.stringify(v ?? "");
247
+ switch (tool) {
248
+ case "Bash":
249
+ return s(i.command).split(`
250
+ `)[0] ?? "";
251
+ case "Read":
252
+ case "Edit":
253
+ case "Write":
254
+ case "MultiEdit":
255
+ case "NotebookEdit":
256
+ return s(i.file_path);
257
+ case "Glob":
258
+ case "Grep":
259
+ return s(i.pattern) + (i.path ? ` in ${s(i.path)}` : "");
260
+ case "Agent":
261
+ case "Task":
262
+ return s(i.description ?? i.prompt).slice(0, 80);
263
+ case "WebFetch":
264
+ return s(i.url);
265
+ case "WebSearch":
266
+ return s(i.query);
267
+ default: {
268
+ const first = Object.entries(i)[0];
269
+ return first ? `${first[0]}=${s(first[1]).slice(0, 80)}` : "";
270
+ }
271
+ }
272
+ }
273
+ function normalizeHook(event, raw, projectId, ts = new Date().toISOString()) {
274
+ const type = MAP[event] ?? "agent.text";
275
+ const tool = raw.tool_name;
276
+ let summary;
277
+ switch (event) {
278
+ case "SessionStart":
279
+ summary = `session started (${raw.source ?? "startup"})`;
280
+ break;
281
+ case "UserPromptSubmit":
282
+ summary = (raw.prompt ?? "").split(`
283
+ `)[0]?.slice(0, 120) ?? "";
284
+ break;
285
+ case "PreToolUse":
286
+ case "PostToolUse":
287
+ summary = `${tool ?? "?"} ${summarizeToolInput(tool, raw.tool_input)}`.trim();
288
+ break;
289
+ case "SubagentStart":
290
+ summary = `subagent ${raw.agent_type ?? ""} started`.trim();
291
+ break;
292
+ case "SubagentStop":
293
+ summary = `subagent ${raw.agent_type ?? ""} stopped`.trim();
294
+ break;
295
+ case "Stop":
296
+ summary = "turn finished \xB7 waiting for input";
297
+ break;
298
+ case "SessionEnd":
299
+ summary = `session ended (${raw.reason ?? ""})`.trim();
300
+ break;
301
+ case "Notification":
302
+ summary = raw.message ?? "notification";
303
+ break;
304
+ case "PreCompact":
305
+ summary = "context compaction";
306
+ break;
307
+ default:
308
+ summary = event;
309
+ }
310
+ const payload = { hook: event, cwd: raw.cwd ?? null, summary };
311
+ if (tool)
312
+ payload.tool = tool;
313
+ if (raw.tool_input !== undefined)
314
+ payload.toolInput = raw.tool_input;
315
+ if (raw.tool_response !== undefined)
316
+ payload.toolResponse = raw.tool_response;
317
+ if (raw.agent_id)
318
+ payload.agentId = raw.agent_id;
319
+ if (raw.agent_type)
320
+ payload.agentType = raw.agent_type;
321
+ if (raw.prompt)
322
+ payload.prompt = raw.prompt;
323
+ return { ts, type, projectId, sessionId: raw.session_id ?? null, payload, raw };
324
+ }
325
+ // packages/core/src/config.ts
326
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
327
+ import { join as join2 } from "path";
328
+ var DEFAULT_CONFIG = {
329
+ daemon: { port: 7777 },
330
+ tasks: { source: null },
331
+ rules: {
332
+ shared_tree: "ask",
333
+ destructive_git: "ask",
334
+ pattern_kill: "ask",
335
+ protected_ports: "ask",
336
+ no_foreign_worktree: "ask",
337
+ claim_required_to_write: "off",
338
+ protected: { ports: [] }
339
+ }
340
+ };
341
+ var MODES = ["ask", "deny", "off"];
342
+ function isRecord(v) {
343
+ return typeof v === "object" && v !== null && !Array.isArray(v);
344
+ }
345
+ function merge(a, b) {
346
+ if (!isRecord(a) || !isRecord(b))
347
+ return b === undefined ? a : b;
348
+ const out = { ...a };
349
+ for (const [k, v] of Object.entries(b))
350
+ out[k] = merge(a[k], v);
351
+ return out;
352
+ }
353
+ function parseToml(text, source) {
354
+ try {
355
+ return Bun.TOML.parse(text) ?? {};
356
+ } catch (e) {
357
+ console.error(`swarm: ignoring invalid TOML in ${source}: ${e.message}`);
358
+ return {};
359
+ }
360
+ }
361
+ function validate(c) {
362
+ const mode = (v, fallback) => MODES.includes(v) ? v : fallback;
363
+ const port = Number(c.daemon?.port);
364
+ const source = c.tasks?.source;
365
+ return {
366
+ ...c,
367
+ daemon: { port: Number.isInteger(port) && port > 0 && port < 65536 ? port : 7777 },
368
+ tasks: {
369
+ source: typeof source === "string" && source.trim() && !source.startsWith("/") ? source.trim() : null
370
+ },
371
+ rules: {
372
+ ...c.rules,
373
+ shared_tree: mode(c.rules?.shared_tree, "ask"),
374
+ destructive_git: mode(c.rules?.destructive_git, "ask"),
375
+ pattern_kill: mode(c.rules?.pattern_kill, "ask"),
376
+ protected_ports: mode(c.rules?.protected_ports, "ask"),
377
+ no_foreign_worktree: mode(c.rules?.no_foreign_worktree, "ask"),
378
+ claim_required_to_write: mode(c.rules?.claim_required_to_write, "off"),
379
+ protected: {
380
+ ports: Array.isArray(c.rules?.protected?.ports) ? c.rules.protected.ports.filter((p) => Number.isInteger(p) && p > 0 && p < 65536) : []
381
+ }
382
+ }
383
+ };
384
+ }
385
+ function loadConfig(opts = {}) {
386
+ const home = opts.home ?? process.env.SWARM_HOME ?? join2(process.env.HOME ?? "", ".swarm");
387
+ let cfg = DEFAULT_CONFIG;
388
+ const globalPath = join2(home, "config.toml");
389
+ if (existsSync2(globalPath))
390
+ cfg = merge(cfg, parseToml(readFileSync2(globalPath, "utf8"), globalPath));
391
+ if (opts.repoRoot) {
392
+ const repoPath = join2(opts.repoRoot, ".swarm.toml");
393
+ if (existsSync2(repoPath))
394
+ cfg = merge(cfg, parseToml(readFileSync2(repoPath, "utf8"), repoPath));
395
+ }
396
+ return validate(cfg);
397
+ }
398
+ // packages/core/src/forge.ts
399
+ function parseRemote(url) {
400
+ const m = url.match(/^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/) ?? url.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?\/?$/);
401
+ if (!m)
402
+ return null;
403
+ const host = m[1]?.toLowerCase();
404
+ const repo = m[2];
405
+ if (!host || !repo)
406
+ return null;
407
+ if (host === "github.com" || host.startsWith("github."))
408
+ return { forge: "github", host, repo };
409
+ if (host.includes("gitlab"))
410
+ return { forge: "gitlab", host, repo };
411
+ return null;
412
+ }
413
+ function normalizeGithub(raw, repo) {
414
+ if (!Array.isArray(raw))
415
+ return [];
416
+ return raw.map((p) => {
417
+ const r = p;
418
+ const rollup = Array.isArray(r.statusCheckRollup) ? r.statusCheckRollup : [];
419
+ const states = rollup.map((c) => String(c.conclusion ?? c.state ?? "").toUpperCase());
420
+ const checks = !states.length ? "none" : states.some((s) => ["FAILURE", "ERROR", "TIMED_OUT", "CANCELLED"].includes(s)) ? "fail" : states.some((s) => ["", "PENDING", "IN_PROGRESS", "QUEUED", "EXPECTED"].includes(s)) ? "pending" : "pass";
421
+ const decision = String(r.reviewDecision ?? "");
422
+ return {
423
+ forge: "github",
424
+ repo,
425
+ number: Number(r.number),
426
+ title: String(r.title ?? ""),
427
+ branch: String(r.headRefName ?? ""),
428
+ author: String(r.author?.login ?? ""),
429
+ url: String(r.url ?? ""),
430
+ draft: !!r.isDraft,
431
+ checks,
432
+ review: decision === "APPROVED" ? "approved" : decision === "CHANGES_REQUESTED" ? "changes" : "none",
433
+ mergeable: String(r.mergeable ?? "").toUpperCase() !== "CONFLICTING",
434
+ createdAt: String(r.createdAt ?? "")
435
+ };
436
+ });
437
+ }
438
+ function normalizeGitlab(raw, repo) {
439
+ if (!Array.isArray(raw))
440
+ return [];
441
+ return raw.map((p) => {
442
+ const r = p;
443
+ const pipeline = r.head_pipeline ?? r.pipeline;
444
+ const status = String(pipeline?.status ?? "");
445
+ const checks = !status ? "none" : ["failed", "canceled"].includes(status) ? "fail" : ["success"].includes(status) ? "pass" : "pending";
446
+ return {
447
+ forge: "gitlab",
448
+ repo,
449
+ number: Number(r.iid),
450
+ title: String(r.title ?? ""),
451
+ branch: String(r.source_branch ?? ""),
452
+ author: String(r.author?.username ?? ""),
453
+ url: String(r.web_url ?? ""),
454
+ draft: !!(r.draft ?? r.work_in_progress),
455
+ checks,
456
+ review: Number(r.approvals_before_merge ?? 0) > 0 || Array.isArray(r.approved_by) && r.approved_by.length > 0 ? "approved" : "none",
457
+ mergeable: String(r.detailed_merge_status ?? r.merge_status ?? "") !== "cannot_be_merged",
458
+ createdAt: String(r.created_at ?? "")
459
+ };
460
+ });
461
+ }
462
+ // packages/core/src/ledger.ts
463
+ var DEFAULT_LEASE_MINUTES = 45;
464
+ function isExpired(claim, now) {
465
+ return new Date(claim.expiresAt).getTime() < now;
466
+ }
467
+ function isActive(claim, now) {
468
+ return claim.state === "held" && !isExpired(claim, now);
469
+ }
470
+ function nextExpiry(now, leaseMinutes = DEFAULT_LEASE_MINUTES) {
471
+ return new Date(now + leaseMinutes * 60000).toISOString();
472
+ }
473
+ function canClaim(existing, task, owner, now) {
474
+ const active = existing.find((c) => c.task === task && isActive(c, now));
475
+ if (active && active.owner !== owner) {
476
+ return { ok: false, reason: "held", heldBy: active.owner, until: active.expiresAt };
477
+ }
478
+ return { ok: true };
479
+ }
480
+ function canRelease(work, force) {
481
+ if (force)
482
+ return { ok: true };
483
+ if (work.dirty)
484
+ return { ok: false, reason: "dirty" };
485
+ if (work.unpushed)
486
+ return { ok: false, reason: "unpushed" };
487
+ return { ok: true };
488
+ }
489
+ function reapAction(claim, now, worktreeExists, work) {
490
+ if (isActive(claim, now))
491
+ return "not-expired";
492
+ if (!worktreeExists)
493
+ return "reap";
494
+ if (work && (work.dirty || work.unpushed))
495
+ return "keep-orphaned";
496
+ return "reap";
497
+ }
498
+ function claimRefusalMessage(d, task) {
499
+ return `${task} is held by ${d.heldBy} until ${d.until}. ` + "Pick another task or coordinate with the holder \u2014 claims fail closed on purpose.";
500
+ }
501
+ function releaseRefusalMessage(d, worktree) {
502
+ return d.reason === "dirty" ? `${worktree} has uncommitted changes. Commit and push them, or re-run with --force to discard.` : `${worktree} has unpushed commits. Push them, or re-run with --force to discard the worktree.`;
503
+ }
504
+ // packages/core/src/pricing.ts
505
+ var PRICES = {
506
+ "claude-opus-4": { input: 15, output: 75, cacheWrite: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
507
+ "claude-opus-4-5": { input: 5, output: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
508
+ "claude-opus-4-6": { input: 5, output: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
509
+ "claude-sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
510
+ "claude-haiku-4-5": { input: 1, output: 5, cacheWrite: 1.25, cacheWrite1h: 2, cacheRead: 0.1 },
511
+ "claude-3-5-haiku": { input: 0.8, output: 4, cacheWrite: 1, cacheWrite1h: 1.6, cacheRead: 0.08 },
512
+ "claude-opus-5": { input: 5, output: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
513
+ "claude-sonnet-5": { input: 2, output: 10, cacheWrite: 2.5, cacheWrite1h: 4, cacheRead: 0.2 },
514
+ "claude-fable-5": { input: 10, output: 50, cacheWrite: 12.5, cacheWrite1h: 20, cacheRead: 1 },
515
+ "gpt-4o": { input: 2.5, output: 10, cacheWrite: 2.5, cacheRead: 1.25 },
516
+ "gpt-4o-mini": { input: 0.15, output: 0.6, cacheWrite: 0.15, cacheRead: 0.075 },
517
+ "gpt-4.1": { input: 2, output: 8, cacheWrite: 2, cacheRead: 0.5 },
518
+ "gpt-4.1-mini": { input: 0.4, output: 1.6, cacheWrite: 0.4, cacheRead: 0.1 },
519
+ "gpt-5": { input: 1.25, output: 10, cacheWrite: 1.25, cacheRead: 0.125 },
520
+ o3: { input: 2, output: 8, cacheWrite: 2, cacheRead: 0.5 },
521
+ "o4-mini": { input: 1.1, output: 4.4, cacheWrite: 1.1, cacheRead: 0.275 },
522
+ "gemini-2.5-pro": { input: 1.25, output: 10, cacheWrite: 1.25, cacheRead: 0.31 },
523
+ "gemini-2.5-flash": { input: 0.3, output: 2.5, cacheWrite: 0.3, cacheRead: 0.075 },
524
+ "deepseek-chat": { input: 0.27, output: 1.1, cacheWrite: 0.27, cacheRead: 0.07 },
525
+ "deepseek-reasoner": { input: 0.55, output: 2.19, cacheWrite: 0.55, cacheRead: 0.14 },
526
+ "grok-4": { input: 3, output: 15, cacheWrite: 3, cacheRead: 0.75 },
527
+ "grok-3": { input: 3, output: 15, cacheWrite: 3, cacheRead: 0.75 },
528
+ "grok-code-fast": { input: 0.2, output: 1.5, cacheWrite: 0.2, cacheRead: 0.02 },
529
+ "grok-composer": { input: 0.2, output: 1.5, cacheWrite: 0.2, cacheRead: 0.02 }
530
+ };
531
+ function priceFor(model, table = PRICES) {
532
+ if (!model)
533
+ return null;
534
+ const m = model.toLowerCase();
535
+ let best = null;
536
+ for (const k of Object.keys(table)) {
537
+ if (m.startsWith(k) && (!best || k.length > best.length))
538
+ best = k;
539
+ }
540
+ return best ? table[best] ?? null : null;
541
+ }
542
+ function costUsd(model, u, table = PRICES) {
543
+ const p = priceFor(model, table);
544
+ if (!p)
545
+ return null;
546
+ const w5 = u.cacheWrite - (u.cacheWrite1h ?? 0);
547
+ return (u.input * p.input + u.output * p.output + w5 * p.cacheWrite + (u.cacheWrite1h ?? 0) * (p.cacheWrite1h ?? p.cacheWrite) + u.cacheRead * p.cacheRead) / 1e6;
548
+ }
549
+ function fromLiteLLM(json) {
550
+ const out = {};
551
+ for (const [k, v] of Object.entries(json)) {
552
+ if (typeof v.input_cost_per_token !== "number")
553
+ continue;
554
+ if (k.includes("/") || k.includes("ft:"))
555
+ continue;
556
+ const n = (x, fb) => typeof x === "number" ? x * 1e6 : fb;
557
+ const input = n(v.input_cost_per_token, 0);
558
+ out[k] = {
559
+ input,
560
+ output: n(v.output_cost_per_token, 0),
561
+ cacheWrite: n(v.cache_creation_input_token_cost, input * 1.25),
562
+ cacheWrite1h: n(v.cache_creation_input_token_cost_above_1hr, input * 2),
563
+ cacheRead: n(v.cache_read_input_token_cost, input * 0.1)
564
+ };
565
+ }
566
+ return out;
567
+ }
568
+ // packages/core/src/processes.ts
569
+ function pickPort(from, taken, isFree, span = 200) {
570
+ const t = new Set(taken);
571
+ for (let p = from;p < Math.min(from + span, 65536); p++) {
572
+ if (t.has(p))
573
+ continue;
574
+ if (isFree(p))
575
+ return p;
576
+ }
577
+ return null;
578
+ }
579
+ function isOurs(row, alive, currentStartTime) {
580
+ if (!alive)
581
+ return false;
582
+ if (row.startTime == null || currentStartTime == null)
583
+ return true;
584
+ return row.startTime === currentStartTime;
585
+ }
586
+ var DEFAULT_FROM_PORT = 3400;
587
+ // packages/core/src/project-id.ts
588
+ function fnv1a(input) {
589
+ let h = 2166136261;
590
+ for (let i = 0;i < input.length; i++) {
591
+ h ^= input.charCodeAt(i);
592
+ h = Math.imul(h, 16777619) >>> 0;
593
+ }
594
+ return h.toString(16).padStart(8, "0");
595
+ }
596
+ function projectIdentity(opts) {
597
+ const key = opts.commonDir ?? opts.root;
598
+ const parts = opts.root.split("/").filter(Boolean);
599
+ const name = parts[parts.length - 1] ?? opts.root;
600
+ return { id: `p_${fnv1a(key)}`, root: opts.root, commonDir: opts.commonDir, name };
601
+ }
602
+ // packages/core/src/resources.ts
603
+ var DEFAULT_RESOURCE_LEASE_MINUTES = 60;
604
+ function isTrackedPid(pid) {
605
+ return pid != null && pid > 0;
606
+ }
607
+ function isAliveHolding(r, now, pidAlive) {
608
+ if (r.released)
609
+ return false;
610
+ if (isTrackedPid(r.pid))
611
+ return pidAlive(r.pid);
612
+ if (r.expiresAt != null)
613
+ return new Date(r.expiresAt).getTime() > now;
614
+ return true;
615
+ }
616
+ function canAcquire(existing, requester, now, pidAlive) {
617
+ if (!existing || !isAliveHolding(existing, now, pidAlive))
618
+ return { ok: true };
619
+ if (existing.owner === requester.owner)
620
+ return { ok: true };
621
+ return { ok: false, reason: "held", holder: existing };
622
+ }
623
+ function acquireRefusalMessage(holder) {
624
+ const via = isTrackedPid(holder.pid) ? `pid ${holder.pid}` : holder.expiresAt ? `lease until ${holder.expiresAt}` : "unbounded";
625
+ return `Resource "${holder.name}" is held by ${holder.owner} (${via}).` + ` Pick another name, coordinate with the holder, or wait for release/reap.`;
626
+ }
627
+ // packages/core/src/rules.ts
628
+ var LIVE_WINDOW_MS = 10 * 60000;
629
+ function otherLiveInSameTree(current, sessions, now, withinMs = LIVE_WINDOW_MS) {
630
+ if (!current.toplevel)
631
+ return null;
632
+ for (const s of sessions) {
633
+ if (s.id === current.id)
634
+ continue;
635
+ if (s.state === "ended")
636
+ continue;
637
+ if (s.toplevel !== current.toplevel)
638
+ continue;
639
+ if (now - new Date(s.lastSeenAt).getTime() > withinMs)
640
+ continue;
641
+ return s;
642
+ }
643
+ return null;
644
+ }
645
+ function isBroadStage(cmd) {
646
+ const c = cmd.trim();
647
+ if (/\bgit\s+add\s+(-A\b|--all\b|\.(\s|$))/.test(c))
648
+ return true;
649
+ if (/\bgit\s+commit\b[^|&;]*\s-[a-zA-Z]*a/.test(c))
650
+ return true;
651
+ if (/\bgit\s+add\s*$/.test(c))
652
+ return true;
653
+ return false;
654
+ }
655
+ function isDestructiveGit(cmd) {
656
+ const c = cmd.trim();
657
+ return /\bgit\s+reset\s+[^|&;]*--hard\b/.test(c) || /\bgit\s+checkout\s+(--\s+)?\.(\s|$)/.test(c) || /\bgit\s+checkout\s+-f\b/.test(c) || /\bgit\s+restore\s+(--\s+)?\.(\s|$)/.test(c) || /\bgit\s+clean\s+[^|&;]*-[a-zA-Z]*f/.test(c) || /\bgit\s+stash\s+(drop|clear)\b/.test(c) || /\bgit\s+branch\s+[^|&;]*-[a-zA-Z]*D/.test(c);
658
+ }
659
+ function isPatternKill(cmd) {
660
+ return /\bpkill\s+-f\b/.test(cmd) || /\bpgrep\s+-f\b[^|]*\|\s*[^|]*\bkill\b/.test(cmd);
661
+ }
662
+ function killedPorts(cmd) {
663
+ const ports = new Set;
664
+ const killy = /\b(kill|fuser\s+-[a-z]*k|kill-port)\b/.test(cmd);
665
+ if (!killy)
666
+ return [];
667
+ for (const m of cmd.matchAll(/(?:-i\s*:?|:)(\d{2,5})\b/g))
668
+ ports.add(Number(m[1]));
669
+ for (const m of cmd.matchAll(/\bkill-port\s+(\d{2,5})/g))
670
+ ports.add(Number(m[1]));
671
+ for (const m of cmd.matchAll(/\bfuser\s+-[a-z]*k\s+(\d{2,5})/g))
672
+ ports.add(Number(m[1]));
673
+ return [...ports];
674
+ }
675
+ var DEFAULT_MODES = {
676
+ shared_tree: "ask",
677
+ destructive_git: "ask",
678
+ pattern_kill: "ask",
679
+ protected_ports: "ask",
680
+ no_foreign_worktree: "ask",
681
+ claim_required_to_write: "off",
682
+ protected: { ports: [] }
683
+ };
684
+ function guardBash(cmd, current, sessions, now, modes = DEFAULT_MODES) {
685
+ const other = () => otherLiveInSameTree(current, sessions, now);
686
+ const hit = (rule, reason) => {
687
+ const mode = modes[rule];
688
+ return mode === "off" ? { action: "allow" } : { action: mode, rule, reason };
689
+ };
690
+ if (modes.protected_ports !== "off" && modes.protected.ports.length) {
691
+ const target = killedPorts(cmd).filter((p) => modes.protected.ports.includes(p));
692
+ if (target.length) {
693
+ const d = hit("protected_ports", `Port${target.length > 1 ? "s" : ""} ${target.join(", ")} ${target.length > 1 ? "are" : "is"} protected in the Swarm config \u2014 something the owner relies on is listening there. Don't kill it.`);
694
+ if (d.action !== "allow")
695
+ return d;
696
+ }
697
+ }
698
+ if (modes.pattern_kill !== "off" && isPatternKill(cmd)) {
699
+ const d = hit("pattern_kill", "This kills processes by command pattern \u2014 it will match every process on the machine that fits, including other agents' or the owner's. Kill by pid instead.");
700
+ if (d.action !== "allow")
701
+ return d;
702
+ }
703
+ if (modes.shared_tree !== "off" && isBroadStage(cmd)) {
704
+ const o = other();
705
+ if (o) {
706
+ const d = hit("shared_tree", `Another session (${o.id.slice(0, 8)}) is active in this same checkout. \`git add -A\` / \`git commit -a\` will sweep its uncommitted changes into your commit. Stage explicit paths (\`git add <path>\`), or give each session its own git worktree.`);
707
+ if (d.action !== "allow")
708
+ return d;
709
+ }
710
+ }
711
+ if (modes.destructive_git !== "off" && isDestructiveGit(cmd)) {
712
+ const o = other();
713
+ if (o) {
714
+ const d = hit("destructive_git", `Another session (${o.id.slice(0, 8)}) is active in this same checkout and may have uncommitted work. This command can discard it. Coordinate, or use a separate git worktree.`);
715
+ if (d.action !== "allow")
716
+ return d;
717
+ }
718
+ }
719
+ return { action: "allow" };
720
+ }
721
+ function norm(p) {
722
+ const parts = [];
723
+ for (const seg of p.split("/")) {
724
+ if (seg === "" || seg === ".")
725
+ continue;
726
+ if (seg === "..")
727
+ parts.pop();
728
+ else
729
+ parts.push(seg);
730
+ }
731
+ return `/${parts.join("/")}`;
732
+ }
733
+ function isInside(path, dir) {
734
+ if (!path || !dir)
735
+ return false;
736
+ const a = norm(path);
737
+ const d = norm(dir);
738
+ return a === d || a.startsWith(`${d}/`);
739
+ }
740
+ function absolutePath(path, cwd) {
741
+ if (path.startsWith("/"))
742
+ return path;
743
+ if (path.startsWith("~/"))
744
+ return path;
745
+ return `${cwd.replace(/\/+$/, "")}/${path}`;
746
+ }
747
+ var WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
748
+ function guardWrite(target, current, claims, modes = DEFAULT_MODES, kind = "file") {
749
+ const hit = (rule, reason) => {
750
+ const mode = modes[rule];
751
+ return mode === "off" ? { action: "allow" } : { action: mode, rule, reason };
752
+ };
753
+ const held = claims.filter((c) => c.worktree);
754
+ const mine = held.find((c) => isInside(current.cwd, c.worktree)) ?? null;
755
+ if (modes.no_foreign_worktree !== "off") {
756
+ const foreign = held.find((c) => isInside(target, c.worktree) && c !== mine);
757
+ if (foreign) {
758
+ const d = hit("no_foreign_worktree", kind === "bash" ? `This command runs inside the worktree for "${foreign.task}", held by ${foreign.owner}. Never touch a worktree you don't hold \u2014 work in your own checkout, or claim the task.` : `${target} is inside the worktree for "${foreign.task}", held by ${foreign.owner}. Never touch a worktree you don't hold \u2014 edit your own checkout, or claim the task.`);
759
+ if (d.action !== "allow")
760
+ return d;
761
+ }
762
+ }
763
+ if (modes.claim_required_to_write !== "off" && kind === "file" && current.toplevel && !mine) {
764
+ const inShared = isInside(target, current.toplevel) && !held.some((c) => isInside(target, c.worktree));
765
+ if (inShared) {
766
+ const d = hit("claim_required_to_write", `This repo requires a claim before writing to its shared checkout. Run \`swarm claim <task>\` (or the swarm_claim MCP tool) and work in the worktree it creates.`);
767
+ if (d.action !== "allow")
768
+ return d;
769
+ }
770
+ }
771
+ return { action: "allow" };
772
+ }
773
+ // packages/core/src/tasks.ts
774
+ var ID_RE = /^[A-Za-z][A-Za-z0-9_-]*\d[\w.-]*$/;
775
+ var DEP_RE = /[A-Za-z][A-Za-z0-9_-]*\d[\w.]*/g;
776
+ function splitRow(line) {
777
+ const t = line.trim().replace(/^\|/, "").replace(/\|$/, "");
778
+ const cells = [];
779
+ let cur = "";
780
+ let code = false;
781
+ for (const ch of t) {
782
+ if (ch === "`")
783
+ code = !code;
784
+ if (ch === "|" && !code) {
785
+ cells.push(cur.trim());
786
+ cur = "";
787
+ } else
788
+ cur += ch;
789
+ }
790
+ cells.push(cur.trim());
791
+ return cells;
792
+ }
793
+ function isSeparator(line) {
794
+ return /^\|?\s*:?-{2,}/.test(line.trim()) && !/[A-Za-z]/.test(line);
795
+ }
796
+ function statusOf(cell) {
797
+ const c = cell.trim();
798
+ if (/^(\u2705|\u2611|\u2714|\[x\]|done|shipped|complete)/i.test(c))
799
+ return "done";
800
+ if (/^(\uD83D\uDFE1|\uD83D\uDFE0|\uD83D\uDD35|\[~\]|wip|active|doing|in[- ]progress|held)/i.test(c))
801
+ return "active";
802
+ return "todo";
803
+ }
804
+ function parseMarkdownTasks(text) {
805
+ const out = [];
806
+ const lines = text.split(/\r?\n/);
807
+ let milestone = null;
808
+ let cols = null;
809
+ for (let i = 0;i < lines.length; i++) {
810
+ const line = lines[i] ?? "";
811
+ const h = /^#{1,6}\s+(.+)/.exec(line);
812
+ if (h) {
813
+ milestone = (h[1] ?? "").trim();
814
+ cols = null;
815
+ continue;
816
+ }
817
+ if (!line.trim().startsWith("|")) {
818
+ cols = null;
819
+ continue;
820
+ }
821
+ const cells = splitRow(line);
822
+ if (!cols) {
823
+ const lc = cells.map((c) => c.toLowerCase());
824
+ const id2 = lc.indexOf("id");
825
+ const task = lc.findIndex((c) => c === "task" || c === "title");
826
+ if (id2 < 0 || task < 0 || !isSeparator(lines[i + 1] ?? ""))
827
+ continue;
828
+ cols = {
829
+ id: id2,
830
+ task,
831
+ depends: lc.findIndex((c) => c.startsWith("depend")),
832
+ status: lc.indexOf("status")
833
+ };
834
+ i++;
835
+ continue;
836
+ }
837
+ const id = cells[cols.id] ?? "";
838
+ if (!ID_RE.test(id))
839
+ continue;
840
+ const depCell = cols.depends >= 0 ? cells[cols.depends] ?? "" : "";
841
+ const depends = /^(\u2014|-|\u2013|none)?$/.test(depCell.trim()) ? [] : depCell.match(DEP_RE) ?? [];
842
+ const statusText = cols.status >= 0 ? cells[cols.status] ?? "" : "";
843
+ out.push({
844
+ id,
845
+ title: (cells[cols.task] ?? "").replace(/\*\*/g, ""),
846
+ depends,
847
+ status: statusOf(statusText),
848
+ statusText,
849
+ milestone
850
+ });
851
+ }
852
+ return out;
853
+ }
854
+ function depsDone(task, all) {
855
+ return task.depends.every((d) => {
856
+ const exact = all.find((t) => t.id === d);
857
+ if (exact)
858
+ return exact.status === "done";
859
+ const under = all.filter((t) => t.id.startsWith(`${d}.`));
860
+ return under.length === 0 || under.every((t) => t.status === "done");
861
+ });
862
+ }
863
+ function taskBoard(tasks, activeClaims) {
864
+ return tasks.map((t) => {
865
+ const claim = activeClaims.find((c) => c.task === t.id) ?? null;
866
+ return {
867
+ ...t,
868
+ claimedBy: claim?.owner ?? null,
869
+ ready: t.status === "todo" && !claim && depsDone(t, tasks)
870
+ };
871
+ });
872
+ }
873
+ // packages/daemon/src/app.ts
874
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, realpathSync as realpathSync3 } from "fs";
875
+ import { homedir as homedir4 } from "os";
876
+ import { dirname as dirname2, join as join6 } from "path";
877
+ import { fileURLToPath } from "url";
878
+
879
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/compose.js
880
+ var compose = (middleware, onError, onNotFound) => {
881
+ return (context, next) => {
882
+ let index = -1;
883
+ return dispatch(0);
884
+ async function dispatch(i) {
885
+ if (i <= index) {
886
+ throw new Error("next() called multiple times");
887
+ }
888
+ index = i;
889
+ let res;
890
+ let isError = false;
891
+ let handler;
892
+ if (middleware[i]) {
893
+ handler = middleware[i][0][0];
894
+ context.req.routeIndex = i;
895
+ } else {
896
+ handler = i === middleware.length && next || undefined;
897
+ }
898
+ if (handler) {
899
+ try {
900
+ res = await handler(context, () => dispatch(i + 1));
901
+ } catch (err) {
902
+ if (err instanceof Error && onError) {
903
+ context.error = err;
904
+ res = await onError(err, context);
905
+ isError = true;
906
+ } else {
907
+ throw err;
908
+ }
909
+ }
910
+ } else {
911
+ if (context.finalized === false && onNotFound) {
912
+ res = await onNotFound(context);
913
+ }
914
+ }
915
+ if (res && (context.finalized === false || isError)) {
916
+ context.res = res;
917
+ }
918
+ return context;
919
+ }
920
+ };
921
+ };
922
+
923
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/request/constants.js
924
+ var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
925
+
926
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/utils/buffer.js
927
+ var bufferToFormData = (arrayBuffer, contentType) => {
928
+ const response = new Response(arrayBuffer, {
929
+ headers: {
930
+ "Content-Type": contentType.replace(/^[^;]+/, (mediaType) => mediaType.toLowerCase())
931
+ }
932
+ });
933
+ return response.formData();
934
+ };
935
+
936
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/utils/body.js
937
+ var isRawRequest = (request) => ("headers" in request);
938
+ var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
939
+ const { all = false, dot = false } = options;
940
+ const headers = isRawRequest(request) ? request.headers : request.raw.headers;
941
+ const contentType = headers.get("Content-Type");
942
+ const mediaType = contentType?.split(";")[0].trim().toLowerCase();
943
+ if (mediaType === "multipart/form-data" || mediaType === "application/x-www-form-urlencoded") {
944
+ return parseFormData(request, { all, dot });
945
+ }
946
+ return {};
947
+ };
948
+ async function parseFormData(request, options) {
949
+ if (!isRawRequest(request) && request.bodyCache.formData) {
950
+ return convertFormDataToBodyData(await request.bodyCache.formData, options);
951
+ }
952
+ const headers = isRawRequest(request) ? request.headers : request.raw.headers;
953
+ const arrayBuffer = await request.arrayBuffer();
954
+ const formDataPromise = bufferToFormData(arrayBuffer, headers.get("Content-Type") || "");
955
+ if (!isRawRequest(request)) {
956
+ request.bodyCache.formData = formDataPromise;
957
+ }
958
+ const formData = await formDataPromise;
959
+ if (formData) {
960
+ return convertFormDataToBodyData(formData, options);
961
+ }
962
+ return {};
963
+ }
964
+ function convertFormDataToBodyData(formData, options) {
965
+ const form = /* @__PURE__ */ Object.create(null);
966
+ formData.forEach((value, key) => {
967
+ const shouldParseAllValues = options.all || key.endsWith("[]");
968
+ if (!shouldParseAllValues) {
969
+ form[key] = value;
970
+ } else {
971
+ handleParsingAllValues(form, key, value);
972
+ }
973
+ });
974
+ if (options.dot) {
975
+ Object.entries(form).forEach(([key, value]) => {
976
+ const shouldParseDotValues = key.includes(".");
977
+ if (shouldParseDotValues) {
978
+ handleParsingNestedValues(form, key, value);
979
+ delete form[key];
980
+ }
981
+ });
982
+ }
983
+ return form;
984
+ }
985
+ var handleParsingAllValues = (form, key, value) => {
986
+ if (form[key] !== undefined) {
987
+ if (Array.isArray(form[key])) {
988
+ form[key].push(value);
989
+ } else {
990
+ form[key] = [form[key], value];
991
+ }
992
+ } else {
993
+ if (!key.endsWith("[]")) {
994
+ form[key] = value;
995
+ } else {
996
+ form[key] = [value];
997
+ }
998
+ }
999
+ };
1000
+ var handleParsingNestedValues = (form, key, value) => {
1001
+ if (/(?:^|\.)__proto__\./.test(key)) {
1002
+ return;
1003
+ }
1004
+ let nestedForm = form;
1005
+ const keys = key.split(".");
1006
+ keys.forEach((key2, index) => {
1007
+ if (index === keys.length - 1) {
1008
+ nestedForm[key2] = value;
1009
+ } else {
1010
+ if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
1011
+ nestedForm[key2] = /* @__PURE__ */ Object.create(null);
1012
+ }
1013
+ nestedForm = nestedForm[key2];
1014
+ }
1015
+ });
1016
+ };
1017
+
1018
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/utils/url.js
1019
+ var splitPath = (path) => {
1020
+ const paths = path.split("/");
1021
+ if (paths[0] === "") {
1022
+ paths.shift();
1023
+ }
1024
+ return paths;
1025
+ };
1026
+ var splitRoutingPath = (routePath) => {
1027
+ const { groups, path } = extractGroupsFromPath(routePath);
1028
+ const paths = splitPath(path);
1029
+ return replaceGroupMarks(paths, groups);
1030
+ };
1031
+ var extractGroupsFromPath = (path) => {
1032
+ const groups = [];
1033
+ path = path.replace(/\{[^}]+\}/g, (match, index) => {
1034
+ const mark = `@${index}`;
1035
+ groups.push([mark, match]);
1036
+ return mark;
1037
+ });
1038
+ return { groups, path };
1039
+ };
1040
+ var replaceGroupMarks = (paths, groups) => {
1041
+ for (let i = groups.length - 1;i >= 0; i--) {
1042
+ const [mark] = groups[i];
1043
+ for (let j = paths.length - 1;j >= 0; j--) {
1044
+ if (paths[j].includes(mark)) {
1045
+ paths[j] = paths[j].replace(mark, groups[i][1]);
1046
+ break;
1047
+ }
1048
+ }
1049
+ }
1050
+ return paths;
1051
+ };
1052
+ var patternCache = {};
1053
+ var getPattern = (label, next) => {
1054
+ if (label === "*") {
1055
+ return "*";
1056
+ }
1057
+ const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
1058
+ if (match) {
1059
+ const cacheKey = `${label}#${next}`;
1060
+ if (!patternCache[cacheKey]) {
1061
+ if (match[2]) {
1062
+ patternCache[cacheKey] = next && next[0] !== ":" && next[0] !== "*" ? [cacheKey, match[1], new RegExp(`^${match[2]}(?=/${next})`)] : [label, match[1], new RegExp(`^${match[2]}$`)];
1063
+ } else {
1064
+ patternCache[cacheKey] = [label, match[1], true];
1065
+ }
1066
+ }
1067
+ return patternCache[cacheKey];
1068
+ }
1069
+ return null;
1070
+ };
1071
+ var tryDecode = (str, decoder) => {
1072
+ try {
1073
+ return decoder(str);
1074
+ } catch {
1075
+ return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => {
1076
+ try {
1077
+ return decoder(match);
1078
+ } catch {
1079
+ return match;
1080
+ }
1081
+ });
1082
+ }
1083
+ };
1084
+ var tryDecodeURI = (str) => tryDecode(str, decodeURI);
1085
+ var getPath = (request) => {
1086
+ const url = request.url;
1087
+ const start = url.indexOf("/", url.indexOf(":") + 4);
1088
+ let i = start;
1089
+ for (;i < url.length; i++) {
1090
+ const charCode = url.charCodeAt(i);
1091
+ if (charCode === 37) {
1092
+ const queryIndex = url.indexOf("?", i);
1093
+ const hashIndex = url.indexOf("#", i);
1094
+ const end = queryIndex === -1 ? hashIndex === -1 ? undefined : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
1095
+ const path = url.slice(start, end);
1096
+ return tryDecodeURI(path.includes("%25") ? path.replace(/%25/g, "%2525") : path);
1097
+ } else if (charCode === 63 || charCode === 35) {
1098
+ break;
1099
+ }
1100
+ }
1101
+ return url.slice(start, i);
1102
+ };
1103
+ var getPathNoStrict = (request) => {
1104
+ const result = getPath(request);
1105
+ return result.length > 1 && result.at(-1) === "/" ? result.slice(0, -1) : result;
1106
+ };
1107
+ var mergePath = (base, sub, ...rest) => {
1108
+ if (rest.length) {
1109
+ sub = mergePath(sub, ...rest);
1110
+ }
1111
+ return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
1112
+ };
1113
+ var checkOptionalParameter = (path) => {
1114
+ if (path.charCodeAt(path.length - 1) !== 63 || !path.includes(":")) {
1115
+ return null;
1116
+ }
1117
+ const segments = path.split("/");
1118
+ const results = [];
1119
+ let basePath = "";
1120
+ segments.forEach((segment) => {
1121
+ if (segment !== "" && !/\:/.test(segment)) {
1122
+ basePath += "/" + segment;
1123
+ } else if (/\:/.test(segment)) {
1124
+ if (segment.charCodeAt(segment.length - 1) === 63) {
1125
+ if (results.length === 0 && basePath === "") {
1126
+ results.push("/");
1127
+ } else {
1128
+ results.push(basePath);
1129
+ }
1130
+ const optionalSegment = segment.slice(0, -1);
1131
+ basePath += "/" + optionalSegment;
1132
+ results.push(basePath);
1133
+ } else {
1134
+ basePath += "/" + segment;
1135
+ }
1136
+ }
1137
+ });
1138
+ return results.filter((v, i, a) => a.indexOf(v) === i);
1139
+ };
1140
+ var tryDecodeURIComponent = (str) => str.indexOf("%") !== -1 ? tryDecode(str, decodeURIComponent_) : str;
1141
+ var _decodeURI = (value) => {
1142
+ if (value.indexOf("+") !== -1) {
1143
+ value = value.replace(/\+/g, " ");
1144
+ }
1145
+ return tryDecodeURIComponent(value);
1146
+ };
1147
+ var _getQueryParam = (url, key, multiple) => {
1148
+ let encoded;
1149
+ if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
1150
+ let keyIndex2 = url.indexOf("?", 8);
1151
+ if (keyIndex2 === -1) {
1152
+ return;
1153
+ }
1154
+ if (!url.startsWith(key, keyIndex2 + 1)) {
1155
+ keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
1156
+ }
1157
+ while (keyIndex2 !== -1) {
1158
+ const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
1159
+ if (trailingKeyCode === 61) {
1160
+ const valueIndex = keyIndex2 + key.length + 2;
1161
+ const endIndex = url.indexOf("&", valueIndex);
1162
+ return _decodeURI(url.slice(valueIndex, endIndex === -1 ? undefined : endIndex));
1163
+ } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {
1164
+ return "";
1165
+ }
1166
+ keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
1167
+ }
1168
+ encoded = /[%+]/.test(url);
1169
+ if (!encoded) {
1170
+ return;
1171
+ }
1172
+ }
1173
+ const results = /* @__PURE__ */ Object.create(null);
1174
+ encoded ??= /[%+]/.test(url);
1175
+ let keyIndex = url.indexOf("?", 8);
1176
+ while (keyIndex !== -1) {
1177
+ const nextKeyIndex = url.indexOf("&", keyIndex + 1);
1178
+ let valueIndex = url.indexOf("=", keyIndex);
1179
+ if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {
1180
+ valueIndex = -1;
1181
+ }
1182
+ let name = url.slice(keyIndex + 1, valueIndex === -1 ? nextKeyIndex === -1 ? undefined : nextKeyIndex : valueIndex);
1183
+ if (encoded) {
1184
+ name = _decodeURI(name);
1185
+ }
1186
+ keyIndex = nextKeyIndex;
1187
+ if (name === "") {
1188
+ continue;
1189
+ }
1190
+ let value;
1191
+ if (valueIndex === -1) {
1192
+ value = "";
1193
+ } else {
1194
+ value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? undefined : nextKeyIndex);
1195
+ if (encoded) {
1196
+ value = _decodeURI(value);
1197
+ }
1198
+ }
1199
+ if (multiple) {
1200
+ if (!(results[name] && Array.isArray(results[name]))) {
1201
+ results[name] = [];
1202
+ }
1203
+ results[name].push(value);
1204
+ } else {
1205
+ results[name] ??= value;
1206
+ }
1207
+ }
1208
+ return key ? results[key] : results;
1209
+ };
1210
+ var getQueryParam = _getQueryParam;
1211
+ var getQueryParams = (url, key) => {
1212
+ return _getQueryParam(url, key, true);
1213
+ };
1214
+ var decodeURIComponent_ = decodeURIComponent;
1215
+
1216
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/request.js
1217
+ var HonoRequest = class {
1218
+ raw;
1219
+ #validatedData;
1220
+ #matchResult;
1221
+ routeIndex = 0;
1222
+ path;
1223
+ bodyCache = {};
1224
+ constructor(request, path = "/", matchResult = [[]]) {
1225
+ this.raw = request;
1226
+ this.path = path;
1227
+ this.#matchResult = matchResult;
1228
+ }
1229
+ param(key) {
1230
+ return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
1231
+ }
1232
+ #getDecodedParam(key) {
1233
+ const paramKey = this.#matchResult[0][this.routeIndex][1][key];
1234
+ const param = this.#getParamValue(paramKey);
1235
+ return param && tryDecodeURIComponent(param);
1236
+ }
1237
+ #getAllDecodedParams() {
1238
+ const decoded = {};
1239
+ const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
1240
+ for (const key of keys) {
1241
+ const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
1242
+ if (value !== undefined) {
1243
+ decoded[key] = tryDecodeURIComponent(value);
1244
+ }
1245
+ }
1246
+ return decoded;
1247
+ }
1248
+ #getParamValue(paramKey) {
1249
+ return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
1250
+ }
1251
+ query(key) {
1252
+ return getQueryParam(this.url, key);
1253
+ }
1254
+ queries(key) {
1255
+ return getQueryParams(this.url, key);
1256
+ }
1257
+ header(name) {
1258
+ if (name) {
1259
+ return this.raw.headers.get(name) ?? undefined;
1260
+ }
1261
+ const headerData = /* @__PURE__ */ Object.create(null);
1262
+ this.raw.headers.forEach((value, key) => {
1263
+ headerData[key] = value;
1264
+ });
1265
+ return headerData;
1266
+ }
1267
+ async parseBody(options) {
1268
+ return parseBody(this, options);
1269
+ }
1270
+ #cachedBody = (key) => {
1271
+ const { bodyCache, raw } = this;
1272
+ const cachedBody = bodyCache[key];
1273
+ if (cachedBody) {
1274
+ return cachedBody;
1275
+ }
1276
+ for (const anyCachedKey in bodyCache) {
1277
+ return bodyCache[anyCachedKey].then((body) => {
1278
+ if (anyCachedKey === "json") {
1279
+ body = JSON.stringify(body);
1280
+ }
1281
+ return new Response(body)[key]();
1282
+ });
1283
+ }
1284
+ return bodyCache[key] = raw[key]();
1285
+ };
1286
+ json() {
1287
+ return this.#cachedBody("text").then((text) => JSON.parse(text));
1288
+ }
1289
+ text() {
1290
+ return this.#cachedBody("text");
1291
+ }
1292
+ arrayBuffer() {
1293
+ return this.#cachedBody("arrayBuffer");
1294
+ }
1295
+ bytes() {
1296
+ return this.#cachedBody("arrayBuffer").then((buffer) => new Uint8Array(buffer));
1297
+ }
1298
+ blob() {
1299
+ return this.#cachedBody("blob");
1300
+ }
1301
+ formData() {
1302
+ return this.#cachedBody("formData");
1303
+ }
1304
+ addValidatedData(target, data) {
1305
+ (this.#validatedData ??= {})[target] = data;
1306
+ }
1307
+ valid(target) {
1308
+ return this.#validatedData?.[target];
1309
+ }
1310
+ get url() {
1311
+ return this.raw.url;
1312
+ }
1313
+ get method() {
1314
+ return this.raw.method;
1315
+ }
1316
+ get [GET_MATCH_RESULT]() {
1317
+ return this.#matchResult;
1318
+ }
1319
+ get matchedRoutes() {
1320
+ return this.#matchResult[0].map(([[, route]]) => route);
1321
+ }
1322
+ get routePath() {
1323
+ return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
1324
+ }
1325
+ };
1326
+
1327
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/utils/html.js
1328
+ var HtmlEscapedCallbackPhase = {
1329
+ Stringify: 1,
1330
+ BeforeStream: 2,
1331
+ Stream: 3
1332
+ };
1333
+ var raw = (value, callbacks) => {
1334
+ const escapedString = new String(value);
1335
+ escapedString.isEscaped = true;
1336
+ escapedString.callbacks = callbacks;
1337
+ return escapedString;
1338
+ };
1339
+ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
1340
+ if (typeof str === "object" && !(str instanceof String)) {
1341
+ if (!(str instanceof Promise)) {
1342
+ str = str.toString();
1343
+ }
1344
+ if (str instanceof Promise) {
1345
+ str = await str;
1346
+ }
1347
+ }
1348
+ const callbacks = str.callbacks;
1349
+ if (!callbacks?.length) {
1350
+ return Promise.resolve(str);
1351
+ }
1352
+ if (buffer) {
1353
+ buffer[0] += str;
1354
+ } else {
1355
+ buffer = [str];
1356
+ }
1357
+ const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then((res) => Promise.all(res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))).then(() => buffer[0]));
1358
+ if (preserveCallbacks) {
1359
+ return raw(await resStr, callbacks);
1360
+ } else {
1361
+ return resStr;
1362
+ }
1363
+ };
1364
+
1365
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/context.js
1366
+ var TEXT_PLAIN = "text/plain; charset=UTF-8";
1367
+ var setDefaultContentType = (contentType, headers) => {
1368
+ return {
1369
+ "Content-Type": contentType,
1370
+ ...headers
1371
+ };
1372
+ };
1373
+ var createResponseInstance = (body, init) => new Response(body, init);
1374
+ var Context = class {
1375
+ #rawRequest;
1376
+ #req;
1377
+ env = {};
1378
+ #var;
1379
+ finalized = false;
1380
+ error;
1381
+ #status;
1382
+ #executionCtx;
1383
+ #res;
1384
+ #layout;
1385
+ #renderer;
1386
+ #notFoundHandler;
1387
+ #preparedHeaders;
1388
+ #matchResult;
1389
+ #path;
1390
+ constructor(req, options) {
1391
+ this.#rawRequest = req;
1392
+ if (options) {
1393
+ this.#executionCtx = options.executionCtx;
1394
+ this.env = options.env;
1395
+ this.#notFoundHandler = options.notFoundHandler;
1396
+ this.#path = options.path;
1397
+ this.#matchResult = options.matchResult;
1398
+ }
1399
+ }
1400
+ get req() {
1401
+ this.#req ??= new HonoRequest(this.#rawRequest, this.#path, this.#matchResult);
1402
+ return this.#req;
1403
+ }
1404
+ get event() {
1405
+ if (this.#executionCtx && "respondWith" in this.#executionCtx) {
1406
+ return this.#executionCtx;
1407
+ } else {
1408
+ throw Error("This context has no FetchEvent");
1409
+ }
1410
+ }
1411
+ get executionCtx() {
1412
+ if (this.#executionCtx) {
1413
+ return this.#executionCtx;
1414
+ } else {
1415
+ throw Error("This context has no ExecutionContext");
1416
+ }
1417
+ }
1418
+ get res() {
1419
+ return this.#res ||= createResponseInstance(null, {
1420
+ headers: this.#preparedHeaders ??= new Headers
1421
+ });
1422
+ }
1423
+ set res(_res) {
1424
+ if (this.#res && _res) {
1425
+ _res = createResponseInstance(_res.body, _res);
1426
+ for (const [k, v] of this.#res.headers.entries()) {
1427
+ if (k === "content-type") {
1428
+ continue;
1429
+ }
1430
+ if (k === "set-cookie") {
1431
+ const cookies = this.#res.headers.getSetCookie();
1432
+ _res.headers.delete("set-cookie");
1433
+ for (const cookie of cookies) {
1434
+ _res.headers.append("set-cookie", cookie);
1435
+ }
1436
+ } else {
1437
+ _res.headers.set(k, v);
1438
+ }
1439
+ }
1440
+ }
1441
+ this.#res = _res;
1442
+ this.finalized = true;
1443
+ }
1444
+ render = (...args) => {
1445
+ this.#renderer ??= (content) => this.html(content);
1446
+ return this.#renderer(...args);
1447
+ };
1448
+ setLayout = (layout) => this.#layout = layout;
1449
+ getLayout = () => this.#layout;
1450
+ setRenderer = (renderer) => {
1451
+ this.#renderer = renderer;
1452
+ };
1453
+ header = (name, value, options) => {
1454
+ if (this.finalized) {
1455
+ this.#res = createResponseInstance(this.#res.body, this.#res);
1456
+ }
1457
+ const headers = this.#res ? this.#res.headers : this.#preparedHeaders ??= new Headers;
1458
+ if (value === undefined) {
1459
+ headers.delete(name);
1460
+ } else if (options?.append) {
1461
+ headers.append(name, value);
1462
+ } else {
1463
+ headers.set(name, value);
1464
+ }
1465
+ };
1466
+ status = (status) => {
1467
+ this.#status = status;
1468
+ };
1469
+ set = (key, value) => {
1470
+ this.#var ??= /* @__PURE__ */ new Map;
1471
+ this.#var.set(key, value);
1472
+ };
1473
+ get = (key) => {
1474
+ return this.#var ? this.#var.get(key) : undefined;
1475
+ };
1476
+ get var() {
1477
+ if (!this.#var) {
1478
+ return {};
1479
+ }
1480
+ return Object.fromEntries(this.#var);
1481
+ }
1482
+ #newResponse(data, arg, headers) {
1483
+ let responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders;
1484
+ if (typeof arg === "object" && arg.headers) {
1485
+ responseHeaders ??= new Headers;
1486
+ for (const [key, value] of new Headers(arg.headers)) {
1487
+ if (key === "set-cookie") {
1488
+ responseHeaders.append(key, value);
1489
+ } else {
1490
+ responseHeaders.set(key, value);
1491
+ }
1492
+ }
1493
+ }
1494
+ if (headers) {
1495
+ if (!responseHeaders) {
1496
+ let count = 0;
1497
+ for (const k in headers) {
1498
+ if (++count > 1 || typeof headers[k] !== "string") {
1499
+ responseHeaders = new Headers;
1500
+ break;
1501
+ }
1502
+ }
1503
+ }
1504
+ if (responseHeaders) {
1505
+ for (const k in headers) {
1506
+ const v = headers[k];
1507
+ if (typeof v === "string") {
1508
+ responseHeaders.set(k, v);
1509
+ } else {
1510
+ responseHeaders.delete(k);
1511
+ for (const v2 of v) {
1512
+ responseHeaders.append(k, v2);
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1517
+ }
1518
+ const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
1519
+ return createResponseInstance(data, {
1520
+ status,
1521
+ headers: responseHeaders ?? headers
1522
+ });
1523
+ }
1524
+ newResponse = (...args) => this.#newResponse(...args);
1525
+ body = (data, arg, headers) => this.#newResponse(data, arg, headers);
1526
+ text = (text, arg, headers) => {
1527
+ return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized ? new Response(text) : this.#newResponse(text, arg, setDefaultContentType(TEXT_PLAIN, headers));
1528
+ };
1529
+ json = (object, arg, headers) => {
1530
+ return this.#newResponse(JSON.stringify(object), arg, setDefaultContentType("application/json", headers));
1531
+ };
1532
+ html = (html, arg, headers) => {
1533
+ const res = (html2) => this.#newResponse(html2, arg, setDefaultContentType("text/html; charset=UTF-8", headers));
1534
+ return typeof html === "object" ? resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then(res) : res(html);
1535
+ };
1536
+ redirect = (location, status) => {
1537
+ const locationString = String(location);
1538
+ this.header("Location", !/[^\x00-\xFF]/.test(locationString) ? locationString : encodeURI(locationString));
1539
+ return this.newResponse(null, status ?? 302);
1540
+ };
1541
+ notFound = () => {
1542
+ this.#notFoundHandler ??= () => createResponseInstance();
1543
+ return this.#notFoundHandler(this);
1544
+ };
1545
+ };
1546
+
1547
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router.js
1548
+ var METHOD_NAME_ALL = "ALL";
1549
+ var METHOD_NAME_ALL_LOWERCASE = "all";
1550
+ var METHODS = ["get", "post", "put", "delete", "options", "patch", "query"];
1551
+ var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is already built.";
1552
+ var UnsupportedPathError = class extends Error {
1553
+ };
1554
+
1555
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/utils/constants.js
1556
+ var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
1557
+
1558
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/hono-base.js
1559
+ var notFoundHandler = (c) => {
1560
+ return c.text("404 Not Found", 404);
1561
+ };
1562
+ var errorHandler = (err, c) => {
1563
+ if ("getResponse" in err) {
1564
+ const res = err.getResponse();
1565
+ return c.newResponse(res.body, res);
1566
+ }
1567
+ console.error(err);
1568
+ return c.text("Internal Server Error", 500);
1569
+ };
1570
+ var Hono = class _Hono {
1571
+ get;
1572
+ post;
1573
+ put;
1574
+ delete;
1575
+ options;
1576
+ patch;
1577
+ query;
1578
+ all;
1579
+ on;
1580
+ use;
1581
+ router;
1582
+ getPath;
1583
+ _basePath = "/";
1584
+ #path = "/";
1585
+ routes = [];
1586
+ constructor(options = {}) {
1587
+ const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE];
1588
+ allMethods.forEach((method) => {
1589
+ this[method] = (args1, ...args) => {
1590
+ if (typeof args1 === "string") {
1591
+ this.#path = args1;
1592
+ } else {
1593
+ this.#addRoute(method, this.#path, args1);
1594
+ }
1595
+ args.forEach((handler) => {
1596
+ this.#addRoute(method, this.#path, handler);
1597
+ });
1598
+ return this;
1599
+ };
1600
+ });
1601
+ this.on = (method, path, ...handlers) => {
1602
+ for (const p of [path].flat()) {
1603
+ this.#path = p;
1604
+ for (const m of [method].flat()) {
1605
+ handlers.map((handler) => {
1606
+ this.#addRoute(m.toUpperCase(), this.#path, handler);
1607
+ });
1608
+ }
1609
+ }
1610
+ return this;
1611
+ };
1612
+ this.use = (arg1, ...handlers) => {
1613
+ if (typeof arg1 === "string") {
1614
+ this.#path = arg1;
1615
+ } else {
1616
+ this.#path = "*";
1617
+ handlers.unshift(arg1);
1618
+ }
1619
+ handlers.forEach((handler) => {
1620
+ this.#addRoute(METHOD_NAME_ALL, this.#path, handler);
1621
+ });
1622
+ return this;
1623
+ };
1624
+ const { strict, ...optionsWithoutStrict } = options;
1625
+ Object.assign(this, optionsWithoutStrict);
1626
+ this.getPath = strict ?? true ? options.getPath ?? getPath : getPathNoStrict;
1627
+ }
1628
+ #clone() {
1629
+ const clone = new _Hono({
1630
+ router: this.router,
1631
+ getPath: this.getPath
1632
+ });
1633
+ clone.errorHandler = this.errorHandler;
1634
+ clone.#notFoundHandler = this.#notFoundHandler;
1635
+ clone.routes = this.routes;
1636
+ return clone;
1637
+ }
1638
+ #notFoundHandler = notFoundHandler;
1639
+ errorHandler = errorHandler;
1640
+ route(path, app) {
1641
+ const subApp = this.basePath(path);
1642
+ app.routes.map((r) => {
1643
+ let handler;
1644
+ if (app.errorHandler === errorHandler) {
1645
+ handler = r.handler;
1646
+ } else {
1647
+ handler = async (c, next) => (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res;
1648
+ handler[COMPOSED_HANDLER] = r.handler;
1649
+ }
1650
+ subApp.#addRoute(r.method, r.path, handler, r.basePath);
1651
+ });
1652
+ return this;
1653
+ }
1654
+ basePath(path) {
1655
+ const subApp = this.#clone();
1656
+ subApp._basePath = mergePath(this._basePath, path);
1657
+ return subApp;
1658
+ }
1659
+ onError = (handler) => {
1660
+ this.errorHandler = handler;
1661
+ return this;
1662
+ };
1663
+ notFound = (handler) => {
1664
+ this.#notFoundHandler = handler;
1665
+ return this;
1666
+ };
1667
+ mount(path, applicationHandler, options) {
1668
+ let replaceRequest;
1669
+ let optionHandler;
1670
+ if (options) {
1671
+ if (typeof options === "function") {
1672
+ optionHandler = options;
1673
+ } else {
1674
+ optionHandler = options.optionHandler;
1675
+ if (options.replaceRequest === false) {
1676
+ replaceRequest = (request) => request;
1677
+ } else {
1678
+ replaceRequest = options.replaceRequest;
1679
+ }
1680
+ }
1681
+ }
1682
+ const getOptions = optionHandler ? (c) => {
1683
+ const options2 = optionHandler(c);
1684
+ return Array.isArray(options2) ? options2 : [options2];
1685
+ } : (c) => {
1686
+ let executionContext = undefined;
1687
+ try {
1688
+ executionContext = c.executionCtx;
1689
+ } catch {}
1690
+ return [c.env, executionContext];
1691
+ };
1692
+ replaceRequest ||= (() => {
1693
+ const mergedPath = mergePath(this._basePath, path);
1694
+ const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
1695
+ return (request) => {
1696
+ const url = new URL(request.url);
1697
+ url.pathname = this.getPath(request).slice(pathPrefixLength) || "/";
1698
+ return new Request(url, request);
1699
+ };
1700
+ })();
1701
+ const handler = async (c, next) => {
1702
+ const res = await applicationHandler(replaceRequest(c.req.raw), ...getOptions(c));
1703
+ if (res) {
1704
+ return res;
1705
+ }
1706
+ await next();
1707
+ };
1708
+ this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"), handler);
1709
+ return this;
1710
+ }
1711
+ #addRoute(method, path, handler, baseRoutePath) {
1712
+ method = method.toUpperCase();
1713
+ path = mergePath(this._basePath, path);
1714
+ const r = {
1715
+ basePath: baseRoutePath !== undefined ? mergePath(this._basePath, baseRoutePath) : this._basePath,
1716
+ path,
1717
+ method,
1718
+ handler
1719
+ };
1720
+ this.router.add(method, path, [handler, r]);
1721
+ this.routes.push(r);
1722
+ }
1723
+ #handleError(err, c) {
1724
+ if (err instanceof Error) {
1725
+ return this.errorHandler(err, c);
1726
+ }
1727
+ throw err;
1728
+ }
1729
+ #dispatch(request, executionCtx, env, method) {
1730
+ if (method === "HEAD") {
1731
+ return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
1732
+ }
1733
+ const path = this.getPath(request, { env });
1734
+ const matchResult = this.router.match(method, path);
1735
+ const c = new Context(request, {
1736
+ path,
1737
+ matchResult,
1738
+ env,
1739
+ executionCtx,
1740
+ notFoundHandler: this.#notFoundHandler
1741
+ });
1742
+ if (matchResult[0].length === 1) {
1743
+ let res;
1744
+ try {
1745
+ res = matchResult[0][0][0][0](c, async () => {
1746
+ c.res = await this.#notFoundHandler(c);
1747
+ });
1748
+ } catch (err) {
1749
+ return this.#handleError(err, c);
1750
+ }
1751
+ return res instanceof Promise ? res.then((resolved) => resolved || (c.finalized ? c.res : this.#notFoundHandler(c))).catch((err) => this.#handleError(err, c)) : res ?? this.#notFoundHandler(c);
1752
+ }
1753
+ const composed = compose(matchResult[0], this.errorHandler, this.#notFoundHandler);
1754
+ return (async () => {
1755
+ try {
1756
+ const context = await composed(c);
1757
+ if (!context.finalized) {
1758
+ throw new Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");
1759
+ }
1760
+ return context.res;
1761
+ } catch (err) {
1762
+ return this.#handleError(err, c);
1763
+ }
1764
+ })();
1765
+ }
1766
+ fetch = (request, ...rest) => {
1767
+ return this.#dispatch(request, rest[1], rest[0], request.method);
1768
+ };
1769
+ request = (input, requestInit, Env, executionCtx) => {
1770
+ if (input instanceof Request) {
1771
+ return this.fetch(requestInit ? new Request(input, requestInit) : input, Env, executionCtx);
1772
+ }
1773
+ input = input.toString();
1774
+ return this.fetch(new Request(/^https?:\/\//.test(input) ? input : `http://localhost${mergePath("/", input)}`, requestInit), Env, executionCtx);
1775
+ };
1776
+ fire = () => {
1777
+ addEventListener("fetch", (event) => {
1778
+ event.respondWith(this.#dispatch(event.request, event, undefined, event.request.method));
1779
+ });
1780
+ };
1781
+ };
1782
+
1783
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/reg-exp-router/matcher.js
1784
+ var emptyParam = [];
1785
+ function match(method, path) {
1786
+ const matchers = this.buildAllMatchers();
1787
+ const match2 = (method2, path2) => {
1788
+ const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
1789
+ const staticMatch = matcher[2][path2];
1790
+ if (staticMatch) {
1791
+ return staticMatch;
1792
+ }
1793
+ const match3 = path2.match(matcher[0]);
1794
+ if (!match3) {
1795
+ return [[], emptyParam];
1796
+ }
1797
+ const index = match3.indexOf("", 1);
1798
+ return [matcher[1][index], match3];
1799
+ };
1800
+ this.match = match2;
1801
+ return match2(method, path);
1802
+ }
1803
+
1804
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/reg-exp-router/node.js
1805
+ var LABEL_REG_EXP_STR = "[^/]+";
1806
+ var ONLY_WILDCARD_REG_EXP_STR = ".*";
1807
+ var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
1808
+ var PATH_ERROR = /* @__PURE__ */ Symbol();
1809
+ var regExpMetaChars = new Set(".\\+*[^]$()");
1810
+ function compareKey(a, b) {
1811
+ if (a.length === 1) {
1812
+ return b.length === 1 ? a < b ? -1 : 1 : -1;
1813
+ }
1814
+ if (b.length === 1) {
1815
+ return 1;
1816
+ }
1817
+ if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) {
1818
+ return b === TAIL_WILDCARD_REG_EXP_STR ? -1 : 1;
1819
+ } else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) {
1820
+ return -1;
1821
+ }
1822
+ if (a === LABEL_REG_EXP_STR) {
1823
+ return 1;
1824
+ } else if (b === LABEL_REG_EXP_STR) {
1825
+ return -1;
1826
+ }
1827
+ return a.length === b.length ? a < b ? -1 : 1 : b.length - a.length;
1828
+ }
1829
+ var Node = class _Node {
1830
+ #index;
1831
+ #varIndex;
1832
+ #children = /* @__PURE__ */ Object.create(null);
1833
+ insert(tokens, index, paramMap, context, isStatic) {
1834
+ let node = this;
1835
+ for (let i = 0, len = tokens.length;i < len; i++) {
1836
+ const token = tokens[i];
1837
+ const pattern = token.length === 1 ? token === "*" ? i === len - 1 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : null : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
1838
+ let nextNode;
1839
+ if (pattern) {
1840
+ const name = pattern[1];
1841
+ let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
1842
+ if (name && pattern[2]) {
1843
+ if (regexpStr === ".*") {
1844
+ throw PATH_ERROR;
1845
+ }
1846
+ regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
1847
+ if (/\((?!\?:)/.test(regexpStr)) {
1848
+ throw PATH_ERROR;
1849
+ }
1850
+ if (regexpStr.length === 1 && regExpMetaChars.has(regexpStr)) {
1851
+ throw PATH_ERROR;
1852
+ }
1853
+ }
1854
+ nextNode = node.#children[regexpStr];
1855
+ if (!nextNode) {
1856
+ if (regexpStr !== ONLY_WILDCARD_REG_EXP_STR && regexpStr !== TAIL_WILDCARD_REG_EXP_STR) {
1857
+ for (const k in node.#children) {
1858
+ if ((regexpStr.length > 1 || k.length > 1) && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR) {
1859
+ throw PATH_ERROR;
1860
+ }
1861
+ }
1862
+ }
1863
+ nextNode = node.#children[regexpStr] = new _Node;
1864
+ }
1865
+ if (name !== "") {
1866
+ nextNode.#varIndex ??= context.varIndex++;
1867
+ paramMap.push([name, nextNode.#varIndex]);
1868
+ }
1869
+ } else {
1870
+ nextNode = node.#children[token];
1871
+ if (!nextNode) {
1872
+ for (const k in node.#children) {
1873
+ if (k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR) {
1874
+ throw PATH_ERROR;
1875
+ }
1876
+ }
1877
+ nextNode = node.#children[token] = new _Node;
1878
+ }
1879
+ }
1880
+ node = nextNode;
1881
+ }
1882
+ if (node.#index !== undefined) {
1883
+ throw PATH_ERROR;
1884
+ }
1885
+ node.#index = isStatic ? -1 : index;
1886
+ }
1887
+ buildRegExpStr() {
1888
+ const childKeys = Object.keys(this.#children).sort(compareKey);
1889
+ const strList = childKeys.map((k) => {
1890
+ const c = this.#children[k];
1891
+ const childStr = c.buildRegExpStr();
1892
+ return childStr === "" ? "" : (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + childStr;
1893
+ }).filter(Boolean);
1894
+ if (typeof this.#index === "number" && this.#index !== -1) {
1895
+ strList.unshift(`#${this.#index}`);
1896
+ }
1897
+ if (strList.length === 0) {
1898
+ return "";
1899
+ }
1900
+ if (strList.length === 1) {
1901
+ return strList[0];
1902
+ }
1903
+ return "(?:" + strList.join("|") + ")";
1904
+ }
1905
+ };
1906
+
1907
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/reg-exp-router/trie.js
1908
+ var Trie = class {
1909
+ #context = { varIndex: 0 };
1910
+ #root = new Node;
1911
+ #index = 0;
1912
+ paths = /* @__PURE__ */ Object.create(null);
1913
+ insert(path, isStatic) {
1914
+ if (isStatic) {
1915
+ this.#root.insert(path.split(""), 0, [], this.#context, true);
1916
+ return;
1917
+ }
1918
+ const paramAssoc = [];
1919
+ const groups = [];
1920
+ let markedPath = path;
1921
+ for (let i = 0;; ) {
1922
+ let replaced = false;
1923
+ markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
1924
+ const mark = `@\\${i}`;
1925
+ groups[i] = [mark, m];
1926
+ i++;
1927
+ replaced = true;
1928
+ return mark;
1929
+ });
1930
+ if (!replaced) {
1931
+ break;
1932
+ }
1933
+ }
1934
+ const tokens = markedPath.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
1935
+ for (let i = groups.length - 1;i >= 0; i--) {
1936
+ const [mark] = groups[i];
1937
+ for (let j = tokens.length - 1;j >= 0; j--) {
1938
+ if (tokens[j].indexOf(mark) !== -1) {
1939
+ tokens[j] = tokens[j].replace(mark, groups[i][1]);
1940
+ break;
1941
+ }
1942
+ }
1943
+ }
1944
+ this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
1945
+ this.paths[path] = [this.#index++, paramAssoc];
1946
+ }
1947
+ buildRegExp() {
1948
+ let regexp = this.#root.buildRegExpStr();
1949
+ if (regexp === "") {
1950
+ return [/^$/, [], []];
1951
+ }
1952
+ let captureIndex = 0;
1953
+ const indexReplacementMap = [];
1954
+ const paramReplacementMap = [];
1955
+ regexp = regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g, (_, handlerIndex, paramIndex) => {
1956
+ if (handlerIndex !== undefined) {
1957
+ indexReplacementMap[++captureIndex] = Number(handlerIndex);
1958
+ return "$()";
1959
+ }
1960
+ if (paramIndex !== undefined) {
1961
+ paramReplacementMap[Number(paramIndex)] = ++captureIndex;
1962
+ return "";
1963
+ }
1964
+ return "";
1965
+ });
1966
+ return [new RegExp(`^${regexp}`), indexReplacementMap, paramReplacementMap];
1967
+ }
1968
+ };
1969
+
1970
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/reg-exp-router/router.js
1971
+ var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
1972
+ function buildWildcardRegExp(path) {
1973
+ return wildcardRegExpCache[path] ??= new RegExp(path === "*" ? "" : `^${path.replace(/\/\*$|([.\\+*[^\]$()])/g, (_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)")}$`);
1974
+ }
1975
+ function clearWildcardRegExpCache() {
1976
+ wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
1977
+ }
1978
+ function findMiddleware(middleware, path) {
1979
+ if (!middleware) {
1980
+ return;
1981
+ }
1982
+ for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
1983
+ if (buildWildcardRegExp(k).test(path)) {
1984
+ return [...middleware[k]];
1985
+ }
1986
+ }
1987
+ return;
1988
+ }
1989
+ var RegExpRouter = class {
1990
+ name = "RegExpRouter";
1991
+ #middleware;
1992
+ #routes;
1993
+ #tries;
1994
+ constructor() {
1995
+ this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
1996
+ this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
1997
+ this.#tries = { [METHOD_NAME_ALL]: new Trie };
1998
+ }
1999
+ #insertPath(method, path) {
2000
+ try {
2001
+ this.#tries[method].insert(path, !/\*|\/:/.test(path));
2002
+ } catch (e) {
2003
+ throw e === PATH_ERROR ? new UnsupportedPathError(path) : e;
2004
+ }
2005
+ }
2006
+ add(method, path, handler) {
2007
+ const middleware = this.#middleware;
2008
+ const routes = this.#routes;
2009
+ if (!middleware || !routes) {
2010
+ throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
2011
+ }
2012
+ if (!middleware[method]) {
2013
+ this.#tries[method] = new Trie;
2014
+ [middleware, routes].forEach((handlerMap) => {
2015
+ handlerMap[method] = /* @__PURE__ */ Object.create(null);
2016
+ Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => {
2017
+ handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]];
2018
+ this.#insertPath(method, p);
2019
+ });
2020
+ });
2021
+ }
2022
+ if (path === "/*") {
2023
+ path = "*";
2024
+ }
2025
+ const paramCount = (path.match(/\/:/g) || []).length;
2026
+ if (/\*$/.test(path)) {
2027
+ const re = buildWildcardRegExp(path);
2028
+ Object.keys(middleware).forEach((m) => {
2029
+ if ((method === METHOD_NAME_ALL || method === m) && !middleware[m][path]) {
2030
+ this.#insertPath(m, path);
2031
+ middleware[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
2032
+ }
2033
+ });
2034
+ Object.keys(middleware).forEach((m) => {
2035
+ if (method === METHOD_NAME_ALL || method === m) {
2036
+ Object.keys(middleware[m]).forEach((p) => {
2037
+ re.test(p) && middleware[m][p].push([handler, paramCount]);
2038
+ });
2039
+ }
2040
+ });
2041
+ Object.keys(routes).forEach((m) => {
2042
+ if (method === METHOD_NAME_ALL || method === m) {
2043
+ Object.keys(routes[m]).forEach((p) => re.test(p) && routes[m][p].push([handler, paramCount]));
2044
+ }
2045
+ });
2046
+ return;
2047
+ }
2048
+ const paths = checkOptionalParameter(path) || [path];
2049
+ for (let i = 0, len = paths.length;i < len; i++) {
2050
+ const path2 = paths[i];
2051
+ Object.keys(routes).forEach((m) => {
2052
+ if (method === METHOD_NAME_ALL || method === m) {
2053
+ if (!routes[m][path2]) {
2054
+ this.#insertPath(m, path2);
2055
+ routes[m][path2] = [
2056
+ ...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
2057
+ ];
2058
+ }
2059
+ routes[m][path2].push([handler, paramCount - len + i + 1]);
2060
+ }
2061
+ });
2062
+ }
2063
+ }
2064
+ match = match;
2065
+ buildAllMatchers() {
2066
+ const matchers = /* @__PURE__ */ Object.create(null);
2067
+ Object.keys(this.#routes).concat(Object.keys(this.#middleware)).forEach((method) => {
2068
+ matchers[method] ||= this.#buildMatcher(method);
2069
+ });
2070
+ this.#middleware = this.#routes = this.#tries = undefined;
2071
+ clearWildcardRegExpCache();
2072
+ return matchers;
2073
+ }
2074
+ #buildMatcher(method) {
2075
+ const middleware = this.#middleware[method];
2076
+ const routes = this.#routes[method];
2077
+ const trie = this.#tries[method];
2078
+ const staticMap = /* @__PURE__ */ Object.create(null);
2079
+ const handlerData = [];
2080
+ [middleware, routes].forEach((r) => {
2081
+ for (const path in r) {
2082
+ const handlers = r[path];
2083
+ const pathData = trie.paths[path];
2084
+ if (!pathData) {
2085
+ staticMap[path] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
2086
+ continue;
2087
+ }
2088
+ const paramAssoc = pathData[1];
2089
+ handlerData[pathData[0]] = handlers.map(([h, paramCount]) => {
2090
+ const paramIndexMap = /* @__PURE__ */ Object.create(null);
2091
+ paramCount -= 1;
2092
+ for (;paramCount >= 0; paramCount--) {
2093
+ const [key, value] = paramAssoc[paramCount];
2094
+ paramIndexMap[key] = value;
2095
+ }
2096
+ return [h, paramIndexMap];
2097
+ });
2098
+ }
2099
+ });
2100
+ const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
2101
+ for (let i = 0, len = handlerData.length;i < len; i++) {
2102
+ for (let j = 0, len2 = handlerData[i].length;j < len2; j++) {
2103
+ const map = handlerData[i][j]?.[1];
2104
+ if (!map) {
2105
+ continue;
2106
+ }
2107
+ const keys = Object.keys(map);
2108
+ for (let k = 0, len3 = keys.length;k < len3; k++) {
2109
+ map[keys[k]] = paramReplacementMap[map[keys[k]]];
2110
+ }
2111
+ }
2112
+ }
2113
+ const handlerMap = [];
2114
+ for (const i in indexReplacementMap) {
2115
+ handlerMap[i] = handlerData[indexReplacementMap[i]];
2116
+ }
2117
+ return [regexp, handlerMap, staticMap];
2118
+ }
2119
+ };
2120
+
2121
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
2122
+ var PreparedRegExpRouter = class {
2123
+ name = "PreparedRegExpRouter";
2124
+ #matchers;
2125
+ #relocateMap;
2126
+ constructor(matchers, relocateMap) {
2127
+ this.#matchers = matchers;
2128
+ this.#relocateMap = relocateMap;
2129
+ }
2130
+ #addWildcard(method, handlerData) {
2131
+ const matcher = this.#matchers[method];
2132
+ matcher[1].forEach((list) => list && list.push(handlerData));
2133
+ Object.values(matcher[2]).forEach((list) => list[0].push(handlerData));
2134
+ }
2135
+ #addPath(method, path, handler, indexes, map) {
2136
+ const matcher = this.#matchers[method];
2137
+ if (!map) {
2138
+ matcher[2][path][0].push([handler, {}]);
2139
+ } else {
2140
+ indexes.forEach((index) => {
2141
+ if (typeof index === "number") {
2142
+ matcher[1][index].push([handler, map]);
2143
+ } else {
2144
+ matcher[2][index || path][0].push([handler, map]);
2145
+ }
2146
+ });
2147
+ }
2148
+ }
2149
+ add(method, path, handler) {
2150
+ if (!this.#matchers[method]) {
2151
+ const all = this.#matchers[METHOD_NAME_ALL];
2152
+ const staticMap = {};
2153
+ for (const key in all[2]) {
2154
+ staticMap[key] = [all[2][key][0].slice(), emptyParam];
2155
+ }
2156
+ this.#matchers[method] = [
2157
+ all[0],
2158
+ all[1].map((list) => Array.isArray(list) ? list.slice() : 0),
2159
+ staticMap
2160
+ ];
2161
+ }
2162
+ if (path === "/*" || path === "*") {
2163
+ const handlerData = [handler, {}];
2164
+ if (method === METHOD_NAME_ALL) {
2165
+ for (const m in this.#matchers) {
2166
+ this.#addWildcard(m, handlerData);
2167
+ }
2168
+ } else {
2169
+ this.#addWildcard(method, handlerData);
2170
+ }
2171
+ return;
2172
+ }
2173
+ const data = this.#relocateMap[path];
2174
+ if (!data) {
2175
+ throw new Error(`Path ${path} is not registered`);
2176
+ }
2177
+ for (const [indexes, map] of data) {
2178
+ if (method === METHOD_NAME_ALL) {
2179
+ for (const m in this.#matchers) {
2180
+ this.#addPath(m, path, handler, indexes, map);
2181
+ }
2182
+ } else {
2183
+ this.#addPath(method, path, handler, indexes, map);
2184
+ }
2185
+ }
2186
+ }
2187
+ buildAllMatchers() {
2188
+ return this.#matchers;
2189
+ }
2190
+ match = match;
2191
+ };
2192
+
2193
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/smart-router/router.js
2194
+ var SmartRouter = class {
2195
+ name = "SmartRouter";
2196
+ #routers = [];
2197
+ #routes = [];
2198
+ constructor(init) {
2199
+ this.#routers = init.routers;
2200
+ }
2201
+ add(method, path, handler) {
2202
+ if (!this.#routes) {
2203
+ throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
2204
+ }
2205
+ this.#routes.push([method, path, handler]);
2206
+ }
2207
+ match(method, path) {
2208
+ if (!this.#routes) {
2209
+ throw new Error("Fatal error");
2210
+ }
2211
+ const routers = this.#routers;
2212
+ const routes = this.#routes;
2213
+ const len = routers.length;
2214
+ let i = 0;
2215
+ let res;
2216
+ for (;i < len; i++) {
2217
+ const router = routers[i];
2218
+ try {
2219
+ for (let i2 = 0, len2 = routes.length;i2 < len2; i2++) {
2220
+ router.add(...routes[i2]);
2221
+ }
2222
+ res = router.match(method, path);
2223
+ } catch (e) {
2224
+ if (e instanceof UnsupportedPathError) {
2225
+ continue;
2226
+ }
2227
+ throw e;
2228
+ }
2229
+ this.match = router.match.bind(router);
2230
+ this.#routers = [router];
2231
+ this.#routes = undefined;
2232
+ break;
2233
+ }
2234
+ if (i === len) {
2235
+ throw new Error("Fatal error");
2236
+ }
2237
+ this.name = `SmartRouter + ${this.activeRouter.name}`;
2238
+ return res;
2239
+ }
2240
+ get activeRouter() {
2241
+ if (this.#routes || this.#routers.length !== 1) {
2242
+ throw new Error("No active router has been determined yet.");
2243
+ }
2244
+ return this.#routers[0];
2245
+ }
2246
+ };
2247
+
2248
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/trie-router/node.js
2249
+ var emptyParams = /* @__PURE__ */ Object.create(null);
2250
+ var order = 0;
2251
+ var Node2 = class _Node2 {
2252
+ #methods = [];
2253
+ #children = /* @__PURE__ */ Object.create(null);
2254
+ #patterns = [];
2255
+ #pattern;
2256
+ #params = emptyParams;
2257
+ insert(method, path, handler) {
2258
+ let curNode = this;
2259
+ const parts = splitRoutingPath(path);
2260
+ const possibleKeys = /* @__PURE__ */ new Set;
2261
+ let i = 0;
2262
+ for (const p of parts) {
2263
+ const nextP = parts[++i];
2264
+ const pattern = getPattern(p, nextP) || (nextP === undefined && p && p.indexOf("*") === p.length - 1 ? p : null);
2265
+ const isParam = Array.isArray(pattern);
2266
+ const key = isParam ? pattern[0] : pattern || p;
2267
+ const child = curNode.#children[key] ||= new _Node2;
2268
+ if (pattern && !child.#pattern) {
2269
+ child.#pattern = pattern;
2270
+ curNode.#patterns.push(child);
2271
+ }
2272
+ curNode = child;
2273
+ if (isParam) {
2274
+ possibleKeys.add(pattern[1]);
2275
+ }
2276
+ }
2277
+ curNode.#methods.push({
2278
+ [method]: {
2279
+ handler,
2280
+ possibleKeys: [...possibleKeys],
2281
+ score: ++order
2282
+ }
2283
+ });
2284
+ }
2285
+ #pushHandlerSets(handlerSets, node, method, nodeParams, params) {
2286
+ for (let i = 0, len = node.#methods.length;i < len; i++) {
2287
+ const m = node.#methods[i];
2288
+ const handlerSet = m[method] || m[METHOD_NAME_ALL];
2289
+ if (handlerSet) {
2290
+ handlerSet.params = /* @__PURE__ */ Object.create(null);
2291
+ handlerSets.push(handlerSet);
2292
+ for (let i2 = 0, len2 = handlerSet.possibleKeys.length;i2 < len2; i2++) {
2293
+ const key = handlerSet.possibleKeys[i2];
2294
+ handlerSet.params[key] = params?.[key] && !i2 ? params[key] : nodeParams[key] ?? params?.[key];
2295
+ }
2296
+ }
2297
+ }
2298
+ }
2299
+ search(method, path) {
2300
+ const handlerSets = [];
2301
+ this.#params = emptyParams;
2302
+ const curNode = this;
2303
+ let curNodes = [curNode];
2304
+ const parts = splitPath(path);
2305
+ const curNodesQueue = [];
2306
+ const len = parts.length;
2307
+ let partOffsets = null;
2308
+ for (let i = 0;i < len; i++) {
2309
+ const part = parts[i];
2310
+ const isLast = i === len - 1;
2311
+ const tempNodes = [];
2312
+ for (let j = 0, len2 = curNodes.length;j < len2; j++) {
2313
+ const node = curNodes[j];
2314
+ const nextNode = node.#children[part];
2315
+ if (nextNode) {
2316
+ nextNode.#params = node.#params;
2317
+ if (isLast) {
2318
+ if (nextNode.#children["*"]) {
2319
+ this.#pushHandlerSets(handlerSets, nextNode.#children["*"], method, node.#params);
2320
+ }
2321
+ this.#pushHandlerSets(handlerSets, nextNode, method, node.#params);
2322
+ } else {
2323
+ tempNodes.push(nextNode);
2324
+ }
2325
+ }
2326
+ for (const child of node.#patterns) {
2327
+ const pattern = child.#pattern;
2328
+ const params = node.#params === emptyParams ? {} : { ...node.#params };
2329
+ if (typeof pattern === "string") {
2330
+ if (pattern === "*" || part.startsWith(pattern.slice(0, -1))) {
2331
+ this.#pushHandlerSets(handlerSets, child, method, node.#params);
2332
+ if (pattern === "*") {
2333
+ child.#params = params;
2334
+ tempNodes.push(child);
2335
+ }
2336
+ }
2337
+ continue;
2338
+ }
2339
+ const [, name, matcher] = pattern;
2340
+ if (!part && matcher === true) {
2341
+ continue;
2342
+ }
2343
+ if (matcher !== true) {
2344
+ if (!partOffsets) {
2345
+ partOffsets = [];
2346
+ let offset = path[0] === "/" ? 1 : 0;
2347
+ for (let p = 0;p < len; p++) {
2348
+ partOffsets[p] = offset;
2349
+ offset += parts[p].length + 1;
2350
+ }
2351
+ }
2352
+ const restPathString = path.slice(partOffsets[i]);
2353
+ const m = matcher.exec(restPathString);
2354
+ if (m) {
2355
+ params[name] = m[0];
2356
+ this.#pushHandlerSets(handlerSets, child, method, node.#params, params);
2357
+ if (m[0].length === restPathString.length && child.#children["*"]) {
2358
+ this.#pushHandlerSets(handlerSets, child.#children["*"], method, node.#params, params);
2359
+ }
2360
+ for (const _ in child.#children) {
2361
+ child.#params = params;
2362
+ const componentCount = m[0].match(/\//g)?.length ?? 0;
2363
+ const targetCurNodes = curNodesQueue[componentCount] ||= [];
2364
+ targetCurNodes.push(child);
2365
+ break;
2366
+ }
2367
+ continue;
2368
+ }
2369
+ }
2370
+ if (matcher === true || matcher.test(part)) {
2371
+ params[name] = part;
2372
+ if (isLast) {
2373
+ this.#pushHandlerSets(handlerSets, child, method, params, node.#params);
2374
+ if (child.#children["*"]) {
2375
+ this.#pushHandlerSets(handlerSets, child.#children["*"], method, params, node.#params);
2376
+ }
2377
+ } else {
2378
+ child.#params = params;
2379
+ tempNodes.push(child);
2380
+ }
2381
+ }
2382
+ }
2383
+ }
2384
+ const shifted = curNodesQueue.shift();
2385
+ curNodes = shifted ? tempNodes.concat(shifted) : tempNodes;
2386
+ }
2387
+ if (handlerSets[1]) {
2388
+ handlerSets.sort((a, b) => {
2389
+ return a.score - b.score;
2390
+ });
2391
+ }
2392
+ return [handlerSets.map(({ handler, params }) => [handler, params])];
2393
+ }
2394
+ };
2395
+
2396
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/trie-router/router.js
2397
+ var TrieRouter = class {
2398
+ name = "TrieRouter";
2399
+ #node = new Node2;
2400
+ add(method, path, handler) {
2401
+ for (const result of checkOptionalParameter(path) || [path]) {
2402
+ this.#node.insert(method, result, handler);
2403
+ }
2404
+ }
2405
+ match(method, path) {
2406
+ return this.#node.search(method, path);
2407
+ }
2408
+ };
2409
+
2410
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/hono.js
2411
+ var Hono2 = class extends Hono {
2412
+ constructor(options = {}) {
2413
+ super(options);
2414
+ this.router = options.router ?? new SmartRouter({
2415
+ routers: [new RegExpRouter, new TrieRouter]
2416
+ });
2417
+ }
2418
+ };
2419
+
2420
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/utils/stream.js
2421
+ var StreamingApi = class {
2422
+ writer;
2423
+ encoder;
2424
+ writable;
2425
+ abortSubscribers = [];
2426
+ responseReadable;
2427
+ aborted = false;
2428
+ closed = false;
2429
+ constructor(writable, _readable) {
2430
+ this.writable = writable;
2431
+ this.writer = writable.getWriter();
2432
+ this.encoder = new TextEncoder;
2433
+ const reader = _readable.getReader();
2434
+ this.abortSubscribers.push(async () => {
2435
+ await reader.cancel();
2436
+ });
2437
+ this.responseReadable = new ReadableStream({
2438
+ async pull(controller) {
2439
+ const { done, value } = await reader.read();
2440
+ done ? controller.close() : controller.enqueue(value);
2441
+ },
2442
+ cancel: () => {
2443
+ if (!this.closed) {
2444
+ this.abort();
2445
+ }
2446
+ }
2447
+ });
2448
+ }
2449
+ async write(input) {
2450
+ try {
2451
+ if (typeof input === "string") {
2452
+ input = this.encoder.encode(input);
2453
+ }
2454
+ await this.writer.write(input);
2455
+ } catch {}
2456
+ return this;
2457
+ }
2458
+ async writeln(input) {
2459
+ await this.write(input + `
2460
+ `);
2461
+ return this;
2462
+ }
2463
+ sleep(ms) {
2464
+ return new Promise((res) => setTimeout(res, ms));
2465
+ }
2466
+ async close() {
2467
+ this.closed = true;
2468
+ try {
2469
+ await this.writer.close();
2470
+ } catch {}
2471
+ }
2472
+ async pipe(body) {
2473
+ this.writer.releaseLock();
2474
+ try {
2475
+ await body.pipeTo(this.writable, { preventClose: true, preventAbort: true });
2476
+ } finally {
2477
+ this.writer = this.writable.getWriter();
2478
+ }
2479
+ }
2480
+ onAbort(listener) {
2481
+ this.abortSubscribers.push(listener);
2482
+ }
2483
+ abort() {
2484
+ if (!this.aborted) {
2485
+ this.aborted = true;
2486
+ this.abortSubscribers.forEach((subscriber) => subscriber());
2487
+ }
2488
+ }
2489
+ };
2490
+
2491
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/helper/streaming/utils.js
2492
+ var isOldBunVersion = () => {
2493
+ const version = typeof Bun !== "undefined" ? Bun.version : undefined;
2494
+ if (version === undefined) {
2495
+ return false;
2496
+ }
2497
+ const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
2498
+ isOldBunVersion = () => result;
2499
+ return result;
2500
+ };
2501
+
2502
+ // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/helper/streaming/sse.js
2503
+ var SSEStreamingApi = class extends StreamingApi {
2504
+ constructor(writable, readable) {
2505
+ super(writable, readable);
2506
+ }
2507
+ async writeSSE(message) {
2508
+ const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
2509
+ const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
2510
+ return `data: ${line}`;
2511
+ }).join(`
2512
+ `);
2513
+ for (const key of ["event", "id"]) {
2514
+ const value = message[key];
2515
+ if (value && /[\r\n]/.test(value)) {
2516
+ throw new Error(`${key} must not contain "\\r" or "\\n"`);
2517
+ }
2518
+ }
2519
+ const sseData = [
2520
+ message.event && `event: ${message.event}`,
2521
+ dataLines,
2522
+ message.id !== undefined && `id: ${message.id}`,
2523
+ message.retry !== undefined && `retry: ${message.retry}`
2524
+ ].filter(Boolean).join(`
2525
+ `) + `
2526
+
2527
+ `;
2528
+ await this.write(sseData);
2529
+ }
2530
+ };
2531
+ var run = async (stream, cb, onError) => {
2532
+ try {
2533
+ await cb(stream);
2534
+ } catch (e) {
2535
+ if (e instanceof Error && onError) {
2536
+ await onError(e, stream);
2537
+ await stream.writeSSE({
2538
+ event: "error",
2539
+ data: e.message
2540
+ });
2541
+ } else {
2542
+ console.error(e);
2543
+ }
2544
+ } finally {
2545
+ stream.close();
2546
+ }
2547
+ };
2548
+ var contextStash = /* @__PURE__ */ new WeakMap;
2549
+ var streamSSE = (c, cb, onError) => {
2550
+ const { readable, writable } = new TransformStream;
2551
+ const stream = new SSEStreamingApi(writable, readable);
2552
+ if (isOldBunVersion()) {
2553
+ c.req.raw.signal.addEventListener("abort", () => {
2554
+ if (!stream.closed) {
2555
+ stream.abort();
2556
+ }
2557
+ });
2558
+ }
2559
+ contextStash.set(stream.responseReadable, c);
2560
+ c.header("Transfer-Encoding", "chunked");
2561
+ c.header("Content-Type", "text/event-stream");
2562
+ c.header("Cache-Control", "no-cache");
2563
+ c.header("Connection", "keep-alive");
2564
+ run(stream, cb, onError);
2565
+ return c.newResponse(stream.responseReadable);
2566
+ };
2567
+
2568
+ // packages/daemon/src/forge.ts
2569
+ import { existsSync as existsSync3 } from "fs";
2570
+ import { homedir as homedir2 } from "os";
2571
+ import { join as join3 } from "path";
2572
+ var EXTRA_BIN_DIRS = [
2573
+ "/opt/homebrew/bin",
2574
+ "/usr/local/bin",
2575
+ "/home/linuxbrew/.linuxbrew/bin",
2576
+ join3(homedir2(), ".local", "bin"),
2577
+ join3(homedir2(), "bin")
2578
+ ];
2579
+ function findBin(name) {
2580
+ if (!name)
2581
+ return null;
2582
+ const onPath = Bun.which(name);
2583
+ if (onPath)
2584
+ return onPath;
2585
+ for (const d of EXTRA_BIN_DIRS) {
2586
+ const p = join3(d, name);
2587
+ if (existsSync3(p))
2588
+ return p;
2589
+ }
2590
+ return null;
2591
+ }
2592
+ var GH_FIELDS = "number,title,headRefName,url,author,isDraft,mergeable,reviewDecision,statusCheckRollup,createdAt";
2593
+
2594
+ class ForgeService {
2595
+ store;
2596
+ cache = new Map;
2597
+ inflight = new Set;
2598
+ constructor(store) {
2599
+ this.store = store;
2600
+ }
2601
+ prs() {
2602
+ this.refresh();
2603
+ const all = [...this.cache.values()].flatMap((c) => c.prs);
2604
+ return all.sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
2605
+ }
2606
+ async refresh(maxAgeMs = 120000) {
2607
+ const projects = this.store.projects();
2608
+ await Promise.all(projects.map(async (p) => {
2609
+ const hit = this.cache.get(p.id);
2610
+ if (hit && Date.now() - hit.at < maxAgeMs)
2611
+ return;
2612
+ if (this.inflight.has(p.id))
2613
+ return;
2614
+ this.inflight.add(p.id);
2615
+ try {
2616
+ const prs = await this.poll(p.id, p.root);
2617
+ this.cache.set(p.id, { at: Date.now(), prs });
2618
+ } catch {
2619
+ this.cache.set(p.id, { at: Date.now(), prs: this.cache.get(p.id)?.prs ?? [] });
2620
+ } finally {
2621
+ this.inflight.delete(p.id);
2622
+ }
2623
+ }));
2624
+ }
2625
+ remote(root) {
2626
+ const r = Bun.spawnSync(["git", "-C", root, "remote", "get-url", "origin"]);
2627
+ if (r.exitCode !== 0)
2628
+ return null;
2629
+ return parseRemote(new TextDecoder().decode(r.stdout).trim());
2630
+ }
2631
+ async run(cmd, cwd) {
2632
+ const bin = findBin(cmd[0]);
2633
+ if (!bin)
2634
+ return null;
2635
+ const proc = Bun.spawn([bin, ...cmd.slice(1)], { cwd, stdout: "pipe", stderr: "ignore" });
2636
+ const out = await new Response(proc.stdout).text();
2637
+ return await proc.exited === 0 ? out : null;
2638
+ }
2639
+ async poll(projectId, root) {
2640
+ const remote = this.remote(root);
2641
+ if (!remote)
2642
+ return [];
2643
+ let prs = [];
2644
+ if (remote.forge === "github") {
2645
+ const out = await this.run(["gh", "pr", "list", "--json", GH_FIELDS], root);
2646
+ if (out)
2647
+ prs = normalizeGithub(JSON.parse(out), remote.repo);
2648
+ } else {
2649
+ const out = await this.run(["glab", "mr", "list", "--output", "json"], root);
2650
+ if (out)
2651
+ prs = normalizeGitlab(JSON.parse(out), remote.repo);
2652
+ }
2653
+ return prs.map((pr) => ({ ...pr, projectId, projectRoot: root }));
2654
+ }
2655
+ async merge(projectId, number) {
2656
+ const p = this.store.projects().find((x) => x.id === projectId);
2657
+ if (!p)
2658
+ return { ok: false, output: "unknown project" };
2659
+ const remote = this.remote(p.root);
2660
+ if (!remote)
2661
+ return { ok: false, output: "no forge remote" };
2662
+ const cmd = remote.forge === "github" ? ["gh", "pr", "merge", String(number), "--squash"] : ["glab", "mr", "merge", String(number), "--squash", "--yes"];
2663
+ const bin = findBin(cmd[0]);
2664
+ if (!bin)
2665
+ return { ok: false, output: `${cmd[0] ?? "forge CLI"} is not installed` };
2666
+ const proc = Bun.spawn([bin, ...cmd.slice(1)], { cwd: p.root, stdout: "pipe", stderr: "pipe" });
2667
+ const out = await new Response(proc.stdout).text() + await new Response(proc.stderr).text();
2668
+ const ok = await proc.exited === 0;
2669
+ if (ok)
2670
+ this.cache.delete(projectId);
2671
+ return { ok, output: out.trim().slice(0, 800) };
2672
+ }
2673
+ }
2674
+
2675
+ // packages/daemon/src/store.ts
2676
+ import { Database } from "bun:sqlite";
2677
+ import {
2678
+ closeSync,
2679
+ existsSync as existsSync4,
2680
+ mkdirSync as mkdirSync2,
2681
+ openSync,
2682
+ readdirSync,
2683
+ readFileSync as readFileSync3,
2684
+ readSync,
2685
+ realpathSync as realpathSync2,
2686
+ renameSync,
2687
+ statSync,
2688
+ writeFileSync as writeFileSync2
2689
+ } from "fs";
2690
+ import { homedir as homedir3 } from "os";
2691
+ import { basename, dirname, join as join5 } from "path";
2692
+
2693
+ // packages/daemon/src/git.ts
2694
+ import { realpathSync } from "fs";
2695
+ import { join as join4 } from "path";
2696
+ function git(cwd, args) {
2697
+ try {
2698
+ const r = Bun.spawnSync(["git", "-C", cwd, ...args], { stdout: "pipe", stderr: "ignore" });
2699
+ return r.exitCode === 0 ? r.stdout.toString() : null;
2700
+ } catch {
2701
+ return null;
2702
+ }
2703
+ }
2704
+ function gitCommonDir(cwd) {
2705
+ const out = git(cwd, ["rev-parse", "--git-common-dir"])?.trim();
2706
+ if (!out)
2707
+ return null;
2708
+ try {
2709
+ return realpathSync(out.startsWith("/") ? out : join4(cwd, out));
2710
+ } catch {
2711
+ return null;
2712
+ }
2713
+ }
2714
+ function gitToplevel(cwd) {
2715
+ const out = git(cwd, ["rev-parse", "--show-toplevel"])?.trim();
2716
+ if (!out)
2717
+ return null;
2718
+ try {
2719
+ return realpathSync(out);
2720
+ } catch {
2721
+ return null;
2722
+ }
2723
+ }
2724
+ function parseWorktreeList(out) {
2725
+ const wts = [];
2726
+ let cur = null;
2727
+ const flush = () => {
2728
+ if (cur?.path) {
2729
+ wts.push({
2730
+ path: cur.path,
2731
+ branch: cur.branch ?? null,
2732
+ head: (cur.head ?? "").slice(0, 7),
2733
+ main: wts.length === 0,
2734
+ dirty: -1,
2735
+ ahead: -1
2736
+ });
2737
+ }
2738
+ cur = null;
2739
+ };
2740
+ for (const line of out.split(`
2741
+ `)) {
2742
+ if (line.startsWith("worktree ")) {
2743
+ flush();
2744
+ cur = { path: line.slice(9) };
2745
+ } else if (line.startsWith("HEAD ") && cur)
2746
+ cur.head = line.slice(5);
2747
+ else if (line.startsWith("branch ") && cur)
2748
+ cur.branch = line.slice(7).replace(/^refs\/heads\//, "");
2749
+ else if (line === "")
2750
+ flush();
2751
+ }
2752
+ flush();
2753
+ return wts;
2754
+ }
2755
+ function applyStatus(w, st, ah) {
2756
+ w.dirty = st === null ? -1 : st.split(`
2757
+ `).filter(Boolean).length;
2758
+ const a = ah?.trim();
2759
+ w.ahead = a === undefined || a === "" ? -1 : Number(a);
2760
+ }
2761
+ async function gitAsync(cwd, args) {
2762
+ try {
2763
+ const p = Bun.spawn(["git", "-C", cwd, ...args], { stdout: "pipe", stderr: "ignore" });
2764
+ const [out, code] = await Promise.all([new Response(p.stdout).text(), p.exited]);
2765
+ return code === 0 ? out : null;
2766
+ } catch {
2767
+ return null;
2768
+ }
2769
+ }
2770
+ async function listWorktreesAsync(root) {
2771
+ const out = await gitAsync(root, ["worktree", "list", "--porcelain"]);
2772
+ if (!out)
2773
+ return [];
2774
+ const wts = parseWorktreeList(out);
2775
+ await Promise.all(wts.map(async (w) => {
2776
+ const [st, ah] = await Promise.all([
2777
+ gitAsync(w.path, ["status", "--porcelain", "--untracked-files=no"]),
2778
+ gitAsync(w.path, ["rev-list", "--count", "@{upstream}..HEAD"])
2779
+ ]);
2780
+ applyStatus(w, st, ah);
2781
+ }));
2782
+ return wts;
2783
+ }
2784
+ var branchCache = new Map;
2785
+ function currentBranch(cwd) {
2786
+ const hit = branchCache.get(cwd);
2787
+ const now = Date.now();
2788
+ if (hit && now - hit.t < 5000)
2789
+ return hit.v;
2790
+ const v = git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"])?.trim() ?? null;
2791
+ branchCache.set(cwd, { v: v === "HEAD" ? "(detached)" : v, t: now });
2792
+ return branchCache.get(cwd)?.v ?? null;
2793
+ }
2794
+ function worktreeAdd(repoRoot, path, branch, baseRef = "HEAD") {
2795
+ const branchExists = git(repoRoot, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]) !== null;
2796
+ const args = branchExists ? ["worktree", "add", path, branch] : ["worktree", "add", "-b", branch, path, baseRef];
2797
+ if (git(repoRoot, args) === null)
2798
+ return null;
2799
+ try {
2800
+ return realpathSync(path);
2801
+ } catch {
2802
+ return path;
2803
+ }
2804
+ }
2805
+ function worktreeRemove(repoRoot, path, force) {
2806
+ const args = ["worktree", "remove", path];
2807
+ if (force)
2808
+ args.push("--force");
2809
+ return git(repoRoot, args) !== null;
2810
+ }
2811
+ function heldWork(path) {
2812
+ const status = git(path, ["status", "--porcelain"]);
2813
+ const dirty = status !== null && status.trim().length > 0;
2814
+ const count = (args) => {
2815
+ const out = git(path, ["rev-list", "--count", ...args])?.trim();
2816
+ return out !== undefined && out !== "" ? Number(out) : 0;
2817
+ };
2818
+ let unpushed;
2819
+ if (git(path, ["rev-parse", "--verify", "--quiet", "@{upstream}"]) !== null) {
2820
+ unpushed = count(["@{upstream}..HEAD"]) > 0;
2821
+ } else {
2822
+ const baselines = ["--remotes"];
2823
+ for (const b of ["main", "master"]) {
2824
+ if (git(path, ["rev-parse", "--verify", "--quiet", `refs/heads/${b}`]) !== null)
2825
+ baselines.push(b);
2826
+ }
2827
+ unpushed = baselines.length > 1 ? count(["HEAD", "--not", ...baselines]) > 0 : false;
2828
+ }
2829
+ return { dirty, unpushed };
2830
+ }
2831
+
2832
+ // packages/daemon/src/store.ts
2833
+ var SCHEMA = `
2834
+ CREATE TABLE IF NOT EXISTS projects (id TEXT PRIMARY KEY, root TEXT, common_dir TEXT, name TEXT, discovered INTEGER, created_at TEXT);
2835
+ CREATE TABLE IF NOT EXISTS sessions (
2836
+ id TEXT PRIMARY KEY, project_id TEXT, kind TEXT, parent_id TEXT, cwd TEXT, branch TEXT, transcript_path TEXT,
2837
+ title TEXT, model TEXT, version TEXT, started_at TEXT, ended_at TEXT, last_seen_at TEXT,
2838
+ last TEXT, last_type TEXT, last_text TEXT, state TEXT, tool_calls INTEGER DEFAULT 0, subagents INTEGER DEFAULT 0,
2839
+ tool_counts TEXT DEFAULT '{}'
2840
+ );
2841
+ CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT, type TEXT, project_id TEXT, session_id TEXT, payload TEXT, raw TEXT);
2842
+ CREATE INDEX IF NOT EXISTS events_session ON events(session_id, seq);
2843
+ CREATE INDEX IF NOT EXISTS events_type_seq ON events(type, seq);
2844
+ CREATE TABLE IF NOT EXISTS turns (
2845
+ id TEXT PRIMARY KEY, session_id TEXT, agent_id TEXT, ts TEXT, model TEXT, effort TEXT, sidechain INTEGER,
2846
+ input INTEGER, output INTEGER, cache_write INTEGER, cache_write_1h INTEGER, cache_read INTEGER, thinking INTEGER,
2847
+ cost_usd REAL, text TEXT, tools TEXT
2848
+ );
2849
+ CREATE INDEX IF NOT EXISTS turns_session ON turns(session_id, ts);
2850
+ CREATE INDEX IF NOT EXISTS turns_ts ON turns(ts);
2851
+ CREATE TABLE IF NOT EXISTS tails (path TEXT PRIMARY KEY, session_id TEXT, agent_id TEXT, offset INTEGER);
2852
+ CREATE TABLE IF NOT EXISTS resources (
2853
+ name TEXT, project_id TEXT, kind TEXT, owner TEXT, session_id TEXT,
2854
+ pid INTEGER, port INTEGER, acquired_at TEXT, expires_at TEXT, released INTEGER DEFAULT 0,
2855
+ PRIMARY KEY (name, project_id)
2856
+ );
2857
+ CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
2858
+ CREATE TABLE IF NOT EXISTS incident_acks (seq INTEGER PRIMARY KEY, acked_at TEXT);
2859
+ CREATE TABLE IF NOT EXISTS processes (
2860
+ pid INTEGER, start_time TEXT, project_id TEXT, session_id TEXT, kind TEXT, name TEXT, port INTEGER,
2861
+ cwd TEXT, cmd TEXT, owner TEXT, log TEXT, started_at TEXT, ended_at TEXT
2862
+ );
2863
+ CREATE INDEX IF NOT EXISTS processes_live ON processes(ended_at, project_id);
2864
+ CREATE TABLE IF NOT EXISTS claims (
2865
+ project_id TEXT, task TEXT, owner TEXT, worktree TEXT, branch TEXT,
2866
+ acquired_at TEXT, expires_at TEXT, released_at TEXT, state TEXT,
2867
+ PRIMARY KEY (project_id, task)
2868
+ );
2869
+ `;
2870
+ var IDLE_MS = 10 * 60000;
2871
+
2872
+ class Store {
2873
+ db;
2874
+ prices = { ...PRICES };
2875
+ home;
2876
+ listeners = new Set;
2877
+ wtCache = new Map;
2878
+ wtInflight = new Map;
2879
+ cwdProject = new Map;
2880
+ lastTail = new Map;
2881
+ gen = 0;
2882
+ memo = new Map;
2883
+ constructor(home = swarmHome()) {
2884
+ mkdirSync2(home, { recursive: true });
2885
+ this.home = home;
2886
+ this.db = new Database(join5(home, "swarm.db"));
2887
+ this.loadPricing();
2888
+ this.db.exec("PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA mmap_size=268435456; PRAGMA cache_size=-32000;");
2889
+ this.db.exec(SCHEMA);
2890
+ this.ensureColumn("sessions", "agent", "TEXT DEFAULT 'claude-code'");
2891
+ this.ensureColumn("projects", "sort_order", "INTEGER");
2892
+ this.migrateProjectsJson(join5(home, "projects.json"));
2893
+ this.reconcileMovedProjects();
2894
+ this.slimExistingEvents();
2895
+ this.retypeNotificationIncidents();
2896
+ }
2897
+ retypeNotificationIncidents() {
2898
+ if (this.meta("notifications_retyped") === "1")
2899
+ return;
2900
+ this.db.exec(`UPDATE events SET type = 'session.notification' WHERE type = 'incident.opened' AND payload NOT LIKE '%"rule"%'`);
2901
+ this.setMeta("notifications_retyped", "1");
2902
+ }
2903
+ meta(key) {
2904
+ const r = this.db.query("SELECT value FROM meta WHERE key = ?").get(key);
2905
+ return r?.value ?? null;
2906
+ }
2907
+ setMeta(key, value) {
2908
+ this.db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
2909
+ }
2910
+ slimExistingEvents() {
2911
+ if (this.meta("events_slim") === "1")
2912
+ return;
2913
+ const rows = this.db.query("SELECT seq, payload, raw FROM events WHERE length(payload) > ? OR length(raw) > ?").all(TOOL_RESPONSE_MAX + TOOL_INPUT_MAX, TOOL_INPUT_MAX);
2914
+ const upd = this.db.query("UPDATE events SET payload = ?, raw = ? WHERE seq = ?");
2915
+ this.db.transaction(() => {
2916
+ for (const r of rows) {
2917
+ let payload;
2918
+ let raw2;
2919
+ try {
2920
+ payload = JSON.parse(r.payload);
2921
+ raw2 = r.raw ? JSON.parse(r.raw) : undefined;
2922
+ } catch {
2923
+ continue;
2924
+ }
2925
+ const slim = slimForStorage({ payload, raw: raw2 });
2926
+ upd.run(JSON.stringify(slim.payload ?? null), slim.raw === undefined ? null : JSON.stringify(slim.raw), r.seq);
2927
+ }
2928
+ this.setMeta("events_slim", "1");
2929
+ })();
2930
+ if (rows.length) {
2931
+ try {
2932
+ this.db.exec("VACUUM");
2933
+ } catch {}
2934
+ }
2935
+ }
2936
+ reconcileMovedProjects() {
2937
+ const all = this.projects();
2938
+ for (const stale of all) {
2939
+ if (existsSync4(stale.root))
2940
+ continue;
2941
+ const live = all.filter((p) => p.id !== stale.id && p.name === stale.name && existsSync4(p.root));
2942
+ if (live.length !== 1)
2943
+ continue;
2944
+ this.mergeProject(stale.id, live[0].id);
2945
+ }
2946
+ }
2947
+ mergeProject(from, into) {
2948
+ const src = this.project(from);
2949
+ const dst = this.project(into);
2950
+ if (!src || !dst || from === into)
2951
+ return false;
2952
+ this.db.transaction(() => {
2953
+ for (const t of ["sessions", "events"])
2954
+ this.db.query(`UPDATE ${t} SET project_id = ? WHERE project_id = ?`).run(into, from);
2955
+ this.db.query("UPDATE OR IGNORE resources SET project_id = ? WHERE project_id = ?").run(into, from);
2956
+ this.db.query("UPDATE OR IGNORE claims SET project_id = ? WHERE project_id = ?").run(into, from);
2957
+ this.db.query("DELETE FROM resources WHERE project_id = ?").run(from);
2958
+ this.db.query("DELETE FROM claims WHERE project_id = ?").run(from);
2959
+ if (!src.discovered)
2960
+ this.db.query("UPDATE projects SET discovered = 0, name = ? WHERE id = ?").run(src.name, into);
2961
+ this.db.query("DELETE FROM projects WHERE id = ?").run(from);
2962
+ })();
2963
+ this.cwdProject.clear();
2964
+ this.touch();
2965
+ return true;
2966
+ }
2967
+ ensureColumn(table, col, decl) {
2968
+ const cols = this.db.query(`PRAGMA table_info(${table})`).all();
2969
+ if (!cols.some((c) => c.name === col)) {
2970
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${col} ${decl}`);
2971
+ }
2972
+ }
2973
+ migrateProjectsJson(file) {
2974
+ if (!existsSync4(file))
2975
+ return;
2976
+ try {
2977
+ const list = JSON.parse(readFileSync3(file, "utf8"));
2978
+ const ins = this.db.query("INSERT OR IGNORE INTO projects (id, root, common_dir, name, discovered, created_at) VALUES (?, ?, ?, ?, ?, ?)");
2979
+ for (const p of list)
2980
+ ins.run(p.id, p.root, p.commonDir, p.name, p.discovered ? 1 : 0, p.createdAt);
2981
+ renameSync(file, `${file}.migrated`);
2982
+ } catch {}
2983
+ }
2984
+ topCache = new Map;
2985
+ toplevel(cwd) {
2986
+ const hit = this.topCache.get(cwd);
2987
+ if (hit && Date.now() - hit.t < 1e4)
2988
+ return hit.v;
2989
+ const v = cwd && existsSync4(cwd) ? gitToplevel(cwd) : null;
2990
+ this.topCache.set(cwd, { v, t: Date.now() });
2991
+ return v;
2992
+ }
2993
+ rulesCache = new Map;
2994
+ taskCache = new Map;
2995
+ tasks(projectId) {
2996
+ const p = this.project(projectId);
2997
+ if (!p)
2998
+ return null;
2999
+ const source = loadConfig({ repoRoot: p.root }).tasks.source;
3000
+ if (!source)
3001
+ return null;
3002
+ const path = join5(p.root, source);
3003
+ if (!existsSync4(path))
3004
+ return { source, tasks: [] };
3005
+ const mtime = statSync(path).mtimeMs;
3006
+ let hit = this.taskCache.get(projectId);
3007
+ if (!hit || hit.path !== path || hit.mtime !== mtime) {
3008
+ hit = { path, mtime, tasks: parseMarkdownTasks(readFileSync3(path, "utf8")) };
3009
+ this.taskCache.set(projectId, hit);
3010
+ }
3011
+ const now = Date.now();
3012
+ const active = this.claimRows(projectId).filter((c) => isActive(c, now));
3013
+ return { source, tasks: taskBoard(hit.tasks, active) };
3014
+ }
3015
+ rulesFor(repoRoot) {
3016
+ const key = repoRoot ?? "";
3017
+ const hit = this.rulesCache.get(key);
3018
+ if (hit && Date.now() - hit.at < 30000)
3019
+ return hit.rules;
3020
+ const rules2 = loadConfig({ repoRoot }).rules;
3021
+ this.rulesCache.set(key, { at: Date.now(), rules: rules2 });
3022
+ return rules2;
3023
+ }
3024
+ guardHook(raw2) {
3025
+ const tool = typeof raw2.tool_name === "string" ? raw2.tool_name : "";
3026
+ const input = raw2.tool_input ?? {};
3027
+ const id = typeof raw2.session_id === "string" ? raw2.session_id : "";
3028
+ const cwd = typeof raw2.cwd === "string" ? raw2.cwd : "";
3029
+ const isWrite = WRITE_TOOLS.has(tool) && typeof input.file_path === "string";
3030
+ const cmd = tool === "Bash" ? input.command : undefined;
3031
+ if (!isWrite && !cmd)
3032
+ return null;
3033
+ const current = { id, cwd, toplevel: this.toplevel(cwd) };
3034
+ const modes = this.rulesFor(current.toplevel);
3035
+ if (modes.no_foreign_worktree !== "off" || modes.claim_required_to_write !== "off") {
3036
+ const target = isWrite ? absolutePath(input.file_path, cwd) : cwd;
3037
+ const w = guardWrite(target, current, this.heldWorktrees(), modes, isWrite ? "file" : "bash");
3038
+ if (w.action !== "allow")
3039
+ return this.openIncident(w, cwd, id, isWrite ? `${tool} ${target}` : cmd);
3040
+ }
3041
+ if (!cmd)
3042
+ return null;
3043
+ const rows = this.db.query("SELECT id, cwd, last_seen_at, state FROM sessions WHERE state != 'ended' AND last_seen_at > ?").all(new Date(Date.now() - LIVE_WINDOW_MS - 1e4).toISOString());
3044
+ const sessions = rows.map((r) => ({
3045
+ id: r.id,
3046
+ toplevel: this.toplevel(r.cwd),
3047
+ lastSeenAt: r.last_seen_at,
3048
+ state: r.state
3049
+ }));
3050
+ const d = guardBash(cmd, current, sessions, Date.now(), {
3051
+ ...modes,
3052
+ protected: { ports: [...new Set([...modes.protected.ports, ...this.heldPorts()])] }
3053
+ });
3054
+ if (d.action === "allow")
3055
+ return null;
3056
+ return this.openIncident(d, cwd, id, cmd);
3057
+ }
3058
+ openIncident(d, cwd, sessionId, command) {
3059
+ const project = cwd && existsSync4(cwd) ? this.resolveProject(cwd) : null;
3060
+ this.append({
3061
+ ts: new Date().toISOString(),
3062
+ type: "incident.opened",
3063
+ projectId: project?.id ?? "p_unknown",
3064
+ sessionId: sessionId || null,
3065
+ payload: { rule: d.rule, action: d.action, command: command.slice(0, 400), reason: d.reason }
3066
+ });
3067
+ return d;
3068
+ }
3069
+ heldWorktreesCache = null;
3070
+ heldWorktrees() {
3071
+ if (this.heldWorktreesCache && Date.now() - this.heldWorktreesCache.at < 2000)
3072
+ return this.heldWorktreesCache.v;
3073
+ const v = this.db.query("SELECT task, owner, worktree FROM claims WHERE state = 'held' AND expires_at > ?").all(new Date().toISOString()).filter((c) => c.worktree);
3074
+ this.heldWorktreesCache = { at: Date.now(), v };
3075
+ return v;
3076
+ }
3077
+ loadPricing() {
3078
+ this.prices = { ...PRICES };
3079
+ for (const f of ["pricing.litellm.json", "pricing.json"]) {
3080
+ const p = join5(this.home, f);
3081
+ if (!existsSync4(p))
3082
+ continue;
3083
+ try {
3084
+ const j = JSON.parse(readFileSync3(p, "utf8"));
3085
+ const table = f === "pricing.json" ? j : fromLiteLLM(j);
3086
+ Object.assign(this.prices, table);
3087
+ } catch {}
3088
+ }
3089
+ }
3090
+ async refreshPricing(url = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json") {
3091
+ const r = await fetch(url, { signal: AbortSignal.timeout(1e4) });
3092
+ if (!r.ok)
3093
+ throw new Error(`pricing fetch ${r.status}`);
3094
+ const j = await r.json();
3095
+ const slim = Object.fromEntries(Object.entries(j).filter(([k, v]) => typeof v.input_cost_per_token === "number" && !k.includes("/")));
3096
+ writeFileSync2(join5(this.home, "pricing.litellm.json"), JSON.stringify(slim, null, 1));
3097
+ this.loadPricing();
3098
+ this.reprice();
3099
+ }
3100
+ reprice() {
3101
+ const rows = this.db.query("SELECT id, model, input, output, cache_write, cache_write_1h, cache_read FROM turns").all();
3102
+ const up = this.db.query("UPDATE turns SET cost_usd = ? WHERE id = ?");
3103
+ const tx = this.db.transaction(() => {
3104
+ for (const r of rows)
3105
+ up.run(costUsd(r.model, {
3106
+ input: r.input,
3107
+ output: r.output,
3108
+ cacheWrite: r.cache_write,
3109
+ cacheWrite1h: r.cache_write_1h,
3110
+ cacheRead: r.cache_read
3111
+ }, this.prices), r.id);
3112
+ });
3113
+ tx();
3114
+ }
3115
+ projects() {
3116
+ return this.db.query("SELECT * FROM projects ORDER BY discovered, sort_order IS NULL, sort_order, name COLLATE NOCASE").all().map((r) => ({
3117
+ id: r.id,
3118
+ root: r.root,
3119
+ commonDir: r.common_dir ?? null,
3120
+ name: r.name,
3121
+ discovered: Boolean(r.discovered),
3122
+ order: typeof r.sort_order === "number" ? r.sort_order : null,
3123
+ createdAt: r.created_at
3124
+ }));
3125
+ }
3126
+ project(id) {
3127
+ const r = this.db.query("SELECT * FROM projects WHERE id = ?").get(id);
3128
+ if (!r)
3129
+ return;
3130
+ return {
3131
+ id: r.id,
3132
+ root: r.root,
3133
+ commonDir: r.common_dir ?? null,
3134
+ name: r.name,
3135
+ discovered: Boolean(r.discovered),
3136
+ order: typeof r.sort_order === "number" ? r.sort_order : null,
3137
+ createdAt: r.created_at
3138
+ };
3139
+ }
3140
+ memoised(key, ttlMs, compute) {
3141
+ const hit = this.memo.get(key);
3142
+ const now = Date.now();
3143
+ if (hit && hit.gen === this.gen && now - hit.t < ttlMs)
3144
+ return hit.v;
3145
+ const v = compute();
3146
+ this.memo.set(key, { gen: this.gen, t: now, v });
3147
+ return v;
3148
+ }
3149
+ touch() {
3150
+ this.gen++;
3151
+ }
3152
+ resolveProject(path, explicit = false, name) {
3153
+ if (!explicit) {
3154
+ const hit = this.cwdProject.get(path);
3155
+ if (hit && Date.now() - hit.t < 60000) {
3156
+ const p2 = this.project(hit.id);
3157
+ if (p2)
3158
+ return p2;
3159
+ }
3160
+ }
3161
+ const p = this.resolveProjectUncached(path, explicit, name);
3162
+ this.cwdProject.set(path, { id: p.id, t: Date.now() });
3163
+ return p;
3164
+ }
3165
+ resolveProjectUncached(path, explicit, name) {
3166
+ const root = gitToplevel(path) ?? realpathSync2(path);
3167
+ const ident = projectIdentity({ root, commonDir: gitCommonDir(root) });
3168
+ const existing = this.project(ident.id);
3169
+ if (!existing) {
3170
+ const p = {
3171
+ ...ident,
3172
+ discovered: !explicit,
3173
+ order: null,
3174
+ createdAt: new Date().toISOString()
3175
+ };
3176
+ if (name)
3177
+ p.name = name;
3178
+ this.db.query("INSERT INTO projects (id, root, common_dir, name, discovered, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(p.id, p.root, p.commonDir, p.name, p.discovered ? 1 : 0, p.createdAt);
3179
+ this.reconcileMovedProjects();
3180
+ return this.project(p.id) ?? p;
3181
+ }
3182
+ if (explicit) {
3183
+ this.db.query("UPDATE projects SET discovered = 0, name = ? WHERE id = ?").run(name ?? existing.name, existing.id);
3184
+ return { ...existing, discovered: false, name: name ?? existing.name };
3185
+ }
3186
+ return existing;
3187
+ }
3188
+ updateProject(id, patch) {
3189
+ const cur = this.project(id);
3190
+ if (!cur)
3191
+ return;
3192
+ if (patch.pinned !== undefined)
3193
+ this.db.query("UPDATE projects SET discovered = ? WHERE id = ?").run(patch.pinned ? 0 : 1, id);
3194
+ if (patch.name)
3195
+ this.db.query("UPDATE projects SET name = ? WHERE id = ?").run(patch.name, id);
3196
+ return this.project(id);
3197
+ }
3198
+ reorderProjects(ids) {
3199
+ const upd = this.db.query("UPDATE projects SET sort_order = ? WHERE id = ?");
3200
+ this.db.transaction(() => {
3201
+ ids.forEach((id, i) => {
3202
+ upd.run(i, id);
3203
+ });
3204
+ })();
3205
+ this.touch();
3206
+ return this.projects();
3207
+ }
3208
+ removeProject(id) {
3209
+ this.cwdProject.clear();
3210
+ this.wtCache.delete(id);
3211
+ this.touch();
3212
+ return this.db.query("DELETE FROM projects WHERE id = ?").run(id).changes > 0;
3213
+ }
3214
+ append(e) {
3215
+ const slim = slimForStorage(e);
3216
+ const r = this.db.query("INSERT INTO events (ts, type, project_id, session_id, payload, raw) VALUES (?, ?, ?, ?, ?, ?)").run(e.ts, e.type, e.projectId, e.sessionId, JSON.stringify(slim.payload ?? null), slim.raw === undefined ? null : JSON.stringify(slim.raw));
3217
+ const stored = { ...e, seq: Number(r.lastInsertRowid) };
3218
+ this.projectSession(stored);
3219
+ this.touch();
3220
+ const wire = toWire(stored);
3221
+ for (const l of this.listeners)
3222
+ l(wire);
3223
+ return stored;
3224
+ }
3225
+ prune(days = 30) {
3226
+ const cutoff = new Date(Date.now() - days * 86400000).toISOString();
3227
+ const n = this.db.query("DELETE FROM events WHERE ts < ? AND type != 'incident.opened'").run(cutoff).changes;
3228
+ const old = new Date(Date.now() - 7 * 86400000).toISOString();
3229
+ this.db.query("UPDATE events SET raw = NULL WHERE ts < ? AND raw IS NOT NULL").run(old);
3230
+ if (n > 0)
3231
+ this.touch();
3232
+ return n;
3233
+ }
3234
+ ingestHook(event, raw2) {
3235
+ const cwd = typeof raw2.cwd === "string" ? raw2.cwd : process.cwd();
3236
+ const project = existsSync4(cwd) ? this.resolveProject(cwd) : null;
3237
+ const e = this.append(normalizeHook(event, raw2, project?.id ?? "p_unknown"));
3238
+ if (e.sessionId && typeof raw2.transcript_path === "string") {
3239
+ this.db.query("UPDATE sessions SET transcript_path = ? WHERE id = ? AND transcript_path IS NULL").run(raw2.transcript_path, e.sessionId);
3240
+ const last = this.lastTail.get(e.sessionId) ?? 0;
3241
+ if (Date.now() - last > 2000) {
3242
+ this.lastTail.set(e.sessionId, Date.now());
3243
+ this.tailSession(e.sessionId);
3244
+ }
3245
+ }
3246
+ return e;
3247
+ }
3248
+ projectSession(e) {
3249
+ if (!e.sessionId)
3250
+ return;
3251
+ const p = e.payload;
3252
+ const row = this.db.query("SELECT id, tool_counts FROM sessions WHERE id = ?").get(e.sessionId);
3253
+ const branch = p.cwd && existsSync4(p.cwd) ? currentBranch(p.cwd) : null;
3254
+ if (!row) {
3255
+ this.db.query("INSERT INTO sessions (id, project_id, kind, cwd, branch, started_at, last_seen_at, last, last_type, state) VALUES (?, ?, 'interactive', ?, ?, ?, ?, ?, ?, 'active')").run(e.sessionId, e.projectId, p.cwd ?? "", branch, e.ts, e.ts, p.summary ?? e.type, e.type);
3256
+ }
3257
+ const counts = JSON.parse(row?.tool_counts ?? "{}");
3258
+ if (e.type === "tool.requested" && p.tool)
3259
+ counts[p.tool] = (counts[p.tool] ?? 0) + 1;
3260
+ const state = e.type === "session.ended" ? "ended" : p.hook === "Stop" || e.type === "session.notification" ? "waiting" : "active";
3261
+ this.db.query(`UPDATE sessions SET last_seen_at = ?, last = ?, last_type = ?, state = ?, tool_counts = ?,
3262
+ tool_calls = tool_calls + ?, subagents = MAX(0, subagents + ?),
3263
+ project_id = CASE WHEN ? != 'p_unknown' THEN ? ELSE project_id END,
3264
+ cwd = COALESCE(?, cwd), branch = COALESCE(?, branch),
3265
+ ended_at = CASE WHEN ? = 'session.ended' THEN ? ELSE ended_at END
3266
+ WHERE id = ?`).run(e.ts, p.summary ?? e.type, e.type, state, JSON.stringify(counts), e.type === "tool.requested" ? 1 : 0, e.type === "subagent.started" ? 1 : e.type === "subagent.stopped" ? -1 : 0, e.projectId, e.projectId, p.cwd ?? null, branch, e.type, e.ts, e.sessionId);
3267
+ }
3268
+ readFrom(path, offset) {
3269
+ try {
3270
+ const size = statSync(path).size;
3271
+ if (size <= offset)
3272
+ return null;
3273
+ const fd = openSync(path, "r");
3274
+ const buf = Buffer.alloc(size - offset);
3275
+ readSync(fd, buf, 0, buf.length, offset);
3276
+ closeSync(fd);
3277
+ const text = buf.toString("utf8");
3278
+ const cut = text.lastIndexOf(`
3279
+ `);
3280
+ if (cut < 0)
3281
+ return null;
3282
+ return {
3283
+ chunk: text.slice(0, cut + 1),
3284
+ next: offset + Buffer.byteLength(text.slice(0, cut + 1))
3285
+ };
3286
+ } catch {
3287
+ return null;
3288
+ }
3289
+ }
3290
+ persistTurns(sessionId, agentId, turns) {
3291
+ const up = this.db.query(`INSERT INTO turns (id, session_id, agent_id, ts, model, effort, sidechain, input, output, cache_write, cache_write_1h, cache_read, thinking, cost_usd, text, tools)
3292
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3293
+ ON CONFLICT(id) DO UPDATE SET input=excluded.input, output=excluded.output, cache_write=excluded.cache_write, cache_write_1h=excluded.cache_write_1h,
3294
+ cache_read=excluded.cache_read, thinking=excluded.thinking, cost_usd=excluded.cost_usd, text=CASE WHEN excluded.text != '' THEN excluded.text ELSE turns.text END, tools=excluded.tools`);
3295
+ const tx = this.db.transaction((ts) => {
3296
+ for (const t of ts) {
3297
+ up.run(t.id, sessionId, agentId, t.ts, t.model, t.effort, t.sidechain ? 1 : 0, t.usage.input, t.usage.output, t.usage.cacheWrite, t.usage.cacheWrite1h ?? 0, t.usage.cacheRead, t.usage.thinking, costUsd(t.model, t.usage, this.prices), t.text, JSON.stringify(t.tools));
3298
+ }
3299
+ });
3300
+ if (turns.length)
3301
+ this.touch();
3302
+ tx(turns);
3303
+ }
3304
+ tailFile(path, sessionId, agentId) {
3305
+ const row = this.db.query("SELECT offset FROM tails WHERE path = ?").get(path);
3306
+ const r = this.readFrom(path, row?.offset ?? 0);
3307
+ if (!r)
3308
+ return 0;
3309
+ const d = parseTranscriptChunk(r.chunk);
3310
+ this.persistTurns(sessionId, agentId, d.turns);
3311
+ const lastText = [...d.turns].reverse().find((t) => t.text && !t.sidechain)?.text ?? null;
3312
+ const lastModel = [...d.turns].reverse().find((t) => !t.sidechain)?.model ?? null;
3313
+ this.db.query("UPDATE sessions SET title = COALESCE(?, title), model = COALESCE(?, model), version = COALESCE(?, version), last_text = COALESCE(?, last_text), branch = COALESCE(branch, ?), last_seen_at = MAX(COALESCE(last_seen_at, ''), ?) WHERE id = ?").run(d.title, agentId ? null : lastModel, d.version, agentId ? null : lastText, d.branch, new Date().toISOString(), sessionId);
3314
+ this.db.query("INSERT INTO tails (path, session_id, agent_id, offset) VALUES (?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET offset = excluded.offset").run(path, sessionId, agentId, r.next);
3315
+ return d.turns.length;
3316
+ }
3317
+ tailSession(sessionId) {
3318
+ const s = this.db.query("SELECT transcript_path FROM sessions WHERE id = ?").get(sessionId);
3319
+ if (!s?.transcript_path || !existsSync4(s.transcript_path))
3320
+ return 0;
3321
+ let n = this.tailFile(s.transcript_path, sessionId, null);
3322
+ const subDir = join5(dirname(s.transcript_path), basename(s.transcript_path, ".jsonl"), "subagents");
3323
+ for (const f of this.subagentFiles(subDir)) {
3324
+ n += this.tailFile(join5(subDir, f), sessionId, f.replace(/^agent-|\.jsonl$/g, ""));
3325
+ }
3326
+ return n;
3327
+ }
3328
+ subDirCache = new Map;
3329
+ subagentFiles(dir) {
3330
+ let mtime;
3331
+ try {
3332
+ mtime = statSync(dir).mtimeMs;
3333
+ } catch {
3334
+ return [];
3335
+ }
3336
+ const hit = this.subDirCache.get(dir);
3337
+ if (hit && hit.mtime === mtime)
3338
+ return hit.files;
3339
+ const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
3340
+ this.subDirCache.set(dir, { mtime, files });
3341
+ return files;
3342
+ }
3343
+ hasActiveSessions() {
3344
+ const since = new Date(Date.now() - IDLE_MS).toISOString();
3345
+ return this.db.query("SELECT 1 AS x FROM sessions WHERE state != 'ended' AND last_seen_at > ? LIMIT 1").get(since) !== null;
3346
+ }
3347
+ tailActive() {
3348
+ const since = new Date(Date.now() - IDLE_MS).toISOString();
3349
+ const ids = this.db.query("SELECT id FROM sessions WHERE state != 'ended' AND last_seen_at > ? AND transcript_path IS NOT NULL").all(since);
3350
+ let n = 0;
3351
+ for (const { id } of ids)
3352
+ n += this.tailSession(id);
3353
+ return n;
3354
+ }
3355
+ codexRoot() {
3356
+ return process.env.SWARM_CODEX_DIR ?? join5(homedir3(), ".codex", "sessions");
3357
+ }
3358
+ codexRolloutFiles(sinceMs) {
3359
+ const root = this.codexRoot();
3360
+ const out = [];
3361
+ const ls = (p) => {
3362
+ try {
3363
+ return readdirSync(p);
3364
+ } catch {
3365
+ return [];
3366
+ }
3367
+ };
3368
+ for (const y of ls(root)) {
3369
+ if (!/^\d{4}$/.test(y))
3370
+ continue;
3371
+ for (const m of ls(join5(root, y))) {
3372
+ if (!/^\d\d$/.test(m))
3373
+ continue;
3374
+ for (const day of ls(join5(root, y, m))) {
3375
+ if (!/^\d\d$/.test(day))
3376
+ continue;
3377
+ if (Date.parse(`${y}-${m}-${day}T23:59:59Z`) < sinceMs)
3378
+ continue;
3379
+ const dir = join5(root, y, m, day);
3380
+ for (const f of ls(dir)) {
3381
+ if (f.startsWith("rollout-") && f.endsWith(".jsonl"))
3382
+ out.push(join5(dir, f));
3383
+ }
3384
+ }
3385
+ }
3386
+ }
3387
+ return out;
3388
+ }
3389
+ tailCodex(windowMs = 3 * 24 * 60 * 60000) {
3390
+ if (!existsSync4(this.codexRoot()))
3391
+ return 0;
3392
+ let n = 0;
3393
+ for (const path of this.codexRolloutFiles(Date.now() - windowMs)) {
3394
+ n += this.ingestLog(path, "codex", parseCodexRollout);
3395
+ }
3396
+ return n;
3397
+ }
3398
+ grokRoot() {
3399
+ return process.env.SWARM_GROK_DIR ?? join5(homedir3(), ".grok", "sessions");
3400
+ }
3401
+ grokSummary = new Map;
3402
+ tailGrok(windowMs = 3 * 24 * 60 * 60000) {
3403
+ const root = this.grokRoot();
3404
+ if (!existsSync4(root))
3405
+ return 0;
3406
+ const since = Date.now() - windowMs;
3407
+ const ls = (p) => {
3408
+ try {
3409
+ return readdirSync(p);
3410
+ } catch {
3411
+ return [];
3412
+ }
3413
+ };
3414
+ let n = 0;
3415
+ for (const enc of ls(root)) {
3416
+ if (!enc.includes("%2F") && !enc.startsWith("/"))
3417
+ continue;
3418
+ let cwd = "";
3419
+ try {
3420
+ cwd = decodeURIComponent(enc);
3421
+ } catch {
3422
+ cwd = enc;
3423
+ }
3424
+ const cwdDir = join5(root, enc);
3425
+ for (const sid of ls(cwdDir)) {
3426
+ const path = join5(cwdDir, sid, "updates.jsonl");
3427
+ if (!existsSync4(path))
3428
+ continue;
3429
+ try {
3430
+ if (statSync(path).mtimeMs < since)
3431
+ continue;
3432
+ } catch {
3433
+ continue;
3434
+ }
3435
+ const sumPath = join5(cwdDir, sid, "summary.json");
3436
+ let title;
3437
+ let fresh = false;
3438
+ try {
3439
+ const m = statSync(sumPath).mtimeMs;
3440
+ const hit = this.grokSummary.get(sumPath);
3441
+ if (hit && hit.mtime === m)
3442
+ title = hit.title;
3443
+ else {
3444
+ const sum = JSON.parse(readFileSync3(sumPath, "utf8"));
3445
+ title = sum.session_summary;
3446
+ this.grokSummary.set(sumPath, { mtime: m, title });
3447
+ fresh = true;
3448
+ }
3449
+ } catch {}
3450
+ n += this.ingestLog(path, "grok", parseGrokUpdates, cwd, title);
3451
+ if (title && fresh) {
3452
+ this.db.query("UPDATE sessions SET title = ? WHERE id = ? AND (title IS NULL OR title = '')").run(title, sid);
3453
+ }
3454
+ }
3455
+ }
3456
+ return n;
3457
+ }
3458
+ ingestLog(path, agent, parse, cwdHint, titleHint) {
3459
+ const off = this.db.query("SELECT offset FROM tails WHERE path = ?").get(path) ?? { offset: 0 };
3460
+ const r = this.readFrom(path, off.offset);
3461
+ if (!r)
3462
+ return 0;
3463
+ const d = parse(r.chunk);
3464
+ const sid = d.sessionId;
3465
+ if (!sid)
3466
+ return 0;
3467
+ const mtime = (() => {
3468
+ try {
3469
+ return statSync(path).mtimeMs;
3470
+ } catch {
3471
+ return Date.now();
3472
+ }
3473
+ })();
3474
+ this.ensureAgentSession(sid, agent, d.cwd ?? cwdHint ?? "", mtime);
3475
+ this.persistTurns(sid, null, d.turns);
3476
+ const lastText = [...d.turns].reverse().find((t) => t.text)?.text ?? null;
3477
+ const state = Date.now() - mtime < 90000 ? "active" : "ended";
3478
+ const lastSeen = new Date(mtime).toISOString();
3479
+ this.db.query("UPDATE sessions SET title = COALESCE(title, ?), model = COALESCE(?, model), last_text = COALESCE(?, last_text), last_seen_at = ?, state = ?, ended_at = CASE WHEN ? = 'ended' AND ended_at IS NULL THEN ? ELSE ended_at END WHERE id = ?").run(d.title ?? titleHint ?? null, d.model ?? null, lastText, lastSeen, state, state, lastSeen, sid);
3480
+ this.db.query("INSERT INTO tails (path, session_id, agent_id, offset) VALUES (?, ?, NULL, ?) ON CONFLICT(path) DO UPDATE SET offset = excluded.offset").run(path, sid, r.next);
3481
+ return d.turns.length;
3482
+ }
3483
+ ensureAgentSession(sid, agent, cwd, mtime) {
3484
+ if (this.db.query("SELECT 1 FROM sessions WHERE id = ?").get(sid))
3485
+ return;
3486
+ const project = cwd && existsSync4(cwd) ? this.resolveProject(cwd) : null;
3487
+ const ts = new Date(mtime).toISOString();
3488
+ this.db.query("INSERT INTO sessions (id, project_id, kind, agent, cwd, branch, started_at, last_seen_at, last, last_type, state) VALUES (?, ?, 'interactive', ?, ?, ?, ?, ?, '', '', 'active')").run(sid, project?.id ?? "p_unknown", agent, cwd, cwd && existsSync4(cwd) ? currentBranch(cwd) : null, ts, ts);
3489
+ }
3490
+ claimRows(projectId) {
3491
+ return this.db.query("SELECT * FROM claims WHERE project_id = ?").all(projectId).map((r) => ({
3492
+ task: r.task,
3493
+ owner: r.owner ?? "",
3494
+ worktree: r.worktree ?? "",
3495
+ branch: r.branch ?? "",
3496
+ acquiredAt: r.acquired_at,
3497
+ expiresAt: r.expires_at,
3498
+ state: r.state
3499
+ }));
3500
+ }
3501
+ claims(projectId) {
3502
+ const rows = this.db.query(projectId ? "SELECT * FROM claims WHERE project_id = ? ORDER BY acquired_at DESC" : "SELECT * FROM claims ORDER BY acquired_at DESC").all(...projectId ? [projectId] : []).map((r) => ({
3503
+ projectId: r.project_id,
3504
+ task: r.task,
3505
+ owner: r.owner ?? "",
3506
+ worktree: r.worktree ?? "",
3507
+ branch: r.branch ?? "",
3508
+ acquiredAt: r.acquired_at,
3509
+ expiresAt: r.expires_at,
3510
+ releasedAt: r.released_at ?? null,
3511
+ state: r.state
3512
+ }));
3513
+ const now = Date.now();
3514
+ for (const c of rows)
3515
+ if (c.state === "held" && new Date(c.expiresAt).getTime() < now)
3516
+ c.state = "expired";
3517
+ return rows;
3518
+ }
3519
+ worktreePath(projectId, task) {
3520
+ const slug = (x) => x.replace(/[^a-zA-Z0-9._-]+/g, "-").toLowerCase();
3521
+ const p = this.project(projectId);
3522
+ return join5(this.home, "worktrees", slug(p?.name ?? projectId), slug(task));
3523
+ }
3524
+ claim(projectId, task, owner, baseRef = "HEAD") {
3525
+ const p = this.project(projectId);
3526
+ if (!p)
3527
+ return { ok: false, error: "unknown project" };
3528
+ const now = Date.now();
3529
+ const decision = canClaim(this.claimRows(projectId), task, owner, now);
3530
+ if (!decision.ok)
3531
+ return { ok: false, error: claimRefusalMessage(decision, task) };
3532
+ const branch = `task/${task}`;
3533
+ const worktree = this.worktreePath(projectId, task);
3534
+ if (existsSync4(worktree))
3535
+ return { ok: false, error: `${worktree} already exists; release ${task} first` };
3536
+ mkdirSync2(dirname(worktree), { recursive: true });
3537
+ const created = worktreeAdd(p.root, worktree, branch, baseRef);
3538
+ if (!created)
3539
+ return { ok: false, error: `git worktree add failed for ${task}` };
3540
+ this.invalidateWorktrees(projectId);
3541
+ const expiresAt = nextExpiry(now);
3542
+ const acquiredAt = new Date(now).toISOString();
3543
+ this.db.query(`INSERT INTO claims (project_id, task, owner, worktree, branch, acquired_at, expires_at, released_at, state)
3544
+ VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 'held')
3545
+ ON CONFLICT(project_id, task) DO UPDATE SET owner=excluded.owner, worktree=excluded.worktree, branch=excluded.branch,
3546
+ acquired_at=excluded.acquired_at, expires_at=excluded.expires_at, released_at=NULL, state='held'`).run(projectId, task, owner, created, branch, acquiredAt, expiresAt);
3547
+ this.append({
3548
+ ts: acquiredAt,
3549
+ type: "claim.acquired",
3550
+ projectId,
3551
+ sessionId: null,
3552
+ payload: { task, owner, worktree: created, branch, summary: `claim ${task} by ${owner}` }
3553
+ });
3554
+ return { ok: true, task, owner, worktree: created, branch, expiresAt };
3555
+ }
3556
+ renew(projectId, task) {
3557
+ const row = this.db.query("SELECT state FROM claims WHERE project_id = ? AND task = ?").get(projectId, task);
3558
+ if (!row)
3559
+ return { ok: false, error: `no claim on ${task}` };
3560
+ const expiresAt = nextExpiry(Date.now());
3561
+ this.db.query("UPDATE claims SET expires_at = ?, state = 'held' WHERE project_id = ? AND task = ?").run(expiresAt, projectId, task);
3562
+ this.append({
3563
+ ts: new Date().toISOString(),
3564
+ type: "claim.renewed",
3565
+ projectId,
3566
+ sessionId: null,
3567
+ payload: { task, expiresAt, summary: `renew ${task}` }
3568
+ });
3569
+ return { ok: true, task, expiresAt };
3570
+ }
3571
+ release(projectId, task, force = false) {
3572
+ const p = this.project(projectId);
3573
+ const row = this.db.query("SELECT * FROM claims WHERE project_id = ? AND task = ?").get(projectId, task);
3574
+ if (!row)
3575
+ return { ok: false, error: `no claim on ${task}` };
3576
+ const worktree = row.worktree ?? "";
3577
+ if (worktree && existsSync4(worktree)) {
3578
+ const work = heldWork(worktree);
3579
+ const can = canRelease(work, force);
3580
+ if (!can.ok)
3581
+ return {
3582
+ ok: false,
3583
+ error: releaseRefusalMessage(can, worktree),
3584
+ refused: can.reason
3585
+ };
3586
+ if (p && !worktreeRemove(p.root, worktree, force))
3587
+ return { ok: false, error: `git worktree remove failed for ${worktree}` };
3588
+ this.invalidateWorktrees(projectId);
3589
+ }
3590
+ const releasedAt = new Date().toISOString();
3591
+ this.db.query("UPDATE claims SET state = 'released', released_at = ? WHERE project_id = ? AND task = ?").run(releasedAt, projectId, task);
3592
+ this.append({
3593
+ ts: releasedAt,
3594
+ type: "claim.released",
3595
+ projectId,
3596
+ sessionId: null,
3597
+ payload: { task, summary: `release ${task}` }
3598
+ });
3599
+ return { ok: true, task };
3600
+ }
3601
+ reap(projectId) {
3602
+ const scope = projectId ? [projectId] : this.projects().map((p) => p.id);
3603
+ const now = Date.now();
3604
+ const result = [];
3605
+ for (const pid of scope) {
3606
+ const p = this.project(pid);
3607
+ for (const c of this.claimRows(pid)) {
3608
+ if (c.state !== "held" && c.state !== "expired")
3609
+ continue;
3610
+ if (isActive({ ...c, state: "held" }, now))
3611
+ continue;
3612
+ const exists = c.worktree ? existsSync4(c.worktree) : false;
3613
+ const work = exists ? heldWork(c.worktree) : null;
3614
+ const action = reapAction({ ...c, state: "held" }, now, exists, work);
3615
+ if (action === "not-expired")
3616
+ continue;
3617
+ if (action === "reap") {
3618
+ if (exists && p) {
3619
+ worktreeRemove(p.root, c.worktree, false);
3620
+ this.invalidateWorktrees(pid);
3621
+ }
3622
+ this.db.query("UPDATE claims SET state = 'reaped', released_at = ? WHERE project_id = ? AND task = ?").run(new Date(now).toISOString(), pid, c.task);
3623
+ this.append({
3624
+ ts: new Date(now).toISOString(),
3625
+ type: "claim.released",
3626
+ projectId: pid,
3627
+ sessionId: null,
3628
+ payload: { task: c.task, summary: `reaped ${c.task}` }
3629
+ });
3630
+ } else {
3631
+ this.db.query("UPDATE claims SET state = 'orphaned' WHERE project_id = ? AND task = ?").run(pid, c.task);
3632
+ this.append({
3633
+ ts: new Date(now).toISOString(),
3634
+ type: "claim.orphaned",
3635
+ projectId: pid,
3636
+ sessionId: null,
3637
+ payload: {
3638
+ task: c.task,
3639
+ worktree: c.worktree,
3640
+ summary: `orphaned ${c.task} (holds work)`
3641
+ }
3642
+ });
3643
+ }
3644
+ result.push({ task: c.task, projectId: pid, action });
3645
+ }
3646
+ }
3647
+ return result;
3648
+ }
3649
+ since(seq, limit = 5000, full = false) {
3650
+ const rows = this.db.query(`SELECT ${full ? "*" : WIRE_COLS} FROM events WHERE seq > ? ORDER BY seq LIMIT ?`).all(seq, limit);
3651
+ return rows.map(full ? rowToEvent : wireRowToEvent);
3652
+ }
3653
+ sessionEvents(id, limit = 500, after = 0) {
3654
+ const rows = this.db.query(`SELECT * FROM (SELECT ${WIRE_COLS} FROM events WHERE session_id = ? AND seq > ? ORDER BY seq DESC LIMIT ?) ORDER BY seq`).all(id, after, limit);
3655
+ return rows.map(wireRowToEvent);
3656
+ }
3657
+ event(seq) {
3658
+ const r = this.db.query("SELECT * FROM events WHERE seq = ?").get(seq);
3659
+ return r ? rowToEvent(r) : null;
3660
+ }
3661
+ sessionTurns(id, limit = 500, afterTs) {
3662
+ return this.db.query("SELECT * FROM turns WHERE session_id = ? AND ts > ? ORDER BY ts DESC LIMIT ?").all(id, afterTs ?? "", limit).reverse().map((r) => ({
3663
+ id: r.id,
3664
+ agentId: r.agent_id,
3665
+ ts: r.ts,
3666
+ model: r.model,
3667
+ effort: r.effort,
3668
+ sidechain: Boolean(r.sidechain),
3669
+ input: r.input,
3670
+ output: r.output,
3671
+ cacheWrite: r.cache_write,
3672
+ cacheRead: r.cache_read,
3673
+ thinking: r.thinking,
3674
+ costUsd: r.cost_usd,
3675
+ text: r.text,
3676
+ tools: JSON.parse(r.tools || "[]")
3677
+ }));
3678
+ }
3679
+ subscribe(l) {
3680
+ this.listeners.add(l);
3681
+ return () => this.listeners.delete(l);
3682
+ }
3683
+ worktrees(projectId, ttlMs = 15000) {
3684
+ const hit = this.wtCache.get(projectId);
3685
+ if (!hit || Date.now() - hit.t >= ttlMs)
3686
+ this.refreshWorktrees(projectId);
3687
+ return hit?.v ?? [];
3688
+ }
3689
+ refreshWorktrees(projectId) {
3690
+ const inflight = this.wtInflight.get(projectId);
3691
+ if (inflight)
3692
+ return inflight;
3693
+ const p = this.project(projectId);
3694
+ if (!p)
3695
+ return Promise.resolve([]);
3696
+ const run2 = listWorktreesAsync(p.root).then((v) => {
3697
+ this.wtCache.set(projectId, { v, t: Date.now() });
3698
+ return v;
3699
+ }).finally(() => this.wtInflight.delete(projectId));
3700
+ this.wtInflight.set(projectId, run2);
3701
+ return run2;
3702
+ }
3703
+ invalidateWorktrees(projectId) {
3704
+ if (projectId)
3705
+ this.wtCache.delete(projectId);
3706
+ else
3707
+ this.wtCache.clear();
3708
+ }
3709
+ async refreshAllWorktrees() {
3710
+ await Promise.all(this.projects().map((p) => this.refreshWorktrees(p.id)));
3711
+ }
3712
+ sessions() {
3713
+ const rows = this.db.query(`SELECT s.*, COUNT(t.id) AS turns, COALESCE(SUM(t.input),0) AS input, COALESCE(SUM(t.output),0) AS output,
3714
+ COALESCE(SUM(t.cache_write),0) AS cache_write, COALESCE(SUM(t.cache_read),0) AS cache_read, COALESCE(SUM(t.thinking),0) AS thinking,
3715
+ SUM(t.cost_usd) AS cost_usd, MAX(t.cost_usd IS NULL AND t.id IS NOT NULL) AS unpriced,
3716
+ (SELECT model FROM turns lt WHERE lt.session_id = s.id AND lt.agent_id IS NULL AND lt.sidechain = 0 ORDER BY lt.ts DESC LIMIT 1) AS live_model,
3717
+ (SELECT COUNT(DISTINCT model) FROM turns lm WHERE lm.session_id = s.id AND lm.agent_id IS NULL AND lm.sidechain = 0) AS model_count
3718
+ FROM sessions s LEFT JOIN turns t ON t.session_id = s.id
3719
+ GROUP BY s.id ORDER BY s.last_seen_at DESC LIMIT 200`).all();
3720
+ const idleBefore = Date.now() - IDLE_MS;
3721
+ const sparkRows = this.db.query(`SELECT session_id, output, cost_usd FROM (
3722
+ SELECT t.session_id, t.output, t.cost_usd, t.ts,
3723
+ ROW_NUMBER() OVER (PARTITION BY t.session_id ORDER BY t.ts DESC) AS rn
3724
+ FROM turns t WHERE t.agent_id IS NULL AND t.sidechain = 0
3725
+ ) WHERE rn <= 24 ORDER BY ts`).all();
3726
+ const sparks = new Map;
3727
+ for (const x of sparkRows) {
3728
+ const a = sparks.get(x.session_id) ?? [];
3729
+ a.push([x.output, x.cost_usd]);
3730
+ sparks.set(x.session_id, a);
3731
+ }
3732
+ return rows.map((r) => {
3733
+ let state = r.state;
3734
+ if (state !== "ended" && new Date(r.last_seen_at).getTime() < idleBefore)
3735
+ state = "idle";
3736
+ return {
3737
+ id: r.id,
3738
+ projectId: r.project_id,
3739
+ kind: r.kind,
3740
+ agent: r.agent ?? "claude-code",
3741
+ parentId: r.parent_id ?? null,
3742
+ cwd: r.cwd,
3743
+ branch: r.branch ?? null,
3744
+ transcriptPath: r.transcript_path ?? null,
3745
+ title: r.title ?? null,
3746
+ model: r.live_model ?? r.model ?? null,
3747
+ models: r.model_count ?? 0,
3748
+ version: r.version ?? null,
3749
+ startedAt: r.started_at,
3750
+ endedAt: r.ended_at ?? null,
3751
+ lastSeenAt: r.last_seen_at,
3752
+ last: r.last,
3753
+ lastType: r.last_type,
3754
+ lastText: r.last_text ?? null,
3755
+ state,
3756
+ toolCalls: r.tool_calls,
3757
+ subagents: r.subagents,
3758
+ turns: r.turns,
3759
+ tokens: {
3760
+ input: r.input,
3761
+ output: r.output,
3762
+ cacheWrite: r.cache_write,
3763
+ cacheRead: r.cache_read,
3764
+ thinking: r.thinking
3765
+ },
3766
+ costUsd: r.unpriced ? null : r.cost_usd ?? 0,
3767
+ toolCounts: JSON.parse(r.tool_counts || "{}"),
3768
+ spark: sparks.get(r.id) ?? []
3769
+ };
3770
+ });
3771
+ }
3772
+ spend() {
3773
+ const dayStart = new Date;
3774
+ dayStart.setHours(0, 0, 0, 0);
3775
+ const today = dayStart.toISOString();
3776
+ const q = (where, by) => this.db.query(`SELECT ${by} AS key, SUM(t.cost_usd) AS cost, SUM(t.input + t.cache_write + t.cache_read) AS input, SUM(t.output) AS output, COUNT(*) AS turns
3777
+ FROM turns t JOIN sessions s ON s.id = t.session_id ${where} GROUP BY key`).all(today);
3778
+ const daily = this.db.query(`SELECT date(t.ts, 'localtime') AS day, s.project_id AS projectId, COALESCE(s.agent, 'claude-code') AS agent,
3779
+ SUM(t.cost_usd) AS cost, SUM(t.output) AS output, COUNT(*) AS turns
3780
+ FROM turns t JOIN sessions s ON s.id = t.session_id WHERE t.ts > date('now', '-90 days') GROUP BY day, projectId, agent ORDER BY day`).all();
3781
+ const hourly = this.db.query(`SELECT CAST(strftime('%w', t.ts, 'localtime') AS INTEGER) AS dow, CAST(strftime('%H', t.ts, 'localtime') AS INTEGER) AS hour,
3782
+ s.project_id AS projectId, SUM(t.cost_usd) AS cost, COUNT(*) AS turns
3783
+ FROM turns t JOIN sessions s ON s.id = t.session_id WHERE t.ts > date('now', '-28 days') GROUP BY dow, hour, projectId`).all();
3784
+ return {
3785
+ hourly,
3786
+ byProjectToday: q("WHERE t.ts >= ?", "s.project_id"),
3787
+ byProjectAll: q("WHERE ? IS NOT NULL", "s.project_id"),
3788
+ byModelToday: q("WHERE t.ts >= ?", "t.model"),
3789
+ byModelAll: q("WHERE ? IS NOT NULL", "t.model"),
3790
+ byAgentToday: q("WHERE t.ts >= ?", "COALESCE(s.agent, 'claude-code')"),
3791
+ byAgentAll: q("WHERE ? IS NOT NULL", "COALESCE(s.agent, 'claude-code')"),
3792
+ daily
3793
+ };
3794
+ }
3795
+ stats(projectId) {
3796
+ const scope = projectId ? "s.project_id = ?" : "? IS NULL";
3797
+ const arg = projectId ?? null;
3798
+ const totals = this.db.query(`SELECT COUNT(t.id) AS turns, COUNT(DISTINCT t.session_id) AS sessions,
3799
+ COALESCE(SUM(t.input),0) AS input, COALESCE(SUM(t.output),0) AS output,
3800
+ COALESCE(SUM(t.cache_write),0) AS cache_write, COALESCE(SUM(t.cache_read),0) AS cache_read,
3801
+ COALESCE(SUM(t.thinking),0) AS thinking, SUM(t.cost_usd) AS cost,
3802
+ COALESCE(SUM(t.sidechain),0) AS sidechain, MIN(t.ts) AS first_ts, MAX(t.ts) AS last_ts
3803
+ FROM turns t JOIN sessions s ON s.id = t.session_id WHERE ${scope}`).get(arg);
3804
+ const sess = this.db.query(`SELECT COUNT(*) AS sessions, COALESCE(SUM(s.tool_calls),0) AS tool_calls, COALESCE(SUM(s.subagents),0) AS subagents,
3805
+ SUM(s.kind = 'subagent') AS subagent_sessions
3806
+ FROM sessions s WHERE ${scope}`).get(arg);
3807
+ const daily = this.db.query(`SELECT date(t.ts, 'localtime') AS day, SUM(t.input) AS input, SUM(t.output) AS output, SUM(t.cache_write) AS cacheWrite,
3808
+ SUM(t.cache_read) AS cacheRead, SUM(t.thinking) AS thinking, SUM(t.cost_usd) AS cost, COUNT(*) AS turns
3809
+ FROM turns t JOIN sessions s ON s.id = t.session_id WHERE ${scope} AND t.ts > datetime('now', '-366 days') GROUP BY day ORDER BY day`).all(arg);
3810
+ const byHour = this.db.query(`SELECT CAST(strftime('%H', t.ts, 'localtime') AS INTEGER) AS hour, COUNT(*) AS turns, SUM(t.output) AS output, SUM(t.cost_usd) AS cost
3811
+ FROM turns t JOIN sessions s ON s.id = t.session_id WHERE ${scope} GROUP BY hour ORDER BY hour`).all(arg);
3812
+ const byModel = this.db.query(`SELECT t.model AS model, COUNT(*) AS turns, SUM(t.output) AS output, SUM(t.cost_usd) AS cost
3813
+ FROM turns t JOIN sessions s ON s.id = t.session_id WHERE ${scope} GROUP BY t.model ORDER BY output DESC`).all(arg);
3814
+ const tools = {};
3815
+ for (const r of this.db.query(`SELECT s.tool_counts AS tc FROM sessions s WHERE ${scope}`).all(arg)) {
3816
+ for (const [k, v] of Object.entries(JSON.parse(r.tc || "{}")))
3817
+ tools[k] = (tools[k] ?? 0) + v;
3818
+ }
3819
+ const sessionRow = (order2) => this.db.query(`SELECT s.id, s.title, s.project_id AS projectId, s.started_at AS startedAt, s.last_seen_at AS lastSeenAt,
3820
+ COUNT(t.id) AS turns, SUM(t.cost_usd) AS cost, SUM(t.output) AS output, s.tool_calls AS toolCalls
3821
+ FROM sessions s JOIN turns t ON t.session_id = s.id WHERE ${scope} AND s.kind != 'subagent'
3822
+ GROUP BY s.id ORDER BY ${order2} DESC LIMIT 1`).get(arg);
3823
+ const biggestTurn = this.db.query(`SELECT t.session_id AS sessionId, s.title, t.ts, t.output, t.thinking, t.cost_usd AS cost, t.model
3824
+ FROM turns t JOIN sessions s ON s.id = t.session_id WHERE ${scope} ORDER BY t.output DESC LIMIT 1`).get(arg);
3825
+ const busiestDay = daily.slice().sort((a, b) => (b.cost ?? 0) - (a.cost ?? 0) || b.turns - a.turns)[0] ?? null;
3826
+ return {
3827
+ totals: {
3828
+ turns: Number(totals.turns ?? 0),
3829
+ sessions: Number(sess.sessions ?? 0),
3830
+ sessionsWithTurns: Number(totals.sessions ?? 0),
3831
+ subagentSessions: Number(sess.subagent_sessions ?? 0),
3832
+ toolCalls: Number(sess.tool_calls ?? 0),
3833
+ subagents: Number(sess.subagents ?? 0),
3834
+ sidechainTurns: Number(totals.sidechain ?? 0),
3835
+ input: Number(totals.input ?? 0),
3836
+ output: Number(totals.output ?? 0),
3837
+ cacheWrite: Number(totals.cache_write ?? 0),
3838
+ cacheRead: Number(totals.cache_read ?? 0),
3839
+ thinking: Number(totals.thinking ?? 0),
3840
+ cost: totals.cost == null ? null : Number(totals.cost),
3841
+ firstTs: totals.first_ts ?? null,
3842
+ lastTs: totals.last_ts ?? null
3843
+ },
3844
+ daily,
3845
+ byHour,
3846
+ byModel,
3847
+ tools: Object.entries(tools).sort((a, b) => b[1] - a[1]).slice(0, 15),
3848
+ records: {
3849
+ costliestSession: sessionRow("cost"),
3850
+ longestSession: sessionRow("turns"),
3851
+ longestWallSession: sessionRow("(julianday(s.last_seen_at) - julianday(s.started_at))"),
3852
+ biggestTurn,
3853
+ busiestDay
3854
+ }
3855
+ };
3856
+ }
3857
+ incidents(limit = 50, opts = {}) {
3858
+ const where = ["e.type = 'incident.opened'"];
3859
+ const args = [];
3860
+ if (opts.open)
3861
+ where.push("a.seq IS NULL");
3862
+ if (opts.projectId) {
3863
+ where.push("e.project_id = ?");
3864
+ args.push(opts.projectId);
3865
+ }
3866
+ args.push(limit);
3867
+ const rows = this.db.query(`SELECT e.seq, e.ts, e.project_id, e.session_id, e.payload, a.acked_at FROM events e
3868
+ LEFT JOIN incident_acks a ON a.seq = e.seq WHERE ${where.join(" AND ")} ORDER BY e.seq DESC LIMIT ?`).all(...args);
3869
+ return rows.map((r) => ({
3870
+ seq: r.seq,
3871
+ ts: r.ts,
3872
+ projectId: r.project_id,
3873
+ sessionId: r.session_id,
3874
+ acked: r.acked_at,
3875
+ ...JSON.parse(r.payload || "{}")
3876
+ }));
3877
+ }
3878
+ openIncidents(projectId) {
3879
+ const r = this.db.query(`SELECT COUNT(*) AS n FROM events e LEFT JOIN incident_acks a ON a.seq = e.seq
3880
+ WHERE e.type = 'incident.opened' AND a.seq IS NULL${projectId ? " AND e.project_id = ?" : ""}`).get(...projectId ? [projectId] : []);
3881
+ return r.n;
3882
+ }
3883
+ ackIncident(seq) {
3884
+ const row = this.db.query("SELECT seq FROM events WHERE seq = ? AND type = 'incident.opened'").get(seq);
3885
+ if (!row)
3886
+ return false;
3887
+ this.db.query("INSERT OR IGNORE INTO incident_acks (seq, acked_at) VALUES (?, ?)").run(seq, new Date().toISOString());
3888
+ this.touch();
3889
+ return true;
3890
+ }
3891
+ ackAllIncidents(projectId) {
3892
+ const at = new Date().toISOString();
3893
+ const r = this.db.query(`INSERT OR IGNORE INTO incident_acks (seq, acked_at)
3894
+ SELECT e.seq, ? FROM events e LEFT JOIN incident_acks a ON a.seq = e.seq
3895
+ WHERE e.type = 'incident.opened' AND a.seq IS NULL${projectId ? " AND e.project_id = ?" : ""}`).run(at, ...projectId ? [projectId] : []);
3896
+ this.touch();
3897
+ return Number(r.changes);
3898
+ }
3899
+ static pidAlive(pid) {
3900
+ try {
3901
+ process.kill(pid, 0);
3902
+ return true;
3903
+ } catch {
3904
+ return false;
3905
+ }
3906
+ }
3907
+ rowToResource(r) {
3908
+ return {
3909
+ name: r.name,
3910
+ kind: r.kind ?? "custom",
3911
+ projectId: r.project_id || null,
3912
+ owner: r.owner,
3913
+ sessionId: r.session_id || null,
3914
+ pid: isTrackedPid(r.pid) ? r.pid : null,
3915
+ port: r.port ?? null,
3916
+ acquiredAt: r.acquired_at,
3917
+ expiresAt: r.expires_at || null,
3918
+ released: !!r.released
3919
+ };
3920
+ }
3921
+ resources(projectId) {
3922
+ this.reapResources();
3923
+ const rows = projectId ? this.db.query("SELECT * FROM resources WHERE released = 0 AND (project_id = ? OR project_id = '')").all(projectId) : this.db.query("SELECT * FROM resources WHERE released = 0").all();
3924
+ return rows.map((r) => this.rowToResource(r));
3925
+ }
3926
+ heldPorts() {
3927
+ return this.db.query("SELECT port FROM resources WHERE released = 0 AND port IS NOT NULL").all().map((r) => r.port);
3928
+ }
3929
+ reapResources() {
3930
+ const rows = this.db.query("SELECT * FROM resources WHERE released = 0").all();
3931
+ const now = Date.now();
3932
+ let n = 0;
3933
+ for (const raw2 of rows)
3934
+ if (this.reapIfDead(this.rowToResource(raw2), now))
3935
+ n++;
3936
+ return n;
3937
+ }
3938
+ reapIfDead(r, now = Date.now()) {
3939
+ if (r.released || isAliveHolding(r, now, Store.pidAlive))
3940
+ return false;
3941
+ this.db.query("UPDATE resources SET released = 1 WHERE name = ? AND project_id = ?").run(r.name, r.projectId ?? "");
3942
+ this.append({
3943
+ ts: new Date().toISOString(),
3944
+ type: "resource.reaped",
3945
+ projectId: r.projectId ?? "p_unknown",
3946
+ sessionId: r.sessionId,
3947
+ payload: { name: r.name, owner: r.owner, pid: r.pid, port: r.port }
3948
+ });
3949
+ return true;
3950
+ }
3951
+ static processStartTime(pid) {
3952
+ try {
3953
+ const r = Bun.spawnSync(["ps", "-o", "lstart=", "-p", String(pid)], {
3954
+ stdout: "pipe",
3955
+ stderr: "ignore"
3956
+ });
3957
+ const out = r.stdout.toString().trim();
3958
+ return out || null;
3959
+ } catch {
3960
+ return null;
3961
+ }
3962
+ }
3963
+ rowToProcess(r) {
3964
+ return {
3965
+ pid: r.pid,
3966
+ startTime: r.start_time ?? null,
3967
+ projectId: r.project_id,
3968
+ sessionId: r.session_id ?? null,
3969
+ kind: r.kind,
3970
+ name: r.name,
3971
+ port: r.port ?? null,
3972
+ cwd: r.cwd ?? "",
3973
+ cmd: r.cmd ?? "",
3974
+ owner: r.owner ?? "",
3975
+ log: r.log ?? null,
3976
+ startedAt: r.started_at,
3977
+ endedAt: r.ended_at ?? null
3978
+ };
3979
+ }
3980
+ processes(projectId) {
3981
+ const rows = this.db.query(projectId ? "SELECT rowid, * FROM processes WHERE ended_at IS NULL AND project_id = ? ORDER BY started_at DESC" : "SELECT rowid, * FROM processes WHERE ended_at IS NULL ORDER BY started_at DESC").all(...projectId ? [projectId] : []).map((r) => ({ rowid: r.rowid, p: this.rowToProcess(r) }));
3982
+ const live = [];
3983
+ for (const { rowid, p } of rows) {
3984
+ if (this.processIsOurs(p))
3985
+ live.push(p);
3986
+ else
3987
+ this.endProcess(rowid, p, "exited");
3988
+ }
3989
+ return live;
3990
+ }
3991
+ processIsOurs(p) {
3992
+ const alive = Store.pidAlive(p.pid);
3993
+ return isOurs(p, alive, alive ? Store.processStartTime(p.pid) : null);
3994
+ }
3995
+ endProcess(rowid, p, how) {
3996
+ const at = new Date().toISOString();
3997
+ this.db.query("UPDATE processes SET ended_at = ? WHERE rowid = ?").run(at, rowid);
3998
+ this.db.query("UPDATE resources SET released = 1 WHERE name = ? AND project_id = ? AND pid = ?").run(p.name, p.projectId, p.pid);
3999
+ this.append({
4000
+ ts: at,
4001
+ type: "process.exited",
4002
+ projectId: p.projectId,
4003
+ sessionId: p.sessionId,
4004
+ payload: { pid: p.pid, name: p.name, kind: p.kind, port: p.port, how }
4005
+ });
4006
+ }
4007
+ reapProcesses() {
4008
+ const before = this.db.query("SELECT COUNT(*) AS n FROM processes WHERE ended_at IS NULL").get().n;
4009
+ return before - this.processes().length;
4010
+ }
4011
+ takenPorts() {
4012
+ const held = this.heldPorts();
4013
+ const procs = this.db.query("SELECT port FROM processes WHERE ended_at IS NULL AND port IS NOT NULL").all().map((r) => r.port);
4014
+ return [...held, ...procs];
4015
+ }
4016
+ static portFree(port) {
4017
+ try {
4018
+ const l = Bun.listen({ hostname: "127.0.0.1", port, socket: { data() {} } });
4019
+ l.stop(true);
4020
+ return true;
4021
+ } catch {
4022
+ return false;
4023
+ }
4024
+ }
4025
+ allocatePort(from = DEFAULT_FROM_PORT) {
4026
+ return pickPort(from, this.takenPorts(), Store.portFree);
4027
+ }
4028
+ registerProcess(input) {
4029
+ if (!isTrackedPid(input.pid))
4030
+ return { ok: false, reason: "a real pid is required" };
4031
+ if (!this.project(input.projectId))
4032
+ return { ok: false, reason: "unknown project" };
4033
+ if (!Store.pidAlive(input.pid))
4034
+ return { ok: false, reason: `pid ${input.pid} is not running` };
4035
+ const res = this.acquireResource({
4036
+ name: input.name,
4037
+ projectId: input.projectId,
4038
+ kind: "process",
4039
+ owner: input.owner,
4040
+ sessionId: input.sessionId ?? null,
4041
+ pid: input.pid,
4042
+ port: input.port ?? null
4043
+ });
4044
+ if (!res.ok)
4045
+ return res;
4046
+ const p = {
4047
+ pid: input.pid,
4048
+ startTime: Store.processStartTime(input.pid),
4049
+ projectId: input.projectId,
4050
+ sessionId: this.knownSession(input.sessionId),
4051
+ kind: input.kind,
4052
+ name: input.name,
4053
+ port: input.port ?? null,
4054
+ cwd: input.cwd,
4055
+ cmd: input.cmd,
4056
+ owner: input.owner,
4057
+ log: input.log ?? null,
4058
+ startedAt: new Date().toISOString(),
4059
+ endedAt: null
4060
+ };
4061
+ this.db.query("UPDATE processes SET ended_at = ? WHERE ended_at IS NULL AND project_id = ? AND name = ?").run(p.startedAt, p.projectId, p.name);
4062
+ this.db.query(`INSERT INTO processes (pid, start_time, project_id, session_id, kind, name, port, cwd, cmd, owner, log, started_at, ended_at)
4063
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(p.pid, p.startTime, p.projectId, p.sessionId, p.kind, p.name, p.port, p.cwd, p.cmd, p.owner, p.log, p.startedAt);
4064
+ this.append({
4065
+ ts: p.startedAt,
4066
+ type: "process.started",
4067
+ projectId: p.projectId,
4068
+ sessionId: p.sessionId,
4069
+ payload: { pid: p.pid, name: p.name, kind: p.kind, port: p.port, cmd: p.cmd.slice(0, 200) }
4070
+ });
4071
+ this.touch();
4072
+ return { ok: true, process: p };
4073
+ }
4074
+ async stopProcess(pid, projectId, graceMs = 3000) {
4075
+ const raw2 = this.db.query(`SELECT rowid, * FROM processes WHERE pid = ? AND ended_at IS NULL${projectId ? " AND project_id = ?" : ""}`).get(...projectId ? [pid, projectId] : [pid]);
4076
+ if (!raw2)
4077
+ return { ok: false, reason: "not a registered process" };
4078
+ const p = this.rowToProcess(raw2);
4079
+ const rowid = raw2.rowid;
4080
+ if (!this.processIsOurs(p)) {
4081
+ this.endProcess(rowid, p, "exited");
4082
+ return { ok: true, reason: "already gone" };
4083
+ }
4084
+ try {
4085
+ process.kill(pid, "SIGTERM");
4086
+ } catch {}
4087
+ const deadline = Date.now() + graceMs;
4088
+ while (Date.now() < deadline && Store.pidAlive(pid))
4089
+ await Bun.sleep(100);
4090
+ if (Store.pidAlive(pid) && this.processIsOurs(p)) {
4091
+ try {
4092
+ process.kill(pid, "SIGKILL");
4093
+ } catch {}
4094
+ }
4095
+ this.endProcess(rowid, p, "stopped");
4096
+ this.touch();
4097
+ return { ok: true };
4098
+ }
4099
+ knownSession(id) {
4100
+ if (!id)
4101
+ return null;
4102
+ return this.db.query("SELECT 1 FROM sessions WHERE id = ?").get(id) ? id : null;
4103
+ }
4104
+ acquireResource(input) {
4105
+ const key = input.projectId ?? "";
4106
+ const raw2 = this.db.query("SELECT * FROM resources WHERE name = ? AND project_id = ?").get(input.name, key);
4107
+ let existing = raw2 ? this.rowToResource(raw2) : null;
4108
+ if (existing && this.reapIfDead(existing))
4109
+ existing = null;
4110
+ const d = canAcquire(existing, { owner: input.owner }, Date.now(), Store.pidAlive);
4111
+ if (!d.ok)
4112
+ return { ok: false, reason: acquireRefusalMessage(d.holder) };
4113
+ const pid = isTrackedPid(input.pid) ? input.pid : null;
4114
+ const expiresAt = pid != null ? null : new Date(Date.now() + (input.leaseMinutes ?? DEFAULT_RESOURCE_LEASE_MINUTES) * 60000).toISOString();
4115
+ const resource = {
4116
+ name: input.name,
4117
+ kind: input.kind ?? (input.port != null ? "port" : pid != null ? "process" : "custom"),
4118
+ projectId: input.projectId ?? null,
4119
+ owner: input.owner,
4120
+ sessionId: this.knownSession(input.sessionId),
4121
+ pid,
4122
+ port: input.port ?? null,
4123
+ acquiredAt: new Date().toISOString(),
4124
+ expiresAt,
4125
+ released: false
4126
+ };
4127
+ this.db.query(`INSERT INTO resources (name, project_id, kind, owner, session_id, pid, port, acquired_at, expires_at, released)
4128
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
4129
+ ON CONFLICT(name, project_id) DO UPDATE SET
4130
+ kind=excluded.kind, owner=excluded.owner, session_id=excluded.session_id, pid=excluded.pid,
4131
+ port=excluded.port, acquired_at=excluded.acquired_at, expires_at=excluded.expires_at, released=0`).run(resource.name, key, resource.kind, resource.owner, resource.sessionId, resource.pid, resource.port, resource.acquiredAt, resource.expiresAt);
4132
+ this.append({
4133
+ ts: resource.acquiredAt,
4134
+ type: "resource.acquired",
4135
+ projectId: resource.projectId ?? "p_unknown",
4136
+ sessionId: resource.sessionId,
4137
+ payload: {
4138
+ name: resource.name,
4139
+ kind: resource.kind,
4140
+ owner: resource.owner,
4141
+ pid: resource.pid,
4142
+ port: resource.port
4143
+ }
4144
+ });
4145
+ return { ok: true, resource };
4146
+ }
4147
+ releaseResource(name, projectId, owner, force = false) {
4148
+ const raw2 = this.db.query("SELECT * FROM resources WHERE name = ? AND project_id = ? AND released = 0").get(name, projectId ?? "");
4149
+ if (!raw2)
4150
+ return { ok: false, reason: "not held" };
4151
+ const r = this.rowToResource(raw2);
4152
+ if (!force) {
4153
+ if (!owner)
4154
+ return { ok: false, reason: `held by ${r.owner}; pass owner or force` };
4155
+ if (r.owner !== owner)
4156
+ return { ok: false, reason: `held by ${r.owner}, not ${owner}` };
4157
+ }
4158
+ this.db.query("UPDATE resources SET released = 1 WHERE name = ? AND project_id = ?").run(name, projectId ?? "");
4159
+ this.append({
4160
+ ts: new Date().toISOString(),
4161
+ type: "resource.released",
4162
+ projectId: r.projectId ?? "p_unknown",
4163
+ sessionId: r.sessionId,
4164
+ payload: {
4165
+ name: r.name,
4166
+ owner: r.owner,
4167
+ by: owner ?? null,
4168
+ forced: force && owner !== r.owner
4169
+ }
4170
+ });
4171
+ return { ok: true };
4172
+ }
4173
+ seq() {
4174
+ return this.db.query("SELECT COALESCE(MAX(seq),0) AS seq FROM events").get().seq;
4175
+ }
4176
+ snapshot() {
4177
+ const worktrees = {};
4178
+ const projects = this.projects();
4179
+ for (const p of projects)
4180
+ worktrees[p.id] = this.worktrees(p.id);
4181
+ return {
4182
+ projects,
4183
+ worktrees,
4184
+ sessions: this.memoised("sessions", 2000, () => this.sessions()),
4185
+ spend: this.memoised("spend", 30000, () => this.spend()),
4186
+ claims: this.claims(),
4187
+ processes: this.memoised("processes", 5000, () => this.processes()),
4188
+ incidents: this.memoised("incidents", 30000, () => this.incidents(20, { open: true })),
4189
+ openIncidents: this.memoised("openIncidents", 30000, () => this.openIncidents()),
4190
+ resources: this.resources(),
4191
+ seq: this.seq()
4192
+ };
4193
+ }
4194
+ }
4195
+ var WIRE_COLS = "seq, ts, type, project_id, session_id, json_remove(payload, '$.toolInput', '$.toolResponse', '$.prompt') AS payload";
4196
+ var RAW_TOOL_KEYS = ["tool_input", "tool_response", "toolInput", "toolResponse", "toolResult"];
4197
+ var TOOL_INPUT_MAX = 2048;
4198
+ var TOOL_RESPONSE_MAX = 4096;
4199
+ function clip(v, max) {
4200
+ if (v === undefined)
4201
+ return;
4202
+ const s = JSON.stringify(v);
4203
+ if (s.length <= max)
4204
+ return v;
4205
+ return { truncated: true, bytes: s.length, preview: s.slice(0, max) };
4206
+ }
4207
+ function slimForStorage(e) {
4208
+ const p = e.payload;
4209
+ let payload = p;
4210
+ if (p && typeof p === "object" && (("toolInput" in p) || ("toolResponse" in p))) {
4211
+ payload = {
4212
+ ...p,
4213
+ toolInput: clip(p.toolInput, TOOL_INPUT_MAX),
4214
+ toolResponse: clip(p.toolResponse, TOOL_RESPONSE_MAX)
4215
+ };
4216
+ }
4217
+ let raw2 = e.raw;
4218
+ if (raw2 && typeof raw2 === "object") {
4219
+ const r = raw2;
4220
+ if (RAW_TOOL_KEYS.some((k) => (k in r))) {
4221
+ const rest = {};
4222
+ for (const k of Object.keys(r))
4223
+ if (!RAW_TOOL_KEYS.includes(k))
4224
+ rest[k] = r[k];
4225
+ raw2 = rest;
4226
+ }
4227
+ }
4228
+ return { payload, raw: raw2 };
4229
+ }
4230
+ function toWire(e) {
4231
+ const { raw: _raw, ...rest } = e;
4232
+ const p = rest.payload;
4233
+ if (p && typeof p === "object" && (("toolInput" in p) || ("toolResponse" in p) || ("prompt" in p))) {
4234
+ const { toolInput: _a, toolResponse: _b, prompt: _c, ...small } = p;
4235
+ return { ...rest, payload: small };
4236
+ }
4237
+ return rest;
4238
+ }
4239
+ function wireRowToEvent(r) {
4240
+ const p = JSON.parse(r.payload ?? "null");
4241
+ return {
4242
+ seq: r.seq,
4243
+ ts: r.ts,
4244
+ type: r.type,
4245
+ projectId: r.project_id,
4246
+ sessionId: r.session_id ?? null,
4247
+ payload: p
4248
+ };
4249
+ }
4250
+ function rowToEvent(r) {
4251
+ const e = {
4252
+ seq: r.seq,
4253
+ ts: r.ts,
4254
+ type: r.type,
4255
+ projectId: r.project_id,
4256
+ sessionId: r.session_id ?? null,
4257
+ payload: JSON.parse(r.payload ?? "null")
4258
+ };
4259
+ if (r.raw)
4260
+ e.raw = JSON.parse(r.raw);
4261
+ return e;
4262
+ }
4263
+
4264
+ // packages/daemon/src/app.ts
4265
+ var VERSION = "0.4.0";
4266
+ var WEB_DIR = (() => {
4267
+ if (process.env.SWARM_WEB_DIR)
4268
+ return process.env.SWARM_WEB_DIR;
4269
+ const here = dirname2(fileURLToPath(import.meta.url));
4270
+ const dev = join6(here, "../../web/public");
4271
+ return existsSync5(join6(dev, "index.html")) ? dev : join6(here, "../web");
4272
+ })();
4273
+ var REPLAY_TAIL = 200;
4274
+ var wireCache = new WeakMap;
4275
+ function wireJson(e) {
4276
+ let s = wireCache.get(e);
4277
+ if (!s) {
4278
+ s = JSON.stringify(e);
4279
+ wireCache.set(e, s);
4280
+ }
4281
+ return s;
4282
+ }
4283
+ function createApp(store = new Store) {
4284
+ const app = new Hono2;
4285
+ const forge2 = new ForgeService(store);
4286
+ app.get("/v1/health", (c) => c.json({ ok: true, version: VERSION }));
4287
+ app.get("/v1/projects", (c) => c.json(store.snapshot().projects));
4288
+ app.post("/v1/projects", async (c) => {
4289
+ const { path, name } = await c.req.json();
4290
+ if (!path)
4291
+ return c.json({ error: "path required" }, 400);
4292
+ try {
4293
+ return c.json(store.resolveProject(path, true, name), 201);
4294
+ } catch (e) {
4295
+ return c.json({ error: e.message }, 400);
4296
+ }
4297
+ });
4298
+ app.put("/v1/projects/order", async (c) => {
4299
+ const { ids } = await c.req.json().catch(() => ({}));
4300
+ if (!Array.isArray(ids) || !ids.every((x) => typeof x === "string"))
4301
+ return c.json({ error: "ids: string[] required" }, 400);
4302
+ return c.json(store.reorderProjects(ids));
4303
+ });
4304
+ app.patch("/v1/projects/:id", async (c) => {
4305
+ const { pinned, name } = await c.req.json().catch(() => ({}));
4306
+ const p = store.updateProject(c.req.param("id"), { pinned, name });
4307
+ return p ? c.json(p) : c.json({ error: "not found" }, 404);
4308
+ });
4309
+ app.delete("/v1/projects/:id", (c) => store.removeProject(c.req.param("id")) ? c.body(null, 204) : c.json({ error: "not found" }, 404));
4310
+ app.get("/v1/fs/ls", (c) => {
4311
+ const q = c.req.query("path");
4312
+ let dir;
4313
+ try {
4314
+ dir = realpathSync3(q && existsSync5(q) ? q : homedir4());
4315
+ } catch {
4316
+ dir = homedir4();
4317
+ }
4318
+ try {
4319
+ const entries = readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, repo: existsSync5(join6(dir, e.name, ".git")) })).sort((a, b) => a.name.localeCompare(b.name));
4320
+ const parent = dirname2(dir);
4321
+ return c.json({ path: dir, parent: parent === dir ? null : parent, entries });
4322
+ } catch (e) {
4323
+ return c.json({ error: e.message, path: dir }, 400);
4324
+ }
4325
+ });
4326
+ app.get("/v1/state", (c) => c.json(store.snapshot()));
4327
+ app.get("/v1/stats", (c) => c.json(store.stats(c.req.query("project") || undefined)));
4328
+ app.get("/v1/incidents", (c) => c.json(store.incidents(Number(c.req.query("limit") ?? 50), {
4329
+ open: c.req.query("open") === "1",
4330
+ projectId: c.req.query("project") || undefined
4331
+ })));
4332
+ app.post("/v1/incidents/ack", async (c) => {
4333
+ const body = await c.req.json().catch(() => ({}));
4334
+ return c.json({ ok: true, acked: store.ackAllIncidents(body.project || undefined) });
4335
+ });
4336
+ app.post("/v1/incidents/:seq/ack", (c) => {
4337
+ const seq = Number(c.req.param("seq"));
4338
+ if (!Number.isInteger(seq) || !store.ackIncident(seq))
4339
+ return c.json({ ok: false, error: "no such incident" }, 404);
4340
+ return c.json({ ok: true });
4341
+ });
4342
+ app.get("/v1/resources", (c) => c.json(store.resources(c.req.query("project"))));
4343
+ app.post("/v1/resources", async (c) => {
4344
+ const b = await c.req.json().catch(() => ({}));
4345
+ if (!b.name || !b.owner)
4346
+ return c.json({ ok: false, error: "name and owner are required" }, 400);
4347
+ const r = store.acquireResource({ ...b, name: b.name, owner: b.owner });
4348
+ return r.ok ? c.json(r, 201) : c.json({ ok: false, error: r.reason }, 409);
4349
+ });
4350
+ app.get("/v1/prs", (c) => c.json(forge2.prs()));
4351
+ app.post("/v1/prs/merge", async (c) => {
4352
+ const b = await c.req.json().catch(() => ({}));
4353
+ if (!b.projectId || !b.number)
4354
+ return c.json({ error: "projectId and number are required" }, 400);
4355
+ const r = await forge2.merge(b.projectId, b.number);
4356
+ return r.ok ? c.json(r) : c.json({ error: r.output || "merge failed" }, 409);
4357
+ });
4358
+ app.delete("/v1/resources/:name", (c) => {
4359
+ const r = store.releaseResource(c.req.param("name"), c.req.query("project") ?? null, c.req.query("owner"), c.req.query("force") === "1" || c.req.query("force") === "true");
4360
+ return r.ok ? c.json(r) : c.json({ ok: false, error: r.reason }, r.reason === "not held" ? 404 : 409);
4361
+ });
4362
+ app.get("/v1/processes", (c) => c.json(store.processes(c.req.query("project") || undefined)));
4363
+ app.post("/v1/ports/allocate", async (c) => {
4364
+ const b = await c.req.json().catch(() => ({}));
4365
+ const port = store.allocatePort(Number.isInteger(b.from) && b.from > 0 ? b.from : undefined);
4366
+ return port ? c.json({ ok: true, port }) : c.json({ ok: false, error: "no free port" }, 503);
4367
+ });
4368
+ app.post("/v1/processes", async (c) => {
4369
+ const b = await c.req.json().catch(() => ({}));
4370
+ if (!b.pid || !b.projectId || !b.name || !b.cwd)
4371
+ return c.json({ ok: false, error: "pid, projectId, name and cwd required" }, 400);
4372
+ const r = store.registerProcess({
4373
+ pid: Number(b.pid),
4374
+ projectId: b.projectId,
4375
+ sessionId: b.sessionId ?? null,
4376
+ kind: b.kind === "serve" ? "serve" : "proc",
4377
+ name: b.name,
4378
+ port: b.port ?? null,
4379
+ cwd: b.cwd,
4380
+ cmd: b.cmd ?? "",
4381
+ owner: b.owner ?? "cli",
4382
+ log: b.log ?? null
4383
+ });
4384
+ return r.ok ? c.json(r, 201) : c.json({ ok: false, error: r.reason }, 409);
4385
+ });
4386
+ app.delete("/v1/processes/:pid", async (c) => {
4387
+ const pid = Number(c.req.param("pid"));
4388
+ if (!Number.isInteger(pid) || pid <= 0)
4389
+ return c.json({ ok: false, error: "bad pid" }, 400);
4390
+ const r = await store.stopProcess(pid, c.req.query("project") || null);
4391
+ return r.ok ? c.json(r) : c.json({ ok: false, error: r.reason }, 404);
4392
+ });
4393
+ app.get("/v1/tasks", (c) => {
4394
+ const project = c.req.query("project");
4395
+ if (!project)
4396
+ return c.json({ error: "project required" }, 400);
4397
+ const t = store.tasks(project);
4398
+ return c.json(t ?? { source: null, tasks: [] });
4399
+ });
4400
+ app.get("/v1/claims", (c) => c.json(store.claims(c.req.query("project"))));
4401
+ app.post("/v1/claims", async (c) => {
4402
+ const b = await c.req.json();
4403
+ if (!b.projectId || !b.task)
4404
+ return c.json({ error: "projectId and task required" }, 400);
4405
+ const r = store.claim(b.projectId, b.task, b.owner ?? "cli", b.baseRef);
4406
+ return c.json(r, r.ok ? 201 : 409);
4407
+ });
4408
+ app.post("/v1/claims/renew", async (c) => {
4409
+ const b = await c.req.json();
4410
+ const r = store.renew(b.projectId ?? "", b.task ?? "");
4411
+ return c.json(r, r.ok ? 200 : 404);
4412
+ });
4413
+ app.post("/v1/claims/release", async (c) => {
4414
+ const b = await c.req.json();
4415
+ const r = store.release(b.projectId ?? "", b.task ?? "", b.force ?? false);
4416
+ return c.json(r, r.ok ? 200 : 409);
4417
+ });
4418
+ app.post("/v1/claims/reap", async (c) => {
4419
+ const b = await c.req.json().catch(() => ({}));
4420
+ return c.json({ reaped: store.reap(b.projectId) });
4421
+ });
4422
+ app.get("/v1/sessions/:id/events", (c) => {
4423
+ const id = c.req.param("id");
4424
+ const after = Number(c.req.query("after") ?? 0);
4425
+ const afterTs = c.req.query("afterTs") || undefined;
4426
+ return c.json({
4427
+ events: store.sessionEvents(id, 500, after),
4428
+ turns: store.sessionTurns(id, 500, afterTs),
4429
+ seq: store.seq()
4430
+ });
4431
+ });
4432
+ app.get("/v1/events/:seq", (c) => {
4433
+ const e = store.event(Number(c.req.param("seq")));
4434
+ return e ? c.json(e) : c.json({ error: "not found" }, 404);
4435
+ });
4436
+ app.get("/v1/spend", (c) => c.json(store.spend()));
4437
+ app.post("/v1/pricing/refresh", async (c) => {
4438
+ try {
4439
+ await store.refreshPricing();
4440
+ return c.json({ ok: true, models: Object.keys(store.prices).length });
4441
+ } catch (e) {
4442
+ return c.json({ error: e.message }, 502);
4443
+ }
4444
+ });
4445
+ app.get("/v1/pricing", (c) => c.json(store.prices));
4446
+ app.post("/v1/sessions/:id/tail", (c) => c.json({ turns: store.tailSession(c.req.param("id")) }));
4447
+ app.post("/v1/hook/:event", async (c) => {
4448
+ const event = c.req.param("event");
4449
+ const raw2 = await c.req.json().catch(() => ({}));
4450
+ store.ingestHook(event, raw2);
4451
+ if (event === "PreToolUse" && process.env.SWARM_GUARD !== "off") {
4452
+ const guard = store.guardHook(raw2);
4453
+ if (guard) {
4454
+ return c.json({
4455
+ hookSpecificOutput: {
4456
+ hookEventName: "PreToolUse",
4457
+ permissionDecision: guard.action,
4458
+ permissionDecisionReason: `[swarm] ${guard.reason}`
4459
+ }
4460
+ });
4461
+ }
4462
+ }
4463
+ return c.json({});
4464
+ });
4465
+ app.post("/v1/events", async (c) => {
4466
+ const e = await c.req.json();
4467
+ return c.json(store.append(e), 201);
4468
+ });
4469
+ app.get("/v1/events", (c) => {
4470
+ const full = c.req.query("full") === "1";
4471
+ const raw2 = Number(c.req.query("since") ?? 0);
4472
+ const since = raw2 > 0 ? raw2 : Math.max(0, store.seq() - REPLAY_TAIL);
4473
+ return streamSSE(c, async (stream2) => {
4474
+ for (const e of store.since(since, 5000, full)) {
4475
+ await stream2.writeSSE({ id: String(e.seq), event: e.type, data: JSON.stringify(e) });
4476
+ }
4477
+ await stream2.writeSSE({ event: "ping", data: "" });
4478
+ await new Promise((resolve) => {
4479
+ const off = store.subscribe((e) => {
4480
+ stream2.writeSSE({ id: String(e.seq), event: e.type, data: wireJson(e) });
4481
+ });
4482
+ const beat = setInterval(() => void stream2.writeSSE({ event: "ping", data: "" }), 15000);
4483
+ stream2.onAbort(() => {
4484
+ clearInterval(beat);
4485
+ off();
4486
+ resolve();
4487
+ });
4488
+ });
4489
+ });
4490
+ });
4491
+ app.get("/", (c) => c.html(readFileSync4(join6(WEB_DIR, "index.html"), "utf8")));
4492
+ const MIME = { js: "text/javascript", css: "text/css" };
4493
+ app.get("/:file{[a-z0-9-]+\\.(js|css)}", (c) => {
4494
+ const f = c.req.param("file");
4495
+ const p = join6(WEB_DIR, f);
4496
+ if (!existsSync5(p))
4497
+ return c.text(`${f} not built \u2014 run: bun run build:web`, 404);
4498
+ return c.body(readFileSync4(p, "utf8"), 200, {
4499
+ "content-type": MIME[f.split(".").pop() ?? ""] ?? "text/plain"
4500
+ });
4501
+ });
4502
+ return { app, store, forge: forge2 };
4503
+ }
4504
+
4505
+ // packages/daemon/src/bin.ts
4506
+ var DEFAULT_PORT2 = process.env.SWARM_PORT ? DEFAULT_PORT : loadConfig().daemon.port;
4507
+ var { app, store } = createApp();
4508
+ function serve() {
4509
+ const bind = (p) => Bun.serve({ port: p, hostname: "127.0.0.1", idleTimeout: 0, fetch: app.fetch });
4510
+ try {
4511
+ return bind(DEFAULT_PORT2);
4512
+ } catch (e) {
4513
+ if (process.env.SWARM_STRICT_PORT === "1" || !/EADDRINUSE|in use/i.test(e.message))
4514
+ throw e;
4515
+ console.error(`swarmd: port ${DEFAULT_PORT2} in use \u2014 picking a free port instead.`);
4516
+ return bind(0);
4517
+ }
4518
+ }
4519
+ var server = serve();
4520
+ var port = server.port ?? DEFAULT_PORT2;
4521
+ writeDaemonInfo({ port, pid: process.pid, version: VERSION, startedAt: new Date().toISOString() });
4522
+ var backfillDays = Number(process.env.SWARM_CODEX_BACKFILL_DAYS ?? 30);
4523
+ var backfillMs = backfillDays * 24 * 60 * 60000;
4524
+ store.tailCodex(backfillMs);
4525
+ store.tailGrok(backfillMs);
4526
+ var tick = 0;
4527
+ var tailer = setInterval(() => {
4528
+ tick++;
4529
+ store.tailActive();
4530
+ if (tick % 3 === 0 || store.hasActiveSessions()) {
4531
+ store.tailCodex();
4532
+ store.tailGrok();
4533
+ }
4534
+ store.reapResources();
4535
+ store.reapProcesses();
4536
+ }, 5000);
4537
+ store.refreshAllWorktrees();
4538
+ var wtRefresh = setInterval(() => void store.refreshAllWorktrees(), 15000);
4539
+ store.prune();
4540
+ var pruner = setInterval(() => store.prune(), 24 * 60 * 60000);
4541
+ if (process.env.SWARM_OFFLINE !== "1")
4542
+ store.refreshPricing().catch(() => {});
4543
+ console.log(`swarmd ${VERSION} listening on http://127.0.0.1:${port}`);
4544
+ function shutdown() {
4545
+ clearInterval(tailer);
4546
+ clearInterval(wtRefresh);
4547
+ clearInterval(pruner);
4548
+ clearDaemonInfo();
4549
+ server.stop(true);
4550
+ process.exit(0);
4551
+ }
4552
+ process.on("SIGINT", shutdown);
4553
+ process.on("SIGTERM", shutdown);
4554
+ process.on("exit", () => clearDaemonInfo());