infinity-harness 2.8.9 → 2.8.11

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 (192) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/extensions/infinity-harness/index.js +3614 -0
  3. package/dist/extensions/infinity-harness/index.js.map +1 -0
  4. package/dist/src/approval.js +154 -0
  5. package/dist/src/approval.js.map +1 -0
  6. package/dist/src/core/brief.js +309 -0
  7. package/dist/src/core/brief.js.map +1 -0
  8. package/dist/src/core/config.js +539 -0
  9. package/dist/src/core/config.js.map +1 -0
  10. package/dist/src/core/exec.js +106 -0
  11. package/dist/src/core/exec.js.map +1 -0
  12. package/dist/src/core/featureList.js +365 -0
  13. package/dist/src/core/featureList.js.map +1 -0
  14. package/dist/src/core/fsx.js +123 -0
  15. package/dist/src/core/fsx.js.map +1 -0
  16. package/dist/src/core/gates.js +490 -0
  17. package/dist/src/core/gates.js.map +1 -0
  18. package/dist/src/core/init.js +480 -0
  19. package/dist/src/core/init.js.map +1 -0
  20. package/dist/src/core/lock.js +183 -0
  21. package/dist/src/core/lock.js.map +1 -0
  22. package/dist/src/core/modelRouter.js +185 -0
  23. package/dist/src/core/modelRouter.js.map +1 -0
  24. package/dist/src/core/paths.js +129 -0
  25. package/dist/src/core/paths.js.map +1 -0
  26. package/dist/src/core/phases.js +254 -0
  27. package/dist/src/core/phases.js.map +1 -0
  28. package/dist/src/core/plan.js +15 -0
  29. package/dist/src/core/plan.js.map +1 -0
  30. package/dist/src/core/runState.js +126 -0
  31. package/dist/src/core/runState.js.map +1 -0
  32. package/dist/src/core/settings.js +877 -0
  33. package/dist/src/core/settings.js.map +1 -0
  34. package/dist/src/core/skills.js +319 -0
  35. package/dist/src/core/skills.js.map +1 -0
  36. package/dist/src/core/skillsAudit.js +194 -0
  37. package/dist/src/core/skillsAudit.js.map +1 -0
  38. package/dist/src/core/types.js +77 -0
  39. package/dist/src/core/types.js.map +1 -0
  40. package/dist/src/daemon/budget.js +85 -0
  41. package/dist/src/daemon/budget.js.map +1 -0
  42. package/dist/src/daemon/guard.js +122 -0
  43. package/dist/src/daemon/guard.js.map +1 -0
  44. package/dist/src/daemon/index.js +461 -0
  45. package/dist/src/daemon/index.js.map +1 -0
  46. package/dist/src/daemon/isolation.js +84 -0
  47. package/dist/src/daemon/isolation.js.map +1 -0
  48. package/dist/src/daemon/preflight.js +131 -0
  49. package/dist/src/daemon/preflight.js.map +1 -0
  50. package/dist/src/daemon/server.js +174 -0
  51. package/dist/src/daemon/server.js.map +1 -0
  52. package/dist/src/daemon/supervisorState.js +42 -0
  53. package/dist/src/daemon/supervisorState.js.map +1 -0
  54. package/dist/src/daemon/worker.js +239 -0
  55. package/dist/src/daemon/worker.js.map +1 -0
  56. package/dist/src/daemon/worktree.js +102 -0
  57. package/dist/src/daemon/worktree.js.map +1 -0
  58. package/dist/src/escalate.js +312 -0
  59. package/dist/src/escalate.js.map +1 -0
  60. package/dist/src/exec/piWorker.js +630 -0
  61. package/dist/src/exec/piWorker.js.map +1 -0
  62. package/dist/src/goal.js +317 -0
  63. package/dist/src/goal.js.map +1 -0
  64. package/dist/src/goalLoop.js +374 -0
  65. package/dist/src/goalLoop.js.map +1 -0
  66. package/dist/src/goalSpec.js +397 -0
  67. package/dist/src/goalSpec.js.map +1 -0
  68. package/dist/src/goalState.js +255 -0
  69. package/dist/src/goalState.js.map +1 -0
  70. package/dist/src/handoff.js +205 -0
  71. package/dist/src/handoff.js.map +1 -0
  72. package/dist/src/intake.js +203 -0
  73. package/dist/src/intake.js.map +1 -0
  74. package/dist/src/loop.js +592 -0
  75. package/dist/src/loop.js.map +1 -0
  76. package/dist/src/modelRouter.js +216 -0
  77. package/dist/src/modelRouter.js.map +1 -0
  78. package/dist/src/remote.js +246 -0
  79. package/dist/src/remote.js.map +1 -0
  80. package/dist/src/replan.js +262 -0
  81. package/dist/src/replan.js.map +1 -0
  82. package/dist/src/review.js +54 -0
  83. package/dist/src/review.js.map +1 -0
  84. package/dist/src/rework.js +241 -0
  85. package/dist/src/rework.js.map +1 -0
  86. package/dist/src/runState.js +4 -0
  87. package/dist/src/runState.js.map +1 -0
  88. package/dist/src/scheduler.js +270 -0
  89. package/dist/src/scheduler.js.map +1 -0
  90. package/dist/src/supervisor.js +787 -0
  91. package/dist/src/supervisor.js.map +1 -0
  92. package/dist/src/taskList.js +430 -0
  93. package/dist/src/taskList.js.map +1 -0
  94. package/dist/src/ui/config.js +268 -0
  95. package/dist/src/ui/config.js.map +1 -0
  96. package/dist/src/ui/dashboard.js +1288 -0
  97. package/dist/src/ui/dashboard.js.map +1 -0
  98. package/dist/src/ui/display.js +229 -0
  99. package/dist/src/ui/display.js.map +1 -0
  100. package/dist/src/ui/planTree.js +219 -0
  101. package/dist/src/ui/planTree.js.map +1 -0
  102. package/dist/src/ui/theme.js +298 -0
  103. package/dist/src/ui/theme.js.map +1 -0
  104. package/dist/src/ui/viewState.js +65 -0
  105. package/dist/src/ui/viewState.js.map +1 -0
  106. package/dist/src/ui/widget.js +721 -0
  107. package/dist/src/ui/widget.js.map +1 -0
  108. package/dist/src/ui/wizard.js +540 -0
  109. package/dist/src/ui/wizard.js.map +1 -0
  110. package/dist/src/unstuck.js +210 -0
  111. package/dist/src/unstuck.js.map +1 -0
  112. package/dist/src/worker.js +267 -0
  113. package/dist/src/worker.js.map +1 -0
  114. package/dist/src/workflow.js +250 -0
  115. package/dist/src/workflow.js.map +1 -0
  116. package/dist/tests/approval.test.js +144 -0
  117. package/dist/tests/approval.test.js.map +1 -0
  118. package/dist/tests/brief.test.js +271 -0
  119. package/dist/tests/brief.test.js.map +1 -0
  120. package/dist/tests/config.test.js +320 -0
  121. package/dist/tests/config.test.js.map +1 -0
  122. package/dist/tests/dashboard.test.js +277 -0
  123. package/dist/tests/dashboard.test.js.map +1 -0
  124. package/dist/tests/display.test.js +229 -0
  125. package/dist/tests/display.test.js.map +1 -0
  126. package/dist/tests/escalate.test.js +272 -0
  127. package/dist/tests/escalate.test.js.map +1 -0
  128. package/dist/tests/featureList.test.js +429 -0
  129. package/dist/tests/featureList.test.js.map +1 -0
  130. package/dist/tests/gates.test.js +333 -0
  131. package/dist/tests/gates.test.js.map +1 -0
  132. package/dist/tests/goal.test.js +229 -0
  133. package/dist/tests/goal.test.js.map +1 -0
  134. package/dist/tests/goalLoop.test.js +170 -0
  135. package/dist/tests/goalLoop.test.js.map +1 -0
  136. package/dist/tests/goalSpec.test.js +146 -0
  137. package/dist/tests/goalSpec.test.js.map +1 -0
  138. package/dist/tests/goalState.test.js +202 -0
  139. package/dist/tests/goalState.test.js.map +1 -0
  140. package/dist/tests/handoff.test.js +127 -0
  141. package/dist/tests/handoff.test.js.map +1 -0
  142. package/dist/tests/init.test.js +264 -0
  143. package/dist/tests/init.test.js.map +1 -0
  144. package/dist/tests/intake.test.js +288 -0
  145. package/dist/tests/intake.test.js.map +1 -0
  146. package/dist/tests/loop.test.js +453 -0
  147. package/dist/tests/loop.test.js.map +1 -0
  148. package/dist/tests/modelRouter.test.js +131 -0
  149. package/dist/tests/modelRouter.test.js.map +1 -0
  150. package/dist/tests/phases.test.js +244 -0
  151. package/dist/tests/phases.test.js.map +1 -0
  152. package/dist/tests/planTree.test.js +190 -0
  153. package/dist/tests/planTree.test.js.map +1 -0
  154. package/dist/tests/remote.test.js +380 -0
  155. package/dist/tests/remote.test.js.map +1 -0
  156. package/dist/tests/replan.test.js +113 -0
  157. package/dist/tests/replan.test.js.map +1 -0
  158. package/dist/tests/review.test.js +77 -0
  159. package/dist/tests/review.test.js.map +1 -0
  160. package/dist/tests/rework.test.js +109 -0
  161. package/dist/tests/rework.test.js.map +1 -0
  162. package/dist/tests/routing-live.test.js +121 -0
  163. package/dist/tests/routing-live.test.js.map +1 -0
  164. package/dist/tests/runState.test.js +73 -0
  165. package/dist/tests/runState.test.js.map +1 -0
  166. package/dist/tests/settings.test.js +311 -0
  167. package/dist/tests/settings.test.js.map +1 -0
  168. package/dist/tests/skills.test.js +387 -0
  169. package/dist/tests/skills.test.js.map +1 -0
  170. package/dist/tests/supervisor.test.js +437 -0
  171. package/dist/tests/supervisor.test.js.map +1 -0
  172. package/dist/tests/surface.test.js +203 -0
  173. package/dist/tests/surface.test.js.map +1 -0
  174. package/dist/tests/taskList.test.js +376 -0
  175. package/dist/tests/taskList.test.js.map +1 -0
  176. package/dist/tests/theme.test.js +149 -0
  177. package/dist/tests/theme.test.js.map +1 -0
  178. package/dist/tests/unstuck.test.js +268 -0
  179. package/dist/tests/unstuck.test.js.map +1 -0
  180. package/dist/tests/widget.test.js +424 -0
  181. package/dist/tests/widget.test.js.map +1 -0
  182. package/dist/tests/worker.test.js +306 -0
  183. package/dist/tests/worker.test.js.map +1 -0
  184. package/dist/tests/workflow.test.js +187 -0
  185. package/dist/tests/workflow.test.js.map +1 -0
  186. package/extensions/infinity-harness/index.ts +39 -12
  187. package/package.json +5 -2
  188. package/src/core/modelRouter.ts +23 -1
  189. package/src/daemon/index.ts +3 -3
  190. package/src/daemon/worker.ts +28 -5
  191. package/src/ui/config.ts +25 -1
  192. package/src/ui/wizard.ts +10 -5
