@gethmy/agent 1.7.1 → 1.7.2

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 (77) hide show
  1. package/dist/cli.js +6376 -141
  2. package/dist/index.js +6206 -333
  3. package/package.json +2 -2
  4. package/dist/board-helpers.d.ts +0 -31
  5. package/dist/board-helpers.js +0 -150
  6. package/dist/budget.d.ts +0 -39
  7. package/dist/budget.js +0 -73
  8. package/dist/cli.d.ts +0 -14
  9. package/dist/completion.d.ts +0 -36
  10. package/dist/completion.js +0 -322
  11. package/dist/config-validation.d.ts +0 -23
  12. package/dist/config-validation.js +0 -77
  13. package/dist/config.d.ts +0 -23
  14. package/dist/config.js +0 -103
  15. package/dist/episode-writer.d.ts +0 -116
  16. package/dist/episode-writer.js +0 -349
  17. package/dist/git-diff-stat.d.ts +0 -24
  18. package/dist/git-diff-stat.js +0 -56
  19. package/dist/git-pr.d.ts +0 -38
  20. package/dist/git-pr.js +0 -399
  21. package/dist/http-server.d.ts +0 -66
  22. package/dist/http-server.js +0 -96
  23. package/dist/index.d.ts +0 -5
  24. package/dist/log.d.ts +0 -34
  25. package/dist/log.js +0 -100
  26. package/dist/merge-monitor.d.ts +0 -23
  27. package/dist/merge-monitor.js +0 -169
  28. package/dist/pm.d.ts +0 -14
  29. package/dist/pm.js +0 -63
  30. package/dist/pool.d.ts +0 -71
  31. package/dist/pool.js +0 -259
  32. package/dist/process-group.d.ts +0 -26
  33. package/dist/process-group.js +0 -72
  34. package/dist/progress-tracker.d.ts +0 -82
  35. package/dist/progress-tracker.js +0 -457
  36. package/dist/prompt.d.ts +0 -23
  37. package/dist/prompt.js +0 -160
  38. package/dist/queue.d.ts +0 -39
  39. package/dist/queue.js +0 -100
  40. package/dist/reconcile.d.ts +0 -35
  41. package/dist/reconcile.js +0 -174
  42. package/dist/recovery.d.ts +0 -30
  43. package/dist/recovery.js +0 -141
  44. package/dist/review-completion.d.ts +0 -35
  45. package/dist/review-completion.js +0 -475
  46. package/dist/review-knowledge.d.ts +0 -14
  47. package/dist/review-knowledge.js +0 -89
  48. package/dist/review-prompt.d.ts +0 -12
  49. package/dist/review-prompt.js +0 -103
  50. package/dist/review-worker.d.ts +0 -56
  51. package/dist/review-worker.js +0 -638
  52. package/dist/review-worktree.d.ts +0 -12
  53. package/dist/review-worktree.js +0 -95
  54. package/dist/run-log.d.ts +0 -6
  55. package/dist/run-log.js +0 -19
  56. package/dist/startup-banner.d.ts +0 -29
  57. package/dist/startup-banner.js +0 -143
  58. package/dist/state-store.d.ts +0 -89
  59. package/dist/state-store.js +0 -230
  60. package/dist/stream-parser-selftest.d.ts +0 -9
  61. package/dist/stream-parser-selftest.js +0 -97
  62. package/dist/stream-parser.d.ts +0 -43
  63. package/dist/stream-parser.js +0 -174
  64. package/dist/transitions.d.ts +0 -57
  65. package/dist/transitions.js +0 -131
  66. package/dist/types.d.ts +0 -167
  67. package/dist/types.js +0 -76
  68. package/dist/verification.d.ts +0 -39
  69. package/dist/verification.js +0 -317
  70. package/dist/watcher.d.ts +0 -53
  71. package/dist/watcher.js +0 -153
  72. package/dist/worker.d.ts +0 -54
  73. package/dist/worker.js +0 -507
  74. package/dist/worktree-gc.d.ts +0 -67
  75. package/dist/worktree-gc.js +0 -245
  76. package/dist/worktree.d.ts +0 -18
  77. package/dist/worktree.js +0 -177
