@gobing-ai/spur 0.3.9 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/config/rules/boundary/sp-runtime-path.yaml +4 -0
  2. package/package.json +1 -1
  3. package/spur.js +556 -99
  4. package/web/_astro/{BoardApp.DjpO2XHn.js → BoardApp.C6AZyK1W.js} +106 -105
  5. package/web/_astro/BoardApp.EXjc9f_0.js +1 -0
  6. package/web/_astro/{TaskDetail.DilpuD7R.js → TaskDetail.qCmrwQkG.js} +1 -1
  7. package/web/_astro/{arc.KcqIior8.js → arc.CbRppOdF.js} +1 -1
  8. package/web/_astro/{architectureDiagram-3BPJPVTR.BTFU70BY.js → architectureDiagram-3BPJPVTR.9PfIcrsr.js} +1 -1
  9. package/web/_astro/{blockDiagram-GPEHLZMM.apx49Oy9.js → blockDiagram-GPEHLZMM.D8X1aH9F.js} +1 -1
  10. package/web/_astro/{c4Diagram-AAUBKEIU.BM-J4juX.js → c4Diagram-AAUBKEIU.BnAemEBs.js} +1 -1
  11. package/web/_astro/channel.DDlSVLNA.js +1 -0
  12. package/web/_astro/{chunk-2J33WTMH.K6z7IYZU.js → chunk-2J33WTMH.DzOqcSmH.js} +1 -1
  13. package/web/_astro/{chunk-4BX2VUAB.BDYhxKa0.js → chunk-4BX2VUAB.AqtW-DKu.js} +1 -1
  14. package/web/_astro/{chunk-55IACEB6.BnNOxqnI.js → chunk-55IACEB6.BIRjE6jR.js} +1 -1
  15. package/web/_astro/{chunk-727SXJPM.DNeg6N7v.js → chunk-727SXJPM.CgDzPlSk.js} +1 -1
  16. package/web/_astro/{chunk-AQP2D5EJ.BjILBkba.js → chunk-AQP2D5EJ.DjOW-ScW.js} +1 -1
  17. package/web/_astro/{chunk-FMBD7UC4.DQ5UvvsV.js → chunk-FMBD7UC4.wdJmHym6.js} +1 -1
  18. package/web/_astro/{chunk-ND2GUHAM.Yn1OvRJs.js → chunk-ND2GUHAM.C-Ho8u3p.js} +1 -1
  19. package/web/_astro/{chunk-QZHKN3VN.UErqHgqw.js → chunk-QZHKN3VN.BF1HouUh.js} +1 -1
  20. package/web/_astro/{classDiagram-4FO5ZUOK.BmjVF7iZ.js → classDiagram-4FO5ZUOK.DAyOMMHn.js} +1 -1
  21. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BmjVF7iZ.js → classDiagram-v2-Q7XG4LA2.DAyOMMHn.js} +1 -1
  22. package/web/_astro/{cose-bilkent-S5V4N54A.iKBxnkbb.js → cose-bilkent-S5V4N54A.CNoutjqT.js} +1 -1
  23. package/web/_astro/{dagre-BM42HDAG.OwnCCrs6.js → dagre-BM42HDAG.yQbaJnir.js} +1 -1
  24. package/web/_astro/{diagram-2AECGRRQ.DCdcffxE.js → diagram-2AECGRRQ.Y-0ZX0wP.js} +1 -1
  25. package/web/_astro/{diagram-5GNKFQAL.Ai-LvmVW.js → diagram-5GNKFQAL.Co7hW1JF.js} +1 -1
  26. package/web/_astro/{diagram-KO2AKTUF.B6e21QDN.js → diagram-KO2AKTUF.BW6g9zEB.js} +1 -1
  27. package/web/_astro/{diagram-LMA3HP47.oPfqy1fq.js → diagram-LMA3HP47.5laYcnBs.js} +1 -1
  28. package/web/_astro/{diagram-OG6HWLK6.Dh5QRDCE.js → diagram-OG6HWLK6.DOXfn0B3.js} +1 -1
  29. package/web/_astro/{erDiagram-TEJ5UH35.DpBiOcO3.js → erDiagram-TEJ5UH35.B65zPLW1.js} +1 -1
  30. package/web/_astro/{flowDiagram-I6XJVG4X.NbMxFlVk.js → flowDiagram-I6XJVG4X.EyV3nzyc.js} +1 -1
  31. package/web/_astro/{ganttDiagram-6RSMTGT7.JgZ3fDME.js → ganttDiagram-6RSMTGT7.Dbg3JgqV.js} +1 -1
  32. package/web/_astro/{gitGraphDiagram-PVQCEYII.oMjFeZRt.js → gitGraphDiagram-PVQCEYII.C_h4o8Bn.js} +1 -1
  33. package/web/_astro/{index.CA3GTZM6.css → index.BSoQ0ZL0.css} +1 -1
  34. package/web/_astro/{infoDiagram-5YYISTIA.kaio3YzK.js → infoDiagram-5YYISTIA.DmMa9Qku.js} +1 -1
  35. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CXQfMyeF.js → ishikawaDiagram-YF4QCWOH.GWMrgoLC.js} +1 -1
  36. package/web/_astro/{journeyDiagram-JHISSGLW.AvNKMzx6.js → journeyDiagram-JHISSGLW.BWABlU6N.js} +1 -1
  37. package/web/_astro/{kanban-definition-UN3LZRKU.kv3cZGdE.js → kanban-definition-UN3LZRKU.MbfHFA2-.js} +1 -1
  38. package/web/_astro/{linear.CfLoHWjz.js → linear.BmIHCCO9.js} +1 -1
  39. package/web/_astro/{mermaid.core.BSV8pBn-.js → mermaid.core.CpHEEQAK.js} +4 -4
  40. package/web/_astro/{mindmap-definition-RKZ34NQL.CbL_3b32.js → mindmap-definition-RKZ34NQL.DYK94Ave.js} +1 -1
  41. package/web/_astro/{pieDiagram-4H26LBE5.5BXdOLRb.js → pieDiagram-4H26LBE5.CnEGAkqK.js} +1 -1
  42. package/web/_astro/{quadrantDiagram-W4KKPZXB.krXYbhlh.js → quadrantDiagram-W4KKPZXB.CsByTJjg.js} +1 -1
  43. package/web/_astro/{requirementDiagram-4Y6WPE33.COO4LapZ.js → requirementDiagram-4Y6WPE33.AL6ZRSrQ.js} +1 -1
  44. package/web/_astro/{sankeyDiagram-5OEKKPKP.BvL0Jbdx.js → sankeyDiagram-5OEKKPKP.BLAeuWlE.js} +1 -1
  45. package/web/_astro/{sequenceDiagram-3UESZ5HK.Dk7Tu15E.js → sequenceDiagram-3UESZ5HK.BGpsvxUR.js} +1 -1
  46. package/web/_astro/{stateDiagram-AJRCARHV.Cqh_KmEW.js → stateDiagram-AJRCARHV.BBk0gd25.js} +1 -1
  47. package/web/_astro/{stateDiagram-v2-BHNVJYJU.WapPgDCy.js → stateDiagram-v2-BHNVJYJU.BrqvmSUS.js} +1 -1
  48. package/web/_astro/{timeline-definition-PNZ67QCA.CEVdiX0n.js → timeline-definition-PNZ67QCA.DkZf5CQT.js} +1 -1
  49. package/web/_astro/{vennDiagram-CIIHVFJN.Cd9A63eF.js → vennDiagram-CIIHVFJN.BZvWwMZF.js} +1 -1
  50. package/web/_astro/{wardley-L42UT6IY.DEiaHiIT.js → wardley-L42UT6IY.HfB08mt3.js} +1 -1
  51. package/web/_astro/{wardleyDiagram-YWT4CUSO.B7nGP61E.js → wardleyDiagram-YWT4CUSO.DgpZzmgA.js} +1 -1
  52. package/web/_astro/{xychartDiagram-2RQKCTM6.Bq8CHmxC.js → xychartDiagram-2RQKCTM6.BlgrPsbQ.js} +1 -1
  53. package/web/index.html +2 -2
  54. package/web/_astro/BoardApp.Bcfvp2GH.js +0 -1
  55. package/web/_astro/channel.vSp82e1U.js +0 -1
