@adamancyzhang/claude-orchestrator 0.2.8 → 0.3.1

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 (91) hide show
  1. package/README.md +179 -186
  2. package/dist/cli/commands.d.ts +13 -20
  3. package/dist/cli/commands.js +171 -288
  4. package/dist/cli/commands.js.map +1 -1
  5. package/dist/config.d.ts +25 -11
  6. package/dist/config.js +49 -13
  7. package/dist/config.js.map +1 -1
  8. package/dist/index.js +122 -215
  9. package/dist/index.js.map +1 -1
  10. package/dist/leader/decision-engine.d.ts +35 -0
  11. package/dist/leader/decision-engine.js +102 -0
  12. package/dist/leader/decision-engine.js.map +1 -0
  13. package/dist/leader/event-bus.d.ts +11 -0
  14. package/dist/leader/event-bus.js +21 -0
  15. package/dist/leader/event-bus.js.map +1 -0
  16. package/dist/leader/index.d.ts +7 -0
  17. package/dist/leader/index.js +96 -0
  18. package/dist/leader/index.js.map +1 -0
  19. package/dist/leader/monitor.d.ts +14 -0
  20. package/dist/leader/monitor.js +55 -0
  21. package/dist/leader/monitor.js.map +1 -0
  22. package/dist/leader/orchestrator.d.ts +14 -0
  23. package/dist/leader/orchestrator.js +83 -0
  24. package/dist/leader/orchestrator.js.map +1 -0
  25. package/dist/leader/recovery.d.ts +11 -0
  26. package/dist/leader/recovery.js +61 -0
  27. package/dist/leader/recovery.js.map +1 -0
  28. package/dist/leader/state.d.ts +24 -0
  29. package/dist/leader/state.js +122 -0
  30. package/dist/leader/state.js.map +1 -0
  31. package/dist/leader/task-generator.d.ts +34 -0
  32. package/dist/leader/task-generator.js +93 -0
  33. package/dist/leader/task-generator.js.map +1 -0
  34. package/dist/leader/tui.d.ts +5 -0
  35. package/dist/leader/tui.js +136 -0
  36. package/dist/leader/tui.js.map +1 -0
  37. package/dist/leader/watcher.d.ts +18 -0
  38. package/dist/leader/watcher.js +89 -0
  39. package/dist/leader/watcher.js.map +1 -0
  40. package/dist/models/schemas.d.ts +111 -100
  41. package/dist/models/schemas.js +54 -45
  42. package/dist/models/schemas.js.map +1 -1
  43. package/dist/modules/message-router.d.ts +2 -2
  44. package/dist/modules/message-router.js +10 -16
  45. package/dist/modules/message-router.js.map +1 -1
  46. package/dist/modules/registry.js +3 -3
  47. package/dist/modules/registry.js.map +1 -1
  48. package/dist/modules/task-queue.d.ts +4 -1
  49. package/dist/modules/task-queue.js +114 -10
  50. package/dist/modules/task-queue.js.map +1 -1
  51. package/dist/skills/CLAUDE.md +155 -0
  52. package/dist/skills/claude-code-developer/SKILL.md +325 -0
  53. package/dist/skills/claude-orchestrator/SKILL.md +180 -0
  54. package/dist/skills/task-acceptance/SKILL.md +201 -0
  55. package/dist/skills/task-execution/SKILL.md +142 -0
  56. package/dist/skills/task-planning/SKILL.md +188 -0
  57. package/dist/skills/task-review/SKILL.md +220 -0
  58. package/dist/skills/task-traceability/SKILL.md +154 -0
  59. package/dist/skills/task-verification/SKILL.md +194 -0
  60. package/dist/templates/leader-decide.md +59 -0
  61. package/dist/templates/leader-decompose.md +69 -0
  62. package/dist/templates/worker-accept.md +46 -0
  63. package/dist/templates/worker-build.md +45 -0
  64. package/dist/templates/worker-plan.md +43 -0
  65. package/dist/templates/worker-review.md +46 -0
  66. package/dist/templates/worker-verify.md +47 -0
  67. package/dist/utils/exec.d.ts +8 -0
  68. package/dist/utils/exec.js +45 -0
  69. package/dist/utils/exec.js.map +1 -0
  70. package/dist/worker/watcher.d.ts +19 -0
  71. package/dist/worker/watcher.js +152 -0
  72. package/dist/worker/watcher.js.map +1 -0
  73. package/dist/zk/client.d.ts +5 -5
  74. package/dist/zk/client.js +16 -26
  75. package/dist/zk/client.js.map +1 -1
  76. package/dist/zk/paths.d.ts +9 -9
  77. package/dist/zk/paths.js +4 -5
  78. package/dist/zk/paths.js.map +1 -1
  79. package/dist/zk/watcher.d.ts +0 -2
  80. package/dist/zk/watcher.js +0 -3
  81. package/dist/zk/watcher.js.map +1 -1
  82. package/package.json +3 -6
  83. package/dist/modules/context-store.d.ts +0 -10
  84. package/dist/modules/context-store.js +0 -25
  85. package/dist/modules/context-store.js.map +0 -1
  86. package/dist/modules/message-watcher.d.ts +0 -12
  87. package/dist/modules/message-watcher.js +0 -133
  88. package/dist/modules/message-watcher.js.map +0 -1
  89. package/dist/server.d.ts +0 -2
  90. package/dist/server.js +0 -490
  91. package/dist/server.js.map +0 -1