@@ -0,0 +1,77 @@
1
+ /**
2
+ * infinity-harness — shared domain types.
3
+ *
4
+ * Single source of truth for every shape that crosses a module boundary.
5
+ * Nothing here does I/O; it is safe to import from anywhere.
6
+ */
7
+ // ── Phases ──────────────────────────────────────────────────────────────────
8
+ export const PHASE_ORDER = [
9
+ "init",
10
+ "research",
11
+ "define",
12
+ "plan",
13
+ "build",
14
+ "verify",
15
+ "simplify",
16
+ "review",
17
+ "ship",
18
+ ];
19
+ /** Phases enabled by default — RESEARCH and SIMPLIFY are opt-in. */
20
+ export const DEFAULT_ENABLED_PHASES = [
21
+ "define",
22
+ "plan",
23
+ "build",
24
+ "verify",
25
+ "review",
26
+ "ship",
27
+ ];
28
+ /**
29
+ * Phases whose output a human may want to sign off before the run continues.
30
+ *
31
+ * These are the three that decide *what gets built*. Everything after them is
32
+ * execution: if the definition and the plan are right, an autonomous run that
33
+ * gets BUILD wrong fails a gate and retries, but an autonomous run that got
34
+ * DEFINE wrong spends a weekend building the wrong product perfectly.
35
+ */
36
+ export const APPROVABLE_PHASES = ["research", "define", "plan"];
37
+ /** Which role owns each phase. Drives the "put on this hat" brief line. */
38
+ export const PHASE_ROLE = {
39
+ init: "planner",
40
+ research: "researcher",
41
+ define: "planner",
42
+ plan: "planner",
43
+ build: "generator",
44
+ verify: "evaluator",
45
+ simplify: "simplifier",
46
+ review: "evaluator",
47
+ ship: "evaluator",
48
+ };
49
+ // ── Task / feature status ───────────────────────────────────────────────────
50
+ export const TASK_STATUSES = [
51
+ "pending",
52
+ "in_progress",
53
+ "complete",
54
+ "blocked",
55
+ "rework",
56
+ ];
57
+ export const SUBTASK_STATUSES = ["pending", "in_progress", "complete"];
58
+ /** Levels that can each have their own retry budget and escalation state. */
59
+ export const RETRY_LEVELS = ["goal", "phase", "sprint", "feature", "task", "subtask"];
60
+ export const EXECUTION_ENGINES = ["background", "main-session"];
61
+ export const RESEARCH_DEPTHS = ["standard", "deep", "comprehensive"];
62
+ // ── Errors ──────────────────────────────────────────────────────────────────
63
+ export class ValidationError extends Error {
64
+ name = "ValidationError";
65
+ constructor(message) {
66
+ super(message);
67
+ Object.setPrototypeOf(this, ValidationError.prototype);
68
+ }
69
+ }
70
+ export class HarnessError extends Error {
71
+ name = "HarnessError";
72
+ constructor(message) {
73
+ super(message);
74
+ Object.setPrototypeOf(this, HarnessError.prototype);
75
+ }
76
+ }
77
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,+EAA+E;AAE/E,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,UAAU;IACV,QAAQ;IACR,MAAM;IACN,OAAO;IACP,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,MAAM;CACE,CAAC;AAIX,oEAAoE;AACpE,MAAM,CAAC,MAAM,sBAAsB,GAAY;IAC7C,QAAQ;IACR,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,MAAM;CACP,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAKzE,2EAA2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAAwB;IAC7C,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,WAAW;IAClB,MAAM,EAAE,WAAW;IACnB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,WAAW;IACnB,IAAI,EAAE,WAAW;CAClB,CAAC;AAEF,+EAA+E;AAE/E,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS;IACT,aAAa;IACb,UAAU;IACV,SAAS;IACT,QAAQ;CACA,CAAC;AAIX,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,aAAa,EAAE,UAAU,CAAU,CAAC;AAoGhF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAU,CAAC;AAsC/F,MAAM,CAAC,MAAM,iBAAiB,GAA+B,CAAC,YAAY,EAAE,cAAc,CAAU,CAAC;AA0ErG,MAAM,CAAC,MAAM,eAAe,GAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,CAAU,CAAC;AAwIxG,+EAA+E;AAE/E,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACtB,IAAI,GAAG,iBAAiB,CAAC;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IACzD,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,KAAK;IACnB,IAAI,GAAG,cAAc,CAAC;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;CACF"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * infinity-harness — daemon/budget.ts
3
+ *
4
+ * Per-tier token+cost accounting. Uses pi's UsageTotals shape.
5
+ * - `byTier` holds cumulative per-tier spend (one UsageTotals per A/B/C/D/X).
6
+ * - `cap` is the budget ceiling (costUsd / totalTokens / wallClockMs).
7
+ * - The X-leak tripwire: X tokens outside a consultation worker are a defect.
8
+ *
9
+ * pi reports usage cumulatively per session (last message_end usage IS the
10
+ * session total, not a delta). So per-session we keep the MAX, and per-tier
11
+ * we sum finished sessions' totals via addUsageToTotals semantics.
12
+ */
13
+ import { createUsageTotals } from "../core/runState.js";
14
+ export function emptyBudget() {
15
+ return { byTier: {}, cap: {}, stopOnExhaustion: true };
16
+ }
17
+ export function addUsageForTier(budget, tier, usage) {
18
+ const byTier = (budget.byTier ??= {});
19
+ const cur = byTier[tier] ?? createUsageTotals();
20
+ cur.input += usage.input ?? 0;
21
+ cur.output += usage.output ?? 0;
22
+ cur.cacheRead += usage.cacheRead ?? 0;
23
+ cur.cacheWrite += usage.cacheWrite ?? 0;
24
+ cur.cost += usage.cost ?? 0;
25
+ cur.calls += usage.calls ?? 1;
26
+ byTier[tier] = cur;
27
+ }
28
+ export function totalTokens(budget) {
29
+ let n = 0;
30
+ for (const u of Object.values(budget.byTier ?? {}))
31
+ n += (u.input ?? 0) + (u.output ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0);
32
+ return n;
33
+ }
34
+ export function totalCost(budget) {
35
+ let c = 0;
36
+ for (const u of Object.values(budget.byTier ?? {}))
37
+ c += u.cost ?? 0;
38
+ return c;
39
+ }
40
+ export function isCapExceeded(budget) {
41
+ const cap = budget.cap ?? {};
42
+ const tokens = totalTokens(budget);
43
+ const cost = totalCost(budget);
44
+ if (typeof cap.totalTokens === "number" && cap.totalTokens > 0 && tokens >= cap.totalTokens)
45
+ return { exceeded: true, reason: `token cap ${cap.totalTokens} exceeded (${tokens})` };
46
+ if (typeof cap.costUsd === "number" && cap.costUsd > 0 && cost >= cap.costUsd)
47
+ return { exceeded: true, reason: `cost cap $${cap.costUsd} exceeded ($${cost.toFixed(2)})` };
48
+ return { exceeded: false, reason: null };
49
+ }
50
+ /**
51
+ * X-leak tripwire. True when X has accrued cost/tokens while no consultation
52
+ * worker is alive. The Daemon treats this as a hard stop with reason.
53
+ */
54
+ export function hasXLeak(budget, hasConsultationWorker) {
55
+ if (hasConsultationWorker)
56
+ return false;
57
+ const x = budget.byTier["X"];
58
+ if (!x)
59
+ return false;
60
+ return (x.cost ?? 0) > 0 || (x.input ?? 0) + (x.output ?? 0) > 0 || (x.calls ?? 0) > 0;
61
+ }
62
+ export function xLeakReason(budget) {
63
+ const x = budget.byTier["X"];
64
+ const cost = x?.cost ?? 0;
65
+ const tokens = (x?.input ?? 0) + (x?.output ?? 0);
66
+ return `X accrued ${tokens} tokens ($${cost.toFixed(2)}) with no consultation worker — routing leak`;
67
+ }
68
+ export function budgetFromRunState(runState) {
69
+ if (!runState?.budget)
70
+ return emptyBudget();
71
+ return runState.budget;
72
+ }
73
+ export function budgetSummary(budget) {
74
+ const parts = [];
75
+ for (const tier of ["A", "B", "C", "D", "X"]) {
76
+ const u = budget.byTier[tier];
77
+ if (!u || (!u.calls && !u.cost && !u.input && !u.output))
78
+ continue;
79
+ parts.push(`${tier}:${u.input}+${u.output} $${(u.cost ?? 0).toFixed(2)} ×${u.calls}`);
80
+ }
81
+ const cap = budget.cap ?? {};
82
+ const capStr = [cap.totalTokens ? `tokens ${cap.totalTokens}` : null, cap.costUsd ? `$${cap.costUsd}` : null].filter(Boolean).join(" / ");
83
+ return parts.length ? `${parts.join(" | ")}${capStr ? ` — cap ${capStr}` : ""}` : (capStr ? `cap ${capStr}` : "no spend yet");
84
+ }
85
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.js","sourceRoot":"","sources":["../../../src/daemon/budget.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * infinity-harness — daemon/guard.ts
3
+ *
4
+ * Single-owner lock + daemon.json heartbeat + bounded stop.
5
+ * The Daemon is the single writer; Interfaces are readers. A second pi window
6
+ * on the same project must become a viewer, not a rival Daemon.
7
+ *
8
+ * daemon.json is the trust root: { pid, port, token, startedAt, heartbeatAt, runId }.
9
+ * Token is a random per-run secret; file is 0600. Server binds 127.0.0.1 only.
10
+ * Guides: Daemon writes it, liveness is process.kill(pid,0), heartbeat every 20s,
11
+ * stale after 90s. v2.7 shipped HEARTBEAT_MS/OWNER_STALE_MS at 20s/90s.
12
+ */
13
+ import { randomBytes } from "node:crypto";
14
+ import { existsSync, chmodSync, unlinkSync } from "node:fs";
15
+ import { daemonPath } from "../core/paths.js";
16
+ import { readJsonSafe, writeJsonAtomic, ensureDir } from "../core/fsx.js";
17
+ import { dirname } from "node:path";
18
+ export const HEARTBEAT_MS = 20_000;
19
+ export const OWNER_STALE_MS = 90_000;
20
+ function readDaemonRaw(targetDir) {
21
+ const raw = readJsonSafe(daemonPath(targetDir), null);
22
+ if (!raw || typeof raw.pid !== "number" || !raw.runId)
23
+ return null;
24
+ return raw;
25
+ }
26
+ export function loadDaemon(targetDir) {
27
+ const info = readDaemonRaw(targetDir);
28
+ if (!info)
29
+ return null;
30
+ return info;
31
+ }
32
+ export function isDaemonAlive(info) {
33
+ if (!info)
34
+ return false;
35
+ // Heartbeat stale?
36
+ const age = Date.now() - new Date(info.heartbeatAt).getTime();
37
+ if (Number.isFinite(age) && age > OWNER_STALE_MS) {
38
+ // Also check pid liveness as tiebreaker.
39
+ try {
40
+ process.kill(info.pid, 0);
41
+ return false;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ try {
48
+ process.kill(info.pid, 0);
49
+ return true;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ export function isDaemonRunning(targetDir) {
56
+ const info = loadDaemon(targetDir);
57
+ if (!info)
58
+ return false;
59
+ return isDaemonAlive(info);
60
+ }
61
+ export function writeDaemon(targetDir, info) {
62
+ ensureDir(dirname(daemonPath(targetDir)));
63
+ writeJsonAtomic(daemonPath(targetDir), info);
64
+ try {
65
+ chmodSync(daemonPath(targetDir), 0o600);
66
+ }
67
+ catch { }
68
+ }
69
+ export function heartbeat(targetDir) {
70
+ const info = loadDaemon(targetDir);
71
+ if (!info || !isDaemonAlive(info))
72
+ return null;
73
+ const next = { ...info, heartbeatAt: new Date().toISOString() };
74
+ writeDaemon(targetDir, next);
75
+ return next;
76
+ }
77
+ export function clearDaemon(targetDir) {
78
+ try {
79
+ if (existsSync(daemonPath(targetDir)))
80
+ unlinkSync(daemonPath(targetDir));
81
+ }
82
+ catch { }
83
+ }
84
+ export function newDaemonInfo(runId, port) {
85
+ return {
86
+ pid: process.pid,
87
+ port,
88
+ token: randomBytes(24).toString("base64url"),
89
+ startedAt: new Date().toISOString(),
90
+ heartbeatAt: new Date().toISOString(),
91
+ runId,
92
+ };
93
+ }
94
+ /**
95
+ * Guard: refuse to start a second Daemon when one is alive.
96
+ * Returns null when clear, or the live DaemonInfo when blocked.
97
+ */
98
+ export function guardSingleOwner(targetDir) {
99
+ const live = loadDaemon(targetDir);
100
+ if (live && isDaemonAlive(live))
101
+ return live;
102
+ // Stale file — clear it so the next start succeeds.
103
+ if (live && !isDaemonAlive(live))
104
+ clearDaemon(targetDir);
105
+ return null;
106
+ }
107
+ /**
108
+ * Start heartbeat interval. Returns stop function.
109
+ */
110
+ export function startHeartbeat(targetDir) {
111
+ const timer = setInterval(() => {
112
+ try {
113
+ heartbeat(targetDir);
114
+ }
115
+ catch { }
116
+ }, HEARTBEAT_MS);
117
+ // Don't keep process alive just for heartbeat if nothing else is running.
118
+ if (typeof timer.unref === "function")
119
+ timer.unref();
120
+ return () => clearInterval(timer);
121
+ }
122
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../../src/daemon/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAc,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,UAAU,EAA+B,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEzF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAc,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AACnC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AAWrC,SAAS,aAAa,CAAC,SAAiB;IACtC,MAAM,GAAG,GAAG,YAAY,CAAoB,UAAU,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC;IACzE,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACnE,OAAO,GAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAuB;IACnD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,mBAAmB;IACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC;IAC9D,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,cAAc,EAAE,CAAC;QACjD,yCAAyC;QACzC,IAAI,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;IAC1E,CAAC;IACD,IAAI,CAAC;QAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,IAAgB;IAC7D,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC1C,eAAe,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC;QAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,SAAiB;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,IAAI,GAAe,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IAC5E,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,IAAI,CAAC;QAAC,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAAE,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AAC5F,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,IAAY;IACvD,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI;QACJ,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QAC5C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,IAAI,IAAI,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,oDAAoD;IACpD,IAAI,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;QAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,IAAI,CAAC;YAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACxC,CAAC,EAAE,YAAY,CAAC,CAAC;IACjB,0EAA0E;IAC1E,IAAI,OAAQ,KAAiD,CAAC,KAAK,KAAK,UAAU;QAAG,KAA0C,CAAC,KAAK,EAAE,CAAC;IACxI,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;AACpC,CAAC"}