@lawrence369/loop-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -0
  4. package/dist/agent/activity.d.ts +64 -0
  5. package/dist/agent/activity.js +265 -0
  6. package/dist/agent/launcher.d.ts +42 -0
  7. package/dist/agent/launcher.js +243 -0
  8. package/dist/agent/pty-session.d.ts +113 -0
  9. package/dist/agent/pty-session.js +490 -0
  10. package/dist/agent/ready-detector.d.ts +46 -0
  11. package/dist/agent/ready-detector.js +86 -0
  12. package/dist/agent/wrapper.d.ts +18 -0
  13. package/dist/agent/wrapper.js +110 -0
  14. package/dist/bin/lclaude.d.ts +3 -0
  15. package/dist/bin/lclaude.js +7 -0
  16. package/dist/bin/lcodex.d.ts +3 -0
  17. package/dist/bin/lcodex.js +7 -0
  18. package/dist/bin/lgemini.d.ts +3 -0
  19. package/dist/bin/lgemini.js +7 -0
  20. package/dist/bus/daemon.d.ts +56 -0
  21. package/dist/bus/daemon.js +135 -0
  22. package/dist/bus/event-bus.d.ts +105 -0
  23. package/dist/bus/event-bus.js +157 -0
  24. package/dist/bus/message.d.ts +48 -0
  25. package/dist/bus/message.js +129 -0
  26. package/dist/bus/queue.d.ts +50 -0
  27. package/dist/bus/queue.js +100 -0
  28. package/dist/bus/store.d.ts +88 -0
  29. package/dist/bus/store.js +212 -0
  30. package/dist/bus/subscriber.d.ts +76 -0
  31. package/dist/bus/subscriber.js +187 -0
  32. package/dist/config/index.d.ts +8 -0
  33. package/dist/config/index.js +72 -0
  34. package/dist/config/schema.d.ts +18 -0
  35. package/dist/config/schema.js +58 -0
  36. package/dist/core/conversation.d.ts +34 -0
  37. package/dist/core/conversation.js +289 -0
  38. package/dist/core/engine.d.ts +40 -0
  39. package/dist/core/engine.js +288 -0
  40. package/dist/core/loop.d.ts +33 -0
  41. package/dist/core/loop.js +209 -0
  42. package/dist/core/protocol.d.ts +60 -0
  43. package/dist/core/protocol.js +162 -0
  44. package/dist/core/scoring.d.ts +34 -0
  45. package/dist/core/scoring.js +69 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.js +408 -0
  48. package/dist/orchestrator/daemon.d.ts +74 -0
  49. package/dist/orchestrator/daemon.js +294 -0
  50. package/dist/orchestrator/group.d.ts +73 -0
  51. package/dist/orchestrator/group.js +166 -0
  52. package/dist/orchestrator/ipc-server.d.ts +60 -0
  53. package/dist/orchestrator/ipc-server.js +166 -0
  54. package/dist/orchestrator/scheduler.d.ts +32 -0
  55. package/dist/orchestrator/scheduler.js +95 -0
  56. package/dist/plan/context.d.ts +8 -0
  57. package/dist/plan/context.js +42 -0
  58. package/dist/plan/decisions.d.ts +18 -0
  59. package/dist/plan/decisions.js +143 -0
  60. package/dist/plan/shared-plan.d.ts +33 -0
  61. package/dist/plan/shared-plan.js +211 -0
  62. package/dist/skills/executor.d.ts +7 -0
  63. package/dist/skills/executor.js +11 -0
  64. package/dist/skills/loader.d.ts +16 -0
  65. package/dist/skills/loader.js +80 -0
  66. package/dist/skills/registry.d.ts +13 -0
  67. package/dist/skills/registry.js +54 -0
  68. package/dist/terminal/adapter.d.ts +61 -0
  69. package/dist/terminal/adapter.js +42 -0
  70. package/dist/terminal/detect.d.ts +30 -0
  71. package/dist/terminal/detect.js +77 -0
  72. package/dist/terminal/iterm2-adapter.d.ts +19 -0
  73. package/dist/terminal/iterm2-adapter.js +120 -0
  74. package/dist/terminal/pty-adapter.d.ts +18 -0
  75. package/dist/terminal/pty-adapter.js +84 -0
  76. package/dist/terminal/terminal-adapter.d.ts +17 -0
  77. package/dist/terminal/terminal-adapter.js +94 -0
  78. package/dist/terminal/tmux-adapter.d.ts +18 -0
  79. package/dist/terminal/tmux-adapter.js +127 -0
  80. package/dist/ui/banner.d.ts +3 -0
  81. package/dist/ui/banner.js +145 -0
  82. package/dist/ui/colors.d.ts +41 -0
  83. package/dist/ui/colors.js +65 -0
  84. package/dist/ui/dashboard.d.ts +32 -0
  85. package/dist/ui/dashboard.js +138 -0
  86. package/dist/ui/input.d.ts +10 -0
  87. package/dist/ui/input.js +96 -0
  88. package/dist/ui/interactive.d.ts +13 -0
  89. package/dist/ui/interactive.js +230 -0
  90. package/dist/ui/renderer.d.ts +33 -0
  91. package/dist/ui/renderer.js +106 -0
  92. package/dist/utils/ansi.d.ts +11 -0
  93. package/dist/utils/ansi.js +16 -0
  94. package/dist/utils/fs.d.ts +34 -0
  95. package/dist/utils/fs.js +115 -0
  96. package/dist/utils/lock.d.ts +12 -0
  97. package/dist/utils/lock.js +116 -0
  98. package/dist/utils/process.d.ts +31 -0
  99. package/dist/utils/process.js +111 -0
  100. package/dist/utils/pty-filter.d.ts +31 -0
  101. package/dist/utils/pty-filter.js +187 -0
  102. package/package.json +71 -0
  103. package/skills/loop/SKILL.md +19 -0
  104. package/skills/plan/SKILL.md +9 -0
  105. package/skills/review/SKILL.md +14 -0
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Agent wrapper — entry point for lclaude / lgemini / lcodex binaries.
3
+ *
4
+ * Resolves the engine-specific CLI command, launches it through
5
+ * AgentLauncher, and forwards stdin/stdout for interactive use.
6
+ */
7
+ import { AgentLauncher } from "./launcher.js";
8
+ const ENGINES = {
9
+ claude: {
10
+ command: "claude",
11
+ defaultArgs: [],
12
+ },
13
+ gemini: {
14
+ command: "gemini",
15
+ defaultArgs: [],
16
+ },
17
+ codex: {
18
+ command: "codex",
19
+ defaultArgs: [],
20
+ },
21
+ };
22
+ // ---------------------------------------------------------------------------
23
+ // Main
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Launch a wrapped agent CLI.
27
+ *
28
+ * This function does not return until the agent exits (or is killed).
29
+ * It sets up the full lifecycle: PtySession, detectors, signal handlers,
30
+ * and interactive stdin/stdout forwarding.
31
+ *
32
+ * @param engineName - One of "claude", "gemini", "codex"
33
+ * @param extraArgs - Additional CLI arguments appended after defaults
34
+ */
35
+ export async function launchWrappedAgent(engineName, extraArgs) {
36
+ const spec = ENGINES[engineName];
37
+ if (!spec) {
38
+ console.error(`Unknown engine: ${engineName}`);
39
+ console.error(`Supported engines: ${Object.keys(ENGINES).join(", ")}`);
40
+ process.exit(1);
41
+ }
42
+ const args = [...spec.defaultArgs, ...(extraArgs ?? [])];
43
+ const cwd = process.cwd();
44
+ const launcher = new AgentLauncher(cwd);
45
+ const agent = await launcher.launch({
46
+ agentType: engineName,
47
+ command: spec.command,
48
+ args,
49
+ cwd,
50
+ nickname: process.env.LOOP_NICKNAME,
51
+ });
52
+ // Forward stdin to the PtySession
53
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
54
+ process.stdin.setRawMode(true);
55
+ }
56
+ process.stdin.resume();
57
+ const onStdinData = (data) => {
58
+ const text = typeof data === "string" ? data : data.toString("utf8");
59
+ agent.ptySession.write(text);
60
+ };
61
+ process.stdin.on("data", onStdinData);
62
+ // Forward PTY output to stdout
63
+ agent.ptySession.on("pty-data", (data) => {
64
+ process.stdout.write(data);
65
+ });
66
+ // Handle terminal resize
67
+ const onResize = () => {
68
+ if (agent.ptySession.isAlive) {
69
+ const cols = process.stdout.columns ?? 80;
70
+ const rows = process.stdout.rows ?? 24;
71
+ agent.ptySession.resize(cols, rows);
72
+ }
73
+ };
74
+ if (process.stdout.isTTY) {
75
+ process.stdout.on("resize", onResize);
76
+ }
77
+ // Wait for exit
78
+ return new Promise((resolve) => {
79
+ agent.ptySession.on("exit", async (code) => {
80
+ try {
81
+ // Clean up stdin
82
+ process.stdin.removeListener("data", onStdinData);
83
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
84
+ try {
85
+ process.stdin.setRawMode(false);
86
+ }
87
+ catch {
88
+ // May fail if stream is already closed
89
+ }
90
+ }
91
+ // Clean up resize handler
92
+ if (process.stdout.isTTY) {
93
+ process.stdout.removeListener("resize", onResize);
94
+ }
95
+ // Run cleanup (detectors, logger, sockets)
96
+ await agent.cleanup();
97
+ // Exit with the agent's code
98
+ process.exitCode = code;
99
+ resolve();
100
+ }
101
+ catch (err) {
102
+ // Prevent unhandled rejection from async exit handler
103
+ console.error("Exit handler error:", err instanceof Error ? err.message : String(err));
104
+ process.exitCode = code || 1;
105
+ resolve();
106
+ }
107
+ });
108
+ });
109
+ }
110
+ //# sourceMappingURL=wrapper.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=lclaude.d.ts.map
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { launchWrappedAgent } from "../agent/wrapper.js";
3
+ launchWrappedAgent("claude", process.argv.slice(2)).catch((err) => {
4
+ console.error(err instanceof Error ? err.message : String(err));
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=lclaude.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=lcodex.d.ts.map
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { launchWrappedAgent } from "../agent/wrapper.js";
3
+ launchWrappedAgent("codex", process.argv.slice(2)).catch((err) => {
4
+ console.error(err instanceof Error ? err.message : String(err));
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=lcodex.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=lgemini.d.ts.map
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { launchWrappedAgent } from "../agent/wrapper.js";
3
+ launchWrappedAgent("gemini", process.argv.slice(2)).catch((err) => {
4
+ console.error(err instanceof Error ? err.message : String(err));
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=lgemini.js.map
@@ -0,0 +1,56 @@
1
+ import { EventBus } from "./event-bus.js";
2
+ /**
3
+ * Options for the BusDaemon.
4
+ */
5
+ export interface BusDaemonOptions {
6
+ /** Polling interval in milliseconds. Default: 1000 */
7
+ pollIntervalMs?: number;
8
+ /** How often (in poll cycles) to run cleanup. Default: 5 */
9
+ cleanupEveryNCycles?: number;
10
+ }
11
+ /**
12
+ * Background daemon that polls subscriber queues and delivers messages.
13
+ *
14
+ * In the ufoo model, the daemon checks for pending messages in queue
15
+ * directories and delivers them to agents (via injection). In this
16
+ * simplified version, the daemon:
17
+ *
18
+ * 1. Periodically cleans up dead agents
19
+ * 2. Monitors queue directories for pending messages
20
+ * 3. Emits delivery notifications
21
+ */
22
+ export declare class BusDaemon {
23
+ private readonly eventBus;
24
+ private readonly pollIntervalMs;
25
+ private readonly cleanupEveryNCycles;
26
+ private running;
27
+ private timer;
28
+ private cleanupCounter;
29
+ private lastCounts;
30
+ constructor(eventBus: EventBus, opts?: BusDaemonOptions);
31
+ /**
32
+ * Start the daemon polling loop.
33
+ */
34
+ start(): Promise<void>;
35
+ /**
36
+ * Stop the daemon.
37
+ */
38
+ stop(): Promise<void>;
39
+ /**
40
+ * Check if the daemon is currently running.
41
+ */
42
+ isRunning(): boolean;
43
+ /**
44
+ * Main polling loop.
45
+ */
46
+ private pollLoop;
47
+ /**
48
+ * Single tick: cleanup + check queues.
49
+ */
50
+ private tick;
51
+ /**
52
+ * Check all subscriber queues for pending messages.
53
+ */
54
+ private checkQueues;
55
+ }
56
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1,135 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { safeNameToSubscriber } from "./store.js";
4
+ /**
5
+ * Background daemon that polls subscriber queues and delivers messages.
6
+ *
7
+ * In the ufoo model, the daemon checks for pending messages in queue
8
+ * directories and delivers them to agents (via injection). In this
9
+ * simplified version, the daemon:
10
+ *
11
+ * 1. Periodically cleans up dead agents
12
+ * 2. Monitors queue directories for pending messages
13
+ * 3. Emits delivery notifications
14
+ */
15
+ export class BusDaemon {
16
+ eventBus;
17
+ pollIntervalMs;
18
+ cleanupEveryNCycles;
19
+ running = false;
20
+ timer = null;
21
+ cleanupCounter = 0;
22
+ lastCounts = new Map();
23
+ constructor(eventBus, opts) {
24
+ this.eventBus = eventBus;
25
+ this.pollIntervalMs = opts?.pollIntervalMs ?? 1000;
26
+ this.cleanupEveryNCycles = opts?.cleanupEveryNCycles ?? 5;
27
+ }
28
+ /**
29
+ * Start the daemon polling loop.
30
+ */
31
+ async start() {
32
+ if (this.running)
33
+ return;
34
+ this.running = true;
35
+ this.pollLoop();
36
+ }
37
+ /**
38
+ * Stop the daemon.
39
+ */
40
+ async stop() {
41
+ this.running = false;
42
+ if (this.timer !== null) {
43
+ clearTimeout(this.timer);
44
+ this.timer = null;
45
+ }
46
+ }
47
+ /**
48
+ * Check if the daemon is currently running.
49
+ */
50
+ isRunning() {
51
+ return this.running;
52
+ }
53
+ /**
54
+ * Main polling loop.
55
+ */
56
+ pollLoop() {
57
+ if (!this.running)
58
+ return;
59
+ this.tick()
60
+ .catch((err) => {
61
+ // Log but don't crash
62
+ const message = err instanceof Error ? err.message : String(err);
63
+ process.stderr.write(`[bus-daemon] tick error: ${message}\n`);
64
+ })
65
+ .finally(() => {
66
+ if (this.running) {
67
+ this.timer = setTimeout(() => this.pollLoop(), this.pollIntervalMs);
68
+ }
69
+ });
70
+ }
71
+ /**
72
+ * Single tick: cleanup + check queues.
73
+ */
74
+ async tick() {
75
+ // Periodic cleanup
76
+ this.cleanupCounter++;
77
+ if (this.cleanupCounter >= this.cleanupEveryNCycles) {
78
+ this.cleanupCounter = 0;
79
+ try {
80
+ const subscriberMgr = this.eventBus.getSubscriberManager();
81
+ await subscriberMgr.cleanupInactive();
82
+ }
83
+ catch {
84
+ // Ignore cleanup errors
85
+ }
86
+ }
87
+ // Check all queues
88
+ await this.checkQueues();
89
+ }
90
+ /**
91
+ * Check all subscriber queues for pending messages.
92
+ */
93
+ async checkQueues() {
94
+ const queuesDir = join(this.eventBus.busDir, "queues");
95
+ let entries;
96
+ try {
97
+ entries = await readdir(queuesDir);
98
+ }
99
+ catch {
100
+ // Queues dir may not exist yet
101
+ return;
102
+ }
103
+ for (const safeName of entries) {
104
+ const pendingPath = join(queuesDir, safeName, "pending.jsonl");
105
+ let fileSize;
106
+ try {
107
+ const fileStat = await stat(pendingPath);
108
+ fileSize = fileStat.size;
109
+ }
110
+ catch {
111
+ continue; // File doesn't exist
112
+ }
113
+ if (fileSize === 0)
114
+ continue;
115
+ // Count current messages
116
+ let count = 0;
117
+ try {
118
+ const content = await readFile(pendingPath, "utf8");
119
+ const trimmed = content.trim();
120
+ count = trimmed ? trimmed.split("\n").length : 0;
121
+ }
122
+ catch {
123
+ continue;
124
+ }
125
+ const subscriberId = safeNameToSubscriber(safeName);
126
+ const lastCount = this.lastCounts.get(safeName) ?? 0;
127
+ if (count > lastCount) {
128
+ const now = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
129
+ process.stderr.write(`[bus-daemon] ${now} New messages for ${subscriberId} (${lastCount} -> ${count})\n`);
130
+ }
131
+ this.lastCounts.set(safeName, count);
132
+ }
133
+ }
134
+ }
135
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1,105 @@
1
+ import { BusStore } from "./store.js";
2
+ import { QueueManager } from "./queue.js";
3
+ import { SubscriberManager, type AgentMetadata } from "./subscriber.js";
4
+ import { MessageManager } from "./message.js";
5
+ /**
6
+ * A single event on the bus.
7
+ */
8
+ export interface BusEvent {
9
+ seq: number;
10
+ timestamp: string;
11
+ type: string;
12
+ event: string;
13
+ publisher: string;
14
+ target: string;
15
+ data: Record<string, unknown>;
16
+ }
17
+ /**
18
+ * Summary status of the bus.
19
+ */
20
+ export interface BusStatus {
21
+ id: string;
22
+ agents: number;
23
+ events: number;
24
+ agentList: Array<{
25
+ id: string;
26
+ type: string;
27
+ nickname: string;
28
+ status: string;
29
+ }>;
30
+ }
31
+ /**
32
+ * The main EventBus class. Orchestrates store, queue, subscriber, and
33
+ * message managers to provide a unified API for agent communication.
34
+ */
35
+ export declare class EventBus {
36
+ readonly projectRoot: string;
37
+ readonly busDir: string;
38
+ private store;
39
+ private subscriberManager;
40
+ private messageManager;
41
+ private queueManager;
42
+ constructor(projectRoot: string);
43
+ /**
44
+ * Initialize the event bus (create directory structure, etc).
45
+ */
46
+ init(): Promise<void>;
47
+ /**
48
+ * Gracefully shut down the event bus.
49
+ */
50
+ shutdown(): Promise<void>;
51
+ /**
52
+ * Send a targeted message from a publisher to a target.
53
+ */
54
+ send(publisher: string, target: string, message: string): Promise<BusEvent>;
55
+ /**
56
+ * Broadcast a message from a publisher to all active agents.
57
+ */
58
+ broadcast(publisher: string, message: string): Promise<BusEvent>;
59
+ /**
60
+ * Join an agent to the bus.
61
+ * Returns the subscriber ID.
62
+ */
63
+ join(agentType: string, metadata?: Partial<AgentMetadata>): Promise<string>;
64
+ /**
65
+ * Remove an agent from the bus (mark as inactive).
66
+ */
67
+ leave(subscriberId: string): Promise<void>;
68
+ /**
69
+ * Peek at pending messages for a subscriber without consuming them.
70
+ */
71
+ check(subscriberId: string): Promise<BusEvent[]>;
72
+ /**
73
+ * Consume (read and clear) pending messages for a subscriber.
74
+ */
75
+ consume(subscriberId: string): Promise<BusEvent[]>;
76
+ /**
77
+ * Get the current status of the bus.
78
+ */
79
+ status(): Promise<BusStatus>;
80
+ /**
81
+ * Get all registered agents.
82
+ */
83
+ agents(): Promise<Map<string, AgentMetadata>>;
84
+ /**
85
+ * Resolve a target to subscriber IDs (exposed for daemon/orchestrator use).
86
+ */
87
+ resolveTarget(target: string): Promise<string[]>;
88
+ /**
89
+ * Get the subscriber manager (for orchestrator access).
90
+ */
91
+ getSubscriberManager(): SubscriberManager;
92
+ /**
93
+ * Get the message manager (for orchestrator access).
94
+ */
95
+ getMessageManager(): MessageManager;
96
+ /**
97
+ * Get the queue manager (for daemon access).
98
+ */
99
+ getQueueManager(): QueueManager;
100
+ /**
101
+ * Get the store (for direct access when needed).
102
+ */
103
+ getStore(): BusStore;
104
+ }
105
+ //# sourceMappingURL=event-bus.d.ts.map
@@ -0,0 +1,157 @@
1
+ import { join, basename } from "node:path";
2
+ import { BusStore } from "./store.js";
3
+ import { QueueManager } from "./queue.js";
4
+ import { SubscriberManager } from "./subscriber.js";
5
+ import { MessageManager } from "./message.js";
6
+ /**
7
+ * The main EventBus class. Orchestrates store, queue, subscriber, and
8
+ * message managers to provide a unified API for agent communication.
9
+ */
10
+ export class EventBus {
11
+ projectRoot;
12
+ busDir;
13
+ store;
14
+ subscriberManager;
15
+ messageManager;
16
+ queueManager;
17
+ constructor(projectRoot) {
18
+ this.projectRoot = projectRoot;
19
+ this.busDir = join(projectRoot, ".loop", "bus");
20
+ this.store = new BusStore(this.busDir);
21
+ this.subscriberManager = new SubscriberManager(this.store);
22
+ this.messageManager = new MessageManager(this.busDir, this.subscriberManager);
23
+ this.queueManager = new QueueManager(this.busDir);
24
+ }
25
+ /**
26
+ * Initialize the event bus (create directory structure, etc).
27
+ */
28
+ async init() {
29
+ await this.store.init();
30
+ }
31
+ /**
32
+ * Gracefully shut down the event bus.
33
+ */
34
+ async shutdown() {
35
+ // Currently a no-op; future: close file handles, flush buffers
36
+ }
37
+ /**
38
+ * Send a targeted message from a publisher to a target.
39
+ */
40
+ async send(publisher, target, message) {
41
+ return this.messageManager.createEvent(publisher, target, { message }, "message/targeted");
42
+ }
43
+ /**
44
+ * Broadcast a message from a publisher to all active agents.
45
+ */
46
+ async broadcast(publisher, message) {
47
+ return this.messageManager.createEvent(publisher, "*", { message }, "message/broadcast");
48
+ }
49
+ /**
50
+ * Join an agent to the bus.
51
+ * Returns the subscriber ID.
52
+ */
53
+ async join(agentType, metadata) {
54
+ return this.subscriberManager.register(agentType, metadata);
55
+ }
56
+ /**
57
+ * Remove an agent from the bus (mark as inactive).
58
+ */
59
+ async leave(subscriberId) {
60
+ await this.subscriberManager.unregister(subscriberId);
61
+ }
62
+ /**
63
+ * Peek at pending messages for a subscriber without consuming them.
64
+ */
65
+ async check(subscriberId) {
66
+ // Update last_seen on check
67
+ try {
68
+ await this.subscriberManager.updateMetadata(subscriberId, {
69
+ last_seen: new Date().toISOString(),
70
+ });
71
+ }
72
+ catch {
73
+ // Ignore - subscriber may not be registered
74
+ }
75
+ return this.queueManager.peek(subscriberId);
76
+ }
77
+ /**
78
+ * Consume (read and clear) pending messages for a subscriber.
79
+ */
80
+ async consume(subscriberId) {
81
+ // Update last_seen on consume
82
+ try {
83
+ await this.subscriberManager.updateMetadata(subscriberId, {
84
+ last_seen: new Date().toISOString(),
85
+ });
86
+ }
87
+ catch {
88
+ // Ignore - subscriber may not be registered
89
+ }
90
+ return this.queueManager.dequeue(subscriberId);
91
+ }
92
+ /**
93
+ * Get the current status of the bus.
94
+ */
95
+ async status() {
96
+ // Clean up dead agents first
97
+ await this.subscriberManager.cleanupInactive();
98
+ const agents = await this.subscriberManager.list();
99
+ const totalEvents = await this.store.countEvents();
100
+ const agentList = [];
101
+ let activeCount = 0;
102
+ for (const [id, meta] of agents) {
103
+ agentList.push({
104
+ id,
105
+ type: meta.agent_type,
106
+ nickname: meta.nickname,
107
+ status: meta.status,
108
+ });
109
+ if (meta.status === "active") {
110
+ activeCount++;
111
+ }
112
+ }
113
+ return {
114
+ id: basename(this.projectRoot) || "loop-workspace",
115
+ agents: activeCount,
116
+ events: totalEvents,
117
+ agentList,
118
+ };
119
+ }
120
+ /**
121
+ * Get all registered agents.
122
+ */
123
+ async agents() {
124
+ return this.subscriberManager.list();
125
+ }
126
+ /**
127
+ * Resolve a target to subscriber IDs (exposed for daemon/orchestrator use).
128
+ */
129
+ async resolveTarget(target) {
130
+ return this.messageManager.resolveTarget(target);
131
+ }
132
+ /**
133
+ * Get the subscriber manager (for orchestrator access).
134
+ */
135
+ getSubscriberManager() {
136
+ return this.subscriberManager;
137
+ }
138
+ /**
139
+ * Get the message manager (for orchestrator access).
140
+ */
141
+ getMessageManager() {
142
+ return this.messageManager;
143
+ }
144
+ /**
145
+ * Get the queue manager (for daemon access).
146
+ */
147
+ getQueueManager() {
148
+ return this.queueManager;
149
+ }
150
+ /**
151
+ * Get the store (for direct access when needed).
152
+ */
153
+ getStore() {
154
+ return this.store;
155
+ }
156
+ }
157
+ //# sourceMappingURL=event-bus.js.map
@@ -0,0 +1,48 @@
1
+ import { SubscriberManager } from "./subscriber.js";
2
+ import { QueueManager } from "./queue.js";
3
+ import type { BusEvent } from "./event-bus.js";
4
+ /**
5
+ * Manages message creation, routing, and delivery.
6
+ *
7
+ * Target resolution order:
8
+ * 1. Exact subscriber ID match (e.g. "claude:abc123")
9
+ * 2. Nickname match (e.g. "claude-1")
10
+ * 3. Agent type match (e.g. "claude" -> all active claude agents)
11
+ * 4. Broadcast ("*" -> all active agents)
12
+ */
13
+ export declare class MessageManager {
14
+ private readonly eventsDir;
15
+ private readonly seqFile;
16
+ private readonly seqLockFile;
17
+ private readonly subscriberManager;
18
+ private readonly queueManager;
19
+ constructor(busDir: string, subscriberManager: SubscriberManager);
20
+ /**
21
+ * Get the next monotonically increasing sequence number.
22
+ */
23
+ nextSeq(): Promise<number>;
24
+ /**
25
+ * Resolve a target string to a list of subscriber IDs.
26
+ *
27
+ * Resolution order:
28
+ * 1. Exact subscriber ID match
29
+ * 2. Nickname match
30
+ * 3. Agent type match (all active agents of that type)
31
+ * 4. Broadcast "*" (all active agents)
32
+ */
33
+ resolveTarget(target: string): Promise<string[]>;
34
+ /**
35
+ * Route an event to its target subscribers' queues.
36
+ */
37
+ route(event: BusEvent): Promise<void>;
38
+ /**
39
+ * Create and persist a new event, then route it to targets.
40
+ * Returns the created event.
41
+ */
42
+ createEvent(publisher: string, target: string, data: Record<string, unknown>, type?: string): Promise<BusEvent>;
43
+ /**
44
+ * Get the queue manager (needed by EventBus for consume operations).
45
+ */
46
+ getQueueManager(): QueueManager;
47
+ }
48
+ //# sourceMappingURL=message.d.ts.map