package/spur.js CHANGED
@@ -60765,6 +60765,41 @@ var init_system_event_tap = __esm(() => {
60765
60765
  init_event_names();
60766
60766
  });
60767
60767
 
60768
+ // ../../packages/app/src/services/system-event-emitter.ts
60769
+ class SystemEventEmitter {
60770
+ dao;
60771
+ logger;
60772
+ constructor(dao2, logger3) {
60773
+ this.dao = dao2;
60774
+ this.logger = logger3;
60775
+ }
60776
+ async emit(event2) {
60777
+ const entry = systemEventCatalogEntry(event2.event);
60778
+ if (!entry)
60779
+ return;
60780
+ try {
60781
+ await this.dao.insert({
60782
+ id: createId("sev"),
60783
+ event_name: entry.name,
60784
+ occurred_at: event2.at,
60785
+ actor: extractSystemEventActor(event2),
60786
+ payload_json: safeStringify(normalizeSystemEventPayload(entry, event2))
60787
+ });
60788
+ await this.dao.prune(SYSTEM_EVENTS_CAP);
60789
+ } catch (error51) {
60790
+ this.logger.warn("system_events emitter: persist failed", {
60791
+ event: event2.event,
60792
+ error: error51 instanceof Error ? `${error51.name}: ${error51.message}` : String(error51)
60793
+ });
60794
+ }
60795
+ }
60796
+ }
60797
+ var init_system_event_emitter = __esm(() => {
60798
+ init_src2();
60799
+ init_event_names();
60800
+ init_system_event_tap();
60801
+ });
60802
+
60768
60803
  // ../../packages/app/src/services/task-check.ts
60769
60804
  import { dirname as dirname9, join as join9 } from "path";
