@gethmy/agent 1.4.2 → 1.6.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.
- package/dist/budget.d.ts +4 -2
- package/dist/budget.js +41 -2
- package/dist/completion.d.ts +4 -1
- package/dist/completion.js +102 -8
- package/dist/episode-writer.d.ts +84 -0
- package/dist/episode-writer.js +232 -0
- package/dist/git-pr.d.ts +13 -0
- package/dist/git-pr.js +73 -0
- package/dist/index.js +6 -1
- package/dist/pool.d.ts +6 -1
- package/dist/pool.js +39 -5
- package/dist/progress-tracker.d.ts +2 -0
- package/dist/progress-tracker.js +7 -0
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.js +47 -2
- package/dist/recovery.js +8 -2
- package/dist/review-completion.d.ts +2 -1
- package/dist/review-completion.js +84 -5
- package/dist/review-worker.d.ts +3 -1
- package/dist/review-worker.js +30 -3
- package/dist/state-store.d.ts +16 -0
- package/dist/state-store.js +23 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.js +3 -0
- package/dist/verification.d.ts +7 -1
- package/dist/verification.js +6 -1
- package/dist/worker.js +2 -2
- package/dist/worktree-gc.d.ts +29 -1
- package/dist/worktree-gc.js +108 -1
- package/dist/worktree.d.ts +6 -1
- package/dist/worktree.js +7 -2
- package/package.json +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export interface AgentConfig {
|
|
|
20
20
|
worktree: {
|
|
21
21
|
basePath: string;
|
|
22
22
|
baseBranch: string;
|
|
23
|
+
/** Remote-branch prefix while an attempt is still in-flight or failed. */
|
|
24
|
+
failedBranchPrefix: string;
|
|
25
|
+
/** Remote-branch prefix after a successful run reaches Review. */
|
|
26
|
+
approvedBranchPrefix: string;
|
|
27
|
+
/** Days to keep failed-attempt branches on origin before GC removes them. */
|
|
28
|
+
failedAttemptRetentionDays: number;
|
|
23
29
|
};
|
|
24
30
|
verification: {
|
|
25
31
|
enabled: boolean;
|
|
@@ -98,3 +104,35 @@ export interface RealtimeCredentials {
|
|
|
98
104
|
supabaseUrl: string;
|
|
99
105
|
supabaseAnonKey: string;
|
|
100
106
|
}
|
|
107
|
+
/** Pipeline that produced an episode. */
|
|
108
|
+
export type EpisodeKind = "implement" | "review";
|
|
109
|
+
/** Outcome of an implement run; review verdict maps to its own type. */
|
|
110
|
+
export type EpisodeOutcome = "success" | "failure";
|
|
111
|
+
/**
|
|
112
|
+
* Structured metadata persisted alongside every episode entity in
|
|
113
|
+
* `knowledge_entities.metadata`. Read by the recall path to render the
|
|
114
|
+
* "Similar past tasks" section in subsequent agent prompts.
|
|
115
|
+
*/
|
|
116
|
+
export interface EpisodeMeta {
|
|
117
|
+
episode_kind: EpisodeKind;
|
|
118
|
+
card_short_id: number;
|
|
119
|
+
card_title: string;
|
|
120
|
+
approach_summary: string;
|
|
121
|
+
outcome: EpisodeOutcome;
|
|
122
|
+
quality_score: number;
|
|
123
|
+
duration_ms: number;
|
|
124
|
+
token_cost: {
|
|
125
|
+
input: number;
|
|
126
|
+
output: number;
|
|
127
|
+
usd: number;
|
|
128
|
+
};
|
|
129
|
+
files_touched: number;
|
|
130
|
+
num_turns: number;
|
|
131
|
+
error?: string;
|
|
132
|
+
/** Provenance only — never used as memory scope. */
|
|
133
|
+
agent_session_id?: string;
|
|
134
|
+
/** Set on back-fill from review pipeline. */
|
|
135
|
+
review_session_id?: string;
|
|
136
|
+
/** Set on review-decision entities so back-fill can find the original. */
|
|
137
|
+
original_episode_id?: string;
|
|
138
|
+
}
|
package/dist/types.js
CHANGED
|
@@ -18,6 +18,9 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
18
18
|
worktree: {
|
|
19
19
|
basePath: ".harmony-worktrees",
|
|
20
20
|
baseBranch: "main",
|
|
21
|
+
failedBranchPrefix: "agent-attempts/",
|
|
22
|
+
approvedBranchPrefix: "agent/",
|
|
23
|
+
failedAttemptRetentionDays: 7,
|
|
21
24
|
},
|
|
22
25
|
verification: {
|
|
23
26
|
enabled: true,
|
package/dist/verification.d.ts
CHANGED
|
@@ -12,7 +12,13 @@ export declare function runBuild(worktreePath: string, timeout: number): string[
|
|
|
12
12
|
export declare function runLint(worktreePath: string, timeout: number): string[];
|
|
13
13
|
export declare function runDeepReview(worktreePath: string, config: AgentConfig, workerId: number): Promise<string[]>;
|
|
14
14
|
export declare function attemptAutoFix(worktreePath: string, config: AgentConfig, errors: string[]): void;
|
|
15
|
-
export
|
|
15
|
+
export interface RecoveryInfo {
|
|
16
|
+
/** Remote ref where the failed attempt was pushed. */
|
|
17
|
+
branchName: string;
|
|
18
|
+
/** Public URL of the branch (GitHub/GitLab/Bitbucket tree view), if known. */
|
|
19
|
+
branchUrl: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function reportFindings(client: HarmonyApiClient, cardId: string, result: VerificationResult, recovery?: RecoveryInfo | null): Promise<void>;
|
|
16
22
|
export declare class DevServerReadinessError extends Error {
|
|
17
23
|
constructor(message: string);
|
|
18
24
|
}
|
package/dist/verification.js
CHANGED
|
@@ -160,8 +160,13 @@ export function attemptAutoFix(worktreePath, config, errors) {
|
|
|
160
160
|
stdio: "pipe",
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
-
export async function reportFindings(client, cardId, result) {
|
|
163
|
+
export async function reportFindings(client, cardId, result, recovery) {
|
|
164
164
|
const items = [];
|
|
165
|
+
if (recovery) {
|
|
166
|
+
const cmd = `git fetch && git checkout ${recovery.branchName}`;
|
|
167
|
+
const url = recovery.branchUrl ? ` (${recovery.branchUrl})` : "";
|
|
168
|
+
items.push(`Recovery: \`${cmd}\`${url}`);
|
|
169
|
+
}
|
|
165
170
|
for (const err of result.buildErrors) {
|
|
166
171
|
items.push(`Build: ${err}`);
|
|
167
172
|
}
|
package/dist/worker.js
CHANGED
|
@@ -102,7 +102,7 @@ export class Worker {
|
|
|
102
102
|
try {
|
|
103
103
|
// --- PREPARING ---
|
|
104
104
|
this.state = "preparing";
|
|
105
|
-
this.branchName = makeBranchName(card.short_id, card.title);
|
|
105
|
+
this.branchName = makeBranchName(card.short_id, card.title, this.config.worktree.failedBranchPrefix);
|
|
106
106
|
log.info(this.tag, `Preparing #${card.short_id} "${card.title}"`);
|
|
107
107
|
// Per-card attempt counter resets on success; DLQ triggers off it.
|
|
108
108
|
await this.stateStore.incrementAttempt(card.id);
|
|
@@ -195,7 +195,7 @@ export class Worker {
|
|
|
195
195
|
});
|
|
196
196
|
this.state = "completing";
|
|
197
197
|
await this.recordPhase("completing");
|
|
198
|
-
await runCompletion(this.client, card, this.branchName, this.worktreePath, this.config, this.id, this.lastSessionStats);
|
|
198
|
+
await runCompletion(this.client, card, this.branchName, this.worktreePath, this.config, this.id, this.lastSessionStats, this.workspaceId, this.sessionId, this.stateStore);
|
|
199
199
|
}
|
|
200
200
|
catch (err) {
|
|
201
201
|
this.state = "error";
|
package/dist/worktree-gc.d.ts
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import type { StateStore } from "./state-store.js";
|
|
2
|
+
export interface RemoteBranchGcOptions {
|
|
3
|
+
/** Prefix to scan (e.g. `agent-attempts/`). */
|
|
4
|
+
prefix: string;
|
|
5
|
+
/** Retention in days. Branches older than this are removed. */
|
|
6
|
+
retentionDays: number;
|
|
7
|
+
/** Optional sync clock, for deterministic tests. */
|
|
8
|
+
now?: () => number;
|
|
9
|
+
}
|
|
10
|
+
export interface RemoteBranchGcResult {
|
|
11
|
+
scanned: number;
|
|
12
|
+
removed: string[];
|
|
13
|
+
skipped: string[];
|
|
14
|
+
errors: Array<{
|
|
15
|
+
ref: string;
|
|
16
|
+
error: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
2
19
|
export interface GcResult {
|
|
3
20
|
checked: number;
|
|
4
21
|
removed: string[];
|
|
@@ -27,12 +44,23 @@ export interface GcOptions {
|
|
|
27
44
|
* Returns a summary; callers decide whether to log at info or warn.
|
|
28
45
|
*/
|
|
29
46
|
export declare function runWorktreeGc(basePath: string, store: StateStore, opts?: GcOptions): GcResult;
|
|
47
|
+
/**
|
|
48
|
+
* Sweep stale failed-attempt branches off the remote.
|
|
49
|
+
*
|
|
50
|
+
* Lists `origin/<prefix>*`, asks git for each ref's committer timestamp via
|
|
51
|
+
* `for-each-ref`, and deletes anything older than `retentionDays`. Runs on
|
|
52
|
+
* the same GC tick that handles worktree directories — protecting unpushed
|
|
53
|
+
* commits is the job of the daemon (always push before verify); this sweep
|
|
54
|
+
* just keeps the namespace tidy.
|
|
55
|
+
*/
|
|
56
|
+
export declare function pruneFailedRemoteBranches(opts: RemoteBranchGcOptions): RemoteBranchGcResult;
|
|
30
57
|
export declare class WorktreeGc {
|
|
31
58
|
private basePath;
|
|
32
59
|
private store;
|
|
33
60
|
private intervalMs;
|
|
61
|
+
private remoteOpts?;
|
|
34
62
|
private timer;
|
|
35
|
-
constructor(basePath: string, store: StateStore, intervalMs: number);
|
|
63
|
+
constructor(basePath: string, store: StateStore, intervalMs: number, remoteOpts?: RemoteBranchGcOptions | undefined);
|
|
36
64
|
start(): void;
|
|
37
65
|
stop(): void;
|
|
38
66
|
private tick;
|
package/dist/worktree-gc.js
CHANGED
|
@@ -96,15 +96,114 @@ export function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
96
96
|
}
|
|
97
97
|
return result;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Sweep stale failed-attempt branches off the remote.
|
|
101
|
+
*
|
|
102
|
+
* Lists `origin/<prefix>*`, asks git for each ref's committer timestamp via
|
|
103
|
+
* `for-each-ref`, and deletes anything older than `retentionDays`. Runs on
|
|
104
|
+
* the same GC tick that handles worktree directories — protecting unpushed
|
|
105
|
+
* commits is the job of the daemon (always push before verify); this sweep
|
|
106
|
+
* just keeps the namespace tidy.
|
|
107
|
+
*/
|
|
108
|
+
export function pruneFailedRemoteBranches(opts) {
|
|
109
|
+
const result = {
|
|
110
|
+
scanned: 0,
|
|
111
|
+
removed: [],
|
|
112
|
+
skipped: [],
|
|
113
|
+
errors: [],
|
|
114
|
+
};
|
|
115
|
+
if (!opts.prefix)
|
|
116
|
+
return result;
|
|
117
|
+
// 0 or negative retention is opt-out — caller wants nothing pruned.
|
|
118
|
+
if (!Number.isFinite(opts.retentionDays) || opts.retentionDays <= 0) {
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
const repoRoot = getRepoRoot();
|
|
122
|
+
if (!repoRoot) {
|
|
123
|
+
result.errors.push({ ref: "<repo-root>", error: "not a git repo" });
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
// Refresh the remote refs we know about. Pruned remote branches drop from
|
|
127
|
+
// local tracking, so we never try to delete refs the server already lost.
|
|
128
|
+
try {
|
|
129
|
+
execFileSync("git", ["fetch", "--prune", "origin"], {
|
|
130
|
+
cwd: repoRoot,
|
|
131
|
+
stdio: "pipe",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
result.errors.push({
|
|
136
|
+
ref: "fetch",
|
|
137
|
+
error: err instanceof Error ? err.message : String(err),
|
|
138
|
+
});
|
|
139
|
+
// Continue — for-each-ref may still find usable cached refs.
|
|
140
|
+
}
|
|
141
|
+
const refPattern = `refs/remotes/origin/${opts.prefix}*`;
|
|
142
|
+
let listing = "";
|
|
143
|
+
try {
|
|
144
|
+
listing = execFileSync("git", [
|
|
145
|
+
"for-each-ref",
|
|
146
|
+
"--format=%(refname:strip=3) %(committerdate:unix)",
|
|
147
|
+
refPattern,
|
|
148
|
+
], { cwd: repoRoot, encoding: "utf-8" });
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
result.errors.push({
|
|
152
|
+
ref: refPattern,
|
|
153
|
+
error: err instanceof Error ? err.message : String(err),
|
|
154
|
+
});
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
const cutoffSecs = (opts.now ?? Date.now)() / 1000 - opts.retentionDays * 24 * 60 * 60;
|
|
158
|
+
for (const line of listing.split("\n")) {
|
|
159
|
+
const trimmed = line.trim();
|
|
160
|
+
if (!trimmed)
|
|
161
|
+
continue;
|
|
162
|
+
const sp = trimmed.lastIndexOf(" ");
|
|
163
|
+
if (sp < 0)
|
|
164
|
+
continue;
|
|
165
|
+
const ref = trimmed.slice(0, sp);
|
|
166
|
+
const ts = Number(trimmed.slice(sp + 1));
|
|
167
|
+
result.scanned++;
|
|
168
|
+
if (!Number.isFinite(ts) || ts >= cutoffSecs) {
|
|
169
|
+
result.skipped.push(ref);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
execFileSync("git", ["push", "origin", `:refs/heads/${ref}`], {
|
|
174
|
+
cwd: repoRoot,
|
|
175
|
+
stdio: "pipe",
|
|
176
|
+
});
|
|
177
|
+
result.removed.push(ref);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
result.errors.push({
|
|
181
|
+
ref,
|
|
182
|
+
error: err instanceof Error ? err.message : String(err),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (result.removed.length > 0) {
|
|
187
|
+
log.info(TAG, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
188
|
+
}
|
|
189
|
+
if (result.errors.length > 0) {
|
|
190
|
+
log.warn(TAG, `Remote branch GC had ${result.errors.length} error(s): ${result.errors
|
|
191
|
+
.map((e) => `${e.ref}: ${e.error}`)
|
|
192
|
+
.join("; ")}`);
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
99
196
|
export class WorktreeGc {
|
|
100
197
|
basePath;
|
|
101
198
|
store;
|
|
102
199
|
intervalMs;
|
|
200
|
+
remoteOpts;
|
|
103
201
|
timer = null;
|
|
104
|
-
constructor(basePath, store, intervalMs) {
|
|
202
|
+
constructor(basePath, store, intervalMs, remoteOpts) {
|
|
105
203
|
this.basePath = basePath;
|
|
106
204
|
this.store = store;
|
|
107
205
|
this.intervalMs = intervalMs;
|
|
206
|
+
this.remoteOpts = remoteOpts;
|
|
108
207
|
}
|
|
109
208
|
start() {
|
|
110
209
|
// Run once at startup, then on interval.
|
|
@@ -124,6 +223,14 @@ export class WorktreeGc {
|
|
|
124
223
|
catch (err) {
|
|
125
224
|
log.warn(TAG, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
126
225
|
}
|
|
226
|
+
if (this.remoteOpts) {
|
|
227
|
+
try {
|
|
228
|
+
pruneFailedRemoteBranches(this.remoteOpts);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
log.warn(TAG, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
127
234
|
}
|
|
128
235
|
}
|
|
129
236
|
function getRepoRoot() {
|
package/dist/worktree.d.ts
CHANGED
|
@@ -9,5 +9,10 @@ export declare function createWorktree(basePath: string, baseBranch: string, bra
|
|
|
9
9
|
export declare function cleanupWorktree(worktreePath: string, branchName?: string): void;
|
|
10
10
|
/**
|
|
11
11
|
* Generate a branch name from a card's short ID and title.
|
|
12
|
+
*
|
|
13
|
+
* Agent branches start under the failedBranchPrefix (default `agent-attempts/`)
|
|
14
|
+
* and are renamed to the approvedBranchPrefix (default `agent/`) only after the
|
|
15
|
+
* Review pipeline approves them. Branches under `agent-attempts/` are pruned
|
|
16
|
+
* by the GC after `failedAttemptRetentionDays`.
|
|
12
17
|
*/
|
|
13
|
-
export declare function makeBranchName(shortId: number, title: string): string;
|
|
18
|
+
export declare function makeBranchName(shortId: number, title: string, prefix?: string): string;
|
package/dist/worktree.js
CHANGED
|
@@ -158,8 +158,13 @@ export function cleanupWorktree(worktreePath, branchName) {
|
|
|
158
158
|
}
|
|
159
159
|
/**
|
|
160
160
|
* Generate a branch name from a card's short ID and title.
|
|
161
|
+
*
|
|
162
|
+
* Agent branches start under the failedBranchPrefix (default `agent-attempts/`)
|
|
163
|
+
* and are renamed to the approvedBranchPrefix (default `agent/`) only after the
|
|
164
|
+
* Review pipeline approves them. Branches under `agent-attempts/` are pruned
|
|
165
|
+
* by the GC after `failedAttemptRetentionDays`.
|
|
161
166
|
*/
|
|
162
|
-
export function makeBranchName(shortId, title) {
|
|
167
|
+
export function makeBranchName(shortId, title, prefix = "agent-attempts/") {
|
|
163
168
|
const slug = title
|
|
164
169
|
.toLowerCase()
|
|
165
170
|
.trim()
|
|
@@ -168,5 +173,5 @@ export function makeBranchName(shortId, title) {
|
|
|
168
173
|
.replace(/-+/g, "-")
|
|
169
174
|
.replace(/^-+|-+$/g, "")
|
|
170
175
|
.slice(0, 40);
|
|
171
|
-
return
|
|
176
|
+
return `${prefix}${shortId}-${slug || "task"}`;
|
|
172
177
|
}
|