harnery 0.5.0 → 0.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/commander.d.ts +10 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +43 -29
- package/dist/commands/browse-ai.js +1 -1
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +41 -9
- package/dist/commands/cookies.js +1 -1
- package/dist/commands/decision.d.ts +4 -0
- package/dist/commands/decision.d.ts.map +1 -0
- package/dist/commands/decision.js +354 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +5 -1
- package/dist/commands/fetch.js +1 -1
- package/dist/core/agents/events/consume.d.ts +25 -2
- package/dist/core/agents/events/consume.d.ts.map +1 -1
- package/dist/core/agents/events/consume.js +55 -7
- package/dist/core/agents/events/emit.d.ts +2 -1
- package/dist/core/agents/events/emit.d.ts.map +1 -1
- package/dist/core/agents/events/emit.js +2 -1
- package/dist/core/agents/state/scratch.d.ts +1 -1
- package/dist/core/agents/state/scratch.js +2 -2
- package/dist/core/hooks/effects/index.d.ts.map +1 -1
- package/dist/core/hooks/effects/index.js +2 -1
- package/dist/lib/agent-browser/client.js +1 -1
- package/dist/lib/browser/client.d.ts +14 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +20 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/runts.d.ts +44 -0
- package/dist/lib/browser/runts.d.ts.map +1 -0
- package/dist/lib/browser/runts.js +193 -0
- package/dist/lib/completion/walk.js +1 -1
- package/dist/lib/cookies/client.d.ts +1 -1
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +1 -1
- package/dist/lib/decision/index.d.ts +212 -0
- package/dist/lib/decision/index.d.ts.map +1 -0
- package/dist/lib/decision/index.js +523 -0
- package/dist/lib/docs-lint.d.ts +1 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +49 -0
- package/dist/lib/tunnel/gate.js +1 -1
- package/package.json +3 -1
- package/src/commander.ts +12 -0
- package/src/commands/agents.ts +47 -26
- package/src/commands/browse-ai.ts +1 -1
- package/src/commands/browse.ts +63 -8
- package/src/commands/cookies.ts +1 -1
- package/src/commands/decision.ts +438 -0
- package/src/commands/docs.ts +5 -1
- package/src/commands/fetch.ts +1 -1
- package/src/core/agents/events/consume.ts +65 -7
- package/src/core/agents/events/emit.ts +2 -1
- package/src/core/agents/state/scratch.ts +2 -2
- package/src/core/config.ts +1 -1
- package/src/core/hooks/effects/index.ts +2 -1
- package/src/lib/agent-browser/client.ts +1 -1
- package/src/lib/browser/client.ts +28 -0
- package/src/lib/browser/index.ts +4 -0
- package/src/lib/browser/runts.ts +218 -0
- package/src/lib/completion/walk.ts +1 -1
- package/src/lib/cookies/client.ts +2 -2
- package/src/lib/decision/index.ts +685 -0
- package/src/lib/docs-lint.ts +44 -0
- package/src/lib/tunnel/gate.ts +1 -1
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { emitCanonical, monorepoRoot, normalizeHarness, readHeartbeat, resolveOwner, } from "../core/agents/index.js";
|
|
3
|
+
import { resolveBinName } from "../core/config.js";
|
|
4
|
+
import { archiveDecision, claimDecision, DECISION_STAKES, fileDecision, isStakes, isStatus, isTier, isVerdict, listDecisions, reopenDecision, resolveDecision, reviewDecision, searchDecisions, showDecision, supersedeDecision, triageDecision, wontfixDecision, } from "../lib/decision/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* `harn decision`: the decision docket — a persistent queue of decisions an
|
|
7
|
+
* agent would otherwise route to a human, carried through triage → deliberation
|
|
8
|
+
* → an evidence-cited resolution → async review.
|
|
9
|
+
*
|
|
10
|
+
* The engine is generic: it stores `tier` (0/1/2) + `stakes` but never decides
|
|
11
|
+
* what belongs in which tier — that's host policy, applied by the filing agent
|
|
12
|
+
* (for a host, via its own decision skill + rubric).
|
|
13
|
+
*/
|
|
14
|
+
let emit;
|
|
15
|
+
export function registerDecisionCommand(program, emitParam) {
|
|
16
|
+
emit = emitParam;
|
|
17
|
+
const bin = () => resolveBinName();
|
|
18
|
+
const root = program
|
|
19
|
+
.command("decision")
|
|
20
|
+
.alias("decisions")
|
|
21
|
+
.description("Decision docket: file a decision an agent would otherwise escalate, " +
|
|
22
|
+
"deliberate it, resolve with cited evidence, review async.");
|
|
23
|
+
// ── file ─────────────────────────────────────────────────────────────────
|
|
24
|
+
root
|
|
25
|
+
.command("file <question...>")
|
|
26
|
+
.description("File a decision into the docket. Tier/stakes are the filer's triage call.")
|
|
27
|
+
.option("--tier <0|1|2>", "Human-involvement tier (0 none, 1 review, 2 decide-with-brief)", "2")
|
|
28
|
+
.option("--stakes <small|medium|high>", "Reversal cost / blast radius", "medium")
|
|
29
|
+
.option("--context <text>", "Why it matters, what's blocked")
|
|
30
|
+
.option("--default-taken <text>", "What you proceeded with (always-proceed, tier 0/1)")
|
|
31
|
+
.option("--brief <path>", "Path to a markdown file with the long-form brief")
|
|
32
|
+
.option("--filed-by <name>", "Filer agent name (else resolved from heartbeat)")
|
|
33
|
+
.action((question, opts) => {
|
|
34
|
+
const coordRoot = coordRootOrExit();
|
|
35
|
+
const tier = parseTier(opts.tier);
|
|
36
|
+
const stakes = parseStakes(opts.stakes);
|
|
37
|
+
let brief;
|
|
38
|
+
if (opts.brief) {
|
|
39
|
+
if (!existsSync(opts.brief)) {
|
|
40
|
+
emit.error({ code: "no_brief_file", message: `brief file not found: ${opts.brief}` });
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
brief = readFileSync(opts.brief, "utf8");
|
|
44
|
+
}
|
|
45
|
+
const owner = resolveOwner();
|
|
46
|
+
const hb = owner ? readHeartbeat(owner) : null;
|
|
47
|
+
const r = fileDecision(coordRoot, {
|
|
48
|
+
question: question.join(" "),
|
|
49
|
+
tier,
|
|
50
|
+
stakes,
|
|
51
|
+
context: opts.context,
|
|
52
|
+
defaultTaken: opts.defaultTaken,
|
|
53
|
+
brief,
|
|
54
|
+
filedBy: opts.filedBy ?? hb?.name ?? undefined,
|
|
55
|
+
filedById: owner ?? undefined,
|
|
56
|
+
});
|
|
57
|
+
if (!r.ok)
|
|
58
|
+
return fail("file_failed", r.reason);
|
|
59
|
+
emitDecisionEvent("decision.filed", {
|
|
60
|
+
decision_id: r.manifest.decision_id,
|
|
61
|
+
tier,
|
|
62
|
+
stakes,
|
|
63
|
+
});
|
|
64
|
+
emit.data(r.manifest);
|
|
65
|
+
});
|
|
66
|
+
// ── list ─────────────────────────────────────────────────────────────────
|
|
67
|
+
root
|
|
68
|
+
.command("list")
|
|
69
|
+
.description("List docket decisions (default: active only, newest first).")
|
|
70
|
+
.option("--status <status>", "Filter by status")
|
|
71
|
+
.option("--tier <0|1|2>", "Filter by tier")
|
|
72
|
+
.option("--stakes <small|medium|high>", "Filter by stakes")
|
|
73
|
+
.option("--open", "Only non-terminal decisions (the live queue)")
|
|
74
|
+
.option("--archived", "Include archived (graduated/terminal) decisions")
|
|
75
|
+
.action((opts) => {
|
|
76
|
+
const coordRoot = coordRootOrExit();
|
|
77
|
+
const status = opts.status ? parseStatus(opts.status) : undefined;
|
|
78
|
+
const tier = opts.tier !== undefined ? parseTier(opts.tier) : undefined;
|
|
79
|
+
const stakes = opts.stakes ? parseStakes(opts.stakes) : undefined;
|
|
80
|
+
const rows = listDecisions(coordRoot, {
|
|
81
|
+
status,
|
|
82
|
+
tier,
|
|
83
|
+
stakes,
|
|
84
|
+
openOnly: opts.open,
|
|
85
|
+
includeArchived: opts.archived,
|
|
86
|
+
}).map(summarize);
|
|
87
|
+
emit.data({
|
|
88
|
+
rows,
|
|
89
|
+
meta: {
|
|
90
|
+
total: rows.length,
|
|
91
|
+
filter: { status, tier, stakes, open: !!opts.open, archived: !!opts.archived },
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
// ── show ─────────────────────────────────────────────────────────────────
|
|
96
|
+
root
|
|
97
|
+
.command("show <id>")
|
|
98
|
+
.description("Show one decision: manifest + long-form bodies.")
|
|
99
|
+
.action((id) => {
|
|
100
|
+
const coordRoot = coordRootOrExit();
|
|
101
|
+
const detail = showDecision(coordRoot, id);
|
|
102
|
+
if (!detail)
|
|
103
|
+
return fail("not_found", `no decision "${id}"`);
|
|
104
|
+
emit.data(detail);
|
|
105
|
+
});
|
|
106
|
+
// ── search ───────────────────────────────────────────────────────────────
|
|
107
|
+
root
|
|
108
|
+
.command("search <query...>")
|
|
109
|
+
.description("Substring search over questions, context, resolutions, bodies (incl. archive).")
|
|
110
|
+
.action((query) => {
|
|
111
|
+
const coordRoot = coordRootOrExit();
|
|
112
|
+
const hits = searchDecisions(coordRoot, query.join(" ")).map((h) => ({
|
|
113
|
+
...summarize(h.manifest),
|
|
114
|
+
where: h.where,
|
|
115
|
+
snippet: h.snippet,
|
|
116
|
+
}));
|
|
117
|
+
emit.data({ rows: hits, meta: { total: hits.length, query: query.join(" ") } });
|
|
118
|
+
});
|
|
119
|
+
// ── claim ────────────────────────────────────────────────────────────────
|
|
120
|
+
root
|
|
121
|
+
.command("claim <id>")
|
|
122
|
+
.description("Claim a decision for deliberation (last-writer-wins).")
|
|
123
|
+
.option("--owner <id>", "Claim as this owner (else the current agent)")
|
|
124
|
+
.action((id, opts) => {
|
|
125
|
+
const coordRoot = coordRootOrExit();
|
|
126
|
+
const owner = opts.owner ?? resolveOwner();
|
|
127
|
+
if (!owner) {
|
|
128
|
+
return fail("no_owner", `not in an agent session; pass --owner or run \`${bin()} agents whoami\` to check`);
|
|
129
|
+
}
|
|
130
|
+
const r = claimDecision(coordRoot, id, owner);
|
|
131
|
+
if (!r.ok)
|
|
132
|
+
return fail("claim_failed", r.reason);
|
|
133
|
+
emit.data(r.manifest);
|
|
134
|
+
});
|
|
135
|
+
// ── resolve ──────────────────────────────────────────────────────────────
|
|
136
|
+
root
|
|
137
|
+
.command("resolve <id>")
|
|
138
|
+
.description("Resolve a decision. Evidence (≥1 citation) is required.")
|
|
139
|
+
.requiredOption("--recommendation <text>", "The recommendation")
|
|
140
|
+
.option("--evidence <text>", "A cited fact (query run, file read, cost computed). Repeatable; ≥1 required.", collect, [])
|
|
141
|
+
.option("--confidence <text>", "Confidence in the recommendation")
|
|
142
|
+
.option("--reversal-cost <text>", "Cost to reverse if wrong")
|
|
143
|
+
.option("--wrong-if <text>", "What would make this wrong (pre-mortem)")
|
|
144
|
+
.option("--revisit-when <text>", "Revisit trigger")
|
|
145
|
+
.option("--resolved-by <name>", "Resolver (else the current agent)")
|
|
146
|
+
.action((id, opts) => {
|
|
147
|
+
const coordRoot = coordRootOrExit();
|
|
148
|
+
const owner = resolveOwner();
|
|
149
|
+
const resolvedBy = opts.resolvedBy ?? (owner ? readHeartbeat(owner)?.name : undefined);
|
|
150
|
+
if (!resolvedBy) {
|
|
151
|
+
return fail("no_resolver", "pass --resolved-by (no agent session to infer it from)");
|
|
152
|
+
}
|
|
153
|
+
const r = resolveDecision(coordRoot, id, {
|
|
154
|
+
recommendation: opts.recommendation,
|
|
155
|
+
evidence: opts.evidence,
|
|
156
|
+
confidence: opts.confidence,
|
|
157
|
+
reversal_cost: opts.reversalCost,
|
|
158
|
+
wrong_if: opts.wrongIf,
|
|
159
|
+
revisit_when: opts.revisitWhen,
|
|
160
|
+
resolved_by: resolvedBy,
|
|
161
|
+
});
|
|
162
|
+
if (!r.ok)
|
|
163
|
+
return fail("resolve_failed", r.reason);
|
|
164
|
+
emitDecisionEvent("decision.resolved", {
|
|
165
|
+
decision_id: id,
|
|
166
|
+
tier: r.manifest.tier,
|
|
167
|
+
stakes: r.manifest.stakes,
|
|
168
|
+
confidence: opts.confidence ?? null,
|
|
169
|
+
});
|
|
170
|
+
emit.data(r.manifest);
|
|
171
|
+
});
|
|
172
|
+
// ── review ───────────────────────────────────────────────────────────────
|
|
173
|
+
root
|
|
174
|
+
.command("review <id>")
|
|
175
|
+
.description("Record a review verdict (calibration, not approval — work already proceeded).")
|
|
176
|
+
.requiredOption("--verdict <verdict>", "ratified | overridden | wrong-tier-high | wrong-tier-low")
|
|
177
|
+
.option("--note <text>", "Reviewer note")
|
|
178
|
+
.action((id, opts) => {
|
|
179
|
+
const coordRoot = coordRootOrExit();
|
|
180
|
+
if (!isVerdict(opts.verdict)) {
|
|
181
|
+
return fail("bad_verdict", "verdict must be: ratified | overridden | wrong-tier-high | wrong-tier-low");
|
|
182
|
+
}
|
|
183
|
+
const r = reviewDecision(coordRoot, id, {
|
|
184
|
+
verdict: opts.verdict,
|
|
185
|
+
note: opts.note,
|
|
186
|
+
});
|
|
187
|
+
if (!r.ok)
|
|
188
|
+
return fail("review_failed", r.reason);
|
|
189
|
+
emitDecisionEvent("decision.reviewed", {
|
|
190
|
+
decision_id: id,
|
|
191
|
+
verdict: opts.verdict,
|
|
192
|
+
tier: r.manifest.tier,
|
|
193
|
+
});
|
|
194
|
+
emit.data(r.manifest);
|
|
195
|
+
});
|
|
196
|
+
// ── triage ─────────────────────────────────────────────────────────────────
|
|
197
|
+
// Re-set tier/stakes on an already-filed decision (e.g. after a sweeper or
|
|
198
|
+
// reviewer flags a wrong-tier). Cheap enough to surface in phase 1.
|
|
199
|
+
root
|
|
200
|
+
.command("triage <id>")
|
|
201
|
+
.description("Adjust an already-filed decision's tier/stakes.")
|
|
202
|
+
.option("--tier <0|1|2>", "New tier")
|
|
203
|
+
.option("--stakes <small|medium|high>", "New stakes")
|
|
204
|
+
.action((id, opts) => {
|
|
205
|
+
const coordRoot = coordRootOrExit();
|
|
206
|
+
if (opts.tier === undefined && opts.stakes === undefined) {
|
|
207
|
+
return fail("nothing_to_do", "pass --tier and/or --stakes");
|
|
208
|
+
}
|
|
209
|
+
const r = triageDecision(coordRoot, id, {
|
|
210
|
+
tier: opts.tier !== undefined ? parseTier(opts.tier) : undefined,
|
|
211
|
+
stakes: opts.stakes !== undefined ? parseStakes(opts.stakes) : undefined,
|
|
212
|
+
});
|
|
213
|
+
if (!r.ok)
|
|
214
|
+
return fail("triage_failed", r.reason);
|
|
215
|
+
emit.data(r.manifest);
|
|
216
|
+
});
|
|
217
|
+
// ── archive ─────────────────────────────────────────────────────────────────
|
|
218
|
+
// The graduation exit: a reviewed decision's output has landed in a canonical
|
|
219
|
+
// home (an ADR, AGENTS.md, a code change), so the decision closes and moves to
|
|
220
|
+
// the archive, still searchable as precedent. `--graduated-to` records where.
|
|
221
|
+
root
|
|
222
|
+
.command("archive <id>")
|
|
223
|
+
.description("Archive a reviewed decision (terminal). Record where its output graduated.")
|
|
224
|
+
.option("--graduated-to <ref>", "Where the resolved output landed (e.g. docs/decisions.md#foo)")
|
|
225
|
+
.action((id, opts) => {
|
|
226
|
+
const coordRoot = coordRootOrExit();
|
|
227
|
+
const r = archiveDecision(coordRoot, id, opts.graduatedTo);
|
|
228
|
+
if (!r.ok)
|
|
229
|
+
return fail("archive_failed", r.reason);
|
|
230
|
+
emitDecisionEvent("decision.archived", {
|
|
231
|
+
decision_id: id,
|
|
232
|
+
tier: r.manifest.tier,
|
|
233
|
+
graduated_to: opts.graduatedTo ?? null,
|
|
234
|
+
});
|
|
235
|
+
emit.data(r.manifest);
|
|
236
|
+
});
|
|
237
|
+
// ── reopen ───────────────────────────────────────────────────────────────────
|
|
238
|
+
// The inverse of archive: pull an archived decision back to `reviewed`. The one
|
|
239
|
+
// sanctioned way out of the terminal archive (e.g. a fat-fingered graduated_to
|
|
240
|
+
// or the wrong decision archived). Clears graduated_to; a re-archive sets it fresh.
|
|
241
|
+
root
|
|
242
|
+
.command("reopen <id>")
|
|
243
|
+
.alias("unarchive")
|
|
244
|
+
.description("Reopen an archived decision back to reviewed (the inverse of archive).")
|
|
245
|
+
.action((id) => {
|
|
246
|
+
const coordRoot = coordRootOrExit();
|
|
247
|
+
const r = reopenDecision(coordRoot, id);
|
|
248
|
+
if (!r.ok)
|
|
249
|
+
return fail("reopen_failed", r.reason);
|
|
250
|
+
emitDecisionEvent("decision.reopened", { decision_id: id, tier: r.manifest.tier });
|
|
251
|
+
emit.data(r.manifest);
|
|
252
|
+
});
|
|
253
|
+
// ── supersede ────────────────────────────────────────────────────────────────
|
|
254
|
+
root
|
|
255
|
+
.command("supersede <id>")
|
|
256
|
+
.description("Mark a decision superseded by a newer one (terminal).")
|
|
257
|
+
.option("--by <id>", "The superseding decision's id")
|
|
258
|
+
.action((id, opts) => {
|
|
259
|
+
const coordRoot = coordRootOrExit();
|
|
260
|
+
const r = supersedeDecision(coordRoot, id, opts.by);
|
|
261
|
+
if (!r.ok)
|
|
262
|
+
return fail("supersede_failed", r.reason);
|
|
263
|
+
emitDecisionEvent("decision.superseded", {
|
|
264
|
+
decision_id: id,
|
|
265
|
+
superseded_by: opts.by ?? null,
|
|
266
|
+
});
|
|
267
|
+
emit.data(r.manifest);
|
|
268
|
+
});
|
|
269
|
+
// ── wontfix ──────────────────────────────────────────────────────────────────
|
|
270
|
+
root
|
|
271
|
+
.command("wontfix <id>")
|
|
272
|
+
.description("Close an un-deliberated decision without action (terminal).")
|
|
273
|
+
.option("--reason <text>", "Why it's being closed")
|
|
274
|
+
.action((id, opts) => {
|
|
275
|
+
const coordRoot = coordRootOrExit();
|
|
276
|
+
const r = wontfixDecision(coordRoot, id, opts.reason);
|
|
277
|
+
if (!r.ok)
|
|
278
|
+
return fail("wontfix_failed", r.reason);
|
|
279
|
+
emitDecisionEvent("decision.wontfix", {
|
|
280
|
+
decision_id: id,
|
|
281
|
+
reason: opts.reason ?? null,
|
|
282
|
+
});
|
|
283
|
+
emit.data(r.manifest);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
// ─── helpers ───────────────────────────────────────────────────────────────────
|
|
287
|
+
function coordRootOrExit() {
|
|
288
|
+
const root = monorepoRoot();
|
|
289
|
+
if (!root) {
|
|
290
|
+
emit.error({
|
|
291
|
+
code: "no_coord_root",
|
|
292
|
+
message: "not in a coord-aware repo (no .harnery/ found)",
|
|
293
|
+
});
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
return root;
|
|
297
|
+
}
|
|
298
|
+
function fail(code, message) {
|
|
299
|
+
emit.error({ code, message: message ?? code });
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
function parseTier(raw) {
|
|
303
|
+
const n = Number.parseInt(raw, 10);
|
|
304
|
+
if (!isTier(n))
|
|
305
|
+
fail("bad_tier", `tier must be 0, 1, or 2 (got "${raw}")`);
|
|
306
|
+
return n;
|
|
307
|
+
}
|
|
308
|
+
function parseStakes(raw) {
|
|
309
|
+
if (!isStakes(raw))
|
|
310
|
+
fail("bad_stakes", `stakes must be ${DECISION_STAKES.join(" | ")} (got "${raw}")`);
|
|
311
|
+
return raw;
|
|
312
|
+
}
|
|
313
|
+
function parseStatus(raw) {
|
|
314
|
+
if (!isStatus(raw))
|
|
315
|
+
fail("bad_status", `unknown status "${raw}"`);
|
|
316
|
+
return raw;
|
|
317
|
+
}
|
|
318
|
+
function collect(value, previous) {
|
|
319
|
+
return previous.concat([value]);
|
|
320
|
+
}
|
|
321
|
+
/** Compact row for list/search output — the full manifest is available via `show`. */
|
|
322
|
+
function summarize(m) {
|
|
323
|
+
return {
|
|
324
|
+
decision_id: m.decision_id,
|
|
325
|
+
status: m.status,
|
|
326
|
+
tier: m.tier,
|
|
327
|
+
stakes: m.stakes,
|
|
328
|
+
question: m.question,
|
|
329
|
+
filed_by: m.filed_by ?? null,
|
|
330
|
+
filed_at: m.filed_at,
|
|
331
|
+
claimed_by: m.claimed_by ?? null,
|
|
332
|
+
resolved: !!m.resolution,
|
|
333
|
+
reviewed: !!m.review,
|
|
334
|
+
graduated_to: m.graduated_to ?? null,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Emit a canonical `decision.*` event. Soft: no-ops when there's no agent
|
|
339
|
+
* session to attribute it to (operator-side filing). Powers the docket metrics
|
|
340
|
+
* without new telemetry plumbing.
|
|
341
|
+
*/
|
|
342
|
+
function emitDecisionEvent(type, data) {
|
|
343
|
+
const owner = resolveOwner();
|
|
344
|
+
if (!owner)
|
|
345
|
+
return;
|
|
346
|
+
const hb = readHeartbeat(owner);
|
|
347
|
+
emitCanonical({
|
|
348
|
+
type,
|
|
349
|
+
owner,
|
|
350
|
+
session: hb?.session_id ?? owner,
|
|
351
|
+
harness: normalizeHarness(hb?.platform),
|
|
352
|
+
data,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/commands/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/commands/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AA2B1E,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA+EN"}
|
package/dist/commands/docs.js
CHANGED
|
@@ -9,7 +9,11 @@ function ensureContext(context) {
|
|
|
9
9
|
const opts = { repoRoot: context.repoRoot, submodules: context.submodules };
|
|
10
10
|
initDocs(opts);
|
|
11
11
|
initDocsIndex(opts);
|
|
12
|
-
initDocsLint({
|
|
12
|
+
initDocsLint({
|
|
13
|
+
...opts,
|
|
14
|
+
extraExcludedPrefixes: context.extraDocsExcludedPrefixes,
|
|
15
|
+
docsRootAllowlist: context.docsRootAllowlist,
|
|
16
|
+
});
|
|
13
17
|
initDocsSweep(opts);
|
|
14
18
|
}
|
|
15
19
|
let emit;
|
package/dist/commands/fetch.js
CHANGED
|
@@ -48,7 +48,7 @@ async function runFetch(url, opts, emit, context) {
|
|
|
48
48
|
headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
|
|
49
49
|
}
|
|
50
50
|
const jar = opts.cookies !== false
|
|
51
|
-
? new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "
|
|
51
|
+
? new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-fetch" })
|
|
52
52
|
: null;
|
|
53
53
|
const timeoutMs = Number.parseInt(opts.timeout, 10);
|
|
54
54
|
const ac = new AbortController();
|
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
* with a bounded **tail read** (reading only the last `tailBytes` of the file
|
|
12
12
|
* and locating the cursor there) which turns an O(file-size) read into
|
|
13
13
|
* O(window). If the cursor is older than the window (a long-idle system, or a
|
|
14
|
-
* cursor that's been rotated out) we fall back to a
|
|
15
|
-
*
|
|
14
|
+
* cursor that's been rotated out) we fall back to a wider read that is itself
|
|
15
|
+
* capped (`fallbackCapBytes`): the stream is an append-only ledger that grows
|
|
16
|
+
* without bound, so a whole-file `readFileSync` throws V8's max-string-length
|
|
17
|
+
* error ("Cannot create a string longer than 0x1fffffe8 characters") once it
|
|
18
|
+
* passes ~512MB, which would abort the projection. Events older than the cap are
|
|
19
|
+
* stale for coord-state purposes, so a bounded replay is the correct fallback.
|
|
16
20
|
*
|
|
17
21
|
* Idempotency: projectors are idempotent by `event_id`. The
|
|
18
22
|
* cursor file makes this cheap (we never replay an already-projected event by
|
|
@@ -49,11 +53,30 @@ export interface ConsumeOpts {
|
|
|
49
53
|
* callers should leave it unset.
|
|
50
54
|
*/
|
|
51
55
|
tailBytes?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Cap in bytes for the fall-through read (cursor missed the tail window).
|
|
58
|
+
* Defaults to `HARNERY_AGENT_COORD_FALLBACK_CAP_BYTES` env or 64 MiB. Bounds
|
|
59
|
+
* the read so the unbounded ledger can never overflow V8's max string length.
|
|
60
|
+
* Mainly a test seam; production callers should leave it unset.
|
|
61
|
+
*/
|
|
62
|
+
fallbackCapBytes?: number;
|
|
52
63
|
}
|
|
53
64
|
/**
|
|
54
65
|
* Read all events after the persisted cursor, in chronological (file) order.
|
|
55
66
|
*/
|
|
56
67
|
export declare function consumeSince(coordRoot: string, opts?: ConsumeOpts): ConsumeResult;
|
|
68
|
+
/**
|
|
69
|
+
* Read at most `capBytes` from the tail of the event stream as UTF-8, dropping
|
|
70
|
+
* the (partial) leading line when the file is larger than the cap. Shared
|
|
71
|
+
* bounded reader for CLI consumers (`agents trace` / `agents health`) that must
|
|
72
|
+
* never `readFileSync` the whole unbounded ledger — a >512MB read throws V8's
|
|
73
|
+
* max-string-length error. `truncated` is true when older bytes were skipped, so
|
|
74
|
+
* callers can surface the cap rather than silently under-reporting.
|
|
75
|
+
*/
|
|
76
|
+
export declare function readStreamTailBounded(streamPath: string, capBytes: number): {
|
|
77
|
+
text: string;
|
|
78
|
+
truncated: boolean;
|
|
79
|
+
};
|
|
57
80
|
export declare function readCursor(coordRoot: string): string | null;
|
|
58
81
|
export declare function writeCursor(coordRoot: string, eventId: string): void;
|
|
59
82
|
//# sourceMappingURL=consume.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consume.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/events/consume.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"consume.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/events/consume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAwBH,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,oEAAoE;IACpE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,aAAa,CAuDrF;AAgBD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAWtC;AAiDD,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS3D;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQpE"}
|
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
* with a bounded **tail read** (reading only the last `tailBytes` of the file
|
|
12
12
|
* and locating the cursor there) which turns an O(file-size) read into
|
|
13
13
|
* O(window). If the cursor is older than the window (a long-idle system, or a
|
|
14
|
-
* cursor that's been rotated out) we fall back to a
|
|
15
|
-
*
|
|
14
|
+
* cursor that's been rotated out) we fall back to a wider read that is itself
|
|
15
|
+
* capped (`fallbackCapBytes`): the stream is an append-only ledger that grows
|
|
16
|
+
* without bound, so a whole-file `readFileSync` throws V8's max-string-length
|
|
17
|
+
* error ("Cannot create a string longer than 0x1fffffe8 characters") once it
|
|
18
|
+
* passes ~512MB, which would abort the projection. Events older than the cap are
|
|
19
|
+
* stale for coord-state purposes, so a bounded replay is the correct fallback.
|
|
16
20
|
*
|
|
17
21
|
* Idempotency: projectors are idempotent by `event_id`. The
|
|
18
22
|
* cursor file makes this cheap (we never replay an already-projected event by
|
|
@@ -25,6 +29,11 @@ import { coordEnv } from "../../../lib/env.js";
|
|
|
25
29
|
const STREAM_REL = ".harnery/events.ndjson";
|
|
26
30
|
const CURSOR_REL = ".harnery/.events-cursor";
|
|
27
31
|
const DEFAULT_TAIL_BYTES = 2 * 1024 * 1024; // 2 MiB, thousands of events of headroom
|
|
32
|
+
/** Cap for the fall-through read when the cursor misses the tail window. Well
|
|
33
|
+
* under V8's ~512MB max string length so it never throws on the unbounded
|
|
34
|
+
* ledger; comfortably larger than any realistic cursor drift (the global cursor
|
|
35
|
+
* advances on every agent's turn.stop, so it sits near EOF in practice). */
|
|
36
|
+
const DEFAULT_FALLBACK_CAP_BYTES = 64 * 1024 * 1024; // 64 MiB
|
|
28
37
|
/**
|
|
29
38
|
* Read all events after the persisted cursor, in chronological (file) order.
|
|
30
39
|
*/
|
|
@@ -54,8 +63,19 @@ export function consumeSince(coordRoot, opts = {}) {
|
|
|
54
63
|
}
|
|
55
64
|
// Cursor older than the tail window → fall through to a full read.
|
|
56
65
|
}
|
|
57
|
-
//
|
|
58
|
-
|
|
66
|
+
// Bounded fall-through: first run, replayAll, small file, or cursor not found
|
|
67
|
+
// in the tail. This must NEVER read the whole file: the stream grows without
|
|
68
|
+
// bound and a >512MB readFileSync throws V8's max-string-length error, which
|
|
69
|
+
// would abort the projection. Read at most `cap` bytes from the tail; when we
|
|
70
|
+
// start mid-file, drop the (likely partial) first line.
|
|
71
|
+
const cap = resolveFallbackCap(opts.fallbackCapBytes);
|
|
72
|
+
const readBytes = Math.min(fileSize, cap);
|
|
73
|
+
let text = readTailUtf8(streamPath, fileSize, readBytes);
|
|
74
|
+
if (readBytes < fileSize) {
|
|
75
|
+
const firstNl = text.indexOf("\n");
|
|
76
|
+
text = firstNl >= 0 ? text.slice(firstNl + 1) : "";
|
|
77
|
+
}
|
|
78
|
+
const all = parseLines(text);
|
|
59
79
|
const parsed = eventsAfterCursor(all, cursor);
|
|
60
80
|
if (parsed.foundCursor) {
|
|
61
81
|
return {
|
|
@@ -64,11 +84,18 @@ export function consumeSince(coordRoot, opts = {}) {
|
|
|
64
84
|
streamBytes: fileSize,
|
|
65
85
|
};
|
|
66
86
|
}
|
|
67
|
-
// The cursor names an event
|
|
68
|
-
//
|
|
69
|
-
// event_id).
|
|
87
|
+
// The cursor names an event older than the capped window (or rotated out), so
|
|
88
|
+
// replay what the window holds (safer than silent state drift; the projector
|
|
89
|
+
// is idempotent by event_id). lastEventId null keeps the cursor put.
|
|
70
90
|
return { events: all, lastEventId: null, streamBytes: fileSize };
|
|
71
91
|
}
|
|
92
|
+
function resolveFallbackCap(override) {
|
|
93
|
+
if (override !== undefined && override > 0)
|
|
94
|
+
return override;
|
|
95
|
+
const env = coordEnv("AGENT_COORD_FALLBACK_CAP_BYTES");
|
|
96
|
+
const n = env ? Number(env) : Number.NaN;
|
|
97
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_FALLBACK_CAP_BYTES;
|
|
98
|
+
}
|
|
72
99
|
function resolveTailBytes(override) {
|
|
73
100
|
if (override !== undefined && override > 0)
|
|
74
101
|
return override;
|
|
@@ -76,6 +103,27 @@ function resolveTailBytes(override) {
|
|
|
76
103
|
const n = env ? Number(env) : Number.NaN;
|
|
77
104
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_TAIL_BYTES;
|
|
78
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Read at most `capBytes` from the tail of the event stream as UTF-8, dropping
|
|
108
|
+
* the (partial) leading line when the file is larger than the cap. Shared
|
|
109
|
+
* bounded reader for CLI consumers (`agents trace` / `agents health`) that must
|
|
110
|
+
* never `readFileSync` the whole unbounded ledger — a >512MB read throws V8's
|
|
111
|
+
* max-string-length error. `truncated` is true when older bytes were skipped, so
|
|
112
|
+
* callers can surface the cap rather than silently under-reporting.
|
|
113
|
+
*/
|
|
114
|
+
export function readStreamTailBounded(streamPath, capBytes) {
|
|
115
|
+
if (!existsSync(streamPath))
|
|
116
|
+
return { text: "", truncated: false };
|
|
117
|
+
const fileSize = statSync(streamPath).size;
|
|
118
|
+
const readBytes = Math.min(fileSize, capBytes);
|
|
119
|
+
let text = readTailUtf8(streamPath, fileSize, readBytes);
|
|
120
|
+
const truncated = readBytes < fileSize;
|
|
121
|
+
if (truncated) {
|
|
122
|
+
const nl = text.indexOf("\n");
|
|
123
|
+
text = nl >= 0 ? text.slice(nl + 1) : "";
|
|
124
|
+
}
|
|
125
|
+
return { text, truncated };
|
|
126
|
+
}
|
|
79
127
|
/** Read the trailing `windowBytes` of a file as UTF-8 without loading the rest. */
|
|
80
128
|
function readTailUtf8(streamPath, fileSize, windowBytes) {
|
|
81
129
|
const start = Math.max(0, fileSize - windowBytes);
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* event/schema types).
|
|
6
6
|
*
|
|
7
7
|
* Phase 4: agent-coord uses this from CLI handlers (state.task_set,
|
|
8
|
-
* state.status_checked, state.scratch_append, council.*,
|
|
8
|
+
* state.status_checked, state.scratch_append, council.*, decision.*,
|
|
9
|
+
* presence.*).
|
|
9
10
|
*/
|
|
10
11
|
declare const SCHEMA_VERSION: 1;
|
|
11
12
|
export type Harness = "claude-code" | "cursor" | "codex";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/events/emit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/events/emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,QAAA,MAAM,cAAc,EAAG,CAAU,CAAC;AAKlC,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,MAAM,GAAG,aAAa,GAAG,aAAa,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvE,MAAM,WAAW,QAAQ;IACvB,cAAc,EAAE,OAAO,cAAc,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ,CAexD;AAED,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,QAAQ,CAwBlE"}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* event/schema types).
|
|
6
6
|
*
|
|
7
7
|
* Phase 4: agent-coord uses this from CLI handlers (state.task_set,
|
|
8
|
-
* state.status_checked, state.scratch_append, council.*,
|
|
8
|
+
* state.status_checked, state.scratch_append, council.*, decision.*,
|
|
9
|
+
* presence.*).
|
|
9
10
|
*/
|
|
10
11
|
import { appendFileSync, closeSync, mkdirSync, openSync } from "node:fs";
|
|
11
12
|
import { dirname, join } from "node:path";
|
|
@@ -16,7 +16,7 @@ export declare function appendScratch(coordRoot: string, instanceId: string, cat
|
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
18
|
* Replace the scratchpad with new body, archiving prior contents and
|
|
19
|
-
* appending an "(edited via UI by
|
|
19
|
+
* appending an "(edited via UI by the operator)" audit-marker note.
|
|
20
20
|
*/
|
|
21
21
|
export declare function editScratchpad(coordRoot: string, instanceId: string, newBody: string, summary: string): {
|
|
22
22
|
ok: boolean;
|
|
@@ -58,7 +58,7 @@ export function appendScratch(coordRoot, instanceId, category, body) {
|
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* Replace the scratchpad with new body, archiving prior contents and
|
|
61
|
-
* appending an "(edited via UI by
|
|
61
|
+
* appending an "(edited via UI by the operator)" audit-marker note.
|
|
62
62
|
*/
|
|
63
63
|
export function editScratchpad(coordRoot, instanceId, newBody, summary) {
|
|
64
64
|
if (typeof newBody !== "string") {
|
|
@@ -82,7 +82,7 @@ export function editScratchpad(coordRoot, instanceId, newBody, summary) {
|
|
|
82
82
|
prior += "\n";
|
|
83
83
|
}
|
|
84
84
|
const summaryText = summary && summary.length > 0 ? summary : "(no summary)";
|
|
85
|
-
const auditMarker = `## [${ts}] note (edited via UI by
|
|
85
|
+
const auditMarker = `## [${ts}] note (edited via UI by the operator)\n` +
|
|
86
86
|
`${summaryText}\n` +
|
|
87
87
|
`Pre-edit archived at .harnery/scratch/archived/${instanceId}-${archiveSuffix}.md\n\n`;
|
|
88
88
|
atomicWriteText(target, prior + auditMarker + newBody + (newBody.endsWith("\n") ? "" : "\n"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/effects/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/effects/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjE;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,SAAI,GACX,IAAI,CAYN;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAW3F;AAOD,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAYrD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAa3D;AAED,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAYpE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAkBzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,IAAI,CAqBN;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAa1D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAOnD"}
|
|
@@ -16,6 +16,7 @@ import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { applyDetection } from "../../../lib/presence.js";
|
|
19
|
+
import { resolveBinName } from "../../config.js";
|
|
19
20
|
export { captureImages, imageJanitor } from "./image-capture.js";
|
|
20
21
|
/**
|
|
21
22
|
* Play a notification sound via the cross-platform utility
|
|
@@ -119,7 +120,7 @@ export function scratchArchive(repoRoot, owner) {
|
|
|
119
120
|
*/
|
|
120
121
|
export function syncClaudeSessions(repoRoot, force) {
|
|
121
122
|
try {
|
|
122
|
-
const bin = join(repoRoot, "bin",
|
|
123
|
+
const bin = join(repoRoot, "bin", resolveBinName(repoRoot));
|
|
123
124
|
if (!existsSync(bin))
|
|
124
125
|
return;
|
|
125
126
|
const env = {
|
|
@@ -66,7 +66,7 @@ export class AgentBrowser {
|
|
|
66
66
|
this.cookiesSeeded = true;
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
|
-
const stateFile = this.opts.stateFilePath ?? `/tmp/
|
|
69
|
+
const stateFile = this.opts.stateFilePath ?? `/tmp/harn-agent-browser-state-${process.pid}.json`;
|
|
70
70
|
writeFileSync(stateFile, JSON.stringify(jarStore, null, 2));
|
|
71
71
|
this.exec(["state", "load", stateFile], 10_000);
|
|
72
72
|
this.cookiesSeeded = true;
|