60770
60805
  function isPlaceholderBody(body) {
@@ -62482,11 +62517,282 @@ var init_team_service = __esm(() => {
62482
62517
  init_planning_folders();
62483
62518
  });
62484
62519
 
62520
+ // ../../packages/app/src/services/token-ledger-service.ts
62521
+ import { closeSync as closeSync2, existsSync as existsSync5, fstatSync, openSync as openSync2, readSync } from "fs";
62522
+ import { join as join11 } from "path";
62523
+ function isSparseToolActivity(events2) {
62524
+ return events2.length === 0 || !events2.some((e) => TOOL_ACTIVITY_TYPES.has(e.type));
62525
+ }
62526
+ function clampToolUseLimit(raw) {
62527
+ if (raw === undefined || !Number.isFinite(raw) || raw <= 0) {
62528
+ return TOKEN_LEDGER_DEFAULT_LIMIT;
62529
+ }
62530
+ return Math.min(Math.floor(raw), TOKEN_LEDGER_MAX_LIMIT);
62531
+ }
62532
+ function parseLedgerLine(line) {
62533
+ const trimmed = line.trim();
62534
+ if (!trimmed)
62535
+ return null;
62536
+ try {
62537
+ const obj = JSON.parse(trimmed);
62538
+ if (typeof obj.ts !== "string" || typeof obj.session !== "string" || typeof obj.type !== "string") {
62539
+ return null;
62540
+ }
62541
+ const event2 = {
62542
+ ts: obj.ts,
62543
+ session: obj.session,
62544
+ type: obj.type
62545
+ };
62546
+ if (typeof obj.file === "string")
62547
+ event2.file = obj.file;
62548
+ if (typeof obj.summary === "string")
62549
+ event2.summary = obj.summary;
62550
+ if (typeof obj.tokens === "number" && Number.isFinite(obj.tokens) && obj.tokens >= 0) {
62551
+ event2.tokens = obj.tokens;
62552
+ }
62553
+ if (typeof obj.action === "string")
62554
+ event2.action = obj.action;
62555
+ if (typeof obj.sessionId === "string")
62556
+ event2.sessionId = obj.sessionId;
62557
+ if (typeof obj.agent === "string")
62558
+ event2.agent = obj.agent;
62559
+ if (typeof obj.model === "string")
62560
+ event2.model = obj.model;
62561
+ if (obj.totals !== null && typeof obj.totals === "object") {
62562
+ const t = obj.totals;
62563
+ if (typeof t.reads === "number" && typeof t.writes === "number" && typeof t.tokens === "number") {
62564
+ event2.totals = { reads: t.reads, writes: t.writes, tokens: t.tokens };
62565
+ }
62566
+ }
62567
+ return event2;
62568
+ } catch {
62569
+ return null;
62570
+ }
62571
+ }
62572
+ function tailTokenLedgerFile(filePath, limit, chunkSize = DEFAULT_CHUNK, before) {
62573
+ if (!existsSync5(filePath)) {
62574
+ return { events: [], truncated: false };
62575
+ }
62576
+ const target = limit + 1;
62577
+ const fd = openSync2(filePath, "r");
62578
+ try {
62579
+ const { size } = fstatSync(fd);
62580
+ if (size === 0)
62581
+ return { events: [], truncated: false };
62582
+ let pos = size;
62583
+ let carry = "";
62584
+ const events2 = [];
62585
+ while (pos > 0 && events2.length < target) {
62586
+ const n2 = Math.min(chunkSize, pos);
62587
+ pos -= n2;
62588
+ const buf = Buffer.alloc(n2);
62589
+ readSync(fd, buf, 0, n2, pos);
62590
+ const text4 = buf.toString("utf8") + carry;
62591
+ const parts = text4.split(`
62592
+ `);
62593
+ if (pos > 0) {
62594
+ carry = parts.shift() ?? "";
62595
+ } else {
62596
+ carry = "";
62597
+ }
62598
+ for (let i2 = parts.length - 1;i2 >= 0; i2--) {
62599
+ const evt = parseLedgerLine(parts[i2] ?? "");
62600
+ if (!evt)
62601
+ continue;
62602
+ if (before !== undefined && evt.ts >= before)
62603
+ continue;
62604
+ events2.push(evt);
62605
+ if (events2.length >= target)
62606
+ break;
62607
+ }
62608
+ }
62609
+ const truncated = events2.length > limit;
62610
+ return {
62611
+ events: events2.slice(0, limit),
62612
+ truncated
62613
+ };
62614
+ } finally {
62615
+ closeSync2(fd);
62616
+ }
62617
+ }
62618
+
62619
+ class TokenLedgerService {
62620
+ ledgerPath;
62621
+ chunkSize;
62622
+ constructor(options) {
62623
+ this.ledgerPath = options.ledgerPath ?? join11(options.cwd, TOKEN_LEDGER_RELATIVE_PATH);
62624
+ this.chunkSize = options.chunkSize ?? DEFAULT_CHUNK;
62625
+ }
62626
+ get path() {
62627
+ return this.ledgerPath;
62628
+ }
62629
+ snapshot(limitRawOrOpts, maybeBefore) {
62630
+ let limitRaw;
62631
+ let before;
62632
+ if (typeof limitRawOrOpts === "object" && limitRawOrOpts !== null) {
62633
+ limitRaw = limitRawOrOpts.limit;
62634
+ before = limitRawOrOpts.before;
62635
+ } else {
62636
+ limitRaw = limitRawOrOpts;
62637
+ before = maybeBefore;
62638
+ }
62639
+ if (before !== undefined && (typeof before !== "string" || before.length === 0)) {
62640
+ before = undefined;
62641
+ }
62642
+ const limit = clampToolUseLimit(limitRaw);
62643
+ const capturedAt = new Date().toISOString();
62644
+ const { events: raw, truncated } = tailTokenLedgerFile(this.ledgerPath, limit, this.chunkSize, before);
62645
+ const events2 = raw.map((e, seq) => ({ ...e, seq }));
62646
+ const sparseToolActivity = isSparseToolActivity(events2);
62647
+ const oldest = events2.length > 0 ? events2[events2.length - 1] : undefined;
62648
+ const nextBefore = truncated && oldest ? oldest.ts : null;
62649
+ return {
62650
+ events: events2,
62651
+ count: events2.length,
62652
+ limit,
62653
+ truncated,
62654
+ path: this.ledgerPath,
62655
+ capturedAt,
62656
+ sparseToolActivity,
62657
+ nextBefore
62658
+ };
62659
+ }
62660
+ }
62661
+ var TOOL_ACTIVITY_TYPES, TOKEN_LEDGER_DEFAULT_LIMIT = 200, TOKEN_LEDGER_MAX_LIMIT = 1000, TOKEN_LEDGER_RELATIVE_PATH, DEFAULT_CHUNK;
62662
+ var init_token_ledger_service = __esm(() => {
62663
+ TOOL_ACTIVITY_TYPES = new Set(["read", "write", "bash", "grep", "glob"]);
62664
+ TOKEN_LEDGER_RELATIVE_PATH = join11(".spur", "context", "token-ledger.jsonl");
62665
+ DEFAULT_CHUNK = 64 * 1024;
62666
+ });
62667
+
62668
+ // ../../packages/app/src/services/token-ledger-watcher.ts
62669
+ import { closeSync as closeSync3, existsSync as existsSync6, fstatSync as fstatSync2, openSync as openSync3, readSync as readSync2, watch } from "fs";
62670
+
62671
+ class TokenLedgerWatcher {
62672
+ ledgerPath;
62673
+ debounceMs;
62674
+ listeners = new Set;
62675
+ watcher;
62676
+ offset = 0;
62677
+ debounceTimer;
62678
+ started = false;
62679
+ carry = "";
62680
+ constructor(options) {
62681
+ this.ledgerPath = options.ledgerPath;
62682
+ this.debounceMs = options.debounceMs ?? 50;
62683
+ }
62684
+ start() {
62685
+ if (this.started)
62686
+ return;
62687
+ this.started = true;
62688
+ this.offset = this.currentSize();
62689
+ try {
62690
+ const target = existsSync6(this.ledgerPath) ? this.ledgerPath : this.parentDir();
62691
+ this.watcher = watch(target, () => this.schedulePoll());
62692
+ } catch {
62693
+ this.watcher = undefined;
62694
+ }
62695
+ }
62696
+ stop() {
62697
+ this.started = false;
62698
+ if (this.debounceTimer)
62699
+ clearTimeout(this.debounceTimer);
62700
+ this.debounceTimer = undefined;
62701
+ try {
62702
+ this.watcher?.close();
62703
+ } catch {}
62704
+ this.watcher = undefined;
62705
+ this.listeners.clear();
62706
+ }
62707
+ subscribe(listener) {
62708
+ this.listeners.add(listener);
62709
+ if (!this.started)
62710
+ this.start();
62711
+ return () => {
62712
+ this.listeners.delete(listener);
62713
+ if (this.listeners.size === 0)
62714
+ this.stop();
62715
+ };
62716
+ }
62717
+ get subscriberCount() {
62718
+ return this.listeners.size;
62719
+ }
62720
+ parentDir() {
62721
+ const idx = Math.max(this.ledgerPath.lastIndexOf("/"), this.ledgerPath.lastIndexOf("\\"));
62722
+ return idx > 0 ? this.ledgerPath.slice(0, idx) : ".";
62723
+ }
62724
+ currentSize() {
62725
+ if (!existsSync6(this.ledgerPath))
62726
+ return 0;
62727
+ try {
62728
+ const fd = openSync3(this.ledgerPath, "r");
62729
+ try {
62730
+ return fstatSync2(fd).size;
62731
+ } finally {
62732
+ closeSync3(fd);
62733
+ }
62734
+ } catch {
62735
+ return 0;
62736
+ }
62737
+ }
62738
+ schedulePoll() {
62739
+ if (this.debounceTimer)
62740
+ clearTimeout(this.debounceTimer);
62741
+ this.debounceTimer = setTimeout(() => {
62742
+ this.debounceTimer = undefined;
62743
+ this.pollNewBytes();
62744
+ }, this.debounceMs);
62745
+ }
62746
+ pollNewBytes() {
62747
+ if (!existsSync6(this.ledgerPath)) {
62748
+ this.offset = 0;
62749
+ this.carry = "";
62750
+ return;
62751
+ }
62752
+ let size;
62753
+ try {
62754
+ const fd = openSync3(this.ledgerPath, "r");
62755
+ try {
62756
+ size = fstatSync2(fd).size;
62757
+ if (size < this.offset) {
62758
+ this.offset = 0;
62759
+ this.carry = "";
62760
+ }
62761
+ if (size === this.offset)
62762
+ return;
62763
+ const len = size - this.offset;
62764
+ const buf = Buffer.alloc(len);
62765
+ readSync2(fd, buf, 0, len, this.offset);
62766
+ this.offset = size;
62767
+ const text4 = this.carry + buf.toString("utf8");
62768
+ const parts = text4.split(`
62769
+ `);
62770
+ this.carry = parts.pop() ?? "";
62771
+ for (const line of parts) {
62772
+ const evt = parseLedgerLine(line);
62773
+ if (!evt)
62774
+ continue;
62775
+ for (const listener of this.listeners) {
62776
+ try {
62777
+ listener(evt);
62778
+ } catch {}
62779
+ }
62780
+ }
62781
+ } finally {
62782
+ closeSync3(fd);
62783
+ }
62784
+ } catch {}
62785
+ }
62786
+ }
62787
+ var init_token_ledger_watcher = __esm(() => {
62788
+ init_token_ledger_service();
62789
+ });
62790
+
62485
62791
  // ../../packages/app/src/workflow/actions/agent-run.ts
62486
62792
  import { execFile } from "child_process";
62487
- import { existsSync as existsSync5 } from "fs";
62793
+ import { existsSync as existsSync7 } from "fs";
62488
62794
  import { mkdir, writeFile } from "fs/promises";
62489
- import { dirname as dirname10, isAbsolute as isAbsolute2, join as join11 } from "path";
62795
+ import { dirname as dirname10, isAbsolute as isAbsolute2, join as join12 } from "path";
62490
62796
  import { promisify as promisify3 } from "util";
62491
62797
 
62492
62798
  class AgentRunActionRunner {
@@ -62540,13 +62846,13 @@ class AgentRunActionRunner {
62540
62846
  const { exitCode: exitCode2, answer } = captured;
62541
62847
  const ok2 = exitCode2 === 0;
62542
62848
  if (answerFile !== undefined) {
62543
- const target = isAbsolute2(answerFile) ? answerFile : join11(cwd, answerFile);
62849
+ const target = isAbsolute2(answerFile) ? answerFile : join12(cwd, answerFile);
62544
62850
  await mkdir(dirname10(target), { recursive: true });
62545
62851
  await writeFile(target, answer, "utf8");
62546
62852
  }
62547
62853
  if (ok2 && expectFile !== undefined) {
62548
- const target = isAbsolute2(expectFile) ? expectFile : join11(cwd, expectFile);
62549
- if (!existsSync5(target)) {
62854
+ const target = isAbsolute2(expectFile) ? expectFile : join12(cwd, expectFile);
62855
+ if (!existsSync7(target)) {
62550
62856
  return {
62551
62857
  ok: false,
62552
62858
  data: { exitCode: exitCode2, agent: agentLabel, answer },
@@ -62567,8 +62873,8 @@ class AgentRunActionRunner {
62567
62873
  const exitCode = await this.agentService.run(input, flags);
62568
62874
  const ok = exitCode === 0;
62569
62875
  if (ok && expectFile !== undefined) {
62570
- const target = isAbsolute2(expectFile) ? expectFile : join11(cwd, expectFile);
62571
- if (!existsSync5(target)) {
62876
+ const target = isAbsolute2(expectFile) ? expectFile : join12(cwd, expectFile);
62877
+ if (!existsSync7(target)) {
62572
62878
  return {
62573
62879
  ok: false,
62574
62880
  data: { exitCode, agent: agentLabel },
@@ -62645,7 +62951,7 @@ async function writePartialWorkArtifact(context4, agentLabel, model, captured, c
62645
62951
  ""
62646
62952
  ].join(`
62647
62953
  `);
62648
- const target = join11(cwd, ".spur", "run", `${context4.runId}-${context4.stateOrNodeId}-partial.md`);
62954
+ const target = join12(cwd, ".spur", "run", `${context4.runId}-${context4.stateOrNodeId}-partial.md`);
62649
62955
  await mkdir(dirname10(target), { recursive: true });
62650
62956
  await writeFile(target, body, "utf8");
62651
62957
  } catch {}
@@ -63309,7 +63615,7 @@ var now = () => new Date().toISOString();
63309
63615
  // ../../packages/app/src/services/workflow-service.ts
63310
63616
  import { access as access2, lstat, readdir as readdir3, readFile as readFile2, realpath } from "fs/promises";
63311
63617
  import { homedir as homedir3 } from "os";
63312
- import { join as join12, resolve as resolve3 } from "path";
63618
+ import { join as join13, resolve as resolve3 } from "path";
63313
63619
  function signalSubprocess(pid) {
63314
63620
  try {
63315
63621
  process.kill(-pid, "SIGTERM");
@@ -63513,7 +63819,7 @@ class WorkflowAppService {
63513
63819
  }
63514
63820
  async list(workflowPaths = [".spur/workflows/"]) {
63515
63821
  const projectRoot = this.ctx.cwd;
63516
- const globalRoot = join12(homedir3(), ".config", "spur");
63822
+ const globalRoot = join13(homedir3(), ".config", "spur");
63517
63823
  const layers = [];
63518
63824
  const entries = [];
63519
63825
  const scannedPaths = new Set;
@@ -63849,6 +64155,7 @@ var isActionStarted = (e) => ("kind" in e) && ("node" in e), isActionFinished =
63849
64155
  // ../../packages/app/src/index.ts
63850
64156
  var exports_src2 = {};
63851
64157
  __export(exports_src2, {
64158
+ tailTokenLedgerFile: () => tailTokenLedgerFile,
63852
64159
  systemEventCatalogEntry: () => systemEventCatalogEntry,
63853
64160
  resolvePlanningFolders: () => resolvePlanningFolders,
63854
64161
  renderTesting: () => renderTesting,
@@ -63861,6 +64168,7 @@ __export(exports_src2, {
63861
64168
  readVerdict: () => readVerdict,
63862
64169
  parseVerdict: () => parseVerdict,
63863
64170
  parsePsOutput: () => parsePsOutput,
64171
+ parseLedgerLine: () => parseLedgerLine,
63864
64172
  parseEtimeToSeconds: () => parseEtimeToSeconds,
63865
64173
  normalizeSystemEventPayload: () => normalizeSystemEventPayload,
63866
64174
  hitlConfirmDefault: () => hitlConfirmDefault,
@@ -63868,14 +64176,21 @@ __export(exports_src2, {
63868
64176
  extractSystemEventActor: () => extractSystemEventActor,
63869
64177
  deriveVerdict: () => deriveVerdict,
63870
64178
  createPsProcessInspector: () => createPsProcessInspector,
64179
+ clampToolUseLimit: () => clampToolUseLimit,
63871
64180
  bridgeEventBus: () => bridgeEventBus,
63872
64181
  WorkflowAppService: () => WorkflowAppService,
63873
64182
  UnsupportedProcessPlatformError: () => UnsupportedProcessPlatformError,
64183
+ TokenLedgerWatcher: () => TokenLedgerWatcher,
64184
+ TokenLedgerService: () => TokenLedgerService,
63874
64185
  TeamService: () => TeamService,
63875
64186
  TaskService: () => TaskService,
63876
64187
  TaskCheckService: () => TaskCheckService,
64188
+ TOKEN_LEDGER_RELATIVE_PATH: () => TOKEN_LEDGER_RELATIVE_PATH,
64189
+ TOKEN_LEDGER_MAX_LIMIT: () => TOKEN_LEDGER_MAX_LIMIT,
64190
+ TOKEN_LEDGER_DEFAULT_LIMIT: () => TOKEN_LEDGER_DEFAULT_LIMIT,
63877
64191
  TASK_LIFECYCLE_PROFILE: () => TASK_LIFECYCLE_PROFILE,
63878
64192
  TASK_ACTION_COMMANDS: () => TASK_ACTION_COMMANDS,
64193
+ SystemEventEmitter: () => SystemEventEmitter,
63879
64194
  SupervisorService: () => SupervisorService,
63880
64195
  SYSTEM_EVENT_STREAMED_NAMES: () => SYSTEM_EVENT_STREAMED_NAMES,
63881
64196
  SYSTEM_EVENT_PREFIXES: () => SYSTEM_EVENT_PREFIXES,
@@ -63927,11 +64242,14 @@ var init_src3 = __esm(() => {
63927
64242
  init_process_inspector();
63928
64243
  init_process_inventory_service();
63929
64244
  init_rule_service();
64245
+ init_system_event_emitter();
63930
64246
  init_system_event_tap();
63931
64247
  init_task_check();
63932
64248
  init_task_record();
63933
64249
  init_task_service();
63934
64250
  init_team_service();
64251
+ init_token_ledger_service();
64252
+ init_token_ledger_watcher();
63935
64253
  init_workflow_service();
63936
64254
  init_agent_run();
63937
64255
  init_file_exists();
@@ -71378,13 +71696,38 @@ ${prompt}`;
71378
71696
  // src/commands/feature.ts
71379
71697
  init_src3();
71380
71698
 
71699
+ // src/planning-emitter.ts
71700
+ init_src3();
71701
+ init_src2();
71702
+ function makeWarnLogger(context4) {
71703
+ return {
71704
+ warn: (msg, data) => {
71705
+ context4.output.error(`${msg}${data ? ` ${JSON.stringify(data)}` : ""}`);
71706
+ }
71707
+ };
71708
+ }
71709
+ function makePlanningEmitter(context4) {
71710
+ let cached2;
71711
+ const logger3 = makeWarnLogger(context4);
71712
+ return {
71713
+ async emit(event2) {
71714
+ try {
71715
+ cached2 ??= new SystemEventEmitter(new SystemEventDao(await context4.getDb()), logger3);
71716
+ await cached2.emit(event2);
71717
+ } catch (error51) {
71718
+ context4.output.error(`system_events emitter: ${error51 instanceof Error ? error51.message : String(error51)}`);
71719
+ }
71720
+ }
71721
+ };
71722
+ }
71723
+
71381
71724
  // src/workflow/make-lifecycle-adapter.ts
71382
71725
  init_src3();
71383
71726
  init_loader();
71384
71727
  init_src2();
71385
- import { existsSync as existsSync6 } from "fs";
71728
+ import { existsSync as existsSync8 } from "fs";
71386
71729
  import { homedir as homedir4 } from "os";
71387
- import { join as join13, resolve as resolve4 } from "path";
71730
+ import { join as join14, resolve as resolve4 } from "path";
71388
71731
 
71389
71732
  // src/workflow/resolve-spur-bin.ts
71390
71733
  import { basename as basename2 } from "path";
@@ -71402,23 +71745,23 @@ function resolveSpurBin() {
71402
71745
  }
71403
71746
 
71404
71747
  // src/workflow/make-lifecycle-adapter.ts
71405
- var GLOBAL_CONFIG_DIR = join13(".config", "spur");
71748
+ var GLOBAL_CONFIG_DIR = join14(".config", "spur");
71406
71749
  function globalConfigRoot(context4) {
71407
71750
  const override = context4.env.SPUR_GLOBAL_RULES_DIR;
71408
- return override !== undefined && override.length > 0 ? resolve4(context4.cwd, override) : join13(homedir4(), GLOBAL_CONFIG_DIR);
71751
+ return override !== undefined && override.length > 0 ? resolve4(context4.cwd, override) : join14(homedir4(), GLOBAL_CONFIG_DIR);
71409
71752
  }
71410
71753
  function resolveWorkflowPath(context4, profile) {
71411
71754
  const bundledRoot = bundledConfigRoot();
71412
71755
  if (bundledRoot !== null) {
71413
- const bundledPath = join13(bundledRoot, "workflows", `${profile.workflowName}.yaml`);
71414
- if (existsSync6(bundledPath))
71756
+ const bundledPath = join14(bundledRoot, "workflows", `${profile.workflowName}.yaml`);
71757
+ if (existsSync8(bundledPath))
71415
71758
  return bundledPath;
71416
71759
  }
71417
- const projectPath = join13(context4.cwd, ".spur", "workflows", `${profile.workflowName}.yaml`);
71418
- if (existsSync6(projectPath))
71760
+ const projectPath = join14(context4.cwd, ".spur", "workflows", `${profile.workflowName}.yaml`);
71761
+ if (existsSync8(projectPath))
71419
71762
  return projectPath;
71420
- const globalPath = join13(globalConfigRoot(context4), "workflows", `${profile.workflowName}.yaml`);
71421
- if (existsSync6(globalPath))
71763
+ const globalPath = join14(globalConfigRoot(context4), "workflows", `${profile.workflowName}.yaml`);
71764
+ if (existsSync8(globalPath))
71422
71765
  return globalPath;
71423
71766
  return null;
71424
71767
  }
@@ -71701,7 +72044,8 @@ async function makeService(context4, folderOverride) {
71701
72044
  const lifecycle = makeLifecycleAdapter(context4, FEATURE_LIFECYCLE_PROFILE);
71702
72045
  const writeService = new PlanningWriteService({
71703
72046
  fs: context4.fs,
71704
- ...lifecycle ? { lifecycle } : {}
72047
+ ...lifecycle ? { lifecycle } : {},
72048
+ emitter: makePlanningEmitter(context4)
71705
72049
  });
71706
72050
  return new FeatureService({ fs: context4.fs, writeService, featuresDir, tasksDir });
71707
72051
  }
@@ -71771,13 +72115,13 @@ init_loader();
71771
72115
  init_src2();
71772
72116
  init_dist10();
71773
72117
  import { homedir as homedir5 } from "os";
71774
- import { join as join14, resolve as resolve5 } from "path";
72118
+ import { join as join15, resolve as resolve5 } from "path";
71775
72119
 
71776
72120
  // src/config.ts
71777
72121
  var CLI_CONFIG = {
71778
72122
  binaryName: "spur",
71779
72123
  binaryLabel: "spur",
71780
- binaryVersion: "0.3.9",
72124
+ binaryVersion: "0.3.10",
71781
72125
  configDir: ".spur",
71782
72126
  configFile: ".spur/config.yaml",
71783
72127
  databaseFile: ".spur/spur.db"
@@ -71828,8 +72172,8 @@ var SCAFFOLD_MANIFEST = [
71828
72172
  ];
71829
72173
 
71830
72174
  // src/commands/init.ts
71831
- var GLOBAL_CONFIG_DIR2 = join14(".config", "spur");
71832
- var GLOBAL_RULES_DIR2 = join14(GLOBAL_CONFIG_DIR2, "rules");
72175
+ var GLOBAL_CONFIG_DIR2 = join15(".config", "spur");
72176
+ var GLOBAL_RULES_DIR2 = join15(GLOBAL_CONFIG_DIR2, "rules");
71833
72177
  var GLOBAL_CONFIG_EXAMPLE = "config.example.yaml";
71834
72178
  var GLOBAL_CONFIG_FILE2 = "config.yaml";
71835
72179
  var INDEXED_CONTEXT_MARKER = "## Indexed context";
@@ -71856,7 +72200,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
71856
72200
  `;
71857
72201
  function globalRulesRoot(context4) {
71858
72202
  const override = context4.env.SPUR_GLOBAL_RULES_DIR;
71859
- return override !== undefined && override.length > 0 ? resolve5(context4.cwd, override) : join14(homedir5(), GLOBAL_RULES_DIR2);
72203
+ return override !== undefined && override.length > 0 ? resolve5(context4.cwd, override) : join15(homedir5(), GLOBAL_RULES_DIR2);
71860
72204
  }
71861
72205
  async function writeIfNew(context4, path9, content, force, result) {
71862
72206
  if (!force && await context4.fs.exists(path9)) {
@@ -71873,11 +72217,11 @@ async function seedGlobalRules(context4) {
71873
72217
  const target = globalRulesRoot(context4);
71874
72218
  let written = 0;
71875
72219
  for (const relPath of await listBundledRuleFiles()) {
71876
- const destination = join14(target, relPath);
72220
+ const destination = join15(target, relPath);
71877
72221
  if (await context4.fs.exists(destination))
71878
72222
  continue;
71879
- await context4.fs.ensureDir(join14(target, ...relPath.split("/").slice(0, -1)));
71880
- await context4.fs.writeFile(destination, await context4.fs.readFile(join14(source, relPath)));
72223
+ await context4.fs.ensureDir(join15(target, ...relPath.split("/").slice(0, -1)));
72224
+ await context4.fs.writeFile(destination, await context4.fs.readFile(join15(source, relPath)));
71881
72225
  written += 1;
71882
72226
  }
71883
72227
  return written;
@@ -71887,20 +72231,20 @@ async function seedGlobalConfig(context4) {
71887
72231
  if (source === null)
71888
72232
  return 0;
71889
72233
  const globalOverride = context4.env.SPUR_GLOBAL_RULES_DIR;
71890
- const target = globalOverride !== undefined && globalOverride.length > 0 ? resolve5(context4.cwd, globalOverride) : join14(homedir5(), GLOBAL_CONFIG_DIR2);
72234
+ const target = globalOverride !== undefined && globalOverride.length > 0 ? resolve5(context4.cwd, globalOverride) : join15(homedir5(), GLOBAL_CONFIG_DIR2);
71891
72235
  let written = 0;
71892
72236
  for (const relPath of listBundledConfigFiles()) {
71893
72237
  if (relPath === GLOBAL_CONFIG_EXAMPLE)
71894
72238
  continue;
71895
- const destination = join14(target, relPath);
72239
+ const destination = join15(target, relPath);
71896
72240
  if (await context4.fs.exists(destination))
71897
72241
  continue;
71898
- await context4.fs.ensureDir(join14(target, ...relPath.split("/").slice(0, -1)));
71899
- await context4.fs.writeFile(destination, await context4.fs.readFile(join14(source, relPath)));
72242
+ await context4.fs.ensureDir(join15(target, ...relPath.split("/").slice(0, -1)));
72243
+ await context4.fs.writeFile(destination, await context4.fs.readFile(join15(source, relPath)));
71900
72244
  written += 1;
71901
72245
  }
71902
- const examplePath = join14(source, GLOBAL_CONFIG_EXAMPLE);
71903
- const globalConfigPath = join14(target, GLOBAL_CONFIG_FILE2);
72246
+ const examplePath = join15(source, GLOBAL_CONFIG_EXAMPLE);
72247
+ const globalConfigPath = join15(target, GLOBAL_CONFIG_FILE2);
71904
72248
  if (await context4.fs.exists(examplePath) && !await context4.fs.exists(globalConfigPath)) {
71905
72249
  await context4.fs.ensureDir(target);
71906
72250
  await context4.fs.writeFile(globalConfigPath, await context4.fs.readFile(examplePath));
@@ -71914,7 +72258,7 @@ function registerInitCommand(program2, context4) {
71914
72258
  const force = options.force === true;
71915
72259
  const minimal = options.minimal === true;
71916
72260
  const projectName = options.name ?? "default";
71917
- const configPath = join14(context4.cwd, CLI_CONFIG.configFile);
72261
+ const configPath = join15(context4.cwd, CLI_CONFIG.configFile);
71918
72262
  if (!force && await context4.fs.exists(configPath)) {
71919
72263
  const message = `Already initialized: ${CLI_CONFIG.configFile}. Use --force to overwrite.`;
71920
72264
  context4.output.write(json3 ? toJson2({ ok: false, reason: "already-initialized", config: CLI_CONFIG.configFile }) : message);
@@ -71949,13 +72293,13 @@ function registerInitCommand(program2, context4) {
71949
72293
  ].join(`
71950
72294
  `)}
71951
72295
  `;
71952
- await context4.fs.ensureDir(join14(context4.cwd, CLI_CONFIG.configDir));
72296
+ await context4.fs.ensureDir(join15(context4.cwd, CLI_CONFIG.configDir));
71953
72297
  await context4.fs.writeFile(configPath, configYaml);
71954
72298
  result.created.push(configPath);
71955
- const agentsDir = join14(context4.cwd, CLI_CONFIG.configDir, "agents");
72299
+ const agentsDir = join15(context4.cwd, CLI_CONFIG.configDir, "agents");
71956
72300
  await context4.fs.ensureDir(agentsDir);
71957
- await writeIfNew(context4, join14(agentsDir, ".gitkeep"), "", force, result);
71958
- const gitignorePath = join14(context4.cwd, ".gitignore");
72301
+ await writeIfNew(context4, join15(agentsDir, ".gitkeep"), "", force, result);
72302
+ const gitignorePath = join15(context4.cwd, ".gitignore");
71959
72303
  const contextEntry = ".spur/context/";
71960
72304
  if (await context4.fs.exists(gitignorePath)) {
71961
72305
  const existing = await context4.fs.readFile(gitignorePath);
@@ -71975,20 +72319,20 @@ ${contextEntry}
71975
72319
  const configRoot = bundledConfigRoot();
71976
72320
  if (configRoot !== null) {
71977
72321
  for (const relPath of listBundledProjectSeedFiles()) {
71978
- const sourcePath = join14(configRoot, relPath);
72322
+ const sourcePath = join15(configRoot, relPath);
71979
72323
  if (!await context4.fs.exists(sourcePath))
71980
72324
  continue;
71981
- const targetPath = join14(context4.cwd, CLI_CONFIG.configDir, relPath);
71982
- await context4.fs.ensureDir(join14(targetPath, ".."));
72325
+ const targetPath = join15(context4.cwd, CLI_CONFIG.configDir, relPath);
72326
+ await context4.fs.ensureDir(join15(targetPath, ".."));
71983
72327
  await writeIfNew(context4, targetPath, await context4.fs.readFile(sourcePath), force, result);
71984
72328
  }
71985
72329
  for (const entry of SCAFFOLD_MANIFEST) {
71986
- const sourcePath = join14(configRoot, entry.source);
72330
+ const sourcePath = join15(configRoot, entry.source);
71987
72331
  if (!await context4.fs.exists(sourcePath))
71988
72332
  continue;
71989
- const baseDir = entry.root === true ? context4.cwd : join14(context4.cwd, CLI_CONFIG.configDir);
71990
- const targetPath = join14(baseDir, entry.target);
71991
- await context4.fs.ensureDir(join14(targetPath, ".."));
72333
+ const baseDir = entry.root === true ? context4.cwd : join15(context4.cwd, CLI_CONFIG.configDir);
72334
+ const targetPath = join15(baseDir, entry.target);
72335
+ await context4.fs.ensureDir(join15(targetPath, ".."));
71992
72336
  const entryForce = entry.preserve === true ? false : force;
71993
72337
  let body = await context4.fs.readFile(sourcePath);
71994
72338
  if (entry.target === "AGENTS.md") {
@@ -71998,7 +72342,7 @@ ${contextEntry}
71998
72342
  }
71999
72343
  }
72000
72344
  }
72001
- const agentsMdPath = join14(context4.cwd, "AGENTS.md");
72345
+ const agentsMdPath = join15(context4.cwd, "AGENTS.md");
72002
72346
  if (await context4.fs.exists(agentsMdPath)) {
72003
72347
  const existing = await context4.fs.readFile(agentsMdPath);
72004
72348
  if (!existing.includes(INDEXED_CONTEXT_MARKER)) {
@@ -72176,10 +72520,10 @@ function parseInterval2(raw) {
72176
72520
 
72177
72521
  // src/commands/migrate.ts
72178
72522
  init_src2();
72179
- import { join as join15 } from "path";
72523
+ import { join as join16 } from "path";
72180
72524
  function registerMigrateCommand(program2, context4) {
72181
72525
  program2.command("migrate").summary("apply CLI-owned schema migrations").option("--json", "Output machine-readable JSON").action(async (options) => {
72182
- const migrations = await loadSqlMigrations(join15(context4.cwd, "drizzle")).catch(() => {
72526
+ const migrations = await loadSqlMigrations(join16(context4.cwd, "drizzle")).catch(() => {
72183
72527
  return;
72184
72528
  });
72185
72529
  const applied = await applyCliMigrations(await context4.getDb(), migrations);
@@ -72431,7 +72775,7 @@ function formatTraceDetail(detail) {
72431
72775
 
72432
72776
  // src/commands/serve.ts
72433
72777
  init_src();
72434
- import { join as join19 } from "path";
72778
+ import { join as join20 } from "path";
72435
72779
 
72436
72780
  // ../server/src/index.ts
72437
72781
  init_src();
@@ -72441,11 +72785,11 @@ init_src3();
72441
72785
  init_src();
72442
72786
  init_loader();
72443
72787
  init_src2();
72444
- import { dirname as dirname12, isAbsolute as isAbsolute3, join as join18 } from "path";
72788
+ import { dirname as dirname12, isAbsolute as isAbsolute3, join as join19 } from "path";
72445
72789
  init_dist5();
72446
72790
 
72447
72791
  // ../server/src/bootstrap.ts
72448
- import { join as join16 } from "path";
72792
+ import { join as join17 } from "path";
72449
72793
 
72450
72794
  // ../../node_modules/.bun/radash@12.1.1/node_modules/radash/dist/esm/async.mjs
72451
72795
  var guard = (func, shouldGuard) => {
@@ -78549,23 +78893,128 @@ function parseLimit(raw2, fallback) {
78549
78893
 
78550
78894
  // ../server/src/modules/observability/index.ts
78551
78895
  init_src3();
78896
+ var ledgerWatcher;
78897
+ var watcherPath;
78898
+ function getLedgerWatcher(path9) {
78899
+ if (!ledgerWatcher || watcherPath !== path9) {
78900
+ ledgerWatcher?.stop();
78901
+ ledgerWatcher = new TokenLedgerWatcher({ ledgerPath: path9 });
78902
+ watcherPath = path9;
78903
+ ledgerWatcher.start();
78904
+ }
78905
+ return ledgerWatcher;
78906
+ }
78907
+ function toolUseSsePayload(event2) {
78908
+ return {
78909
+ type: "tool-use",
78910
+ occurredAt: event2.ts,
78911
+ event: event2
78912
+ };
78913
+ }
78914
+ function handleProcesses(ctx) {
78915
+ return async (c3) => {
78916
+ try {
78917
+ const snapshot = await ctx.processInventory().snapshot();
78918
+ return c3.json(snapshot);
78919
+ } catch (err) {
78920
+ if (err instanceof UnsupportedProcessPlatformError) {
78921
+ return c3.json({ error: err.message, code: err.code }, 501);
78922
+ }
78923
+ const message = err instanceof Error ? err.message : String(err);
78924
+ return c3.json({ error: message }, 500);
78925
+ }
78926
+ };
78927
+ }
78928
+ function handleToolUseGet(ctx) {
78929
+ return (c3) => {
78930
+ try {
78931
+ const limitParam = c3.req.query("limit");
78932
+ let limitRaw;
78933
+ if (limitParam !== undefined) {
78934
+ const parsed = Number.parseInt(limitParam, 10);
78935
+ if (!Number.isNaN(parsed))
78936
+ limitRaw = parsed;
78937
+ }
78938
+ const before = c3.req.query("before") ?? undefined;
78939
+ const limit = clampToolUseLimit(limitRaw);
78940
+ const snapshot = ctx.tokenLedger().snapshot({ limit, before });
78941
+ return c3.json(snapshot);
78942
+ } catch (err) {
78943
+ const message = err instanceof Error ? err.message : String(err);
78944
+ return c3.json({ error: message }, 500);
78945
+ }
78946
+ };
78947
+ }
78948
+ function handleToolUseStream(ctx) {
78949
+ return (c3) => {
78950
+ const closed = { current: false };
78951
+ let heartbeatInterval;
78952
+ let unsubscribe;
78953
+ const signal = c3.req.raw.signal;
78954
+ let closeController = () => {};
78955
+ const teardown = () => {
78956
+ if (closed.current)
78957
+ return;
78958
+ closed.current = true;
78959
+ if (heartbeatInterval)
78960
+ clearInterval(heartbeatInterval);
78961
+ try {
78962
+ unsubscribe?.();
78963
+ } catch {}
78964
+ signal.removeEventListener("abort", teardown);
78965
+ closeController();
78966
+ };
78967
+ const stream = new ReadableStream({
78968
+ start(controller) {
78969
+ const encoder = new TextEncoder;
78970
+ closeController = () => {
78971
+ try {
78972
+ controller.close();
78973
+ } catch {}
78974
+ };
78975
+ if (signal.aborted) {
78976
+ teardown();
78977
+ return;
78978
+ }
78979
+ signal.addEventListener("abort", teardown);
78980
+ heartbeatInterval = setInterval(sendSseKeepalive, 15000, closed, controller, encoder);
78981
+ enqueueSseFrame(closed, controller, encoder, {
78982
+ type: "connected",
78983
+ occurredAt: new Date().toISOString()
78984
+ });
78985
+ try {
78986
+ const path9 = ctx.tokenLedger().path;
78987
+ const watcher = getLedgerWatcher(path9);
78988
+ unsubscribe = watcher.subscribe((event2) => {
78989
+ if (!enqueueSseFrame(closed, controller, encoder, toolUseSsePayload(event2)))
78990
+ teardown();
78991
+ });
78992
+ } catch {
78993
+ enqueueSseFrame(closed, controller, encoder, {
78994
+ type: "error",
78995
+ error: "ledger watch unavailable",
78996
+ occurredAt: new Date().toISOString()
78997
+ });
78998
+ }
78999
+ },
79000
+ cancel() {
79001
+ teardown();
79002
+ }
79003
+ });
79004
+ return c3.newResponse(stream, 200, {
79005
+ "Content-Type": "text/event-stream",
79006
+ "Cache-Control": "no-cache"
79007
+ });
79008
+ };
79009
+ }
78552
79010
  var observabilityModule = {
78553
79011
  name: "observability",
78554
79012
  mount(app, ctx) {
78555
79013
  if (!ctx)
78556
79014
  return;
78557
- app.get("/api/observability/processes", async (c3) => {
78558
- try {
78559
- const snapshot = await ctx.processInventory().snapshot();
78560
- return c3.json(snapshot);
78561
- } catch (err) {
78562
- if (err instanceof UnsupportedProcessPlatformError) {
78563
- return c3.json({ error: err.message, code: err.code }, 501);
78564
- }
78565
- const message = err instanceof Error ? err.message : String(err);
78566
- return c3.json({ error: message }, 500);
78567
- }
78568
- });
79015
+ app.get("/api/observability/processes", handleProcesses(ctx));
79016
+ app.get("/api/observability/tool-use", handleToolUseGet(ctx));
79017
+ app.get("/api/observability/tool-use/stream", handleToolUseStream(ctx));
78569
79018
  }
78570
79019
  };
78571
79020
 
@@ -80803,7 +81252,7 @@ function createApp(appRt, opts) {
80803
81252
  app.use("*", async (c3, next) => {
80804
81253
  const pathname = c3.req.path === "/" ? "/index.html" : c3.req.path;
80805
81254
  try {
80806
- const file2 = Bun.file(join16(webDistPath, pathname));
81255
+ const file2 = Bun.file(join17(webDistPath, pathname));
80807
81256
  if (await file2.exists()) {
80808
81257
  const headers = new Headers({ "content-type": file2.type });
80809
81258
  return new Response(file2.stream(), { headers });
@@ -80816,7 +81265,7 @@ function createApp(appRt, opts) {
80816
81265
  return c3.json({ error: "Not Found" }, 404);
80817
81266
  }
80818
81267
  try {
80819
- const indexFile = Bun.file(join16(webDistPath, "index.html"));
81268
+ const indexFile = Bun.file(join17(webDistPath, "index.html"));
80820
81269
  if (await indexFile.exists()) {
80821
81270
  return new Response(indexFile.stream(), {
80822
81271
  headers: { "content-type": "text/html; charset=utf-8" }
@@ -80837,7 +81286,7 @@ init_src3();
80837
81286
  init_src();
80838
81287
  init_src2();
80839
81288
  init_dist5();
80840
- import { dirname as dirname11, join as join17 } from "path";
81289
+ import { dirname as dirname11, join as join18 } from "path";
80841
81290
  var NOOP_OUTPUT = { write: (_s) => {}, error: (_s) => {} };
80842
81291
 
80843
81292
  class LazyPlanningEventEmitter {
@@ -80872,7 +81321,7 @@ class NotConfiguredError extends Error {
80872
81321
  function createServerContext(appRt, options) {
80873
81322
  const cwd = options.cwd;
80874
81323
  const fs3 = options.fs;
80875
- const dbUrl = options.dbUrl ?? join17(cwd, DEFAULT_DATABASE_URL);
81324
+ const dbUrl = options.dbUrl ?? join18(cwd, DEFAULT_DATABASE_URL);
80876
81325
  const eventsBus = options.eventsBus ?? appRt.events;
80877
81326
  const jobQueueEnabled = options.jobQueueEnabled ?? false;
80878
81327
  const folders = options.folders ?? DEFAULT_PLANNING_FOLDERS;
@@ -80890,6 +81339,7 @@ function createServerContext(appRt, options) {
80890
81339
  let teamSvc;
80891
81340
  let supervisorSvc;
80892
81341
  let processInventorySvc;
81342
+ let tokenLedgerSvc;
80893
81343
  let agentSvc;
80894
81344
  let ruleSvc;
80895
81345
  let workflowSvc;
@@ -80980,6 +81430,12 @@ function createServerContext(appRt, options) {
80980
81430
  }
80981
81431
  return processInventorySvc;
80982
81432
  },
81433
+ tokenLedger() {
81434
+ if (!tokenLedgerSvc) {
81435
+ tokenLedgerSvc = new TokenLedgerService({ cwd });
81436
+ }
81437
+ return tokenLedgerSvc;
81438
+ },
80983
81439
  agentService() {
80984
81440
  agentSvc ??= new AgentService({
80985
81441
  cwd,
@@ -81159,15 +81615,15 @@ async function runTaskActionJob(ctx, env, payload, createAgentService = createTa
81159
81615
  }
81160
81616
  }
81161
81617
  async function resolveWebDistPath(configuredPath) {
81162
- const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath : join18(process.cwd(), configuredPath)] : [
81163
- join18(process.cwd(), "dist/web"),
81164
- join18(import.meta.dir, "web"),
81165
- join18(dirname12(process.execPath), "web"),
81166
- join18(dirname12(process.execPath), "../web"),
81167
- join18(import.meta.dir, "../../../dist/web")
81618
+ const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath : join19(process.cwd(), configuredPath)] : [
81619
+ join19(process.cwd(), "dist/web"),
81620
+ join19(import.meta.dir, "web"),
81621
+ join19(dirname12(process.execPath), "web"),
81622
+ join19(dirname12(process.execPath), "../web"),
81623
+ join19(import.meta.dir, "../../../dist/web")
81168
81624
  ];
81169
81625
  for (const candidate of candidates) {
81170
- if (await Bun.file(join18(candidate, "index.html")).exists()) {
81626
+ if (await Bun.file(join19(candidate, "index.html")).exists()) {
81171
81627
  return candidate;
81172
81628
  }
81173
81629
  }
@@ -81295,7 +81751,7 @@ if (false) {}
81295
81751
 
81296
81752
  // src/commands/serve.ts
81297
81753
  function resolveServeDbUrl(cwd, env, configuredUrl) {
81298
- return env.DATABASE_URL === undefined ? join19(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
81754
+ return env.DATABASE_URL === undefined ? join20(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
81299
81755
  }
81300
81756
  function registerServeCommand(program2, context4) {
81301
81757
  program2.command("serve").summary("start the Spur web server (local fallback)").option("--port <n>", "Server port (env: PORT, default: 3000)", parseInt).option("--host <addr>", "Bind address (env: HOST, default: localhost)").option("--no-open", "Skip opening the browser").option("--cwd <path>", "Working directory", context4.cwd).option("--json", "Output { port, url, pid } and exit").action(async (options) => {
@@ -81334,7 +81790,7 @@ function registerServeCommand(program2, context4) {
81334
81790
  }
81335
81791
 
81336
81792
  // src/commands/status.ts
81337
- import { join as join20 } from "path";
81793
+ import { join as join21 } from "path";
81338
81794
 
81339
81795
  // src/errors.ts
81340
81796
  class CommandError extends Error {
@@ -81405,8 +81861,8 @@ function registerStatusCommand(program2, context4) {
81405
81861
  }
81406
81862
  async function runStatusCore(path9, options, context4) {
81407
81863
  const [packageJsonExists, spurConfigExists, git, agentSpecs] = await Promise.all([
81408
- context4.fs.exists(join20(context4.cwd, "package.json")),
81409
- context4.fs.exists(join20(context4.cwd, ".spur", "config.yaml")),
81864
+ context4.fs.exists(join21(context4.cwd, "package.json")),
81865
+ context4.fs.exists(join21(context4.cwd, ".spur", "config.yaml")),
81410
81866
  gitContext(context4.cwd),
81411
81867
  listAgentSpecIds(context4)
81412
81868
  ]);
@@ -81434,14 +81890,14 @@ async function runStatusCore(path9, options, context4) {
81434
81890
  return status.ok ? 0 : 1;
81435
81891
  }
81436
81892
  async function listAgentSpecIds(context4) {
81437
- const dir = join20(context4.cwd, ".spur", "agents");
81893
+ const dir = join21(context4.cwd, ".spur", "agents");
81438
81894
  if (!await context4.fs.exists(dir))
81439
81895
  return [];
81440
81896
  const entries = await context4.fs.readDir(dir);
81441
81897
  return entries.filter((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml")).map((entry) => entry.replace(/\.ya?ml$/, "")).sort();
81442
81898
  }
81443
81899
  async function readTargetStatus(context4, targetPath) {
81444
- const resolved = join20(context4.cwd, targetPath);
81900
+ const resolved = join21(context4.cwd, targetPath);
81445
81901
  const stat2 = await context4.fs.stat(resolved);
81446
81902
  if (stat2 === null)
81447
81903
  throw new CommandError(`status failed: path does not exist at ${resolved}`);
@@ -81452,8 +81908,8 @@ async function readTargetStatus(context4, targetPath) {
81452
81908
  init_src3();
81453
81909
  init_loader();
81454
81910
  init_src2();
81455
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
81456
- import { join as join21 } from "path";
81911
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
81912
+ import { join as join22 } from "path";
81457
81913
  // schemas/section-matrix.schema.json
81458
81914
  var section_matrix_schema_default = {
81459
81915
  $schema: "http://json-schema.org/draft-07/schema#",
@@ -82308,7 +82764,8 @@ async function makeService2(context4, folderOverride, noLifecycle = false) {
82308
82764
  const lifecycle = noLifecycle ? undefined : makeLifecycleAdapter(context4, TASK_LIFECYCLE_PROFILE);
82309
82765
  const writeService = new PlanningWriteService({
82310
82766
  fs: context4.fs,
82311
- ...lifecycle ? { lifecycle } : {}
82767
+ ...lifecycle ? { lifecycle } : {},
82768
+ emitter: makePlanningEmitter(context4)
82312
82769
  });
82313
82770
  return new TaskService({
82314
82771
  fs: context4.fs,
@@ -82327,16 +82784,16 @@ function loadTemplateContent(projectRoot, variant) {
82327
82784
  return templateContentCache.get(variant);
82328
82785
  if (templateMissSet.has(variant))
82329
82786
  return;
82330
- const localPath = join21(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
82331
- if (existsSync7(localPath)) {
82787
+ const localPath = join22(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
82788
+ if (existsSync9(localPath)) {
82332
82789
  const content = readFileSync8(localPath, "utf8");
82333
82790
  templateContentCache.set(variant, content);
82334
82791
  return content;
82335
82792
  }
82336
82793
  const root = bundledConfigRoot();
82337
82794
  if (root !== null) {
82338
- const templatePath = join21(root, "templates", "task", `${variant}.md`);
82339
- if (existsSync7(templatePath)) {
82795
+ const templatePath = join22(root, "templates", "task", `${variant}.md`);
82796
+ if (existsSync9(templatePath)) {
82340
82797
  const content = readFileSync8(templatePath, "utf8");
82341
82798
  templateContentCache.set(variant, content);
82342
82799
  return content;
@@ -82351,14 +82808,14 @@ function loadTemplateBodies(projectRoot, variant) {
82351
82808
  if (cached2 !== undefined)
82352
82809
  return cached2;
82353
82810
  let bodies = {};
82354
- const localPath = join21(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
82355
- if (existsSync7(localPath)) {
82811
+ const localPath = join22(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
82812
+ if (existsSync9(localPath)) {
82356
82813
  bodies = extractTemplateBodies(readFileSync8(localPath, "utf8"));
82357
82814
  } else {
82358
82815
  const root = bundledConfigRoot();
82359
82816
  if (root !== null) {
82360
- const templatePath = join21(root, "templates", "task", `${variant}.md`);
82361
- if (existsSync7(templatePath)) {
82817
+ const templatePath = join22(root, "templates", "task", `${variant}.md`);
82818
+ if (existsSync9(templatePath)) {
82362
82819
  bodies = extractTemplateBodies(readFileSync8(templatePath, "utf8"));
82363
82820
  }
82364
82821
  }
@@ -82392,8 +82849,8 @@ async function loadSectionMatrix(projectRoot) {
82392
82849
  return promise2;
82393
82850
  }
82394
82851
  async function loadSectionMatrixUncached(projectRoot) {
82395
- const localPath = join21(projectRoot, ".spur", "tasks", "section-matrix.yaml");
82396
- if (existsSync7(localPath)) {
82852
+ const localPath = join22(projectRoot, ".spur", "tasks", "section-matrix.yaml");
82853
+ if (existsSync9(localPath)) {
82397
82854
  const data = await loadStructuredSpurConfig(localPath, {
82398
82855
  validateJsonSchema: true,
82399
82856
  embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
@@ -82402,8 +82859,8 @@ async function loadSectionMatrixUncached(projectRoot) {
82402
82859
  }
82403
82860
  const root = bundledConfigRoot();
82404
82861
  if (root !== null) {
82405
- const matrixPath = join21(root, "tasks", "section-matrix.yaml");
82406
- if (existsSync7(matrixPath)) {
82862
+ const matrixPath = join22(root, "tasks", "section-matrix.yaml");
82863
+ if (existsSync9(matrixPath)) {
82407
82864
  const data = await loadStructuredSpurConfig(matrixPath, {
82408
82865
  validateJsonSchema: true,
82409
82866
  embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
@@ -82848,7 +83305,7 @@ init_src3();
82848
83305
  init_src();
82849
83306
  init_src2();
82850
83307
  init_dist5();
82851
- import { dirname as dirname13, join as join22, resolve as resolve7 } from "path";
83308
+ import { dirname as dirname13, join as join23, resolve as resolve7 } from "path";
82852
83309
  import { isatty as isatty4 } from "tty";
82853
83310
 
82854
83311
  // ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
@@ -84262,7 +84719,7 @@ function createCliContext(options) {
84262
84719
  function noopSetExitCode(_code) {}
84263
84720
  async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, dbUrl) {
84264
84721
  const config4 = buildConfigFromEnv(env);
84265
- const configuredUrl = env.DATABASE_URL === undefined ? join22(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
84722
+ const configuredUrl = env.DATABASE_URL === undefined ? join23(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
84266
84723
  const url2 = dbUrl ?? configuredUrl;
84267
84724
  if (url2 !== IN_MEMORY_DATABASE_URL) {
84268
84725
  await createNodeFileSystem3().ensureDir(dirname13(url2));