@gethmy/agent 1.7.0 → 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 (76) hide show
  1. package/README.md +8 -1
  2. package/dist/cli.js +6376 -205
  3. package/dist/index.js +6206 -341
  4. package/package.json +2 -2
  5. package/dist/board-helpers.d.ts +0 -31
  6. package/dist/board-helpers.js +0 -150
  7. package/dist/budget.d.ts +0 -47
  8. package/dist/budget.js +0 -161
  9. package/dist/cli.d.ts +0 -16
  10. package/dist/completion.d.ts +0 -32
  11. package/dist/completion.js +0 -304
  12. package/dist/config-validation.d.ts +0 -23
  13. package/dist/config-validation.js +0 -77
  14. package/dist/config.d.ts +0 -23
  15. package/dist/config.js +0 -103
  16. package/dist/episode-writer.d.ts +0 -84
  17. package/dist/episode-writer.js +0 -232
  18. package/dist/git-pr.d.ts +0 -38
  19. package/dist/git-pr.js +0 -399
  20. package/dist/http-server.d.ts +0 -79
  21. package/dist/http-server.js +0 -114
  22. package/dist/index.d.ts +0 -5
  23. package/dist/log.d.ts +0 -34
  24. package/dist/log.js +0 -100
  25. package/dist/merge-monitor.d.ts +0 -23
  26. package/dist/merge-monitor.js +0 -169
  27. package/dist/pm.d.ts +0 -14
  28. package/dist/pm.js +0 -63
  29. package/dist/pool.d.ts +0 -70
  30. package/dist/pool.js +0 -258
  31. package/dist/process-group.d.ts +0 -26
  32. package/dist/process-group.js +0 -72
  33. package/dist/progress-tracker.d.ts +0 -79
  34. package/dist/progress-tracker.js +0 -442
  35. package/dist/prompt.d.ts +0 -18
  36. package/dist/prompt.js +0 -117
  37. package/dist/queue.d.ts +0 -39
  38. package/dist/queue.js +0 -100
  39. package/dist/reconcile.d.ts +0 -35
  40. package/dist/reconcile.js +0 -174
  41. package/dist/recovery.d.ts +0 -30
  42. package/dist/recovery.js +0 -141
  43. package/dist/review-completion.d.ts +0 -40
  44. package/dist/review-completion.js +0 -474
  45. package/dist/review-knowledge.d.ts +0 -14
  46. package/dist/review-knowledge.js +0 -89
  47. package/dist/review-prompt.d.ts +0 -12
  48. package/dist/review-prompt.js +0 -103
  49. package/dist/review-worker.d.ts +0 -56
  50. package/dist/review-worker.js +0 -638
  51. package/dist/review-worktree.d.ts +0 -12
  52. package/dist/review-worktree.js +0 -95
  53. package/dist/run-log.d.ts +0 -6
  54. package/dist/run-log.js +0 -19
  55. package/dist/startup-banner.d.ts +0 -29
  56. package/dist/startup-banner.js +0 -143
  57. package/dist/state-store.d.ts +0 -88
  58. package/dist/state-store.js +0 -239
  59. package/dist/stream-parser-selftest.d.ts +0 -9
  60. package/dist/stream-parser-selftest.js +0 -97
  61. package/dist/stream-parser.d.ts +0 -43
  62. package/dist/stream-parser.js +0 -174
  63. package/dist/transitions.d.ts +0 -57
  64. package/dist/transitions.js +0 -131
  65. package/dist/types.d.ts +0 -140
  66. package/dist/types.js +0 -79
  67. package/dist/verification.d.ts +0 -39
  68. package/dist/verification.js +0 -317
  69. package/dist/watcher.d.ts +0 -53
  70. package/dist/watcher.js +0 -153
  71. package/dist/worker.d.ts +0 -53
  72. package/dist/worker.js +0 -464
  73. package/dist/worktree-gc.d.ts +0 -67
  74. package/dist/worktree-gc.js +0 -245
  75. package/dist/worktree.d.ts +0 -18
  76. package/dist/worktree.js +0 -177
