@neotx/cli 0.1.0-alpha.24 → 0.1.0-alpha.25

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 (41) hide show
  1. package/dist/child-5X5IHAKS.js +92 -0
  2. package/dist/child-5X5IHAKS.js.map +1 -0
  3. package/dist/child-mode-IB3XSUHD.js +8 -0
  4. package/dist/child-mode-IB3XSUHD.js.map +1 -0
  5. package/dist/chunk-4TQ3Q6IE.js +43 -0
  6. package/dist/chunk-4TQ3Q6IE.js.map +1 -0
  7. package/dist/chunk-6PSXZ3UV.js +46 -0
  8. package/dist/chunk-6PSXZ3UV.js.map +1 -0
  9. package/dist/chunk-V5SN5F73.js +54 -0
  10. package/dist/chunk-V5SN5F73.js.map +1 -0
  11. package/dist/daemon/child-supervisor-worker.js +136 -0
  12. package/dist/daemon/child-supervisor-worker.js.map +1 -0
  13. package/dist/daemon/supervisor-worker.js +9 -1
  14. package/dist/daemon/supervisor-worker.js.map +1 -1
  15. package/dist/daemon/worker.js +16 -3
  16. package/dist/daemon/worker.js.map +1 -1
  17. package/dist/directive-7WM2Q2UW.js +259 -0
  18. package/dist/directive-7WM2Q2UW.js.map +1 -0
  19. package/dist/do-F5XW2ELZ.js +83 -0
  20. package/dist/do-F5XW2ELZ.js.map +1 -0
  21. package/dist/index.js +8 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/{log-PTHLI7ZN.js → log-ZLIAIBZQ.js} +64 -9
  24. package/dist/log-ZLIAIBZQ.js.map +1 -0
  25. package/dist/{memory-SDZ57W2S.js → memory-CW6E65SQ.js} +112 -62
  26. package/dist/memory-CW6E65SQ.js.map +1 -0
  27. package/dist/{run-MWHIQUSY.js → run-NV762V5B.js} +56 -22
  28. package/dist/run-NV762V5B.js.map +1 -0
  29. package/dist/{supervise-XMZRNODO.js → supervise-BWIKWNHH.js} +68 -41
  30. package/dist/supervise-BWIKWNHH.js.map +1 -0
  31. package/dist/{supervisor-3RUX5SPH.js → supervisor-N4D5EWCC.js} +1 -1
  32. package/dist/tui-LSW7VVK6.js +1319 -0
  33. package/dist/tui-LSW7VVK6.js.map +1 -0
  34. package/package.json +4 -4
  35. package/dist/log-PTHLI7ZN.js.map +0 -1
  36. package/dist/memory-SDZ57W2S.js.map +0 -1
  37. package/dist/run-MWHIQUSY.js.map +0 -1
  38. package/dist/supervise-XMZRNODO.js.map +0 -1
  39. package/dist/tui-67VJ5VBA.js +0 -842
  40. package/dist/tui-67VJ5VBA.js.map +0 -1
  41. /package/dist/{supervisor-3RUX5SPH.js.map → supervisor-N4D5EWCC.js.map} +0 -0