package/dist/pool.js DELETED
@@ -1,259 +0,0 @@
1
- import { BudgetGuard } from "./budget.js";
2
- import { log } from "./log.js";
3
- import { PriorityQueue } from "./queue.js";
4
- import { ReviewWorker } from "./review-worker.js";
5
- import { AGENT_NAME, agentIdentifier, } from "./types.js";
6
- import { Worker } from "./worker.js";
7
- const TAG = "pool";
8
- export class Pool {
9
- client;
10
- stateStore;
11
- implWorkers = [];
12
- reviewWorkers = [];
13
- implQueue;
14
- reviewQueue;
15
- budget;
16
- constructor(config, client, userEmail, workspaceId, projectId, stateStore) {
17
- this.client = client;
18
- this.stateStore = stateStore;
19
- this.implQueue = new PriorityQueue(config);
20
- this.reviewQueue = new PriorityQueue(config);
21
- this.budget = new BudgetGuard(config.budget, this.stateStore);
22
- // Create implementation workers
23
- for (let i = 0; i < config.poolSize; i++) {
24
- this.implWorkers.push(new Worker(i, config, client, userEmail, () => {
25
- this.tryDispatchFor(this.implWorkers, this.implQueue, "impl");
26
- }, workspaceId, projectId, stateStore));
27
- }
28
- // Create review workers. IDs offset by `config.poolSize` so impl and
29
- // review worker IDs never collide (verification + review ports both
30
- // derive from the worker ID, so collision would mean port collision).
31
- if (config.review.enabled) {
32
- for (let i = 0; i < config.review.poolSize; i++) {
33
- const reviewWorkerId = config.poolSize + i;
34
- this.reviewWorkers.push(new ReviewWorker(reviewWorkerId, config, client, userEmail, () => {
35
- this.tryDispatchFor(this.reviewWorkers, this.reviewQueue, "review");
36
- }, stateStore, workspaceId, projectId));
37
- }
38
- }
39
- }
40
- /**
41
- * Enqueue a card for processing with the given mode.
42
- *
43
- * Returns async so callers can await the give-up comment / waiting
44
- * signal on skip. Budget checks happen here so the reconciler, realtime
45
- * watcher, and manual API calls all go through the same gate.
46
- */
47
- async enqueue(card, column, labels, subtasks, mode = "implement") {
48
- // Don't enqueue if already in any queue or actively being worked on
49
- if (this.implQueue.has(card.id) ||
50
- this.reviewQueue.has(card.id) ||
51
- this.isCardActive(card.id)) {
52
- log.debug(TAG, `Card ${card.id} already queued or active, skipping`);
53
- return;
54
- }
55
- // Review pickups bypass per-card attempt limits (reviews are cheap
56
- // and orthogonal to implement attempts). Daily budget still applies.
57
- if (mode === "implement") {
58
- const decision = this.budget.check(card.id);
59
- if (!decision.allow) {
60
- if (decision.reason === "daily_budget") {
61
- // Soft pause — surface as `waiting` so the board signals that
62
- // the daemon will pick this card up after the budget resets.
63
- log.warn(TAG, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
64
- await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
65
- }
66
- else {
67
- // max_attempts: the daemon has given up on this card. The worker
68
- // already posted the one-shot give-up comment at the crossing, so
69
- // every later reconcile tick is expected noise — stay quiet until
70
- // the card is reassigned (which resets the attempt counter).
71
- log.debug(TAG, `#${card.short_id} gave up: ${decision.detail}`);
72
- }
73
- return;
74
- }
75
- }
76
- const queue = mode === "review" ? this.reviewQueue : this.implQueue;
77
- queue.enqueue(card, column, labels, mode);
78
- // Store card data for when it gets dispatched
79
- this.cardDataCache.set(card.id, { card, column, labels, subtasks, mode });
80
- const workers = mode === "review" ? this.reviewWorkers : this.implWorkers;
81
- const dispatched = this.tryDispatchFor(workers, queue, mode);
82
- if (!dispatched) {
83
- // No idle worker — the card will wait in the queue. Surface the
84
- // queue position so the board ribbon shows the user something is
85
- // happening even though no worker has the card yet.
86
- const position = queue.cardIds().indexOf(card.id) + 1;
87
- const total = queue.length;
88
- await this.emitWaiting(card.id, position > 0
89
- ? `Queued (${position}/${total}) — waiting for ${mode} worker`
90
- : `Queued — waiting for ${mode} worker`);
91
- }
92
- }
93
- /**
94
- * Best-effort waiting-state emit. Failures are swallowed because we don't
95
- * want a board-API hiccup to drop the queue/budget event in pool.ts.
96
- */
97
- async emitWaiting(cardId, currentTask) {
98
- try {
99
- await this.client.updateAgentProgress(cardId, {
100
- agentIdentifier: agentIdentifier(0),
101
- agentName: AGENT_NAME,
102
- status: "waiting",
103
- currentTask,
104
- });
105
- }
106
- catch (err) {
107
- log.debug(TAG, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
108
- }
109
- }
110
- /**
111
- * Remove a card from any queue or cancel an active worker.
112
- */
113
- async removeCard(cardId) {
114
- // Unassigning is the human's "try again" signal — clear any exhausted
115
- // attempt counter so a reassign starts the card fresh (no DLQ to clear).
116
- await this.stateStore.resetAttempts(cardId);
117
- // Try both queues
118
- for (const queue of [this.implQueue, this.reviewQueue]) {
119
- const removed = queue.remove(cardId);
120
- if (removed) {
121
- this.cardDataCache.delete(cardId);
122
- log.info(TAG, `Removed #${removed.shortId} from ${removed.mode} queue`);
123
- return;
124
- }
125
- }
126
- // Cancel active worker (check impl first, then review)
127
- const worker = this.implWorkers.find((w) => w.cardId === cardId) ??
128
- this.reviewWorkers.find((w) => w.cardId === cardId);
129
- if (worker) {
130
- log.info(TAG, `Cancelling worker ${worker.id} for card ${cardId}`);
131
- await worker.cancel();
132
- }
133
- }
134
- /**
135
- * Check if a card is currently being worked on by any worker.
136
- */
137
- isCardActive(cardId) {
138
- return (this.implWorkers.some((w) => w.cardId === cardId && w.isActive) ||
139
- this.reviewWorkers.some((w) => w.cardId === cardId && w.isActive));
140
- }
141
- /**
142
- * Check if a card is known to the pool (queued or active).
143
- */
144
- isCardKnown(cardId) {
145
- return (this.implQueue.has(cardId) ||
146
- this.reviewQueue.has(cardId) ||
147
- this.isCardActive(cardId));
148
- }
149
- /**
150
- * Get all card IDs that are either queued or active.
151
- */
152
- knownCardIds() {
153
- const ids = new Set([
154
- ...this.implQueue.cardIds(),
155
- ...this.reviewQueue.cardIds(),
156
- ]);
157
- for (const w of this.implWorkers) {
158
- if (w.cardId)
159
- ids.add(w.cardId);
160
- }
161
- for (const w of this.reviewWorkers) {
162
- if (w.cardId)
163
- ids.add(w.cardId);
164
- }
165
- return ids;
166
- }
167
- /**
168
- * Handle an agent command (pause/resume/stop) for a specific card.
169
- */
170
- async handleAgentCommand(cardId, command) {
171
- const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ??
172
- this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
173
- if (!worker) {
174
- log.debug(TAG, `No active worker for card ${cardId}, ignoring ${command}`);
175
- return;
176
- }
177
- log.info(TAG, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
178
- switch (command) {
179
- case "pause":
180
- await worker.pause();
181
- break;
182
- case "resume":
183
- await worker.resume();
184
- break;
185
- case "stop":
186
- await worker.cancel();
187
- break;
188
- }
189
- }
190
- /**
191
- * Point-in-time snapshot for the HTTP /status endpoint. Safe to call
192
- * from anywhere — reads in-memory state only.
193
- */
194
- snapshotWorkers() {
195
- const out = [];
196
- for (const w of this.implWorkers) {
197
- out.push({
198
- id: w.id,
199
- pipeline: "implement",
200
- state: w.state,
201
- cardId: w.cardId,
202
- cardShortId: null,
203
- startedAt: w.startedAt,
204
- branchName: w.branchName,
205
- });
206
- }
207
- for (const w of this.reviewWorkers) {
208
- out.push({
209
- id: w.id,
210
- pipeline: "review",
211
- state: w.state,
212
- cardId: w.cardId,
213
- cardShortId: null,
214
- startedAt: w.startedAt,
215
- branchName: w.branchName,
216
- });
217
- }
218
- return out;
219
- }
220
- snapshotQueues() {
221
- return {
222
- impl: this.implQueue.snapshot(),
223
- review: this.reviewQueue.snapshot(),
224
- };
225
- }
226
- /**
227
- * Gracefully shutdown all workers.
228
- */
229
- async shutdown() {
230
- log.info(TAG, "Shutting down pool...");
231
- const active = [
232
- ...this.implWorkers.filter((w) => w.isActive),
233
- ...this.reviewWorkers.filter((w) => w.isActive),
234
- ];
235
- await Promise.all(active.map((w) => w.cancel()));
236
- log.info(TAG, "Pool shutdown complete");
237
- }
238
- // --- Private ---
239
- cardDataCache = new Map();
240
- tryDispatchFor(workers, queue, label) {
241
- const idle = workers.find((w) => w.isIdle);
242
- if (!idle) {
243
- log.debug(TAG, `No idle ${label} workers (queue: ${queue.length})`);
244
- return false;
245
- }
246
- const next = queue.dequeue();
247
- if (!next)
248
- return false;
249
- const data = this.cardDataCache.get(next.cardId);
250
- if (!data) {
251
- log.warn(TAG, `No cached data for card ${next.cardId}, skipping`);
252
- return false;
253
- }
254
- this.cardDataCache.delete(next.cardId);
255
- log.info(TAG, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
256
- idle.run(data.card, data.column, data.labels, data.subtasks);
257
- return true;
258
- }
259
- }
@@ -1,26 +0,0 @@
1
- import { type ChildProcess, type SpawnOptions } from "node:child_process";
2
- /**
3
- * Spawn a child in its own process group so we can reliably kill the
4
- * whole subtree later. The Claude CLI shells out to git, build tools,
5
- * dev servers, etc. — signalling only the direct child leaves orphans.
6
- *
7
- * - POSIX: `detached: true` puts the child in a new process group whose
8
- * pgid equals its pid. Killing the negative pid signals every member.
9
- * - Windows: `detached: true` creates a new process group that can be
10
- * signalled via the child's pid (no negation).
11
- */
12
- export declare function spawnInGroup(command: string, args: readonly string[], options?: SpawnOptions): ChildProcess;
13
- /**
14
- * Send a signal to every process in the group whose leader is `proc`.
15
- * On POSIX, this is `process.kill(-pid, signal)`. If the group has
16
- * already exited, returns silently.
17
- */
18
- export declare function signalGroup(proc: ChildProcess, signal: NodeJS.Signals): void;
19
- /**
20
- * Escalating termination: SIGINT → wait → SIGTERM → wait → SIGKILL.
21
- * Returns when the process has exited or all signals have been sent.
22
- */
23
- export declare function terminateGroup(proc: ChildProcess, opts: {
24
- sigintTimeoutMs: number;
25
- sigtermTimeoutMs: number;
26
- }): Promise<void>;
@@ -1,72 +0,0 @@
1
- import { spawn, } from "node:child_process";
2
- import { log } from "./log.js";
3
- const TAG = "pgroup";
4
- /**
5
- * Spawn a child in its own process group so we can reliably kill the
6
- * whole subtree later. The Claude CLI shells out to git, build tools,
7
- * dev servers, etc. — signalling only the direct child leaves orphans.
8
- *
9
- * - POSIX: `detached: true` puts the child in a new process group whose
10
- * pgid equals its pid. Killing the negative pid signals every member.
11
- * - Windows: `detached: true` creates a new process group that can be
12
- * signalled via the child's pid (no negation).
13
- */
14
- export function spawnInGroup(command, args, options = {}) {
15
- return spawn(command, args, {
16
- ...options,
17
- detached: true,
18
- // Keep stdio wired up so streaming still works.
19
- stdio: options.stdio ?? ["ignore", "pipe", "pipe"],
20
- });
21
- }
22
- /**
23
- * Send a signal to every process in the group whose leader is `proc`.
24
- * On POSIX, this is `process.kill(-pid, signal)`. If the group has
25
- * already exited, returns silently.
26
- */
27
- export function signalGroup(proc, signal) {
28
- if (!proc.pid || proc.killed)
29
- return;
30
- try {
31
- if (process.platform === "win32") {
32
- // No process groups on Windows; best effort tree kill via the child.
33
- proc.kill(signal);
34
- return;
35
- }
36
- process.kill(-proc.pid, signal);
37
- }
38
- catch (err) {
39
- // ESRCH means the group is already gone — that is the goal, not an error.
40
- const code = err.code;
41
- if (code !== "ESRCH") {
42
- log.warn(TAG, `signal ${signal} to pgid ${proc.pid} failed: ${err instanceof Error ? err.message : err}`);
43
- }
44
- }
45
- }
46
- /**
47
- * Escalating termination: SIGINT → wait → SIGTERM → wait → SIGKILL.
48
- * Returns when the process has exited or all signals have been sent.
49
- */
50
- export async function terminateGroup(proc, opts) {
51
- if (!proc.pid || proc.killed)
52
- return;
53
- // Unpause first in case the process was suspended — otherwise it
54
- // can't react to signals.
55
- signalGroup(proc, "SIGCONT");
56
- const waitForExit = (timeout) => new Promise((resolve) => {
57
- if (proc.killed || proc.exitCode !== null)
58
- return resolve(true);
59
- const timer = setTimeout(() => resolve(false), timeout);
60
- proc.once("exit", () => {
61
- clearTimeout(timer);
62
- resolve(true);
63
- });
64
- });
65
- signalGroup(proc, "SIGINT");
66
- if (await waitForExit(opts.sigintTimeoutMs))
67
- return;
68
- signalGroup(proc, "SIGTERM");
69
- if (await waitForExit(opts.sigtermTimeoutMs))
70
- return;
71
- signalGroup(proc, "SIGKILL");
72
- }
@@ -1,82 +0,0 @@
1
- import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
2
- import type { CostUpdate, StreamParser } from "./stream-parser.js";
3
- export interface ActivityLogEntry {
4
- phase: string | null;
5
- eventType: "tool_start" | "tool_end" | "phase_change" | "error" | "summary";
6
- toolName: string | null;
7
- description: string;
8
- metadata: Record<string, unknown>;
9
- createdAt: string;
10
- }
11
- export declare class ProgressTracker {
12
- private client;
13
- private cardId;
14
- private workerId;
15
- private phase;
16
- private progress;
17
- private toolCallCount;
18
- private hasEdited;
19
- private lastUpdateAt;
20
- private pendingUpdate;
21
- private pendingTask;
22
- private heartbeatTimer;
23
- private stopped;
24
- private lastAction;
25
- private subtaskTotal;
26
- private subtaskCompleted;
27
- private subtaskMode;
28
- private filesEdited;
29
- private filesRead;
30
- private lastCost;
31
- private logBuffer;
32
- private sessionId;
33
- private lastAssistantText;
34
- private assistantTextBlocks;
35
- constructor(client: HarmonyApiClient, cardId: string, workerId: number, subtasks: {
36
- completed: boolean;
37
- }[]);
38
- setSessionId(id: string): void;
39
- /**
40
- * Wire up the parser events and start the heartbeat.
41
- */
42
- attach(parser: StreamParser): void;
43
- /**
44
- * Stop all timers and flush any pending update.
45
- */
46
- stop(): void;
47
- /** Get a summary of the session stats. */
48
- get stats(): {
49
- filesEdited: number;
50
- filesEditedPaths: string[];
51
- filesRead: number;
52
- toolCalls: number;
53
- cost: CostUpdate | null;
54
- lastAssistantText: string;
55
- assistantTextBlocks: string[];
56
- };
57
- private onToolStart;
58
- private onToolEnd;
59
- private onText;
60
- private transitionTo;
61
- private incrementProgress;
62
- private currentTaskLabel;
63
- /**
64
- * Build a human-readable description of what a tool call is doing.
65
- */
66
- private describeToolAction;
67
- /**
68
- * Strip absolute paths to show only meaningful segments from src/ or packages/.
69
- */
70
- private shortPath;
71
- private scheduleUpdate;
72
- private sendUpdate;
73
- private startHeartbeat;
74
- flushFinal(): void;
75
- private pushLogEntry;
76
- private flushActivityLog;
77
- private extractToolMetadata;
78
- /**
79
- * Safely extract a string property from an unknown tool input.
80
- */
81
- private extractString;
82
- }