@@ -1,442 +0,0 @@
1
- import { log } from "./log.js";
2
- import { AGENT_NAME, agentIdentifier } from "./types.js";
3
- const TAG = "progress-tracker";
4
- const THROTTLE_MS = 5_000;
5
- const HEARTBEAT_MS = 60_000;
6
- const MAX_TASK_LENGTH = 120;
7
- const MAX_LOG_BUFFER = 500;
8
- // Hoisted regexes — avoids recompilation on every call
9
- const SENTENCE_SPLIT = /\.\s|\n/;
10
- const ACTION_PREFIX = /^(Let me|I'll|I need to|Now|First|Next|Looking|Checking|Creating|Adding|Updating|Fixing|Refactoring|Moving|The |This )/i;
11
- const GIT_COMMIT_RE = /\bgit\s+commit\b/;
12
- const BUILD_CMD_RE = /\b(test|build|lint|check|tsc|vitest|jest|(?:bun|npm|pnpm|yarn) run (?:build|lint))\b/;
13
- const PHASES = {
14
- exploring: { min: 10, max: 25, label: "Exploring" },
15
- implementing: { min: 25, max: 55, label: "Implementing" },
16
- testing: { min: 55, max: 65, label: "Testing" },
17
- committing: { min: 65, max: 70, label: "Committing" },
18
- finishing: { min: 70, max: 75, label: "Finalizing" },
19
- };
20
- const PHASE_ORDER = {
21
- exploring: 0,
22
- implementing: 1,
23
- testing: 2,
24
- committing: 3,
25
- finishing: 4,
26
- };
27
- // Tools that indicate implementation
28
- const EDIT_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
29
- function truncate(str, max) {
30
- return str.length > max ? `${str.slice(0, max - 3)}...` : str;
31
- }
32
- // Map file-based tools to their display verbs
33
- const FILE_TOOL_VERBS = {
34
- Read: ["Reading", "file_path"],
35
- Edit: ["Editing", "file_path"],
36
- MultiEdit: ["Editing", "file_path"],
37
- Write: ["Writing", "file_path"],
38
- NotebookEdit: ["Editing notebook", "notebook_path"],
39
- };
40
- export class ProgressTracker {
41
- client;
42
- cardId;
43
- workerId;
44
- phase = "exploring";
45
- progress = 10;
46
- toolCallCount = 0;
47
- hasEdited = false;
48
- lastUpdateAt = 0;
49
- pendingUpdate = null;
50
- pendingTask = "";
51
- heartbeatTimer = null;
52
- stopped = false;
53
- // Rich task tracking
54
- lastAction = "";
55
- // Subtask tracking
56
- subtaskTotal;
57
- subtaskCompleted;
58
- subtaskMode;
59
- // Rich activity tracking
60
- filesEdited = new Set();
61
- filesRead = new Set();
62
- lastCost = null;
63
- logBuffer = [];
64
- sessionId = null;
65
- // Last assistant text block — used by the episode write hook to
66
- // capture an approach summary without re-running an LLM (plan §"Write hook").
67
- lastAssistantText = "";
68
- constructor(client, cardId, workerId, subtasks) {
69
- this.client = client;
70
- this.cardId = cardId;
71
- this.workerId = workerId;
72
- this.subtaskTotal = subtasks.length;
73
- this.subtaskCompleted = subtasks.filter((s) => s.completed).length;
74
- this.subtaskMode = subtasks.length > 0;
75
- }
76
- setSessionId(id) {
77
- this.sessionId = id;
78
- }
79
- /**
80
- * Wire up the parser events and start the heartbeat.
81
- */
82
- attach(parser) {
83
- parser.on("tool_start", (name, input) => {
84
- this.onToolStart(name, input);
85
- const desc = this.describeToolAction(name, input);
86
- if (desc) {
87
- this.pushLogEntry({
88
- phase: this.phase,
89
- eventType: "tool_start",
90
- toolName: name,
91
- description: desc,
92
- metadata: this.extractToolMetadata(name, input),
93
- });
94
- }
95
- });
96
- parser.on("tool_end", (name, _id, content) => {
97
- this.onToolEnd(name, content);
98
- this.pushLogEntry({
99
- phase: this.phase,
100
- eventType: "tool_end",
101
- toolName: name,
102
- description: `Completed: ${name}`,
103
- metadata: {},
104
- });
105
- });
106
- parser.on("text", (content) => {
107
- this.onText(content);
108
- });
109
- parser.on("cost_update", (cost) => {
110
- this.lastCost = cost;
111
- });
112
- this.startHeartbeat();
113
- }
114
- /**
115
- * Stop all timers and flush any pending update.
116
- */
117
- stop() {
118
- this.stopped = true;
119
- if (this.pendingUpdate) {
120
- clearTimeout(this.pendingUpdate);
121
- this.pendingUpdate = null;
122
- }
123
- if (this.heartbeatTimer) {
124
- clearTimeout(this.heartbeatTimer);
125
- this.heartbeatTimer = null;
126
- }
127
- }
128
- /** Get a summary of the session stats. */
129
- get stats() {
130
- return {
131
- filesEdited: this.filesEdited.size,
132
- filesRead: this.filesRead.size,
133
- toolCalls: this.toolCallCount,
134
- cost: this.lastCost,
135
- lastAssistantText: this.lastAssistantText,
136
- };
137
- }
138
- onToolStart(name, input) {
139
- this.toolCallCount++;
140
- log.debug(TAG, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
141
- // Track files
142
- const filePath = this.extractString(input, "file_path");
143
- if (filePath) {
144
- if (EDIT_TOOLS.has(name)) {
145
- this.filesEdited.add(filePath);
146
- }
147
- else if (name === "Read" || name === "Glob" || name === "Grep") {
148
- this.filesRead.add(filePath);
149
- }
150
- }
151
- // Detect phase transitions
152
- if (!this.hasEdited && EDIT_TOOLS.has(name)) {
153
- this.hasEdited = true;
154
- this.transitionTo("implementing");
155
- }
156
- else if (this.hasEdited && name === "Bash") {
157
- const cmd = this.extractString(input, "command");
158
- if (cmd && GIT_COMMIT_RE.test(cmd)) {
159
- this.transitionTo("committing");
160
- }
161
- else if (cmd && BUILD_CMD_RE.test(cmd)) {
162
- this.transitionTo("testing");
163
- }
164
- }
165
- else if (name === "mcp__harmony__harmony_end_agent_session") {
166
- this.transitionTo("finishing");
167
- }
168
- // Handle subtask toggling — override heuristic progress
169
- if (name === "mcp__harmony__harmony_toggle_subtask" && this.subtaskMode) {
170
- const val = this.extractString(input, "completed");
171
- const completing = val === null || val === "true";
172
- if (completing) {
173
- this.subtaskCompleted = Math.min(this.subtaskCompleted + 1, this.subtaskTotal);
174
- }
175
- else {
176
- this.subtaskCompleted = Math.max(this.subtaskCompleted - 1, 0);
177
- }
178
- const subtaskProgress = Math.round(10 + (this.subtaskCompleted / this.subtaskTotal) * 60);
179
- this.progress = Math.max(this.progress, subtaskProgress);
180
- this.scheduleUpdate(`Completed subtask ${this.subtaskCompleted}/${this.subtaskTotal}`);
181
- return;
182
- }
183
- // Build rich action description and increment progress
184
- const action = this.describeToolAction(name, input);
185
- if (action) {
186
- this.lastAction = action;
187
- }
188
- this.incrementProgress();
189
- }
190
- onToolEnd(name, content) {
191
- // Detect build/test failures from Bash results
192
- if (name === "Bash" && content && this.phase === "testing") {
193
- const lower = content.slice(-500).toLowerCase();
194
- if (lower.includes("error") &&
195
- (lower.includes("build failed") ||
196
- lower.includes("failed to compile") ||
197
- lower.includes("exit code 1"))) {
198
- this.lastAction = "Build failed — fixing errors";
199
- this.scheduleUpdate(this.lastAction);
200
- }
201
- }
202
- // Reset heartbeat on any activity
203
- this.startHeartbeat();
204
- }
205
- onText(content) {
206
- // Capture brief reasoning snippets from Claude's text output.
207
- // Only update if the text looks like a meaningful status line
208
- // (skip very short fragments from streaming).
209
- const trimmed = content.trim();
210
- if (trimmed.length < 10)
211
- return;
212
- // Always remember the latest non-trivial assistant turn for the episode
213
- // write hook — last-turn trim, no LLM rewrite (plan §"Write hook").
214
- this.lastAssistantText = trimmed;
215
- // Extract first sentence or line as a brief description
216
- const end = trimmed.search(SENTENCE_SPLIT);
217
- const firstLine = (end === -1 ? trimmed : trimmed.slice(0, end)).trim();
218
- if (firstLine.length >= 10 && firstLine.length <= 200) {
219
- if (ACTION_PREFIX.test(firstLine)) {
220
- this.lastAction = truncate(firstLine, MAX_TASK_LENGTH);
221
- }
222
- }
223
- }
224
- transitionTo(newPhase) {
225
- if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
226
- return;
227
- log.info(TAG, `Phase: ${this.phase} → ${newPhase}`);
228
- this.phase = newPhase;
229
- this.progress = Math.max(this.progress, PHASES[newPhase].min);
230
- // Reset stale action from prior phase; new phase starts with its own label
231
- this.lastAction = "";
232
- this.pushLogEntry({
233
- phase: newPhase,
234
- eventType: "phase_change",
235
- toolName: null,
236
- description: `Entering ${newPhase} phase`,
237
- metadata: {},
238
- });
239
- this.scheduleUpdate(PHASES[newPhase].label);
240
- }
241
- incrementProgress() {
242
- const config = PHASES[this.phase];
243
- const range = config.max - config.min;
244
- const step = Math.max(1, Math.round(range / 12));
245
- const newProgress = Math.min(this.progress + step, config.max);
246
- if (newProgress > this.progress) {
247
- this.progress = newProgress;
248
- this.scheduleUpdate(this.currentTaskLabel());
249
- }
250
- }
251
- currentTaskLabel() {
252
- if (this.lastAction)
253
- return this.lastAction;
254
- // Include file count context when available
255
- const edited = this.filesEdited.size;
256
- if (edited > 0) {
257
- return `${PHASES[this.phase].label} (${edited} file${edited > 1 ? "s" : ""} modified)`;
258
- }
259
- return PHASES[this.phase].label;
260
- }
261
- /**
262
- * Build a human-readable description of what a tool call is doing.
263
- */
264
- describeToolAction(name, input) {
265
- // File-based tools: Read, Edit, MultiEdit, Write, NotebookEdit
266
- const fileTool = FILE_TOOL_VERBS[name];
267
- if (fileTool) {
268
- const [verb, key] = fileTool;
269
- const fp = this.extractString(input, key);
270
- return fp ? `${verb} ${this.shortPath(fp)}` : verb;
271
- }
272
- switch (name) {
273
- case "Glob": {
274
- const pattern = this.extractString(input, "pattern");
275
- return pattern ? `Searching for ${pattern}` : "Searching files";
276
- }
277
- case "Grep": {
278
- const pattern = this.extractString(input, "pattern");
279
- return pattern
280
- ? `Searching for "${truncate(pattern, 40)}"`
281
- : "Searching code";
282
- }
283
- case "Bash": {
284
- const cmd = this.extractString(input, "command");
285
- return cmd
286
- ? `Running: ${truncate(cmd.split("\n")[0], 80)}`
287
- : "Running command";
288
- }
289
- case "Agent": {
290
- const desc = this.extractString(input, "description");
291
- return desc
292
- ? `Sub-agent: ${truncate(desc, 60)}`
293
- : "Delegating to sub-agent";
294
- }
295
- default: {
296
- if (name.startsWith("mcp__harmony__harmony_")) {
297
- const toolName = name
298
- .replace("mcp__harmony__harmony_", "")
299
- .replace(/_/g, " ");
300
- return `Harmony: ${toolName}`;
301
- }
302
- if (name.startsWith("mcp__")) {
303
- return `Tool: ${name.split("__").pop()?.replace(/_/g, " ") ?? name}`;
304
- }
305
- return null;
306
- }
307
- }
308
- }
309
- /**
310
- * Strip absolute paths to show only meaningful segments from src/ or packages/.
311
- */
312
- shortPath(filePath) {
313
- const parts = filePath.split("/");
314
- const srcIdx = parts.lastIndexOf("src");
315
- const pkgIdx = parts.lastIndexOf("packages");
316
- const anchor = Math.max(srcIdx, pkgIdx);
317
- if (anchor >= 0) {
318
- return parts.slice(anchor).join("/");
319
- }
320
- return parts.slice(-3).join("/");
321
- }
322
- scheduleUpdate(currentTask) {
323
- if (this.stopped)
324
- return;
325
- const now = Date.now();
326
- const elapsed = now - this.lastUpdateAt;
327
- // Always track latest task so throttled sends use the freshest label
328
- this.pendingTask = currentTask;
329
- if (elapsed >= THROTTLE_MS) {
330
- this.sendUpdate(this.pendingTask);
331
- }
332
- else if (!this.pendingUpdate) {
333
- const delay = THROTTLE_MS - elapsed;
334
- this.pendingUpdate = setTimeout(() => {
335
- this.pendingUpdate = null;
336
- if (!this.stopped) {
337
- // Send the latest task, not the one captured at schedule time
338
- this.sendUpdate(this.pendingTask);
339
- }
340
- }, delay);
341
- }
342
- // If there's already a pending update, pendingTask is now updated — it will use the fresh value
343
- }
344
- sendUpdate(currentTask) {
345
- this.lastUpdateAt = Date.now();
346
- log.debug(TAG, `Progress: ${this.progress}% — ${currentTask}`);
347
- this.client
348
- .updateAgentProgress(this.cardId, {
349
- agentIdentifier: agentIdentifier(this.workerId),
350
- agentName: AGENT_NAME,
351
- status: "working",
352
- currentTask: truncate(currentTask, MAX_TASK_LENGTH),
353
- progressPercent: this.progress,
354
- phase: this.phase,
355
- filesChanged: this.filesEdited.size,
356
- costCents: Math.round((this.lastCost?.totalCostUsd ?? 0) * 100),
357
- inputTokens: this.lastCost?.totalInputTokens ?? 0,
358
- outputTokens: this.lastCost?.totalOutputTokens ?? 0,
359
- cacheCreationInputTokens: this.lastCost?.totalCacheCreationInputTokens ?? 0,
360
- cacheReadInputTokens: this.lastCost?.totalCacheReadInputTokens ?? 0,
361
- modelName: this.lastCost?.modelName,
362
- })
363
- .catch((err) => {
364
- log.warn(TAG, `Failed to send progress update: ${err}`);
365
- });
366
- this.flushActivityLog();
367
- }
368
- startHeartbeat() {
369
- if (this.heartbeatTimer) {
370
- clearTimeout(this.heartbeatTimer);
371
- }
372
- this.heartbeatTimer = setTimeout(() => {
373
- if (!this.stopped) {
374
- const task = this.lastAction
375
- ? `Still working — ${this.lastAction}`
376
- : "Still working...";
377
- this.sendUpdate(truncate(task, MAX_TASK_LENGTH));
378
- this.startHeartbeat();
379
- }
380
- }, HEARTBEAT_MS);
381
- }
382
- flushFinal() {
383
- this.flushActivityLog();
384
- }
385
- pushLogEntry(entry) {
386
- this.logBuffer.push({
387
- ...entry,
388
- createdAt: new Date().toISOString(),
389
- });
390
- if (this.logBuffer.length > MAX_LOG_BUFFER) {
391
- this.logBuffer.shift();
392
- }
393
- }
394
- flushActivityLog() {
395
- if (!this.sessionId || this.logBuffer.length === 0)
396
- return;
397
- const raw = [...this.logBuffer];
398
- this.logBuffer = [];
399
- this.client
400
- .flushActivityLog(this.cardId, {
401
- sessionId: this.sessionId,
402
- entries: raw.map((e) => ({
403
- ...e,
404
- phase: e.phase ?? undefined,
405
- toolName: e.toolName ?? undefined,
406
- })),
407
- })
408
- .catch((err) => {
409
- log.warn(TAG, `Failed to flush activity log: ${err}`);
410
- // Put entries back at the front of the buffer for retry
411
- this.logBuffer.unshift(...raw);
412
- if (this.logBuffer.length > MAX_LOG_BUFFER) {
413
- this.logBuffer.length = MAX_LOG_BUFFER;
414
- }
415
- });
416
- }
417
- extractToolMetadata(_name, input) {
418
- const meta = {};
419
- const fp = this.extractString(input, "file_path");
420
- if (fp)
421
- meta.file_path = fp;
422
- const cmd = this.extractString(input, "command");
423
- if (cmd)
424
- meta.command = cmd.split("\n")[0].slice(0, 200);
425
- const pattern = this.extractString(input, "pattern");
426
- if (pattern)
427
- meta.pattern = pattern;
428
- const desc = this.extractString(input, "description");
429
- if (desc)
430
- meta.description = desc;
431
- return meta;
432
- }
433
- /**
434
- * Safely extract a string property from an unknown tool input.
435
- */
436
- extractString(input, key) {
437
- if (typeof input === "object" && input !== null && key in input) {
438
- return String(input[key]);
439
- }
440
- return null;
441
- }
442
- }
package/dist/prompt.d.ts DELETED
@@ -1,18 +0,0 @@
1
- import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
2
- import type { EnrichedCard } from "./types.js";
3
- /**
4
- * Build the prompt for a card using the shared prompt generation pipeline.
5
- *
6
- * This calls `client.generateCardPrompt()` which assembles relevant memories
7
- * from the knowledge graph, applies role-based framing, and produces a
8
- * context-rich prompt — the same pipeline used by the MCP server.
9
- *
10
- * Falls back to a minimal local prompt if the API call fails.
11
- */
12
- export declare function buildPrompt(enriched: EnrichedCard, branchName: string, worktreePath: string, client: HarmonyApiClient, workspaceId: string, projectId?: string): Promise<string>;
13
- /**
14
- * Recall similar past episodes (implement solution/error type) and render them
15
- * as a "Similar past tasks" section. Returns the empty string on no hits or
16
- * recall failure — never throws.
17
- */
18
- export declare function renderPastEpisodesSection(client: HarmonyApiClient, title: string, description: string, workspaceId: string, projectId?: string): Promise<string>;
package/dist/prompt.js DELETED
@@ -1,117 +0,0 @@
1
- import { log } from "./log.js";
2
- const TAG = "prompt";
3
- /**
4
- * Build the prompt for a card using the shared prompt generation pipeline.
5
- *
6
- * This calls `client.generateCardPrompt()` which assembles relevant memories
7
- * from the knowledge graph, applies role-based framing, and produces a
8
- * context-rich prompt — the same pipeline used by the MCP server.
9
- *
10
- * Falls back to a minimal local prompt if the API call fails.
11
- */
12
- export async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId) {
13
- const { card } = enriched;
14
- // Phase 1.5 read hook: surface similar past episodes for this card. Block
15
- // on recall — v2 §6.3 budget already caps latency. Errors degrade silently
16
- // so prompt build always succeeds (plan §"Read hook").
17
- const pastEpisodesSection = await renderPastEpisodesSection(client, card.title, card.description ?? "", workspaceId, projectId);
18
- try {
19
- const result = await client.generateCardPrompt({
20
- cardId: card.id,
21
- workspaceId,
22
- projectId,
23
- variant: "execute",
24
- customConstraints: `You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
25
- Do NOT push to main. All your work stays on \`${branchName}\`.
26
- When finished, call harmony_end_agent_session with status="completed".`,
27
- });
28
- log.info(TAG, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
29
- return result.prompt + pastEpisodesSection;
30
- }
31
- catch (err) {
32
- const msg = err instanceof Error ? err.message : String(err);
33
- log.warn(TAG, `Failed to generate prompt via API, using fallback: ${msg}`);
34
- return (buildFallbackPrompt(enriched, branchName, worktreePath) +
35
- pastEpisodesSection);
36
- }
37
- }
38
- /**
39
- * Recall similar past episodes (implement solution/error type) and render them
40
- * as a "Similar past tasks" section. Returns the empty string on no hits or
41
- * recall failure — never throws.
42
- */
43
- export async function renderPastEpisodesSection(client, title, description, workspaceId, projectId) {
44
- if (!projectId)
45
- return "";
46
- try {
47
- const query = `${title}\n${description}`.trim();
48
- const { entities } = await client.harmonyRecall({
49
- workspaceId,
50
- projectId,
51
- query,
52
- type: ["solution", "error"],
53
- memory_tier: "episode",
54
- scope: "project",
55
- topK: 3,
56
- });
57
- if (entities.length === 0)
58
- return "";
59
- const bullets = entities
60
- .map((entity) => {
61
- const e = entity;
62
- const meta = e.metadata ?? {};
63
- const outcomeTag = meta.outcome ? `[${meta.outcome}]` : "[?]";
64
- const approach = meta.approach_summary ?? "";
65
- return `- ${outcomeTag} ${e.title ?? "(untitled episode)"}\n Approach: ${approach}`;
66
- })
67
- .join("\n");
68
- return `\n\n## Similar past tasks\n${bullets}`;
69
- }
70
- catch (err) {
71
- log.warn(TAG, "past-episodes recall failed", {
72
- event: "episode_recall_failed",
73
- error: err instanceof Error ? err.message : String(err),
74
- });
75
- return "";
76
- }
77
- }
78
- /**
79
- * Minimal fallback prompt when the API-based generation is unavailable.
80
- */
81
- function buildFallbackPrompt(enriched, branchName, worktreePath) {
82
- const { card, column, labels, subtasks } = enriched;
83
- const labelStr = labels.length > 0 ? labels.map((l) => l.name).join(", ") : "none";
84
- const subtaskStr = subtasks.length > 0
85
- ? subtasks
86
- .map((s) => `- [${s.completed ? "x" : " "}] ${s.title}`)
87
- .join("\n")
88
- : "No subtasks defined.";
89
- const description = card.description?.trim() || "No description provided.";
90
- return `You are an AI agent working on a task from the Harmony project board.
91
-
92
- ## Card: #${card.short_id} - ${card.title}
93
- **Labels**: ${labelStr}
94
- **Column**: ${column.name}
95
- **Priority**: ${card.priority}
96
-
97
- ## Description
98
- ${description}
99
-
100
- ## Subtasks
101
- ${subtaskStr}
102
-
103
- ## Instructions
104
- 1. Read the codebase and understand the context needed for this task
105
- 2. Report progress via harmony_update_agent_progress at key milestones:
106
- - After reading codebase and forming a plan (~20%)
107
- - After each major implementation step (~30-60%)
108
- - After completing each subtask (also toggle via harmony_toggle_subtask)
109
- - Before committing (~65%)
110
- Include a brief currentTask description.
111
- 3. Implement the changes on branch \`${branchName}\`
112
- 4. Commit your work with clear, descriptive commit messages
113
- 5. When finished, call harmony_end_agent_session with status="completed"
114
-
115
- You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
116
- Do NOT push to main. All your work stays on \`${branchName}\`.`;
117
- }
package/dist/queue.d.ts DELETED
@@ -1,39 +0,0 @@
1
- import type { Card, Column, Label } from "@harmony/shared";
2
- import type { AgentConfig, QueueItem, WorkMode } from "./types.js";
3
- /**
4
- * Priority queue for cards waiting to be worked on.
5
- * Sorted by: label priority boost > column position boost > enqueue time (FIFO).
6
- */
7
- export declare class PriorityQueue {
8
- private config;
9
- private items;
10
- constructor(config: AgentConfig);
11
- /**
12
- * Calculate priority score for a card.
13
- */
14
- scoreCard(_card: Card, column: Column, labels: Label[]): number;
15
- /**
16
- * Add a card to the queue. If already present, update its priority.
17
- */
18
- enqueue(card: Card, column: Column, labels: Label[], mode?: WorkMode): void;
19
- /**
20
- * Remove and return the highest-priority item.
21
- */
22
- dequeue(): QueueItem | null;
23
- /**
24
- * Remove a specific card from the queue.
25
- */
26
- remove(cardId: string): QueueItem | null;
27
- /**
28
- * Check if a card is in the queue.
29
- */
30
- has(cardId: string): boolean;
31
- /**
32
- * Get all queued card IDs.
33
- */
34
- cardIds(): string[];
35
- get length(): number;
36
- peek(): QueueItem | null;
37
- /** Copy of the queue in priority order (for introspection). */
38
- snapshot(): QueueItem[];
39
- }