@gethmy/agent 1.0.1 → 1.0.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.
- package/README.md +3 -2
- package/dist/board-helpers.d.ts +10 -2
- package/dist/board-helpers.js +22 -3
- package/dist/completion.d.ts +8 -1
- package/dist/completion.js +14 -4
- package/dist/index.js +9 -5
- package/dist/merge-monitor.js +34 -22
- package/dist/pool.d.ts +4 -0
- package/dist/pool.js +23 -0
- package/dist/progress-tracker.d.ts +28 -3
- package/dist/progress-tracker.js +214 -42
- package/dist/reconcile.js +12 -5
- package/dist/review-completion.d.ts +1 -1
- package/dist/review-completion.js +15 -9
- package/dist/review-prompt.d.ts +1 -1
- package/dist/review-prompt.js +7 -4
- package/dist/review-worker.d.ts +11 -0
- package/dist/review-worker.js +177 -42
- package/dist/stream-parser.d.ts +16 -7
- package/dist/stream-parser.js +25 -11
- package/dist/types.d.ts +2 -0
- package/dist/types.js +3 -0
- package/dist/watcher.d.ts +7 -1
- package/dist/watcher.js +14 -2
- package/dist/worker.d.ts +9 -0
- package/dist/worker.js +63 -4
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -4,10 +4,11 @@ Push-based agent daemon for [Harmony](https://gethmy.com). Watches board assignm
|
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
|
-
- [Node.js](https://nodejs.org) >=
|
|
8
|
-
- [Claude
|
|
7
|
+
- [Node.js](https://nodejs.org) >= 20 or [Bun](https://bun.sh) >= 1.0
|
|
8
|
+
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI installed
|
|
9
9
|
- Git
|
|
10
10
|
- A [Harmony](https://gethmy.com) account with an API key
|
|
11
|
+
- [@gethmy/mcp](https://www.npmjs.com/package/@gethmy/mcp) configured (`npx @gethmy/mcp setup`)
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
package/dist/board-helpers.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
|
|
2
2
|
import type { Card, Label } from "@harmony/shared";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Build a label lookup map from board-level label definitions.
|
|
5
5
|
*/
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function buildLabelMap(boardLabels: Label[]): Map<string, Label>;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a card's `labelIds` to full Label objects using a label map.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveCardLabels(card: Card, labelMap: Map<string, Label>): Label[];
|
|
11
|
+
/**
|
|
12
|
+
* Check if a card has a label by name (case-insensitive).
|
|
13
|
+
*/
|
|
14
|
+
export declare function hasLabel(cardLabels: Label[], labelName: string): boolean;
|
|
7
15
|
/**
|
|
8
16
|
* Move a card to a column by name. Fetches the board to resolve column ID.
|
|
9
17
|
*/
|
package/dist/board-helpers.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { log } from "./log.js";
|
|
2
2
|
const TAG = "board";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Build a label lookup map from board-level label definitions.
|
|
5
5
|
*/
|
|
6
|
-
export function
|
|
7
|
-
|
|
6
|
+
export function buildLabelMap(boardLabels) {
|
|
7
|
+
const map = new Map();
|
|
8
|
+
for (const label of boardLabels) {
|
|
9
|
+
map.set(label.id, label);
|
|
10
|
+
}
|
|
11
|
+
return map;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a card's `labelIds` to full Label objects using a label map.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveCardLabels(card, labelMap) {
|
|
17
|
+
const ids = card.labelIds ?? [];
|
|
18
|
+
return ids
|
|
19
|
+
.map((id) => labelMap.get(id))
|
|
20
|
+
.filter((l) => l !== undefined);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if a card has a label by name (case-insensitive).
|
|
24
|
+
*/
|
|
25
|
+
export function hasLabel(cardLabels, labelName) {
|
|
26
|
+
return cardLabels.some((l) => l.name.toLowerCase() === labelName.toLowerCase());
|
|
8
27
|
}
|
|
9
28
|
/**
|
|
10
29
|
* Move a card to a column by name. Fetches the board to resolve column ID.
|
package/dist/completion.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
|
|
2
2
|
import type { Card } from "@harmony/shared";
|
|
3
|
+
import type { CostUpdate } from "./stream-parser.js";
|
|
3
4
|
import { type AgentConfig } from "./types.js";
|
|
5
|
+
export interface SessionStats {
|
|
6
|
+
filesEdited: number;
|
|
7
|
+
filesRead: number;
|
|
8
|
+
toolCalls: number;
|
|
9
|
+
cost: CostUpdate | null;
|
|
10
|
+
}
|
|
4
11
|
/**
|
|
5
12
|
* Post-work pipeline: push branch, create PR, move card, post summary.
|
|
6
13
|
*/
|
|
7
|
-
export declare function runCompletion(client: HarmonyApiClient, card: Card, branchName: string, worktreePath: string, config: AgentConfig, workerId?: number): Promise<void>;
|
|
14
|
+
export declare function runCompletion(client: HarmonyApiClient, card: Card, branchName: string, worktreePath: string, config: AgentConfig, workerId?: number, sessionStats?: SessionStats): Promise<void>;
|
package/dist/completion.js
CHANGED
|
@@ -6,11 +6,10 @@ import { AGENT_NAME, agentIdentifier } from "./types.js";
|
|
|
6
6
|
import { attemptAutoFix, reportFindings, runVerification, } from "./verification.js";
|
|
7
7
|
import { cleanupWorktree } from "./worktree.js";
|
|
8
8
|
const TAG = "completion";
|
|
9
|
-
// ============ COMPLETION PIPELINE ============
|
|
10
9
|
/**
|
|
11
10
|
* Post-work pipeline: push branch, create PR, move card, post summary.
|
|
12
11
|
*/
|
|
13
|
-
export async function runCompletion(client, card, branchName, worktreePath, config, workerId = 0) {
|
|
12
|
+
export async function runCompletion(client, card, branchName, worktreePath, config, workerId = 0, sessionStats) {
|
|
14
13
|
// Check if there are any commits on the branch
|
|
15
14
|
const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
|
|
16
15
|
if (!hasCommits) {
|
|
@@ -76,7 +75,7 @@ export async function runCompletion(client, card, branchName, worktreePath, conf
|
|
|
76
75
|
}
|
|
77
76
|
// 5. Post summary — always includes branch, optionally PR link
|
|
78
77
|
if (config.completion.postSummary) {
|
|
79
|
-
await postSummary(client, card, branchName, worktreePath, prUrl, config.worktree.baseBranch);
|
|
78
|
+
await postSummary(client, card, branchName, worktreePath, prUrl, config.worktree.baseBranch, sessionStats);
|
|
80
79
|
}
|
|
81
80
|
// 6. End agent session
|
|
82
81
|
await client.endAgentSession(card.id, {
|
|
@@ -96,7 +95,7 @@ function checkHasCommits(worktreePath, baseBranch) {
|
|
|
96
95
|
return false;
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
|
-
async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch) {
|
|
98
|
+
async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
|
|
100
99
|
// Build commit summary
|
|
101
100
|
let commitLog = "";
|
|
102
101
|
try {
|
|
@@ -112,6 +111,17 @@ async function postSummary(client, card, branchName, worktreePath, prUrl, baseBr
|
|
|
112
111
|
parts.push(`PR: ${prUrl}`);
|
|
113
112
|
}
|
|
114
113
|
parts.push(`Branch: \`${branchName}\``);
|
|
114
|
+
if (sessionStats) {
|
|
115
|
+
const statParts = [];
|
|
116
|
+
statParts.push(`${sessionStats.toolCalls} tool calls`);
|
|
117
|
+
statParts.push(`${sessionStats.filesEdited} files edited`);
|
|
118
|
+
statParts.push(`${sessionStats.filesRead} files read`);
|
|
119
|
+
if (sessionStats.cost) {
|
|
120
|
+
statParts.push(`$${sessionStats.cost.totalCostUsd.toFixed(2)} cost`);
|
|
121
|
+
statParts.push(`${sessionStats.cost.numTurns} turns`);
|
|
122
|
+
}
|
|
123
|
+
parts.push(`Stats: ${statParts.join(" · ")}`);
|
|
124
|
+
}
|
|
115
125
|
if (commitLog) {
|
|
116
126
|
parts.push(`\n\`\`\`\n${commitLog}\n\`\`\``);
|
|
117
127
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { buildLabelMap, hasLabel, resolveCardLabels } from "./board-helpers.js";
|
|
3
3
|
import { createApiClient, fetchRealtimeCredentials, loadDaemonConfig, } from "./config.js";
|
|
4
4
|
import { detectGitProvider, validateGitProviderCli } from "./git-pr.js";
|
|
5
5
|
import { log } from "./log.js";
|
|
@@ -101,9 +101,11 @@ async function main() {
|
|
|
101
101
|
if (config.agent.review.enabled && config.agent.review.mergeMonitor) {
|
|
102
102
|
mergeMonitor = new MergeMonitor(client, config.projectId, config.agent);
|
|
103
103
|
}
|
|
104
|
-
// Create watcher (broadcast events from harmony-api)
|
|
104
|
+
// Create watcher (broadcast events from harmony-api + agent commands from UI)
|
|
105
105
|
const watcher = new Watcher(realtimeCreds, config.projectId, async (event) => {
|
|
106
106
|
await handleBroadcast(event, client, pool, config, agentUserId);
|
|
107
|
+
}, async (command) => {
|
|
108
|
+
await pool.handleAgentCommand(command.cardId, command.command);
|
|
107
109
|
});
|
|
108
110
|
// Wire up shutdown
|
|
109
111
|
let shuttingDown = false;
|
|
@@ -160,7 +162,7 @@ async function handleBroadcast(event, client, pool, config, agentUserId) {
|
|
|
160
162
|
*/
|
|
161
163
|
async function tryEnqueueCard(cardId, client, pool, config) {
|
|
162
164
|
const { card } = (await client.getCard(cardId));
|
|
163
|
-
const board = await client.getBoard(config.projectId);
|
|
165
|
+
const board = await client.getBoard(config.projectId, { summary: true });
|
|
164
166
|
const columns = board.columns;
|
|
165
167
|
const column = columns.find((c) => c.id === card.column_id);
|
|
166
168
|
if (!column) {
|
|
@@ -177,12 +179,14 @@ async function tryEnqueueCard(cardId, client, pool, config) {
|
|
|
177
179
|
return;
|
|
178
180
|
}
|
|
179
181
|
const mode = isReviewColumn ? "review" : "implement";
|
|
180
|
-
|
|
182
|
+
// Resolve card labels from labelIds using board-level label definitions
|
|
183
|
+
const labelMap = buildLabelMap((board.labels ?? []));
|
|
184
|
+
const cardLabels = resolveCardLabels(card, labelMap);
|
|
181
185
|
const subtasks = card.subtasks ?? [];
|
|
182
186
|
// Skip already-approved cards in review mode
|
|
183
187
|
if (mode === "review" &&
|
|
184
188
|
config.agent.review.approvedLabel &&
|
|
185
|
-
|
|
189
|
+
hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
186
190
|
log.debug(TAG, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
187
191
|
return;
|
|
188
192
|
}
|
package/dist/merge-monitor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
-
import { addLabelByName,
|
|
3
|
+
import { addLabelByName, buildLabelMap, hasLabel, moveCardToColumn, resolveCardLabels, } from "./board-helpers.js";
|
|
4
4
|
import { checkPrMergeStatus, detectGitProvider, extractPrUrl, } from "./git-pr.js";
|
|
5
5
|
import { log } from "./log.js";
|
|
6
6
|
import { extractBranchFromDescription } from "./review-worktree.js";
|
|
@@ -54,27 +54,39 @@ export class MergeMonitor {
|
|
|
54
54
|
}
|
|
55
55
|
async tick() {
|
|
56
56
|
try {
|
|
57
|
-
const board = await this.client.getBoard(this.projectId
|
|
57
|
+
const board = await this.client.getBoard(this.projectId, {
|
|
58
|
+
labelName: this.config.review.approvedLabel,
|
|
59
|
+
});
|
|
58
60
|
const cards = (board.cards ?? []);
|
|
59
61
|
const columns = (board.columns ?? []);
|
|
62
|
+
// Build label lookup (id → Label) to resolve card.labelIds
|
|
63
|
+
const labelMap = buildLabelMap((board.labels ?? []));
|
|
60
64
|
// Find review column IDs
|
|
61
65
|
const reviewColumnIds = new Set(columns
|
|
62
66
|
.filter((c) => this.config.review.pickupColumns.some((name) => name.toLowerCase() === c.name.toLowerCase()))
|
|
63
67
|
.map((c) => c.id));
|
|
64
|
-
//
|
|
68
|
+
// Cards are already pre-filtered by the approved label server-side via
|
|
69
|
+
// the `labelName` param. The column + label checks below are kept as
|
|
70
|
+
// defense-in-depth against API changes or stale cache.
|
|
65
71
|
const approvedLabel = this.config.review.approvedLabel;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
const candidatesWithLabels = [];
|
|
73
|
+
for (const c of cards) {
|
|
74
|
+
if (c.archived_at || !reviewColumnIds.has(c.column_id))
|
|
75
|
+
continue;
|
|
76
|
+
const labels = resolveCardLabels(c, labelMap);
|
|
77
|
+
if (hasLabel(labels, approvedLabel)) {
|
|
78
|
+
candidatesWithLabels.push({ card: c, labels });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (candidatesWithLabels.length === 0) {
|
|
70
82
|
log.debug(TAG, "No Ready to Merge cards found");
|
|
71
83
|
return;
|
|
72
84
|
}
|
|
73
85
|
// Process max 5 per tick to respect rate limits
|
|
74
|
-
const batch =
|
|
86
|
+
const batch = candidatesWithLabels.slice(0, 5);
|
|
75
87
|
log.debug(TAG, `Checking ${batch.length} Ready to Merge card(s)`);
|
|
76
88
|
// Check PR states concurrently (async, non-blocking)
|
|
77
|
-
const results = await Promise.allSettled(batch.map(async (card) => {
|
|
89
|
+
const results = await Promise.allSettled(batch.map(async ({ card, labels }) => {
|
|
78
90
|
const prUrl = extractPrUrl(card.description ?? null);
|
|
79
91
|
if (!prUrl) {
|
|
80
92
|
log.debug(TAG, `#${card.short_id} has no PR URL — skipping`);
|
|
@@ -83,7 +95,7 @@ export class MergeMonitor {
|
|
|
83
95
|
const state = await checkPrMergeStatus(prUrl, this.cwd, this.provider);
|
|
84
96
|
if (state === "merged") {
|
|
85
97
|
log.info(TAG, `#${card.short_id} PR merged — completing`);
|
|
86
|
-
await this.completeMergedCard(card);
|
|
98
|
+
await this.completeMergedCard(card, labels);
|
|
87
99
|
}
|
|
88
100
|
else {
|
|
89
101
|
log.debug(TAG, `#${card.short_id} PR state: ${state}`);
|
|
@@ -99,7 +111,7 @@ export class MergeMonitor {
|
|
|
99
111
|
log.error(TAG, `Tick failed: ${err instanceof Error ? err.message : err}`);
|
|
100
112
|
}
|
|
101
113
|
}
|
|
102
|
-
async completeMergedCard(card) {
|
|
114
|
+
async completeMergedCard(card, resolvedLabels) {
|
|
103
115
|
// 1. Move to Done — bail if this fails since subsequent steps assume card is in Done
|
|
104
116
|
try {
|
|
105
117
|
await moveCardToColumn(this.client, card, this.config.review.moveToColumn);
|
|
@@ -112,8 +124,7 @@ export class MergeMonitor {
|
|
|
112
124
|
await addLabelByName(this.client, card, this.config.review.mergedLabel, this.config.review.mergedLabelColor);
|
|
113
125
|
// 3. Remove "Ready to Merge" label
|
|
114
126
|
const approvedLabelName = this.config.review.approvedLabel.toLowerCase();
|
|
115
|
-
const
|
|
116
|
-
const approvedLabelObj = cardLabels.find((l) => l.name.toLowerCase() === approvedLabelName);
|
|
127
|
+
const approvedLabelObj = resolvedLabels.find((l) => l.name.toLowerCase() === approvedLabelName);
|
|
117
128
|
if (approvedLabelObj) {
|
|
118
129
|
try {
|
|
119
130
|
await this.client.removeLabelFromCard(card.id, approvedLabelObj.id);
|
|
@@ -123,19 +134,20 @@ export class MergeMonitor {
|
|
|
123
134
|
log.warn(TAG, `Failed to remove label: ${err instanceof Error ? err.message : err}`);
|
|
124
135
|
}
|
|
125
136
|
}
|
|
126
|
-
// 4.
|
|
137
|
+
// 4. Mark done + append merge timestamp to description (idempotent)
|
|
127
138
|
const existing = card.description || "";
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
const alreadyStamped = existing.includes("Merged at");
|
|
140
|
+
try {
|
|
141
|
+
const update = { done: true };
|
|
142
|
+
if (!alreadyStamped) {
|
|
130
143
|
const timestamp = new Date().toISOString();
|
|
131
144
|
const separator = existing ? "\n" : "";
|
|
132
|
-
|
|
133
|
-
description: `${existing}${separator}Merged at ${timestamp}`,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
catch (err) {
|
|
137
|
-
log.warn(TAG, `Failed to update description: ${err instanceof Error ? err.message : err}`);
|
|
145
|
+
update.description = `${existing}${separator}Merged at ${timestamp}`;
|
|
138
146
|
}
|
|
147
|
+
await this.client.updateCard(card.id, update);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
log.warn(TAG, `Failed to update card: ${err instanceof Error ? err.message : err}`);
|
|
139
151
|
}
|
|
140
152
|
// 5. Best-effort: clean up local branch (uses shared extraction with validation)
|
|
141
153
|
const branchName = extractBranchFromDescription(card.description);
|
package/dist/pool.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export declare class Pool {
|
|
|
27
27
|
* Get all card IDs that are either queued or active.
|
|
28
28
|
*/
|
|
29
29
|
knownCardIds(): Set<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Handle an agent command (pause/resume/stop) for a specific card.
|
|
32
|
+
*/
|
|
33
|
+
handleAgentCommand(cardId: string, command: "pause" | "resume" | "stop"): Promise<void>;
|
|
30
34
|
/**
|
|
31
35
|
* Gracefully shutdown all workers.
|
|
32
36
|
*/
|
package/dist/pool.js
CHANGED
|
@@ -99,6 +99,29 @@ export class Pool {
|
|
|
99
99
|
}
|
|
100
100
|
return ids;
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Handle an agent command (pause/resume/stop) for a specific card.
|
|
104
|
+
*/
|
|
105
|
+
async handleAgentCommand(cardId, command) {
|
|
106
|
+
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ??
|
|
107
|
+
this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
108
|
+
if (!worker) {
|
|
109
|
+
log.debug(TAG, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
log.info(TAG, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
113
|
+
switch (command) {
|
|
114
|
+
case "pause":
|
|
115
|
+
await worker.pause();
|
|
116
|
+
break;
|
|
117
|
+
case "resume":
|
|
118
|
+
await worker.resume();
|
|
119
|
+
break;
|
|
120
|
+
case "stop":
|
|
121
|
+
await worker.cancel();
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
102
125
|
/**
|
|
103
126
|
* Gracefully shutdown all workers.
|
|
104
127
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HarmonyApiClient } from "@gethmy/mcp/src/api-client.js";
|
|
2
|
-
import type { StreamParser } from "./stream-parser.js";
|
|
2
|
+
import type { CostUpdate, StreamParser } from "./stream-parser.js";
|
|
3
3
|
export declare class ProgressTracker {
|
|
4
4
|
private client;
|
|
5
5
|
private cardId;
|
|
@@ -10,11 +10,17 @@ export declare class ProgressTracker {
|
|
|
10
10
|
private hasEdited;
|
|
11
11
|
private lastUpdateAt;
|
|
12
12
|
private pendingUpdate;
|
|
13
|
+
private pendingTask;
|
|
13
14
|
private heartbeatTimer;
|
|
14
15
|
private stopped;
|
|
16
|
+
private lastAction;
|
|
15
17
|
private subtaskTotal;
|
|
16
18
|
private subtaskCompleted;
|
|
17
19
|
private subtaskMode;
|
|
20
|
+
private filesEdited;
|
|
21
|
+
private filesRead;
|
|
22
|
+
private lastCost;
|
|
23
|
+
private recentActions;
|
|
18
24
|
constructor(client: HarmonyApiClient, cardId: string, workerId: number, subtasks: {
|
|
19
25
|
completed: boolean;
|
|
20
26
|
}[]);
|
|
@@ -26,14 +32,33 @@ export declare class ProgressTracker {
|
|
|
26
32
|
* Stop all timers and flush any pending update.
|
|
27
33
|
*/
|
|
28
34
|
stop(): void;
|
|
35
|
+
/** Get a summary of the session stats. */
|
|
36
|
+
get stats(): {
|
|
37
|
+
filesEdited: number;
|
|
38
|
+
filesRead: number;
|
|
39
|
+
toolCalls: number;
|
|
40
|
+
cost: CostUpdate | null;
|
|
41
|
+
};
|
|
29
42
|
private onToolStart;
|
|
30
43
|
private onToolEnd;
|
|
44
|
+
private onText;
|
|
31
45
|
private transitionTo;
|
|
32
46
|
private incrementProgress;
|
|
33
47
|
private currentTaskLabel;
|
|
48
|
+
/**
|
|
49
|
+
* Build a human-readable description of what a tool call is doing.
|
|
50
|
+
*/
|
|
51
|
+
private describeToolAction;
|
|
52
|
+
/**
|
|
53
|
+
* Strip absolute paths to show only meaningful segments from src/ or packages/.
|
|
54
|
+
*/
|
|
55
|
+
private shortPath;
|
|
34
56
|
private scheduleUpdate;
|
|
57
|
+
private pushRecentAction;
|
|
35
58
|
private sendUpdate;
|
|
36
59
|
private startHeartbeat;
|
|
37
|
-
|
|
38
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Safely extract a string property from an unknown tool input.
|
|
62
|
+
*/
|
|
63
|
+
private extractString;
|
|
39
64
|
}
|