@@ -0,0 +1,92 @@
1
+ import {
2
+ spawnChildFromCli
3
+ } from "./chunk-4TQ3Q6IE.js";
4
+ import {
5
+ isDaemonRunning
6
+ } from "./chunk-V5SN5F73.js";
7
+ import "./chunk-6PSXZ3UV.js";
8
+ import {
9
+ printError
10
+ } from "./chunk-YQIWMDXL.js";
11
+
12
+ // src/commands/child.ts
13
+ import { defineCommand } from "citty";
14
+ var DEFAULT_SUPERVISOR = "supervisor";
15
+ var spawnCommand = defineCommand({
16
+ meta: {
17
+ name: "spawn",
18
+ description: "Request a supervisor to spawn a child supervisor"
19
+ },
20
+ args: {
21
+ supervisor: {
22
+ type: "string",
23
+ alias: "s",
24
+ description: "Name of the parent supervisor (default: supervisor)",
25
+ default: DEFAULT_SUPERVISOR
26
+ },
27
+ objective: {
28
+ type: "string",
29
+ alias: "o",
30
+ description: "Objective for the child supervisor",
31
+ required: true
32
+ },
33
+ criteria: {
34
+ type: "string",
35
+ alias: "c",
36
+ description: "Comma-separated acceptance criteria",
37
+ required: true
38
+ },
39
+ budget: {
40
+ type: "string",
41
+ alias: "b",
42
+ description: "Max cost in USD for the child supervisor"
43
+ }
44
+ },
45
+ async run({ args }) {
46
+ const supervisorName = args.supervisor;
47
+ const objective = args.objective;
48
+ const criteriaStr = args.criteria;
49
+ const budgetStr = args.budget;
50
+ const running = await isDaemonRunning(supervisorName);
51
+ if (!running) {
52
+ printError(`Supervisor "${supervisorName}" is not running.`);
53
+ printError("Start it first with: neo supervise --detach");
54
+ process.exitCode = 1;
55
+ return;
56
+ }
57
+ const criteria = criteriaStr.split(",").map((s) => s.trim()).filter(Boolean);
58
+ if (criteria.length === 0) {
59
+ printError("At least one acceptance criterion is required.");
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+ const budget = budgetStr ? Number.parseFloat(budgetStr) : void 0;
64
+ if (budgetStr !== void 0 && (Number.isNaN(budget) || budget !== void 0 && budget <= 0)) {
65
+ printError("Budget must be a positive number.");
66
+ process.exitCode = 1;
67
+ return;
68
+ }
69
+ const options = {
70
+ parentName: supervisorName,
71
+ objective,
72
+ acceptanceCriteria: criteria
73
+ };
74
+ if (budget !== void 0) {
75
+ options.maxCostUsd = budget;
76
+ }
77
+ await spawnChildFromCli(options);
78
+ }
79
+ });
80
+ var child_default = defineCommand({
81
+ meta: {
82
+ name: "child",
83
+ description: "Manage child supervisors"
84
+ },
85
+ subCommands: {
86
+ spawn: () => Promise.resolve(spawnCommand)
87
+ }
88
+ });
89
+ export {
90
+ child_default as default
91
+ };
92
+ //# sourceMappingURL=child-5X5IHAKS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/child.ts"],"sourcesContent":["import { defineCommand } from \"citty\";\nimport { spawnChildFromCli } from \"../child-mode.js\";\nimport { isDaemonRunning } from \"../daemon-utils.js\";\nimport { printError } from \"../output.js\";\n\nconst DEFAULT_SUPERVISOR = \"supervisor\";\n\nconst spawnCommand = defineCommand({\n meta: {\n name: \"spawn\",\n description: \"Request a supervisor to spawn a child supervisor\",\n },\n args: {\n supervisor: {\n type: \"string\",\n alias: \"s\",\n description: \"Name of the parent supervisor (default: supervisor)\",\n default: DEFAULT_SUPERVISOR,\n },\n objective: {\n type: \"string\",\n alias: \"o\",\n description: \"Objective for the child supervisor\",\n required: true,\n },\n criteria: {\n type: \"string\",\n alias: \"c\",\n description: \"Comma-separated acceptance criteria\",\n required: true,\n },\n budget: {\n type: \"string\",\n alias: \"b\",\n description: \"Max cost in USD for the child supervisor\",\n },\n },\n async run({ args }) {\n const supervisorName = args.supervisor;\n const objective = args.objective;\n const criteriaStr = args.criteria;\n const budgetStr = args.budget;\n\n // Validate supervisor is running\n const running = await isDaemonRunning(supervisorName);\n if (!running) {\n printError(`Supervisor \"${supervisorName}\" is not running.`);\n printError(\"Start it first with: neo supervise --detach\");\n process.exitCode = 1;\n return;\n }\n\n // Parse criteria\n const criteria = criteriaStr\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n\n if (criteria.length === 0) {\n printError(\"At least one acceptance criterion is required.\");\n process.exitCode = 1;\n return;\n }\n\n // Parse budget\n const budget = budgetStr ? Number.parseFloat(budgetStr) : undefined;\n if (\n budgetStr !== undefined &&\n (Number.isNaN(budget) || (budget !== undefined && budget <= 0))\n ) {\n printError(\"Budget must be a positive number.\");\n process.exitCode = 1;\n return;\n }\n\n // Send spawn request to supervisor\n const options: Parameters<typeof spawnChildFromCli>[0] = {\n parentName: supervisorName,\n objective,\n acceptanceCriteria: criteria,\n };\n if (budget !== undefined) {\n options.maxCostUsd = budget;\n }\n await spawnChildFromCli(options);\n },\n});\n\nexport default defineCommand({\n meta: {\n name: \"child\",\n description: \"Manage child supervisors\",\n },\n subCommands: {\n spawn: () => Promise.resolve(spawnCommand),\n },\n});\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,qBAAqB;AAK9B,IAAM,qBAAqB;AAE3B,IAAM,eAAe,cAAc;AAAA,EACjC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,YAAY;AAAA,MACV,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,WAAW;AAAA,MACT,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,iBAAiB,KAAK;AAC5B,UAAM,YAAY,KAAK;AACvB,UAAM,cAAc,KAAK;AACzB,UAAM,YAAY,KAAK;AAGvB,UAAM,UAAU,MAAM,gBAAgB,cAAc;AACpD,QAAI,CAAC,SAAS;AACZ,iBAAW,eAAe,cAAc,mBAAmB;AAC3D,iBAAW,6CAA6C;AACxD,cAAQ,WAAW;AACnB;AAAA,IACF;AAGA,UAAM,WAAW,YACd,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAEjB,QAAI,SAAS,WAAW,GAAG;AACzB,iBAAW,gDAAgD;AAC3D,cAAQ,WAAW;AACnB;AAAA,IACF;AAGA,UAAM,SAAS,YAAY,OAAO,WAAW,SAAS,IAAI;AAC1D,QACE,cAAc,WACb,OAAO,MAAM,MAAM,KAAM,WAAW,UAAa,UAAU,IAC5D;AACA,iBAAW,mCAAmC;AAC9C,cAAQ,WAAW;AACnB;AAAA,IACF;AAGA,UAAM,UAAmD;AAAA,MACvD,YAAY;AAAA,MACZ;AAAA,MACA,oBAAoB;AAAA,IACtB;AACA,QAAI,WAAW,QAAW;AACxB,cAAQ,aAAa;AAAA,IACvB;AACA,UAAM,kBAAkB,OAAO;AAAA,EACjC;AACF,CAAC;AAED,IAAO,gBAAQ,cAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,aAAa;AAAA,IACX,OAAO,MAAM,QAAQ,QAAQ,YAAY;AAAA,EAC3C;AACF,CAAC;","names":[]}
@@ -0,0 +1,8 @@
1
+ import {
2
+ spawnChildFromCli
3
+ } from "./chunk-4TQ3Q6IE.js";
4
+ import "./chunk-YQIWMDXL.js";
5
+ export {
6
+ spawnChildFromCli
7
+ };
8
+ //# sourceMappingURL=child-mode-IB3XSUHD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,43 @@
1
+ import {
2
+ printSuccess
3
+ } from "./chunk-YQIWMDXL.js";
4
+
5
+ // src/child-mode.ts
6
+ import { randomUUID } from "crypto";
7
+ import { appendFile } from "fs/promises";
8
+ import { getSupervisorInboxPath } from "@neotx/core";
9
+ async function spawnChildFromCli(options) {
10
+ const { parentName, objective, acceptanceCriteria, maxCostUsd } = options;
11
+ const id = randomUUID();
12
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
13
+ const payload = {
14
+ objective,
15
+ acceptanceCriteria
16
+ };
17
+ if (maxCostUsd !== void 0) {
18
+ payload.maxCostUsd = maxCostUsd;
19
+ }
20
+ const message = {
21
+ id,
22
+ from: "api",
23
+ text: `child:spawn ${JSON.stringify(payload)}`,
24
+ timestamp
25
+ };
26
+ const inboxPath = getSupervisorInboxPath(parentName);
27
+ await appendFile(inboxPath, `${JSON.stringify(message)}
28
+ `, "utf-8");
29
+ printSuccess(`Child supervisor spawn requested for parent "${parentName}"`);
30
+ console.log(` Objective: ${objective}`);
31
+ console.log(` Criteria: ${acceptanceCriteria.join(", ")}`);
32
+ if (maxCostUsd !== void 0) {
33
+ console.log(` Budget: $${maxCostUsd.toFixed(2)}`);
34
+ }
35
+ console.log("");
36
+ console.log(" The parent supervisor will spawn the child on its next heartbeat.");
37
+ console.log(" Monitor via: neo supervise");
38
+ }
39
+
40
+ export {
41
+ spawnChildFromCli
42
+ };
43
+ //# sourceMappingURL=chunk-4TQ3Q6IE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/child-mode.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { appendFile } from \"node:fs/promises\";\nimport { getSupervisorInboxPath } from \"@neotx/core\";\nimport { printSuccess } from \"./output.js\";\n\nexport interface ChildModeOptions {\n parentName: string;\n objective: string;\n acceptanceCriteria: string[];\n maxCostUsd?: number;\n}\n\n/**\n * Request the parent supervisor to spawn a child via inbox message.\n * The HeartbeatLoop will read this and call spawnChildSupervisor.\n */\nexport async function spawnChildFromCli(options: ChildModeOptions): Promise<void> {\n const { parentName, objective, acceptanceCriteria, maxCostUsd } = options;\n\n const id = randomUUID();\n const timestamp = new Date().toISOString();\n\n // Send a message to parent's inbox that triggers spawn\n const payload: { objective: string; acceptanceCriteria: string[]; maxCostUsd?: number } = {\n objective,\n acceptanceCriteria,\n };\n if (maxCostUsd !== undefined) {\n payload.maxCostUsd = maxCostUsd;\n }\n\n const message = {\n id,\n from: \"api\" as const,\n text: `child:spawn ${JSON.stringify(payload)}`,\n timestamp,\n };\n\n const inboxPath = getSupervisorInboxPath(parentName);\n await appendFile(inboxPath, `${JSON.stringify(message)}\\n`, \"utf-8\");\n\n printSuccess(`Child supervisor spawn requested for parent \"${parentName}\"`);\n console.log(` Objective: ${objective}`);\n console.log(` Criteria: ${acceptanceCriteria.join(\", \")}`);\n if (maxCostUsd !== undefined) {\n console.log(` Budget: $${maxCostUsd.toFixed(2)}`);\n }\n console.log(\"\");\n console.log(\" The parent supervisor will spawn the child on its next heartbeat.\");\n console.log(\" Monitor via: neo supervise\");\n}\n"],"mappings":";;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,8BAA8B;AAcvC,eAAsB,kBAAkB,SAA0C;AAChF,QAAM,EAAE,YAAY,WAAW,oBAAoB,WAAW,IAAI;AAElE,QAAM,KAAK,WAAW;AACtB,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAGzC,QAAM,UAAoF;AAAA,IACxF;AAAA,IACA;AAAA,EACF;AACA,MAAI,eAAe,QAAW;AAC5B,YAAQ,aAAa;AAAA,EACvB;AAEA,QAAM,UAAU;AAAA,IACd;AAAA,IACA,MAAM;AAAA,IACN,MAAM,eAAe,KAAK,UAAU,OAAO,CAAC;AAAA,IAC5C;AAAA,EACF;AAEA,QAAM,YAAY,uBAAuB,UAAU;AACnD,QAAM,WAAW,WAAW,GAAG,KAAK,UAAU,OAAO,CAAC;AAAA,GAAM,OAAO;AAEnE,eAAa,gDAAgD,UAAU,GAAG;AAC1E,UAAQ,IAAI,gBAAgB,SAAS,EAAE;AACvC,UAAQ,IAAI,gBAAgB,mBAAmB,KAAK,IAAI,CAAC,EAAE;AAC3D,MAAI,eAAe,QAAW;AAC5B,YAAQ,IAAI,iBAAiB,WAAW,QAAQ,CAAC,CAAC,EAAE;AAAA,EACtD;AACA,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,qEAAqE;AACjF,UAAQ,IAAI,8BAA8B;AAC5C;","names":[]}
@@ -0,0 +1,46 @@
1
+ // src/spawn-utils.ts
2
+ import { spawn } from "child_process";
3
+ function spawnWithConfirmation(command, args, options = {}) {
4
+ const { timeoutMs = 1e3, spawnOptions = {}, onComplete } = options;
5
+ return new Promise((resolve) => {
6
+ let resolved = false;
7
+ const safeResolve = (result) => {
8
+ if (resolved) return;
9
+ resolved = true;
10
+ clearTimeout(timer);
11
+ child.removeAllListeners();
12
+ onComplete?.();
13
+ resolve(result);
14
+ };
15
+ const child = spawn(command, args, {
16
+ detached: true,
17
+ stdio: "ignore",
18
+ env: process.env,
19
+ ...spawnOptions
20
+ });
21
+ child.on("error", (err) => {
22
+ safeResolve({ pid: 0, error: err.message });
23
+ });
24
+ child.on("spawn", () => {
25
+ child.unref();
26
+ if (child.pid) {
27
+ safeResolve({ pid: child.pid });
28
+ } else {
29
+ safeResolve({ pid: 0, error: "Spawn succeeded but no PID assigned" });
30
+ }
31
+ });
32
+ const timer = setTimeout(() => {
33
+ child.unref();
34
+ if (child.pid) {
35
+ safeResolve({ pid: child.pid });
36
+ } else {
37
+ safeResolve({ pid: 0, error: "Spawn timeout - no PID available" });
38
+ }
39
+ }, timeoutMs);
40
+ });
41
+ }
42
+
43
+ export {
44
+ spawnWithConfirmation
45
+ };
46
+ //# sourceMappingURL=chunk-6PSXZ3UV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spawn-utils.ts"],"sourcesContent":["import { type SpawnOptions, spawn } from \"node:child_process\";\n\n/**\n * Result of a successful spawn operation.\n */\nexport interface SpawnSuccess {\n pid: number;\n error?: undefined;\n}\n\n/**\n * Result of a failed spawn operation.\n */\nexport interface SpawnError {\n pid: 0;\n error: string;\n}\n\nexport type SpawnResult = SpawnSuccess | SpawnError;\n\nexport interface SpawnWithConfirmationOptions {\n /**\n * Timeout in milliseconds before giving up on spawn confirmation.\n * Defaults to 1000ms.\n */\n timeoutMs?: number;\n /**\n * Additional spawn options passed to child_process.spawn().\n * Note: `detached` and `env` are set by default but can be overridden.\n */\n spawnOptions?: SpawnOptions;\n /**\n * Callback invoked when spawn completes (success, error, or timeout).\n * Use this for cleanup tasks like closing file descriptors.\n * Called before the promise resolves.\n */\n onComplete?: () => void;\n}\n\n/**\n * Spawn a child process with confirmation handling.\n *\n * This helper encapsulates the pattern of:\n * 1. Attaching error/spawn handlers before unref()\n * 2. Waiting for 'spawn' event to confirm the process started\n * 3. Calling unref() only after handlers are attached\n * 4. Resolving with PID only after spawn confirmation\n *\n * This pattern prevents \"ghost runs\" where spawn fails silently because:\n * - Error handlers are attached BEFORE unref(), so errors are captured\n * - PID is only returned AFTER spawn confirmation, ensuring the process exists\n *\n * @param command - The command to spawn (usually process.execPath for Node scripts)\n * @param args - Arguments to pass to the command\n * @param options - Configuration options\n * @returns Promise resolving to SpawnSuccess with PID, or SpawnError with message\n */\nexport function spawnWithConfirmation(\n command: string,\n args: readonly string[],\n options: SpawnWithConfirmationOptions = {},\n): Promise<SpawnResult> {\n const { timeoutMs = 1000, spawnOptions = {}, onComplete } = options;\n\n return new Promise((resolve) => {\n let resolved = false;\n\n const safeResolve = (result: SpawnResult) => {\n if (resolved) return;\n resolved = true;\n clearTimeout(timer);\n // Clean up event listeners to prevent memory leaks\n child.removeAllListeners();\n onComplete?.();\n resolve(result);\n };\n\n const child = spawn(command, args, {\n detached: true,\n stdio: \"ignore\",\n env: process.env,\n ...spawnOptions,\n });\n\n // Capture spawn errors before unref() - these would otherwise be silently discarded\n child.on(\"error\", (err) => {\n safeResolve({ pid: 0, error: err.message });\n });\n\n // Wait for 'spawn' event to confirm process started successfully\n child.on(\"spawn\", () => {\n child.unref();\n\n if (child.pid) {\n safeResolve({ pid: child.pid });\n } else {\n safeResolve({ pid: 0, error: \"Spawn succeeded but no PID assigned\" });\n }\n });\n\n // Safety timeout in case neither event fires (shouldn't happen)\n const timer = setTimeout(() => {\n child.unref();\n if (child.pid) {\n safeResolve({ pid: child.pid });\n } else {\n safeResolve({ pid: 0, error: \"Spawn timeout - no PID available\" });\n }\n }, timeoutMs);\n });\n}\n"],"mappings":";AAAA,SAA4B,aAAa;AAyDlC,SAAS,sBACd,SACA,MACA,UAAwC,CAAC,GACnB;AACtB,QAAM,EAAE,YAAY,KAAM,eAAe,CAAC,GAAG,WAAW,IAAI;AAE5D,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,QAAI,WAAW;AAEf,UAAM,cAAc,CAAC,WAAwB;AAC3C,UAAI,SAAU;AACd,iBAAW;AACX,mBAAa,KAAK;AAElB,YAAM,mBAAmB;AACzB,mBAAa;AACb,cAAQ,MAAM;AAAA,IAChB;AAEA,UAAM,QAAQ,MAAM,SAAS,MAAM;AAAA,MACjC,UAAU;AAAA,MACV,OAAO;AAAA,MACP,KAAK,QAAQ;AAAA,MACb,GAAG;AAAA,IACL,CAAC;AAGD,UAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,kBAAY,EAAE,KAAK,GAAG,OAAO,IAAI,QAAQ,CAAC;AAAA,IAC5C,CAAC;AAGD,UAAM,GAAG,SAAS,MAAM;AACtB,YAAM,MAAM;AAEZ,UAAI,MAAM,KAAK;AACb,oBAAY,EAAE,KAAK,MAAM,IAAI,CAAC;AAAA,MAChC,OAAO;AACL,oBAAY,EAAE,KAAK,GAAG,OAAO,sCAAsC,CAAC;AAAA,MACtE;AAAA,IACF,CAAC;AAGD,UAAM,QAAQ,WAAW,MAAM;AAC7B,YAAM,MAAM;AACZ,UAAI,MAAM,KAAK;AACb,oBAAY,EAAE,KAAK,MAAM,IAAI,CAAC;AAAA,MAChC,OAAO;AACL,oBAAY,EAAE,KAAK,GAAG,OAAO,mCAAmC,CAAC;AAAA,MACnE;AAAA,IACF,GAAG,SAAS;AAAA,EACd,CAAC;AACH;","names":[]}
@@ -0,0 +1,54 @@
1
+ import {
2
+ spawnWithConfirmation
3
+ } from "./chunk-6PSXZ3UV.js";
4
+
5
+ // src/daemon-utils.ts
6
+ import { closeSync, existsSync, openSync } from "fs";
7
+ import { mkdir, readFile } from "fs/promises";
8
+ import path from "path";
9
+ import { fileURLToPath } from "url";
10
+ import {
11
+ getSupervisorDir,
12
+ getSupervisorStatePath,
13
+ isProcessAlive,
14
+ supervisorDaemonStateSchema
15
+ } from "@neotx/core";
16
+ async function readDaemonState(name) {
17
+ const statePath = getSupervisorStatePath(name);
18
+ if (!existsSync(statePath)) return null;
19
+ try {
20
+ const raw = await readFile(statePath, "utf-8");
21
+ return supervisorDaemonStateSchema.parse(JSON.parse(raw));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+ async function isDaemonRunning(name) {
27
+ const state = await readDaemonState(name);
28
+ if (!state || state.status === "stopped") return null;
29
+ if (!isProcessAlive(state.pid)) return null;
30
+ return state;
31
+ }
32
+ async function startDaemonDetached(name) {
33
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
34
+ const workerPath = path.join(__dirname, "daemon", "supervisor-worker.js");
35
+ const packageRoot = path.resolve(__dirname, "..");
36
+ const logDir = getSupervisorDir(name);
37
+ await mkdir(logDir, { recursive: true });
38
+ const logFd = openSync(path.join(logDir, "daemon.log"), "a");
39
+ return spawnWithConfirmation(process.execPath, [workerPath, name], {
40
+ spawnOptions: {
41
+ stdio: ["ignore", logFd, logFd],
42
+ cwd: packageRoot
43
+ },
44
+ onComplete: () => {
45
+ closeSync(logFd);
46
+ }
47
+ });
48
+ }
49
+
50
+ export {
51
+ isDaemonRunning,
52
+ startDaemonDetached
53
+ };
54
+ //# sourceMappingURL=chunk-V5SN5F73.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/daemon-utils.ts"],"sourcesContent":["import { closeSync, existsSync, openSync } from \"node:fs\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n getSupervisorDir,\n getSupervisorStatePath,\n isProcessAlive,\n type SupervisorDaemonState,\n supervisorDaemonStateSchema,\n} from \"@neotx/core\";\nimport { type SpawnResult, spawnWithConfirmation } from \"./spawn-utils.js\";\n\n/**\n * Read and parse supervisor daemon state.\n */\nexport async function readDaemonState(name: string): Promise<SupervisorDaemonState | null> {\n const statePath = getSupervisorStatePath(name);\n if (!existsSync(statePath)) return null;\n try {\n const raw = await readFile(statePath, \"utf-8\");\n return supervisorDaemonStateSchema.parse(JSON.parse(raw));\n } catch {\n return null;\n }\n}\n\n/**\n * Check if daemon is running by verifying state and process liveness.\n */\nexport async function isDaemonRunning(name: string): Promise<SupervisorDaemonState | null> {\n const state = await readDaemonState(name);\n if (!state || state.status === \"stopped\") return null;\n if (!isProcessAlive(state.pid)) return null;\n return state;\n}\n\n/**\n * Start a supervisor daemon in detached mode.\n * Returns a Promise that resolves with the PID after spawn confirmation,\n * or rejects with an error if spawn fails.\n */\nexport async function startDaemonDetached(name: string): Promise<SpawnResult> {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const workerPath = path.join(__dirname, \"daemon\", \"supervisor-worker.js\");\n const packageRoot = path.resolve(__dirname, \"..\");\n\n const logDir = getSupervisorDir(name);\n await mkdir(logDir, { recursive: true });\n const logFd = openSync(path.join(logDir, \"daemon.log\"), \"a\");\n\n return spawnWithConfirmation(process.execPath, [workerPath, name], {\n spawnOptions: {\n stdio: [\"ignore\", logFd, logFd],\n cwd: packageRoot,\n },\n onComplete: () => {\n closeSync(logFd);\n },\n });\n}\n"],"mappings":";;;;;AAAA,SAAS,WAAW,YAAY,gBAAgB;AAChD,SAAS,OAAO,gBAAgB;AAChC,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AAMP,eAAsB,gBAAgB,MAAqD;AACzF,QAAM,YAAY,uBAAuB,IAAI;AAC7C,MAAI,CAAC,WAAW,SAAS,EAAG,QAAO;AACnC,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,WAAW,OAAO;AAC7C,WAAO,4BAA4B,MAAM,KAAK,MAAM,GAAG,CAAC;AAAA,EAC1D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,gBAAgB,MAAqD;AACzF,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,MAAI,CAAC,SAAS,MAAM,WAAW,UAAW,QAAO;AACjD,MAAI,CAAC,eAAe,MAAM,GAAG,EAAG,QAAO;AACvC,SAAO;AACT;AAOA,eAAsB,oBAAoB,MAAoC;AAC5E,QAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,QAAM,aAAa,KAAK,KAAK,WAAW,UAAU,sBAAsB;AACxE,QAAM,cAAc,KAAK,QAAQ,WAAW,IAAI;AAEhD,QAAM,SAAS,iBAAiB,IAAI;AACpC,QAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AACvC,QAAM,QAAQ,SAAS,KAAK,KAAK,QAAQ,YAAY,GAAG,GAAG;AAE3D,SAAO,sBAAsB,QAAQ,UAAU,CAAC,YAAY,IAAI,GAAG;AAAA,IACjE,cAAc;AAAA,MACZ,OAAO,CAAC,UAAU,OAAO,KAAK;AAAA,MAC9B,KAAK;AAAA,IACP;AAAA,IACA,YAAY,MAAM;AAChB,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -0,0 +1,136 @@
1
+ // src/daemon/child-supervisor-worker.ts
2
+ import { mkdir } from "fs/promises";
3
+ import {
4
+ ClaudeAdapter,
5
+ FocusedLoop,
6
+ getFocusedSupervisorDir,
7
+ JsonlSupervisorStore,
8
+ loadGlobalConfig
9
+ } from "@neotx/core";
10
+ async function main() {
11
+ const supervisorId = process.env.NEO_CHILD_SUPERVISOR_ID;
12
+ const objective = process.env.NEO_CHILD_OBJECTIVE;
13
+ const criteriaRaw = process.env.NEO_CHILD_CRITERIA;
14
+ const parentName = process.env.NEO_CHILD_PARENT_NAME;
15
+ const maxCostUsdRaw = process.env.NEO_CHILD_MAX_COST_USD;
16
+ if (!supervisorId || !objective || !criteriaRaw || !parentName) {
17
+ console.error("[child-supervisor-worker] Missing required environment variables");
18
+ process.exit(1);
19
+ }
20
+ let acceptanceCriteria;
21
+ try {
22
+ acceptanceCriteria = JSON.parse(criteriaRaw);
23
+ } catch {
24
+ console.error("[child-supervisor-worker] Invalid NEO_CHILD_CRITERIA JSON");
25
+ process.exit(1);
26
+ }
27
+ const maxCostUsd = maxCostUsdRaw ? Number.parseFloat(maxCostUsdRaw) : void 0;
28
+ const supervisorDir = getFocusedSupervisorDir(supervisorId);
29
+ await mkdir(supervisorDir, { recursive: true });
30
+ await loadGlobalConfig();
31
+ const adapter = new ClaudeAdapter();
32
+ const store = new JsonlSupervisorStore(supervisorDir);
33
+ let totalCostUsd = 0;
34
+ const loop = new FocusedLoop({
35
+ supervisorId,
36
+ objective,
37
+ acceptanceCriteria,
38
+ adapter,
39
+ store,
40
+ onComplete: async (result) => {
41
+ sendToParent({
42
+ type: "complete",
43
+ supervisorId,
44
+ summary: result.summary,
45
+ evidence: result.evidence
46
+ });
47
+ process.exit(0);
48
+ },
49
+ onBlocked: async (blocked) => {
50
+ sendToParent({
51
+ type: "blocked",
52
+ supervisorId,
53
+ reason: blocked.reason,
54
+ question: blocked.question,
55
+ urgency: blocked.urgency
56
+ });
57
+ },
58
+ onProgress: async (summary, costDelta) => {
59
+ totalCostUsd += costDelta;
60
+ sendToParent({
61
+ type: "progress",
62
+ supervisorId,
63
+ summary,
64
+ costDelta
65
+ });
66
+ if (maxCostUsd !== void 0 && totalCostUsd >= maxCostUsd) {
67
+ sendToParent({
68
+ type: "failed",
69
+ supervisorId,
70
+ error: `Budget exceeded: $${totalCostUsd.toFixed(2)} >= $${maxCostUsd.toFixed(2)}`
71
+ });
72
+ process.exit(1);
73
+ }
74
+ }
75
+ });
76
+ process.on("message", (msg) => {
77
+ switch (msg.type) {
78
+ case "stop":
79
+ loop.stop();
80
+ sendToParent({
81
+ type: "failed",
82
+ supervisorId,
83
+ error: "Stopped by parent"
84
+ });
85
+ process.exit(0);
86
+ break;
87
+ case "inject":
88
+ loop.injectContext(msg.context);
89
+ break;
90
+ case "unblock":
91
+ loop.injectContext(`Parent answer: ${msg.answer}`);
92
+ break;
93
+ }
94
+ });
95
+ const reportSession = () => {
96
+ const handle = adapter.getSessionHandle();
97
+ if (handle?.provider === "claude") {
98
+ sendToParent({
99
+ type: "session",
100
+ supervisorId,
101
+ sessionId: handle.sessionId
102
+ });
103
+ }
104
+ };
105
+ try {
106
+ sendToParent({
107
+ type: "progress",
108
+ supervisorId,
109
+ summary: "Child supervisor started",
110
+ costDelta: 0
111
+ });
112
+ await loop.run();
113
+ reportSession();
114
+ } catch (err) {
115
+ const errMsg = err instanceof Error ? err.message : String(err);
116
+ sendToParent({
117
+ type: "failed",
118
+ supervisorId,
119
+ error: errMsg
120
+ });
121
+ process.exit(1);
122
+ }
123
+ }
124
+ function sendToParent(msg) {
125
+ if (process.send) {
126
+ process.send(msg);
127
+ }
128
+ }
129
+ var isDirectExecution = process.argv[1]?.endsWith("child-supervisor-worker.js") || process.argv[1]?.endsWith("child-supervisor-worker.ts");
130
+ if (isDirectExecution) {
131
+ main().catch((err) => {
132
+ console.error("[child-supervisor-worker] Fatal error:", err);
133
+ process.exit(1);
134
+ });
135
+ }
136
+ //# sourceMappingURL=child-supervisor-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/daemon/child-supervisor-worker.ts"],"sourcesContent":["/**\n * Entry point for child supervisor worker process.\n * Spawned by parent supervisor via fork(), communicates via IPC.\n *\n * Required environment variables:\n * - NEO_CHILD_SUPERVISOR_ID: unique ID for this child\n * - NEO_CHILD_OBJECTIVE: the objective to accomplish\n * - NEO_CHILD_CRITERIA: JSON-encoded acceptance criteria array\n * - NEO_CHILD_PARENT_NAME: name of parent supervisor\n * - NEO_CHILD_MAX_COST_USD: optional budget cap\n * - NEO_CHILD_DEPTH: depth level (0 or 1)\n */\n\nimport { mkdir } from \"node:fs/promises\";\nimport {\n ClaudeAdapter,\n FocusedLoop,\n getFocusedSupervisorDir,\n JsonlSupervisorStore,\n loadGlobalConfig,\n type ParentToChildMessage,\n} from \"@neotx/core\";\n\nasync function main(): Promise<void> {\n const supervisorId = process.env.NEO_CHILD_SUPERVISOR_ID;\n const objective = process.env.NEO_CHILD_OBJECTIVE;\n const criteriaRaw = process.env.NEO_CHILD_CRITERIA;\n const parentName = process.env.NEO_CHILD_PARENT_NAME;\n const maxCostUsdRaw = process.env.NEO_CHILD_MAX_COST_USD;\n\n if (!supervisorId || !objective || !criteriaRaw || !parentName) {\n console.error(\"[child-supervisor-worker] Missing required environment variables\");\n process.exit(1);\n }\n\n let acceptanceCriteria: string[];\n try {\n acceptanceCriteria = JSON.parse(criteriaRaw) as string[];\n } catch {\n console.error(\"[child-supervisor-worker] Invalid NEO_CHILD_CRITERIA JSON\");\n process.exit(1);\n }\n\n const maxCostUsd = maxCostUsdRaw ? Number.parseFloat(maxCostUsdRaw) : undefined;\n\n // Create supervisor directory\n const supervisorDir = getFocusedSupervisorDir(supervisorId);\n await mkdir(supervisorDir, { recursive: true });\n\n // Load config (for any future config needs)\n await loadGlobalConfig();\n\n // Create AI adapter (ClaudeAdapter wraps the SDK)\n const adapter = new ClaudeAdapter();\n\n // Create store for session persistence using existing JsonlSupervisorStore\n const store = new JsonlSupervisorStore(supervisorDir);\n\n // Track cumulative cost\n let totalCostUsd = 0;\n\n // Create FocusedLoop\n const loop = new FocusedLoop({\n supervisorId,\n objective,\n acceptanceCriteria,\n adapter,\n store,\n onComplete: async (result) => {\n sendToParent({\n type: \"complete\",\n supervisorId,\n summary: result.summary,\n evidence: result.evidence,\n });\n process.exit(0);\n },\n onBlocked: async (blocked) => {\n sendToParent({\n type: \"blocked\",\n supervisorId,\n reason: blocked.reason,\n question: blocked.question,\n urgency: blocked.urgency,\n });\n },\n onProgress: async (summary, costDelta) => {\n totalCostUsd += costDelta;\n sendToParent({\n type: \"progress\",\n supervisorId,\n summary,\n costDelta,\n });\n\n // Check budget locally as well (defense in depth)\n if (maxCostUsd !== undefined && totalCostUsd >= maxCostUsd) {\n sendToParent({\n type: \"failed\",\n supervisorId,\n error: `Budget exceeded: $${totalCostUsd.toFixed(2)} >= $${maxCostUsd.toFixed(2)}`,\n });\n process.exit(1);\n }\n },\n });\n\n // Handle messages from parent\n process.on(\"message\", (msg: ParentToChildMessage) => {\n switch (msg.type) {\n case \"stop\":\n loop.stop();\n sendToParent({\n type: \"failed\",\n supervisorId,\n error: \"Stopped by parent\",\n });\n process.exit(0);\n break;\n case \"inject\":\n loop.injectContext(msg.context);\n break;\n case \"unblock\":\n loop.injectContext(`Parent answer: ${msg.answer}`);\n break;\n }\n });\n\n // Report session ID once available\n const reportSession = () => {\n const handle = adapter.getSessionHandle();\n if (handle?.provider === \"claude\") {\n sendToParent({\n type: \"session\",\n supervisorId,\n sessionId: handle.sessionId,\n });\n }\n };\n\n // Run the focused loop\n try {\n // Initial progress to indicate we started\n sendToParent({\n type: \"progress\",\n supervisorId,\n summary: \"Child supervisor started\",\n costDelta: 0,\n });\n\n await loop.run();\n\n // Report session after first turn if available\n reportSession();\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n sendToParent({\n type: \"failed\",\n supervisorId,\n error: errMsg,\n });\n process.exit(1);\n }\n}\n\nfunction sendToParent(msg: Record<string, unknown>): void {\n if (process.send) {\n process.send(msg);\n }\n}\n\n// Only run main() when executed directly (not when imported for testing)\n// Use process.argv[1] check for ESM entry point detection\nconst isDirectExecution =\n process.argv[1]?.endsWith(\"child-supervisor-worker.js\") ||\n process.argv[1]?.endsWith(\"child-supervisor-worker.ts\");\n\nif (isDirectExecution) {\n main().catch((err) => {\n console.error(\"[child-supervisor-worker] Fatal error:\", err);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
@@ -6,6 +6,7 @@ import {
6
6
  import { createWriteStream } from "fs";
7
7
  import { mkdir } from "fs/promises";
8
8
  import path from "path";
9
+ import { fileURLToPath } from "url";
9
10
  import { getSupervisorDir, loadGlobalConfig, SupervisorDaemon } from "@neotx/core";
10
11
  async function main() {
11
12
  const name = process.argv[2];
@@ -22,7 +23,14 @@ async function main() {
22
23
  try {
23
24
  const config = await loadGlobalConfig();
24
25
  const defaultInstructionsPath = path.join(resolveAgentsPackageDir(), "SUPERVISOR.md");
25
- const daemon = new SupervisorDaemon({ name, config, defaultInstructionsPath });
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
+ const workerPath = path.join(__dirname, "child-supervisor-worker.js");
28
+ const daemon = new SupervisorDaemon({
29
+ name,
30
+ config,
31
+ defaultInstructionsPath,
32
+ workerPath
33
+ });
26
34
  await daemon.start();
27
35
  } catch (error) {
28
36
  const msg = error instanceof Error ? error.message : String(error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/daemon/supervisor-worker.ts"],"sourcesContent":["/**\n * Detached worker process for the supervisor daemon.\n *\n * Launched via child_process.fork() from the supervise command.\n * Runs the SupervisorDaemon which starts the heartbeat loop,\n * webhook server, and event queue.\n *\n * Usage: node supervisor-worker.js <name>\n */\n\nimport { createWriteStream } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getSupervisorDir, loadGlobalConfig, SupervisorDaemon } from \"@neotx/core\";\nimport { resolveAgentsPackageDir } from \"../resolve.js\";\n\nasync function main(): Promise<void> {\n const name = process.argv[2];\n if (!name) {\n process.stderr.write(\"Usage: supervisor-worker.js <name>\\n\");\n process.exit(1);\n }\n\n // Redirect stdout/stderr to a log file\n const dir = getSupervisorDir(name);\n await mkdir(dir, { recursive: true });\n const logPath = `${dir}/daemon.log`;\n const logStream = createWriteStream(logPath, { flags: \"a\" });\n process.stdout.write = logStream.write.bind(logStream);\n process.stderr.write = logStream.write.bind(logStream);\n\n try {\n const config = await loadGlobalConfig();\n const defaultInstructionsPath = path.join(resolveAgentsPackageDir(), \"SUPERVISOR.md\");\n const daemon = new SupervisorDaemon({ name, config, defaultInstructionsPath });\n await daemon.start();\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[supervisor-worker] Fatal: ${msg}`);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":";;;;;AAUA,SAAS,yBAAyB;AAClC,SAAS,aAAa;AACtB,OAAO,UAAU;AACjB,SAAS,kBAAkB,kBAAkB,wBAAwB;AAGrE,eAAe,OAAsB;AACnC,QAAM,OAAO,QAAQ,KAAK,CAAC;AAC3B,MAAI,CAAC,MAAM;AACT,YAAQ,OAAO,MAAM,sCAAsC;AAC3D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,MAAM,iBAAiB,IAAI;AACjC,QAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,QAAM,UAAU,GAAG,GAAG;AACtB,QAAM,YAAY,kBAAkB,SAAS,EAAE,OAAO,IAAI,CAAC;AAC3D,UAAQ,OAAO,QAAQ,UAAU,MAAM,KAAK,SAAS;AACrD,UAAQ,OAAO,QAAQ,UAAU,MAAM,KAAK,SAAS;AAErD,MAAI;AACF,UAAM,SAAS,MAAM,iBAAiB;AACtC,UAAM,0BAA0B,KAAK,KAAK,wBAAwB,GAAG,eAAe;AACpF,UAAM,SAAS,IAAI,iBAAiB,EAAE,MAAM,QAAQ,wBAAwB,CAAC;AAC7E,UAAM,OAAO,MAAM;AAAA,EACrB,SAAS,OAAO;AACd,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,YAAQ,MAAM,8BAA8B,GAAG,EAAE;AACjD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,KAAK;","names":[]}
1
+ {"version":3,"sources":["../../src/daemon/supervisor-worker.ts"],"sourcesContent":["/**\n * Detached worker process for the supervisor daemon.\n *\n * Launched via child_process.fork() from the supervise command.\n * Runs the SupervisorDaemon which starts the heartbeat loop,\n * webhook server, and event queue.\n *\n * Usage: node supervisor-worker.js <name>\n */\n\nimport { createWriteStream } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getSupervisorDir, loadGlobalConfig, SupervisorDaemon } from \"@neotx/core\";\nimport { resolveAgentsPackageDir } from \"../resolve.js\";\n\nasync function main(): Promise<void> {\n const name = process.argv[2];\n if (!name) {\n process.stderr.write(\"Usage: supervisor-worker.js <name>\\n\");\n process.exit(1);\n }\n\n // Redirect stdout/stderr to a log file\n const dir = getSupervisorDir(name);\n await mkdir(dir, { recursive: true });\n const logPath = `${dir}/daemon.log`;\n const logStream = createWriteStream(logPath, { flags: \"a\" });\n process.stdout.write = logStream.write.bind(logStream);\n process.stderr.write = logStream.write.bind(logStream);\n\n try {\n const config = await loadGlobalConfig();\n const defaultInstructionsPath = path.join(resolveAgentsPackageDir(), \"SUPERVISOR.md\");\n\n // Resolve path to child-supervisor-worker.js (same directory as this file)\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const workerPath = path.join(__dirname, \"child-supervisor-worker.js\");\n\n const daemon = new SupervisorDaemon({\n name,\n config,\n defaultInstructionsPath,\n workerPath,\n });\n await daemon.start();\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[supervisor-worker] Fatal: ${msg}`);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":";;;;;AAUA,SAAS,yBAAyB;AAClC,SAAS,aAAa;AACtB,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,kBAAkB,wBAAwB;AAGrE,eAAe,OAAsB;AACnC,QAAM,OAAO,QAAQ,KAAK,CAAC;AAC3B,MAAI,CAAC,MAAM;AACT,YAAQ,OAAO,MAAM,sCAAsC;AAC3D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,MAAM,iBAAiB,IAAI;AACjC,QAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,QAAM,UAAU,GAAG,GAAG;AACtB,QAAM,YAAY,kBAAkB,SAAS,EAAE,OAAO,IAAI,CAAC;AAC3D,UAAQ,OAAO,QAAQ,UAAU,MAAM,KAAK,SAAS;AACrD,UAAQ,OAAO,QAAQ,UAAU,MAAM,KAAK,SAAS;AAErD,MAAI;AACF,UAAM,SAAS,MAAM,iBAAiB;AACtC,UAAM,0BAA0B,KAAK,KAAK,wBAAwB,GAAG,eAAe;AAGpF,UAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,UAAM,aAAa,KAAK,KAAK,WAAW,4BAA4B;AAEpE,UAAM,SAAS,IAAI,iBAAiB;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,OAAO,MAAM;AAAA,EACrB,SAAS,OAAO;AACd,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,YAAQ,MAAM,8BAA8B,GAAG,EAAE;AACjD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,KAAK;","names":[]}
@@ -7,6 +7,7 @@ import {
7
7
  getRepoRunsDir,
8
8
  getRunDispatchPath,
9
9
  getRunLogPath,
10
+ getWorkerStartedPath,
10
11
  loadGlobalConfig,
11
12
  Orchestrator
12
13
  } from "@neotx/core";
@@ -23,6 +24,13 @@ async function main() {
23
24
  logStream.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
24
25
  `);
25
26
  }
27
+ writeLog(`[worker] Process started (PID ${process.pid}), initializing...`);
28
+ const startedPath = getWorkerStartedPath(repoSlug, runId);
29
+ await writeFile(
30
+ startedPath,
31
+ JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }),
32
+ "utf-8"
33
+ );
26
34
  process.stdout.write = logStream.write.bind(logStream);
27
35
  process.stderr.write = logStream.write.bind(logStream);
28
36
  process.on("uncaughtException", (err) => {
@@ -41,7 +49,7 @@ ${err.stack}`);
41
49
  process.exit(1);
42
50
  });
43
51
  }
44
- writeLog(`[worker] Starting run ${runId} (PID ${process.pid})`);
52
+ writeLog(`[worker] Starting run ${runId}`);
45
53
  const dispatchPath = getRunDispatchPath(repoSlug, runId);
46
54
  const runPath = path.join(getRepoRunsDir(repoSlug), `${runId}.json`);
47
55
  try {
@@ -93,10 +101,15 @@ ${err.stack}`);
93
101
  await updatePersistedRun(runPath, {
94
102
  status: "failed",
95
103
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
96
- }).catch((err) => {
97
- console.debug("[worker] Failed to update persisted run on error:", err);
104
+ }).catch((persistErr) => {
105
+ console.debug("[worker] Failed to update persisted run on error:", persistErr);
106
+ console.error(
107
+ `[worker] CRITICAL: Run ${runId} failed with "${errorMsg}" but could not persist failure state: ${persistErr instanceof Error ? persistErr.message : String(persistErr)}`
108
+ );
98
109
  });
99
110
  } finally {
111
+ await unlink(startedPath).catch(() => {
112
+ });
100
113
  logStream.end();
101
114
  process.exit(0);
102
115
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/daemon/worker.ts"],"sourcesContent":["/**\n * Detached worker process for `neo run -d`.\n *\n * Launched via child_process.fork() from the run command.\n * Reads dispatch parameters from a .dispatch.json file, runs the orchestrator,\n * and persists results. Stdout/stderr are redirected to a log file.\n *\n * Usage: node worker.js <runId> <repoSlug>\n */\n\nimport { createWriteStream, existsSync } from \"node:fs\";\nimport { mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { PersistedRun } from \"@neotx/core\";\nimport {\n AgentRegistry,\n getRepoRunsDir,\n getRunDispatchPath,\n getRunLogPath,\n loadGlobalConfig,\n Orchestrator,\n} from \"@neotx/core\";\n\ninterface DispatchRequest {\n agentName: string;\n repo: string;\n prompt: string;\n branch?: string;\n priority?: \"critical\" | \"high\" | \"medium\" | \"low\";\n metadata?: Record<string, unknown>;\n bundledAgentsDir: string;\n customAgentsDir?: string;\n}\n\nasync function main(): Promise<void> {\n const [runId, repoSlug] = process.argv.slice(2);\n if (!runId || !repoSlug) {\n process.stderr.write(\"Usage: worker.js <runId> <repoSlug>\\n\");\n process.exit(1);\n }\n\n // Redirect stdout/stderr to log file\n const logPath = getRunLogPath(repoSlug, runId);\n await mkdir(path.dirname(logPath), { recursive: true });\n const logStream = createWriteStream(logPath, { flags: \"a\" });\n\n function writeLog(msg: string): void {\n logStream.write(`${new Date().toISOString()} ${msg}\\n`);\n }\n\n process.stdout.write = logStream.write.bind(logStream);\n process.stderr.write = logStream.write.bind(logStream);\n\n // Catch crashes and signals so we always leave a trace\n process.on(\"uncaughtException\", (err) => {\n writeLog(`[worker] UNCAUGHT EXCEPTION: ${err.message}\\n${err.stack}`);\n logStream.end();\n process.exit(1);\n });\n process.on(\"unhandledRejection\", (reason) => {\n writeLog(`[worker] UNHANDLED REJECTION: ${String(reason)}`);\n });\n for (const sig of [\"SIGTERM\", \"SIGINT\", \"SIGHUP\"] as const) {\n process.on(sig, () => {\n writeLog(`[worker] Received ${sig}, exiting`);\n logStream.end();\n process.exit(1);\n });\n }\n\n writeLog(`[worker] Starting run ${runId} (PID ${process.pid})`);\n\n const dispatchPath = getRunDispatchPath(repoSlug, runId);\n const runPath = path.join(getRepoRunsDir(repoSlug), `${runId}.json`);\n\n try {\n // Read dispatch request\n const raw = await readFile(dispatchPath, \"utf-8\");\n const request = JSON.parse(raw) as DispatchRequest;\n\n // Clean up dispatch file\n await unlink(dispatchPath).catch((err) => {\n console.debug(\"[worker] Failed to clean up dispatch file:\", err);\n });\n writeLog(`[worker] Dispatch loaded: agent=${request.agentName} repo=${request.repo}`);\n\n // Load config and agents\n const config = await loadGlobalConfig();\n const agentRegistry = new AgentRegistry(\n request.bundledAgentsDir,\n request.customAgentsDir && existsSync(request.customAgentsDir)\n ? request.customAgentsDir\n : undefined,\n );\n await agentRegistry.load();\n\n const agent = agentRegistry.get(request.agentName);\n if (!agent) {\n throw new Error(`Agent \"${request.agentName}\" not found`);\n }\n\n // Create orchestrator — skip orphan recovery to prevent false positives on concurrent launches\n const orchestrator = new Orchestrator(config, { skipOrphanRecovery: true });\n orchestrator.registerAgent(agent);\n\n // Update persisted run with PID\n await updatePersistedRun(runPath, { pid: process.pid });\n\n // Safety timeout — ensure the process eventually exits\n const safetyTimeout = setTimeout(() => {\n console.error(\"[worker] Safety timeout reached, forcing exit\");\n process.exit(1);\n }, config.sessions.maxDurationMs + 60_000);\n safetyTimeout.unref();\n\n writeLog(\"[worker] Starting orchestrator...\");\n await orchestrator.start();\n\n writeLog(\"[worker] Dispatching...\");\n const result = await orchestrator.dispatch({\n runId,\n agent: request.agentName,\n repo: request.repo,\n prompt: request.prompt,\n ...(request.branch ? { branch: request.branch } : {}),\n priority: request.priority ?? \"medium\",\n metadata: request.metadata,\n });\n\n await orchestrator.shutdown();\n\n console.log(`[worker] Run ${runId} completed: ${result.status}`);\n console.log(`[worker] Cost: $${result.costUsd.toFixed(4)}`);\n if (result.branch) {\n console.log(`[worker] Branch: ${result.branch}`);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n console.error(`[worker] Run ${runId} failed: ${errorMsg}`);\n\n // Update persisted run to failed status\n await updatePersistedRun(runPath, {\n status: \"failed\",\n updatedAt: new Date().toISOString(),\n }).catch((err) => {\n console.debug(\"[worker] Failed to update persisted run on error:\", err);\n });\n } finally {\n logStream.end();\n process.exit(0);\n }\n}\n\nasync function updatePersistedRun(runPath: string, updates: Partial<PersistedRun>): Promise<void> {\n try {\n const raw = await readFile(runPath, \"utf-8\");\n const run = JSON.parse(raw) as PersistedRun;\n Object.assign(run, updates);\n await writeFile(runPath, JSON.stringify(run, null, 2), \"utf-8\");\n } catch {\n // Non-critical\n }\n}\n\nmain();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/daemon/worker.ts"],"sourcesContent":["/**\n * Detached worker process for `neo run -d`.\n *\n * Launched via child_process.fork() from the run command.\n * Reads dispatch parameters from a .dispatch.json file, runs the orchestrator,\n * and persists results. Stdout/stderr are redirected to a log file.\n *\n * Usage: node worker.js <runId> <repoSlug>\n */\n\nimport { createWriteStream, existsSync } from \"node:fs\";\nimport { mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { PersistedRun } from \"@neotx/core\";\nimport {\n AgentRegistry,\n getRepoRunsDir,\n getRunDispatchPath,\n getRunLogPath,\n getWorkerStartedPath,\n loadGlobalConfig,\n Orchestrator,\n} from \"@neotx/core\";\n\ninterface DispatchRequest {\n agentName: string;\n repo: string;\n prompt: string;\n branch?: string;\n priority?: \"critical\" | \"high\" | \"medium\" | \"low\";\n metadata?: Record<string, unknown>;\n bundledAgentsDir: string;\n customAgentsDir?: string;\n}\n\nasync function main(): Promise<void> {\n const [runId, repoSlug] = process.argv.slice(2);\n if (!runId || !repoSlug) {\n process.stderr.write(\"Usage: worker.js <runId> <repoSlug>\\n\");\n process.exit(1);\n }\n\n // Redirect stdout/stderr to log file\n const logPath = getRunLogPath(repoSlug, runId);\n await mkdir(path.dirname(logPath), { recursive: true });\n const logStream = createWriteStream(logPath, { flags: \"a\" });\n\n function writeLog(msg: string): void {\n logStream.write(`${new Date().toISOString()} ${msg}\\n`);\n }\n\n // CRITICAL: Immediate log entry right after stream creation\n // This ensures a trace exists even if the process crashes before any other code runs.\n // Without this, spawn failures leave no evidence in the log file.\n writeLog(`[worker] Process started (PID ${process.pid}), initializing...`);\n\n // CRITICAL: Write startup confirmation file immediately\n // The parent process checks for this file to detect early worker crashes.\n // This must happen as early as possible to minimize the window for undetected failures.\n const startedPath = getWorkerStartedPath(repoSlug, runId);\n await writeFile(\n startedPath,\n JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }),\n \"utf-8\",\n );\n\n process.stdout.write = logStream.write.bind(logStream);\n process.stderr.write = logStream.write.bind(logStream);\n\n // Catch crashes and signals so we always leave a trace\n process.on(\"uncaughtException\", (err) => {\n writeLog(`[worker] UNCAUGHT EXCEPTION: ${err.message}\\n${err.stack}`);\n logStream.end();\n process.exit(1);\n });\n process.on(\"unhandledRejection\", (reason) => {\n writeLog(`[worker] UNHANDLED REJECTION: ${String(reason)}`);\n });\n for (const sig of [\"SIGTERM\", \"SIGINT\", \"SIGHUP\"] as const) {\n process.on(sig, () => {\n writeLog(`[worker] Received ${sig}, exiting`);\n logStream.end();\n process.exit(1);\n });\n }\n\n writeLog(`[worker] Starting run ${runId}`);\n\n const dispatchPath = getRunDispatchPath(repoSlug, runId);\n const runPath = path.join(getRepoRunsDir(repoSlug), `${runId}.json`);\n\n try {\n // Read dispatch request\n const raw = await readFile(dispatchPath, \"utf-8\");\n const request = JSON.parse(raw) as DispatchRequest;\n\n // Clean up dispatch file\n await unlink(dispatchPath).catch((err) => {\n console.debug(\"[worker] Failed to clean up dispatch file:\", err);\n });\n writeLog(`[worker] Dispatch loaded: agent=${request.agentName} repo=${request.repo}`);\n\n // Load config and agents\n const config = await loadGlobalConfig();\n const agentRegistry = new AgentRegistry(\n request.bundledAgentsDir,\n request.customAgentsDir && existsSync(request.customAgentsDir)\n ? request.customAgentsDir\n : undefined,\n );\n await agentRegistry.load();\n\n const agent = agentRegistry.get(request.agentName);\n if (!agent) {\n throw new Error(`Agent \"${request.agentName}\" not found`);\n }\n\n // Create orchestrator — skip orphan recovery to prevent false positives on concurrent launches\n const orchestrator = new Orchestrator(config, { skipOrphanRecovery: true });\n orchestrator.registerAgent(agent);\n\n // Update persisted run with PID\n await updatePersistedRun(runPath, { pid: process.pid });\n\n // Safety timeout — ensure the process eventually exits\n const safetyTimeout = setTimeout(() => {\n console.error(\"[worker] Safety timeout reached, forcing exit\");\n process.exit(1);\n }, config.sessions.maxDurationMs + 60_000);\n safetyTimeout.unref();\n\n writeLog(\"[worker] Starting orchestrator...\");\n await orchestrator.start();\n\n writeLog(\"[worker] Dispatching...\");\n const result = await orchestrator.dispatch({\n runId,\n agent: request.agentName,\n repo: request.repo,\n prompt: request.prompt,\n ...(request.branch ? { branch: request.branch } : {}),\n priority: request.priority ?? \"medium\",\n metadata: request.metadata,\n });\n\n await orchestrator.shutdown();\n\n console.log(`[worker] Run ${runId} completed: ${result.status}`);\n console.log(`[worker] Cost: $${result.costUsd.toFixed(4)}`);\n if (result.branch) {\n console.log(`[worker] Branch: ${result.branch}`);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n console.error(`[worker] Run ${runId} failed: ${errorMsg}`);\n\n // Update persisted run to failed status\n // CRITICAL: Use console.error as fallback to ensure the error is visible\n // even if the run file write fails (prevents silent error loss)\n await updatePersistedRun(runPath, {\n status: \"failed\",\n updatedAt: new Date().toISOString(),\n }).catch((persistErr) => {\n // Log to both debug and stderr to ensure visibility\n console.debug(\"[worker] Failed to update persisted run on error:\", persistErr);\n console.error(\n `[worker] CRITICAL: Run ${runId} failed with \"${errorMsg}\" but could not persist failure state: ${persistErr instanceof Error ? persistErr.message : String(persistErr)}`,\n );\n });\n } finally {\n // Clean up the startup confirmation file\n await unlink(startedPath).catch(() => {\n // Best effort - file may not exist or be already cleaned up\n });\n logStream.end();\n process.exit(0);\n }\n}\n\nasync function updatePersistedRun(runPath: string, updates: Partial<PersistedRun>): Promise<void> {\n try {\n const raw = await readFile(runPath, \"utf-8\");\n const run = JSON.parse(raw) as PersistedRun;\n Object.assign(run, updates);\n await writeFile(runPath, JSON.stringify(run, null, 2), \"utf-8\");\n } catch {\n // Non-critical\n }\n}\n\nmain();\n"],"mappings":"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","names":[]}