@@ -0,0 +1,35 @@
1
+ import { ZkClient } from "../zk/client.js";
2
+ import { TaskQueue } from "../modules/task-queue.js";
3
+ import { MessageRouter } from "../modules/message-router.js";
4
+ import type { Message } from "../models/schemas.js";
5
+ export interface DecisionContext {
6
+ teamStatus: object;
7
+ taskQueues: object;
8
+ chainStatus: object;
9
+ }
10
+ export interface Decision {
11
+ decision: "pass" | "feedback" | "reject";
12
+ reason: string;
13
+ feedbackToWorker?: string;
14
+ nextAction: {
15
+ action: "activate_next_link" | "reassign" | "close_chain" | "broadcast_help" | "none";
16
+ nextLink: string | null;
17
+ suggestedWorker: string | null;
18
+ messageToWorker: string | null;
19
+ };
20
+ }
21
+ export declare class DecisionEngine {
22
+ private zk;
23
+ private taskQueue;
24
+ private messageRouter;
25
+ private command;
26
+ private cacheDir;
27
+ private leaderInstanceId;
28
+ private template;
29
+ private templatePath;
30
+ constructor(zk: ZkClient, taskQueue: TaskQueue, messageRouter: MessageRouter, command: string, cacheDir: string, leaderInstanceId: string, templatePath?: string);
31
+ private loadTemplate;
32
+ evaluate(report: Message, context: DecisionContext): Promise<Decision>;
33
+ private executeDecision;
34
+ private parseOutput;
35
+ }
@@ -0,0 +1,102 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { expandHomeDir } from "../config.js";
4
+ import { execAndCapture } from "../utils/exec.js";
5
+ const NEXT_LINKS = {
6
+ plan: "build",
7
+ build: "verify",
8
+ verify: "review",
9
+ review: "accept",
10
+ accept: null,
11
+ };
12
+ export class DecisionEngine {
13
+ zk;
14
+ taskQueue;
15
+ messageRouter;
16
+ command;
17
+ cacheDir;
18
+ leaderInstanceId;
19
+ template = null;
20
+ templatePath;
21
+ constructor(zk, taskQueue, messageRouter, command, cacheDir, leaderInstanceId, templatePath) {
22
+ this.zk = zk;
23
+ this.taskQueue = taskQueue;
24
+ this.messageRouter = messageRouter;
25
+ this.command = command;
26
+ this.cacheDir = cacheDir;
27
+ this.leaderInstanceId = leaderInstanceId;
28
+ this.templatePath = templatePath ?? path.join(process.cwd(), ".claude-orchestrator", "agents", "leader-decide.md");
29
+ }
30
+ async loadTemplate() {
31
+ if (this.template)
32
+ return this.template;
33
+ try {
34
+ this.template = await fs.promises.readFile(this.templatePath, "utf-8");
35
+ }
36
+ catch {
37
+ throw new Error(`Decide template not found at ${this.templatePath}. Run setup first.`);
38
+ }
39
+ return this.template;
40
+ }
41
+ async evaluate(report, context) {
42
+ const template = await this.loadTemplate();
43
+ const prompt = template
44
+ .replace("{{team_status}}", JSON.stringify(context.teamStatus, null, 2))
45
+ .replace("{{task_queues}}", JSON.stringify(context.taskQueues, null, 2))
46
+ .replace("{{chain_status}}", JSON.stringify(context.chainStatus, null, 2))
47
+ .replace("{{content}}", report.content);
48
+ const uniqueKey = `decide-${Date.now().toString(36)}`;
49
+ const resolvedCacheDir = expandHomeDir(path.join(this.cacheDir, this.leaderInstanceId));
50
+ const logPath = path.join(resolvedCacheDir, `${uniqueKey}.log`);
51
+ const { stdout } = await execAndCapture(this.command, prompt, logPath);
52
+ const decision = this.parseOutput(stdout);
53
+ await this.executeDecision(decision, report);
54
+ return decision;
55
+ }
56
+ async executeDecision(decision, report) {
57
+ const nextLink = decision.nextAction.nextLink;
58
+ const linkOrder = ["plan", "build", "verify", "review", "accept"];
59
+ switch (decision.nextAction.action) {
60
+ case "activate_next_link": {
61
+ if (!nextLink)
62
+ break;
63
+ const pending = await this.zk.listPendingTasks();
64
+ for (const [taskId, data] of pending) {
65
+ if (data.link === nextLink && data.chain_id === report.chain_id) {
66
+ // Task already exists in pending, no need to create
67
+ break;
68
+ }
69
+ }
70
+ break;
71
+ }
72
+ case "close_chain":
73
+ break;
74
+ case "reassign":
75
+ case "broadcast_help":
76
+ case "none":
77
+ break;
78
+ }
79
+ if (decision.decision === "feedback" && decision.feedbackToWorker && report.from_instance) {
80
+ await this.messageRouter.send(this.leaderInstanceId, "Leader", decision.feedbackToWorker, report.from_instance);
81
+ }
82
+ }
83
+ parseOutput(output) {
84
+ const cleaned = output
85
+ .replace(/```json\s*/g, "")
86
+ .replace(/```\s*/g, "")
87
+ .trim();
88
+ const parsed = JSON.parse(cleaned);
89
+ return {
90
+ decision: parsed.decision ?? "pass",
91
+ reason: parsed.reason ?? "",
92
+ feedbackToWorker: parsed.feedback_to_worker,
93
+ nextAction: {
94
+ action: parsed.next_action?.action ?? "none",
95
+ nextLink: parsed.next_action?.next_link ?? null,
96
+ suggestedWorker: parsed.next_action?.suggested_worker ?? null,
97
+ messageToWorker: parsed.next_action?.message_to_worker ?? null,
98
+ },
99
+ };
100
+ }
101
+ }
102
+ //# sourceMappingURL=decision-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-engine.js","sourceRoot":"","sources":["../../src/leader/decision-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAKlC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,UAAU,GAAkC;IAChD,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,IAAI;CACb,CAAC;AAoBF,MAAM,OAAO,cAAc;IAKf;IACA;IACA;IACA;IACA;IACA;IATF,QAAQ,GAAkB,IAAI,CAAC;IAC/B,YAAY,CAAS;IAE7B,YACU,EAAY,EACZ,SAAoB,EACpB,aAA4B,EAC5B,OAAe,EACf,QAAgB,EAChB,gBAAwB,EAChC,YAAqB;QANb,OAAE,GAAF,EAAE,CAAU;QACZ,cAAS,GAAT,SAAS,CAAW;QACpB,kBAAa,GAAb,aAAa,CAAe;QAC5B,YAAO,GAAP,OAAO,CAAQ;QACf,aAAQ,GAAR,QAAQ,CAAQ;QAChB,qBAAgB,GAAhB,gBAAgB,CAAQ;QAGhC,IAAI,CAAC,YAAY,GAAG,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,sBAAsB,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;IACrH,CAAC;IAEO,KAAK,CAAC,YAAY;QACxB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC,QAAQ,CAAC;QACxC,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACzE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,YAAY,oBAAoB,CAAC,CAAC;QACzF,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAAe,EAAE,OAAwB;QACtD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAE3C,MAAM,MAAM,GAAG,QAAQ;aACpB,OAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;aACvE,OAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;aACvE,OAAO,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;aACzE,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAE1C,MAAM,SAAS,GAAG,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;QACtD,MAAM,gBAAgB,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACxF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,GAAG,SAAS,MAAM,CAAC,CAAC;QAEhE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAE1C,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAE7C,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,QAAkB,EAAE,MAAe;QAC/D,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;QAC9C,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAElE,QAAQ,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;YACnC,KAAK,oBAAoB,CAAC,CAAC,CAAC;gBAC1B,IAAI,CAAC,QAAQ;oBAAE,MAAM;gBACrB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC;gBACjD,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;oBACrC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAM,MAAkC,CAAC,QAAQ,EAAE,CAAC;wBAC7F,oDAAoD;wBACpD,MAAM;oBACR,CAAC;gBACH,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,aAAa;gBAChB,MAAM;YACR,KAAK,UAAU,CAAC;YAChB,KAAK,gBAAgB,CAAC;YACtB,KAAK,MAAM;gBACT,MAAM;QACV,CAAC;QAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,UAAU,IAAI,QAAQ,CAAC,gBAAgB,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YAC1F,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAC3B,IAAI,CAAC,gBAAgB,EACrB,QAAQ,EACR,QAAQ,CAAC,gBAAgB,EACzB,MAAM,CAAC,aAAa,CACrB,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,WAAW,CAAC,MAAc;QAChC,MAAM,OAAO,GAAG,MAAM;aACnB,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;aAC1B,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;aACtB,IAAI,EAAE,CAAC;QAEV,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAEnC,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,MAAM;YACnC,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;YAC3B,gBAAgB,EAAE,MAAM,CAAC,kBAAkB;YAC3C,UAAU,EAAE;gBACV,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,IAAI,MAAM;gBAC5C,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,SAAS,IAAI,IAAI;gBAC/C,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,gBAAgB,IAAI,IAAI;gBAC7D,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,iBAAiB,IAAI,IAAI;aAC/D;SACF,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ export type LeaderEventType = "worker_joined" | "worker_left" | "worker_status_changed" | "task_created" | "task_claimed" | "task_completed" | "task_blocked" | "task_failed" | "task_recovered" | "message_received" | "message_processed";
2
+ export interface LeaderEvent {
3
+ type: LeaderEventType;
4
+ [key: string]: unknown;
5
+ }
6
+ export declare class LeaderEventBus {
7
+ private emitter;
8
+ onAll(handler: (event: LeaderEvent) => void): void;
9
+ emit(event: LeaderEvent): void;
10
+ on(type: LeaderEventType, handler: (event: LeaderEvent) => void): void;
11
+ }
@@ -0,0 +1,21 @@
1
+ import { EventEmitter } from "node:events";
2
+ const ALL_EVENT_TYPES = [
3
+ "worker_joined", "worker_left", "worker_status_changed",
4
+ "task_created", "task_claimed", "task_completed", "task_blocked", "task_failed", "task_recovered",
5
+ "message_received", "message_processed",
6
+ ];
7
+ export class LeaderEventBus {
8
+ emitter = new EventEmitter();
9
+ onAll(handler) {
10
+ for (const t of ALL_EVENT_TYPES) {
11
+ this.emitter.on(t, handler);
12
+ }
13
+ }
14
+ emit(event) {
15
+ this.emitter.emit(event.type, event);
16
+ }
17
+ on(type, handler) {
18
+ this.emitter.on(type, handler);
19
+ }
20
+ }
21
+ //# sourceMappingURL=event-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../src/leader/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoB3C,MAAM,eAAe,GAAsB;IACzC,eAAe,EAAE,aAAa,EAAE,uBAAuB;IACvD,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB;IACjG,kBAAkB,EAAE,mBAAmB;CACxC,CAAC;AAEF,MAAM,OAAO,cAAc;IACjB,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IAErC,KAAK,CAAC,OAAqC;QACzC,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,IAAI,CAAC,KAAkB;QACrB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,EAAE,CAAC,IAAqB,EAAE,OAAqC;QAC7D,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ export declare function startLeader(config: {
2
+ zkHosts: string;
3
+ name?: string;
4
+ instanceId?: string;
5
+ command?: string;
6
+ cacheDir?: string;
7
+ }): Promise<void>;
@@ -0,0 +1,96 @@
1
+ import * as os from "node:os";
2
+ import * as path from "node:path";
3
+ import * as fs from "node:fs";
4
+ import { ZkClient, isNodeExists } from "../zk/client.js";
5
+ import { InstanceRegistry } from "../modules/registry.js";
6
+ import { LeaderEventBus } from "./event-bus.js";
7
+ import { LeaderState } from "./state.js";
8
+ import { WorkerMonitor } from "./monitor.js";
9
+ import { TaskOrchestrator } from "./orchestrator.js";
10
+ import { TaskRecovery } from "./recovery.js";
11
+ import { LeaderWatcher } from "./watcher.js";
12
+ import { TaskGenerator } from "./task-generator.js";
13
+ import { DecisionEngine } from "./decision-engine.js";
14
+ import { TaskQueue } from "../modules/task-queue.js";
15
+ import { MessageRouter } from "../modules/message-router.js";
16
+ import { expandHomeDir, loadGlobalConfig, loadInstanceConfig, saveInstanceId } from "../config.js";
17
+ import { LeaderTui } from "./tui.js";
18
+ export async function startLeader(config) {
19
+ const zk = new ZkClient(config.zkHosts);
20
+ await zk.connect();
21
+ const instanceConfig = loadInstanceConfig();
22
+ const globalConfig = loadGlobalConfig();
23
+ const leaderName = config.name || instanceConfig.name || "Leader";
24
+ const command = config.command || globalConfig.commands?.["claude-cli"] || "claude --dangerously-skip-permissions --permission-mode dontAsk";
25
+ const cacheDir = config.cacheDir || globalConfig.cache_dir || "~/.claude-orchestrator/sessions";
26
+ // Create /leader EPHEMERAL node
27
+ const leaderId = crypto.randomUUID().replace(/-/g, "");
28
+ try {
29
+ await zk.createLeader({
30
+ instance_id: leaderId,
31
+ name: leaderName,
32
+ role: "leader",
33
+ started_at: new Date().toISOString(),
34
+ host: os.hostname(),
35
+ pid: process.pid,
36
+ version: "0.3.0",
37
+ });
38
+ }
39
+ catch (err) {
40
+ if (isNodeExists(err)) {
41
+ console.error("Another leader is already running.");
42
+ process.exit(1);
43
+ }
44
+ throw err;
45
+ }
46
+ // Register own instance
47
+ const registry = new InstanceRegistry(zk);
48
+ const instance = await registry.register(leaderName, "leader", leaderId);
49
+ saveInstanceId(instance.id);
50
+ // Initialize CACHE_DIR
51
+ const resolvedCache = expandHomeDir(cacheDir);
52
+ const myCacheDir = path.join(resolvedCache, instance.id);
53
+ await fs.promises.mkdir(myCacheDir, { recursive: true });
54
+ // Initialize EventBus + State
55
+ const eventBus = new LeaderEventBus();
56
+ const state = new LeaderState();
57
+ state.leaderName = leaderName;
58
+ state.leaderInstanceId = instance.id;
59
+ state.cacheDir = resolvedCache;
60
+ eventBus.onAll((event) => state.apply(event));
61
+ // Initialize TaskQueue and MessageRouter for decision engine
62
+ const taskQueue = new TaskQueue(zk);
63
+ const messageRouter = new MessageRouter(zk);
64
+ const taskGenerator = new TaskGenerator(zk, taskQueue, command, resolvedCache, instance.id);
65
+ const decisionEngine = new DecisionEngine(zk, taskQueue, messageRouter, command, resolvedCache, instance.id);
66
+ // Start subsystems
67
+ const leaderWatcher = new LeaderWatcher(zk, eventBus, instance.id, command, resolvedCache, decisionEngine);
68
+ await leaderWatcher.start();
69
+ const monitor = new WorkerMonitor(zk, eventBus);
70
+ await monitor.start();
71
+ const orchestrator = new TaskOrchestrator(zk, eventBus);
72
+ await orchestrator.start();
73
+ const recovery = new TaskRecovery(zk, eventBus);
74
+ recovery.start();
75
+ await recovery.scanOrphans();
76
+ // Initialize TUI
77
+ const tui = new LeaderTui();
78
+ eventBus.onAll(() => tui.render(state));
79
+ tui.render(state);
80
+ // Block on SIGINT
81
+ await new Promise((resolve) => {
82
+ const cleanup = () => {
83
+ tui.destroy();
84
+ resolve();
85
+ };
86
+ process.once("SIGINT", cleanup);
87
+ process.once("SIGTERM", cleanup);
88
+ });
89
+ // Shutdown: stop watchers first, then disconnect
90
+ leaderWatcher.stop();
91
+ monitor.stop();
92
+ orchestrator.stop();
93
+ await new Promise(r => setTimeout(r, 100)); // let pending callbacks drain
94
+ await zk.disconnect();
95
+ }
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/leader/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { ZkClient } from "../zk/client.js";
2
+ import { LeaderEventBus } from "./event-bus.js";
3
+ export declare class WorkerMonitor {
4
+ private zk;
5
+ private eventBus;
6
+ private knownInstances;
7
+ private instanceNames;
8
+ private stopped;
9
+ constructor(zk: ZkClient, eventBus: LeaderEventBus);
10
+ start(): Promise<void>;
11
+ stop(): void;
12
+ private watchLoop;
13
+ private onChildrenChanged;
14
+ }
@@ -0,0 +1,55 @@
1
+ export class WorkerMonitor {
2
+ zk;
3
+ eventBus;
4
+ knownInstances = new Set();
5
+ instanceNames = new Map();
6
+ stopped = false;
7
+ constructor(zk, eventBus) {
8
+ this.zk = zk;
9
+ this.eventBus = eventBus;
10
+ }
11
+ async start() {
12
+ await this.watchLoop();
13
+ }
14
+ stop() {
15
+ this.stopped = true;
16
+ }
17
+ async watchLoop() {
18
+ if (this.stopped)
19
+ return;
20
+ const children = await this.zk.watchInstances(async (newChildren) => {
21
+ if (this.stopped)
22
+ return;
23
+ await this.onChildrenChanged(newChildren);
24
+ this.watchLoop();
25
+ });
26
+ await this.onChildrenChanged(children);
27
+ }
28
+ async onChildrenChanged(children) {
29
+ const curr = new Set(children);
30
+ for (const id of curr) {
31
+ if (!this.knownInstances.has(id)) {
32
+ const data = await this.zk.getInstance(id);
33
+ if (data && data.role !== "leader") {
34
+ const instName = data.name;
35
+ this.eventBus.emit({
36
+ type: "worker_joined",
37
+ instance: data,
38
+ instanceId: id,
39
+ name: instName,
40
+ });
41
+ this.instanceNames.set(id, instName);
42
+ }
43
+ }
44
+ }
45
+ for (const id of this.knownInstances) {
46
+ if (!curr.has(id)) {
47
+ const name = this.instanceNames.get(id) ?? id.slice(0, 8);
48
+ this.eventBus.emit({ type: "worker_left", instanceId: id, name });
49
+ this.instanceNames.delete(id);
50
+ }
51
+ }
52
+ this.knownInstances = curr;
53
+ }
54
+ }
55
+ //# sourceMappingURL=monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../src/leader/monitor.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,aAAa;IAMd;IACA;IANF,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,OAAO,GAAG,KAAK,CAAC;IAExB,YACU,EAAY,EACZ,QAAwB;QADxB,OAAE,GAAF,EAAE,CAAU;QACZ,aAAQ,GAAR,QAAQ,CAAgB;IAC/B,CAAC;IAEJ,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;IACzB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,SAAS;QACrB,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE;YAClE,IAAI,IAAI,CAAC,OAAO;gBAAE,OAAO;YACzB,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;YAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,QAAkB;QAChD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;QAE/B,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACjC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;gBAC3C,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAc,CAAC;oBACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;wBACjB,IAAI,EAAE,eAAe;wBACrB,QAAQ,EAAE,IAAI;wBACd,UAAU,EAAE,EAAE;wBACd,IAAI,EAAE,QAAQ;qBACf,CAAC,CAAC;oBACH,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;QACH,CAAC;QAED,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACrC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBAClB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClE,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QAED,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ import { ZkClient } from "../zk/client.js";
2
+ import { LeaderEventBus } from "./event-bus.js";
3
+ export declare class TaskOrchestrator {
4
+ private zk;
5
+ private eventBus;
6
+ private knownPending;
7
+ private knownClaimed;
8
+ private stopped;
9
+ constructor(zk: ZkClient, eventBus: LeaderEventBus);
10
+ start(): Promise<void>;
11
+ stop(): void;
12
+ private watchPending;
13
+ private watchClaimed;
14
+ }
@@ -0,0 +1,83 @@
1
+ export class TaskOrchestrator {
2
+ zk;
3
+ eventBus;
4
+ knownPending = new Set();
5
+ knownClaimed = new Set();
6
+ stopped = false;
7
+ constructor(zk, eventBus) {
8
+ this.zk = zk;
9
+ this.eventBus = eventBus;
10
+ }
11
+ async start() {
12
+ await this.watchPending();
13
+ await this.watchClaimed();
14
+ }
15
+ stop() {
16
+ this.stopped = true;
17
+ }
18
+ async watchPending() {
19
+ if (this.stopped)
20
+ return;
21
+ const children = await this.zk.watchPendingTasks(async (newChildren) => {
22
+ if (this.stopped)
23
+ return;
24
+ for (const id of newChildren) {
25
+ if (!this.knownPending.has(id)) {
26
+ const data = await this.zk.getPendingTask(id);
27
+ if (data) {
28
+ this.eventBus.emit({ type: "task_created", task: { ...data, id }, taskId: id });
29
+ }
30
+ }
31
+ }
32
+ this.knownPending = new Set(newChildren);
33
+ this.watchPending();
34
+ });
35
+ for (const id of children) {
36
+ const data = await this.zk.getPendingTask(id);
37
+ if (data) {
38
+ this.eventBus.emit({ type: "task_created", task: { ...data, id }, taskId: id });
39
+ }
40
+ }
41
+ this.knownPending = new Set(children);
42
+ }
43
+ async watchClaimed() {
44
+ if (this.stopped)
45
+ return;
46
+ const children = await this.zk.watchClaimedTasks(async (newChildren) => {
47
+ if (this.stopped)
48
+ return;
49
+ for (const name of newChildren) {
50
+ if (!this.knownClaimed.has(name)) {
51
+ const idx = name.indexOf("-");
52
+ if (idx === -1)
53
+ continue;
54
+ const insId = name.substring(0, idx);
55
+ const taskId = name.substring(idx + 1);
56
+ this.eventBus.emit({ type: "task_claimed", taskId, instanceId: insId });
57
+ }
58
+ }
59
+ // Detect completed/released tasks
60
+ for (const name of this.knownClaimed) {
61
+ if (!newChildren.includes(name)) {
62
+ const idx = name.indexOf("-");
63
+ if (idx !== -1) {
64
+ const taskId = name.substring(idx + 1);
65
+ this.eventBus.emit({ type: "task_completed", taskId });
66
+ }
67
+ }
68
+ }
69
+ this.knownClaimed = new Set(newChildren);
70
+ this.watchClaimed();
71
+ });
72
+ for (const name of children) {
73
+ const idx = name.indexOf("-");
74
+ if (idx === -1)
75
+ continue;
76
+ const insId = name.substring(0, idx);
77
+ const taskId = name.substring(idx + 1);
78
+ this.eventBus.emit({ type: "task_claimed", taskId, instanceId: insId });
79
+ }
80
+ this.knownClaimed = new Set(children);
81
+ }
82
+ }
83
+ //# sourceMappingURL=orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/leader/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { ZkClient } from "../zk/client.js";
2
+ import { LeaderEventBus } from "./event-bus.js";
3
+ export declare class TaskRecovery {
4
+ private zk;
5
+ private eventBus;
6
+ constructor(zk: ZkClient, eventBus: LeaderEventBus);
7
+ start(): void;
8
+ scanOrphans(): Promise<void>;
9
+ private recoverOrphan;
10
+ private recoverOrphanedTasks;
11
+ }
@@ -0,0 +1,61 @@
1
+ const MAX_RETRIES = 3;
2
+ export class TaskRecovery {
3
+ zk;
4
+ eventBus;
5
+ constructor(zk, eventBus) {
6
+ this.zk = zk;
7
+ this.eventBus = eventBus;
8
+ }
9
+ start() {
10
+ this.eventBus.on("worker_left", (event) => {
11
+ this.recoverOrphanedTasks(event.instanceId);
12
+ });
13
+ }
14
+ async scanOrphans() {
15
+ if (!this.zk.connected)
16
+ return;
17
+ const instances = await this.zk.listInstances();
18
+ const onlineIds = new Set(instances.map((i) => i.id));
19
+ const claimed = await this.zk.listClaimedTasks();
20
+ for (const [insId, taskId, data] of claimed) {
21
+ if (!onlineIds.has(insId)) {
22
+ await this.recoverOrphan(insId, taskId, data);
23
+ }
24
+ }
25
+ }
26
+ async recoverOrphan(workerId, taskId, data) {
27
+ const retryCount = (data.retry_count ?? 0) + 1;
28
+ if (retryCount > MAX_RETRIES) {
29
+ await this.zk.saveCompletedTask(taskId, {
30
+ ...data,
31
+ status: "failed",
32
+ completed_at: new Date().toISOString(),
33
+ retry_count: retryCount,
34
+ fail_reason: `Max retries (${MAX_RETRIES}) exceeded after worker disconnect`,
35
+ });
36
+ await this.zk.deleteClaimedTask(workerId, taskId);
37
+ this.eventBus.emit({ type: "task_failed", taskId, reason: "Max retries exceeded" });
38
+ }
39
+ else {
40
+ const taskData = { ...data };
41
+ taskData.retry_count = retryCount;
42
+ taskData.status = "pending";
43
+ delete taskData.claimed_by;
44
+ delete taskData.claimed_at;
45
+ const newTaskId = await this.zk.createPendingTask(taskData);
46
+ await this.zk.deleteClaimedTask(workerId, taskId);
47
+ this.eventBus.emit({ type: "task_recovered", taskId, newTaskId, retryCount });
48
+ }
49
+ }
50
+ async recoverOrphanedTasks(workerId) {
51
+ if (!this.zk.connected)
52
+ return;
53
+ const claimed = await this.zk.listClaimedTasks();
54
+ for (const [insId, taskId, data] of claimed) {
55
+ if (insId !== workerId)
56
+ continue;
57
+ await this.recoverOrphan(insId, taskId, data);
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/leader/recovery.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB,MAAM,OAAO,YAAY;IAEb;IACA;IAFV,YACU,EAAY,EACZ,QAAwB;QADxB,OAAE,GAAF,EAAE,CAAU;QACZ,aAAQ,GAAR,QAAQ,CAAgB;IAC/B,CAAC;IAEJ,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;YACxC,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,UAAoB,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS;YAAE,OAAO;QAC/B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAY,CAAC,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC;QACjD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;YAC5C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,aAAa,CACzB,QAAgB,EAChB,MAAc,EACd,IAA6B;QAE7B,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,WAAqB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACzD,IAAI,UAAU,GAAG,WAAW,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,MAAM,EAAE;gBACtC,GAAG,IAAI;gBACP,MAAM,EAAE,QAAQ;gBAChB,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACtC,WAAW,EAAE,UAAU;gBACvB,WAAW,EAAE,gBAAgB,WAAW,oCAAoC;aAC7E,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAClD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;QACtF,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;YAC7B,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAC;YAClC,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC;YAC5B,OAAO,QAAQ,CAAC,UAAU,CAAC;YAC3B,OAAO,QAAQ,CAAC,UAAU,CAAC;YAC3B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YAC5D,MAAM,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAClD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,oBAAoB,CAAC,QAAgB;QACjD,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS;YAAE,OAAO;QAC/B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC;QACjD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;YAC5C,IAAI,KAAK,KAAK,QAAQ;gBAAE,SAAS;YACjC,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,24 @@
1
+ import type { LeaderEvent } from "./event-bus.js";
2
+ export interface WorkerInfo {
3
+ id: string;
4
+ name: string;
5
+ presetRole: string;
6
+ currentRole: string | null;
7
+ status: string;
8
+ currentTaskId: string | null;
9
+ }
10
+ export interface EventLogEntry {
11
+ timestamp: string;
12
+ message: string;
13
+ }
14
+ export declare class LeaderState {
15
+ workers: WorkerInfo[];
16
+ pendingTasks: Record<string, unknown>[];
17
+ claimedTasks: Record<string, unknown>[];
18
+ completedTasks: Record<string, unknown>[];
19
+ events: EventLogEntry[];
20
+ leaderName: string;
21
+ leaderInstanceId: string;
22
+ cacheDir: string;
23
+ apply(event: LeaderEvent): void;
24
+ }