harnery 0.9.0 → 0.11.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/README.md +1 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +6 -0
- package/dist/commands/agents.d.ts +24 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +244 -13
- package/dist/commands/backup.d.ts +5 -4
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +15 -14
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +27 -1
- package/dist/commands/claude-desktop.d.ts +19 -0
- package/dist/commands/claude-desktop.d.ts.map +1 -0
- package/dist/commands/claude-desktop.js +168 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +149 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +2 -3
- package/dist/commands/eml.d.ts +32 -0
- package/dist/commands/eml.d.ts.map +1 -1
- package/dist/commands/eml.js +16 -3
- package/dist/commands/harness.d.ts +11 -0
- package/dist/commands/harness.d.ts.map +1 -0
- package/dist/commands/harness.js +118 -0
- package/dist/commands/presence.d.ts +9 -4
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +88 -5
- package/dist/commands/relay.d.ts +9 -0
- package/dist/commands/relay.d.ts.map +1 -0
- package/dist/commands/relay.js +143 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +5 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +53 -17
- package/dist/core/agents/canonical-emit.d.ts +15 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +41 -3
- package/dist/core/agents/cli.js +4 -2
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +44 -3
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +26 -1
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +4 -37
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +16 -43
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +11 -2
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +3 -3
- package/dist/core/agents/state/names.d.ts +38 -3
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +46 -7
- package/dist/core/agents/state/pidmap.d.ts +5 -1
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +58 -2
- package/dist/core/agents/state/stale-sweep.d.ts +3 -2
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +6 -6
- package/dist/core/config.d.ts +68 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +191 -25
- package/dist/core/context/index.d.ts +144 -0
- package/dist/core/context/index.d.ts.map +1 -0
- package/dist/core/context/index.js +380 -0
- package/dist/core/harnesses/bench.d.ts +29 -0
- package/dist/core/harnesses/bench.d.ts.map +1 -0
- package/dist/core/harnesses/bench.js +151 -0
- package/dist/core/harnesses/index.d.ts +9 -0
- package/dist/core/harnesses/index.d.ts.map +1 -0
- package/dist/core/harnesses/index.js +4 -0
- package/dist/core/harnesses/profiles.d.ts +62 -0
- package/dist/core/harnesses/profiles.d.ts.map +1 -0
- package/dist/core/harnesses/profiles.js +115 -0
- package/dist/core/harnesses/registry.d.ts +15 -0
- package/dist/core/harnesses/registry.d.ts.map +1 -0
- package/dist/core/harnesses/registry.js +113 -0
- package/dist/core/harnesses/types.d.ts +64 -0
- package/dist/core/harnesses/types.d.ts.map +1 -0
- package/dist/core/harnesses/types.js +20 -0
- package/dist/core/hooks/cli.js +280 -63
- package/dist/core/hooks/events/schema.d.ts +38 -1
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.js +3 -0
- package/dist/core/hooks/harness/parse.d.ts +1 -1
- package/dist/core/hooks/harness/parse.d.ts.map +1 -1
- package/dist/core/hooks/harness/parse.js +4 -0
- package/dist/core/presence/blob.d.ts +40 -0
- package/dist/core/presence/blob.d.ts.map +1 -0
- package/dist/core/presence/blob.js +91 -0
- package/dist/core/presence/git.d.ts +68 -0
- package/dist/core/presence/git.d.ts.map +1 -0
- package/dist/core/presence/git.js +173 -0
- package/dist/core/presence/index.d.ts +74 -0
- package/dist/core/presence/index.d.ts.map +1 -0
- package/dist/core/presence/index.js +224 -0
- package/dist/core/presence/relay-client.d.ts +39 -0
- package/dist/core/presence/relay-client.d.ts.map +1 -0
- package/dist/core/presence/relay-client.js +295 -0
- package/dist/core/presence/relay-protocol.d.ts +94 -0
- package/dist/core/presence/relay-protocol.d.ts.map +1 -0
- package/dist/core/presence/relay-protocol.js +169 -0
- package/dist/core/workflow/billing.d.ts +1 -1
- package/dist/core/workflow/billing.d.ts.map +1 -1
- package/dist/core/workflow/billing.js +7 -1
- package/dist/core/workflow/engine.d.ts +5 -0
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +164 -16
- package/dist/core/workflow/harnesses.d.ts +3 -3
- package/dist/core/workflow/harnesses.d.ts.map +1 -1
- package/dist/core/workflow/harnesses.js +8 -17
- package/dist/core/workflow/index.d.ts +5 -0
- package/dist/core/workflow/index.d.ts.map +1 -0
- package/dist/core/workflow/index.js +3 -0
- package/dist/core/workflow/proof.d.ts +42 -0
- package/dist/core/workflow/proof.d.ts.map +1 -0
- package/dist/core/workflow/proof.js +342 -0
- package/dist/core/workflow/spawn-claude.d.ts +4 -1
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +39 -18
- package/dist/core/workflow/spawn-codex.d.ts +4 -1
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +44 -22
- package/dist/core/workflow/spawn-cursor.d.ts +4 -1
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +35 -16
- package/dist/core/workflow/types.d.ts +156 -2
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/types.js +1 -1
- package/dist/lib/browser/client.d.ts +16 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +22 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -0
- package/dist/lib/browser/launch-args.d.ts +21 -0
- package/dist/lib/browser/launch-args.d.ts.map +1 -0
- package/dist/lib/browser/launch-args.js +32 -0
- package/dist/lib/claude-desktop.d.ts +105 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -0
- package/dist/lib/claude-desktop.js +217 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +5 -2
- package/dist/lib/identities/assume.d.ts +51 -0
- package/dist/lib/identities/assume.d.ts.map +1 -0
- package/dist/lib/identities/assume.js +274 -0
- package/dist/lib/identities/index.d.ts +7 -7
- package/dist/lib/identities/index.d.ts.map +1 -1
- package/dist/lib/identities/index.js +24 -24
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +15 -2
- package/package.json +22 -6
- package/schemas/config.schema.json +88 -19
- package/src/commander.ts +6 -0
- package/src/commands/agents.ts +280 -14
- package/src/commands/backup.ts +17 -16
- package/src/commands/browse.ts +32 -0
- package/src/commands/claude-desktop.ts +215 -0
- package/src/commands/context.ts +174 -1
- package/src/commands/doctor.ts +2 -3
- package/src/commands/eml.ts +22 -4
- package/src/commands/harness.ts +147 -0
- package/src/commands/presence.ts +111 -5
- package/src/commands/relay.ts +160 -0
- package/src/commands/sync.ts +4 -0
- package/src/commands/workflow.ts +62 -19
- package/src/core/agents/canonical-emit.ts +41 -3
- package/src/core/agents/cli.ts +4 -2
- package/src/core/agents/render/prompt-context.ts +48 -3
- package/src/core/agents/render/session-context.ts +27 -1
- package/src/core/agents/rules/commit-conflict.ts +4 -33
- package/src/core/agents/session-events.ts +15 -39
- package/src/core/agents/state/heartbeat-projector.ts +10 -1
- package/src/core/agents/state/heartbeat-writer.ts +3 -2
- package/src/core/agents/state/names.ts +68 -9
- package/src/core/agents/state/pidmap.ts +59 -2
- package/src/core/agents/state/stale-sweep.ts +6 -11
- package/src/core/config.ts +248 -24
- package/src/core/context/index.ts +575 -0
- package/src/core/harnesses/bench.ts +214 -0
- package/src/core/harnesses/index.ts +31 -0
- package/src/core/harnesses/profiles.ts +141 -0
- package/src/core/harnesses/registry.ts +140 -0
- package/src/core/harnesses/types.ts +90 -0
- package/src/core/hooks/cli.ts +331 -62
- package/src/core/hooks/events/schema.ts +68 -0
- package/src/core/hooks/harness/events.ts +3 -0
- package/src/core/hooks/harness/parse.ts +7 -1
- package/src/core/presence/blob.ts +125 -0
- package/src/core/presence/git.ts +191 -0
- package/src/core/presence/index.ts +274 -0
- package/src/core/presence/relay-client.ts +322 -0
- package/src/core/presence/relay-protocol.ts +246 -0
- package/src/core/workflow/billing.ts +8 -2
- package/src/core/workflow/engine.ts +182 -16
- package/src/core/workflow/harnesses.ts +14 -19
- package/src/core/workflow/index.ts +43 -0
- package/src/core/workflow/proof.ts +434 -0
- package/src/core/workflow/spawn-claude.ts +39 -20
- package/src/core/workflow/spawn-codex.ts +47 -28
- package/src/core/workflow/spawn-cursor.ts +37 -18
- package/src/core/workflow/types.ts +176 -2
- package/src/lib/browser/client.ts +37 -0
- package/src/lib/browser/index.ts +1 -0
- package/src/lib/browser/launch-args.ts +33 -0
- package/src/lib/claude-desktop.ts +293 -0
- package/src/lib/docs-lint.ts +5 -2
- package/src/lib/identities/assume.ts +363 -0
- package/src/lib/identities/index.ts +28 -24
- package/src/lib/instructions/templates.ts +16 -2
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createBuiltinHarnessRegistry, runHarnessBench, } from "../core/harnesses/index.js";
|
|
2
|
+
const registry = createBuiltinHarnessRegistry();
|
|
3
|
+
export function registerHarnessCommand(program, emit) {
|
|
4
|
+
const command = program
|
|
5
|
+
.command("harness")
|
|
6
|
+
.description("Inspect registered harness capabilities and run their conformance bench.");
|
|
7
|
+
command
|
|
8
|
+
.command("list")
|
|
9
|
+
.description("List registered harness adapters and their high-signal capability claims.")
|
|
10
|
+
.option("--json", "Machine-readable profile catalog")
|
|
11
|
+
.action((opts) => {
|
|
12
|
+
const profiles = registry.list().map((adapter) => adapter.profile);
|
|
13
|
+
if (opts.json) {
|
|
14
|
+
emit.config({ format: "json" });
|
|
15
|
+
emit.data({ harnesses: profiles });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
emit.text(renderProfileTable(profiles));
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
command
|
|
22
|
+
.command("show <id>")
|
|
23
|
+
.description("Show one harness's complete capability declaration.")
|
|
24
|
+
.option("--json", "Machine-readable profile")
|
|
25
|
+
.action((id, opts) => {
|
|
26
|
+
const adapter = registry.get(id);
|
|
27
|
+
if (!adapter) {
|
|
28
|
+
emit.error({
|
|
29
|
+
code: "unknown_harness",
|
|
30
|
+
message: `unknown harness ${JSON.stringify(id)} (registered: ${registry.ids().join(", ")})`,
|
|
31
|
+
});
|
|
32
|
+
emit.setExitCode(1);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (opts.json) {
|
|
36
|
+
emit.config({ format: "json" });
|
|
37
|
+
emit.data(adapter.profile);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
emit.text(renderProfile(adapter.profile));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
command
|
|
44
|
+
.command("bench [harnesses...]")
|
|
45
|
+
.description("Run the offline adapter-contract bench (no model calls); drift exits non-zero.")
|
|
46
|
+
.option("--require-installed", "Also fail when a registered vendor CLI is missing")
|
|
47
|
+
.option("--json", "Machine-readable conformance report")
|
|
48
|
+
.action((harnesses, opts) => {
|
|
49
|
+
try {
|
|
50
|
+
const report = runHarnessBench(registry, { harnesses });
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
emit.config({ format: "json" });
|
|
53
|
+
emit.data(report);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
emit.text(renderBenchReport(report));
|
|
57
|
+
}
|
|
58
|
+
emit.setExitCode(report.drift || (opts.requireInstalled && report.skipped) ? 1 : 0);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
emit.error({ code: "harness_bench_failed", message: error.message });
|
|
62
|
+
emit.setExitCode(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function renderProfileTable(profiles) {
|
|
67
|
+
const rows = profiles.map((profile) => [
|
|
68
|
+
profile.id,
|
|
69
|
+
profile.binary,
|
|
70
|
+
profile.capabilities.modelSelection.support,
|
|
71
|
+
profile.capabilities.effortSelection.support,
|
|
72
|
+
profile.capabilities.sessionId.support,
|
|
73
|
+
profile.capabilities.cost.support,
|
|
74
|
+
]);
|
|
75
|
+
return renderTable(["HARNESS", "BINARY", "MODEL", "EFFORT", "SESSION", "COST"], rows);
|
|
76
|
+
}
|
|
77
|
+
export function renderProfile(profile) {
|
|
78
|
+
const lines = [
|
|
79
|
+
`${profile.displayName} (${profile.id})`,
|
|
80
|
+
`binary: ${profile.binary}`,
|
|
81
|
+
`integration: ${profile.integrationMode}`,
|
|
82
|
+
`auth: ${profile.authModel}`,
|
|
83
|
+
`model family: ${profile.modelFamily}`,
|
|
84
|
+
`effort values: ${profile.effortValues.join(", ") || "none"}`,
|
|
85
|
+
profile.verified
|
|
86
|
+
? `verified: ${profile.verified.date} (${profile.verified.version})`
|
|
87
|
+
: "verified: not recorded",
|
|
88
|
+
"",
|
|
89
|
+
"capabilities:",
|
|
90
|
+
];
|
|
91
|
+
for (const [dimension, claim] of Object.entries(profile.capabilities)) {
|
|
92
|
+
lines.push(` ${dimension.padEnd(18)} ${claim.support}${claim.note ? ` — ${claim.note}` : ""}`);
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
export function renderBenchReport(report) {
|
|
97
|
+
const rows = report.results.map((result) => [
|
|
98
|
+
result.harness,
|
|
99
|
+
result.dimension,
|
|
100
|
+
result.declared,
|
|
101
|
+
result.observed,
|
|
102
|
+
result.verdict,
|
|
103
|
+
]);
|
|
104
|
+
const table = renderTable(["HARNESS", "DIMENSION", "DECLARED", "OBSERVED", "VERDICT"], rows);
|
|
105
|
+
const summary = Object.entries(report.summary)
|
|
106
|
+
.filter(([, count]) => count > 0)
|
|
107
|
+
.map(([verdict, count]) => `${verdict}=${count}`)
|
|
108
|
+
.join(", ");
|
|
109
|
+
return `${table}\n\nmode: ${report.mode} (no model calls)\n${summary}`;
|
|
110
|
+
}
|
|
111
|
+
function renderTable(headers, rows) {
|
|
112
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0)));
|
|
113
|
+
const line = (cells) => cells
|
|
114
|
+
.map((cell, index) => cell.padEnd(widths[index]))
|
|
115
|
+
.join(" ")
|
|
116
|
+
.trimEnd();
|
|
117
|
+
return [line(headers), line(widths.map((width) => "-".repeat(width))), ...rows.map(line)].join("\n");
|
|
118
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import type { EmitContext } from "../commander.js";
|
|
3
3
|
/**
|
|
4
|
-
* `presence`:
|
|
4
|
+
* `presence`: two related surfaces under one name.
|
|
5
|
+
*
|
|
6
|
+
* 1. Mobile/office state for agents (~/.claude/presence): get / set /
|
|
7
|
+
* clear / detect. The state file path is Claude-Code-specific but the
|
|
8
|
+
* logic is generic.
|
|
9
|
+
* 2. Cross-machine session presence (ADR 0016): publish / fetch / peers —
|
|
10
|
+
* the git-refs transport that lets sessions on other machines show up
|
|
11
|
+
* in the peer table via `refs/harnery/presence/<machine>` on origin.
|
|
5
12
|
*
|
|
6
13
|
* Uses the injected EmitContext so composed and standalone consumers share
|
|
7
|
-
* one code path.
|
|
8
|
-
* ~/.claude/) but the detect/set/clear logic is generic: any agent harness
|
|
9
|
-
* can adopt the same convention.
|
|
14
|
+
* one code path.
|
|
10
15
|
*/
|
|
11
16
|
export declare function registerPresenceCommand(program: Command, emit: EmitContext): void;
|
|
12
17
|
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0BnD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAwKjF"}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import { monorepoRoot } from "../core/agents/coord-client.js";
|
|
1
2
|
import { emitCanonical, normalizeHarness, readHeartbeat, resolveOwner, } from "../core/agents/index.js";
|
|
3
|
+
import { fetchPresence, publishPresence, readRemoteMachines, relayDaemonStatus, runRelayDaemon, } from "../core/presence/index.js";
|
|
2
4
|
import { ageSeconds, applyDetection, clearPresence, formatAge, presenceFilePath, readPresence, writePresence, } from "../lib/presence.js";
|
|
3
5
|
/**
|
|
4
|
-
* `presence`:
|
|
6
|
+
* `presence`: two related surfaces under one name.
|
|
7
|
+
*
|
|
8
|
+
* 1. Mobile/office state for agents (~/.claude/presence): get / set /
|
|
9
|
+
* clear / detect. The state file path is Claude-Code-specific but the
|
|
10
|
+
* logic is generic.
|
|
11
|
+
* 2. Cross-machine session presence (ADR 0016): publish / fetch / peers —
|
|
12
|
+
* the git-refs transport that lets sessions on other machines show up
|
|
13
|
+
* in the peer table via `refs/harnery/presence/<machine>` on origin.
|
|
5
14
|
*
|
|
6
15
|
* Uses the injected EmitContext so composed and standalone consumers share
|
|
7
|
-
* one code path.
|
|
8
|
-
* ~/.claude/) but the detect/set/clear logic is generic: any agent harness
|
|
9
|
-
* can adopt the same convention.
|
|
16
|
+
* one code path.
|
|
10
17
|
*/
|
|
11
18
|
export function registerPresenceCommand(program, emit) {
|
|
12
19
|
const cmd = program
|
|
13
20
|
.command("presence")
|
|
14
|
-
.description("
|
|
21
|
+
.description("Presence: mobile/office state (get/set/clear/detect) + cross-machine session presence (publish/fetch/peers)")
|
|
15
22
|
.action(() => {
|
|
16
23
|
printStatus(emit);
|
|
17
24
|
});
|
|
@@ -62,6 +69,74 @@ export function registerPresenceCommand(program, emit) {
|
|
|
62
69
|
path: presenceFilePath(),
|
|
63
70
|
});
|
|
64
71
|
});
|
|
72
|
+
// ---- cross-machine session presence (ADR 0016) ----
|
|
73
|
+
cmd
|
|
74
|
+
.command("publish")
|
|
75
|
+
.description("Publish this machine's live sessions to refs/harnery/presence/<machine> on origin. " +
|
|
76
|
+
"Change-batched with a keepalive; --force pushes regardless. Synchronous (errors report).")
|
|
77
|
+
.option("--force", "Push even if unchanged / within throttle windows")
|
|
78
|
+
.action((opts) => {
|
|
79
|
+
const root = requireRoot(emit);
|
|
80
|
+
const r = publishPresence(root, { force: opts.force, sync: true });
|
|
81
|
+
if (r.status === "error") {
|
|
82
|
+
emit.error({ code: "publish_failed", message: r.error });
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
emit.data(r);
|
|
86
|
+
});
|
|
87
|
+
cmd
|
|
88
|
+
.command("fetch")
|
|
89
|
+
.description("Fetch peer machines' presence refs from origin (throttled; --force bypasses). Synchronous.")
|
|
90
|
+
.option("--force", "Fetch even within the throttle interval")
|
|
91
|
+
.action((opts) => {
|
|
92
|
+
const root = requireRoot(emit);
|
|
93
|
+
const r = fetchPresence(root, { force: opts.force, sync: true });
|
|
94
|
+
if (r.status === "error") {
|
|
95
|
+
emit.error({ code: "fetch_failed", message: r.error });
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
emit.data(r);
|
|
99
|
+
});
|
|
100
|
+
cmd
|
|
101
|
+
.command("relay-daemon")
|
|
102
|
+
.description("Run the relay client daemon in the foreground (normally spawned detached by the hooks " +
|
|
103
|
+
"when presence.relay is configured; holds the WebSocket, publishes on heartbeat change, " +
|
|
104
|
+
"caches peers' blobs for the renderers, exits when the machine goes idle).")
|
|
105
|
+
.action(async () => {
|
|
106
|
+
const root = requireRoot(emit);
|
|
107
|
+
process.exit(await runRelayDaemon(root));
|
|
108
|
+
});
|
|
109
|
+
cmd
|
|
110
|
+
.command("peers")
|
|
111
|
+
.description("Show sessions on other machines (merged from presence refs + the relay daemon's cache)")
|
|
112
|
+
.option("--json", "JSON output")
|
|
113
|
+
.option("--stale", "Include machines past the stale window")
|
|
114
|
+
.action((opts) => {
|
|
115
|
+
const root = requireRoot(emit);
|
|
116
|
+
const machines = readRemoteMachines(root, { includeStale: opts.stale });
|
|
117
|
+
const daemon = relayDaemonStatus(root);
|
|
118
|
+
if (opts.json)
|
|
119
|
+
emit.config({ format: "json" });
|
|
120
|
+
if (opts.json || !process.stdout.isTTY) {
|
|
121
|
+
emit.data({ machines, relay_daemon: daemon });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
emit.text(daemon.running
|
|
125
|
+
? `relay daemon: running (pid ${daemon.pid}, ${daemon.url})\n`
|
|
126
|
+
: "relay daemon: not running (git-refs transport only)\n");
|
|
127
|
+
if (machines.length === 0) {
|
|
128
|
+
emit.text("no remote sessions known (try `presence fetch --force`)\n");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
for (const m of machines) {
|
|
132
|
+
emit.text(`${m.machine} (published ${formatAge(m.age_secs)} ago)\n`);
|
|
133
|
+
for (const a of m.agents) {
|
|
134
|
+
const task = a.task ? ` "${a.task.slice(0, 70)}"` : "";
|
|
135
|
+
const files = a.files_touched?.length ? ` [${a.files_touched.length} files]` : "";
|
|
136
|
+
emit.text(` agent-${a.name ?? a.instance_id.slice(0, 8)}${task}${files}\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
65
140
|
cmd
|
|
66
141
|
.command("detect")
|
|
67
142
|
.description("Internal: read prompt text from stdin, apply detection rules, update state if signal is clear")
|
|
@@ -85,6 +160,14 @@ export function registerPresenceCommand(program, emit) {
|
|
|
85
160
|
}
|
|
86
161
|
});
|
|
87
162
|
}
|
|
163
|
+
function requireRoot(emit) {
|
|
164
|
+
const root = monorepoRoot();
|
|
165
|
+
if (!root) {
|
|
166
|
+
emit.error({ code: "not_in_repo", message: "coord root not found (no .harnery/ upward)" });
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
return root;
|
|
170
|
+
}
|
|
88
171
|
function printStatus(emit) {
|
|
89
172
|
const r = readPresence();
|
|
90
173
|
emit.data({
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { EmitContext } from "../commander.js";
|
|
3
|
+
/** Start the relay server. Exported for in-process integration tests. */
|
|
4
|
+
export declare function startRelayServer(port: number): {
|
|
5
|
+
port: number;
|
|
6
|
+
stop: () => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function registerRelayCommand(program: Command, emit: EmitContext): void;
|
|
9
|
+
//# sourceMappingURL=relay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../../src/commands/relay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAgCnD,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAkGjF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAyB9E"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { MAX_FRAME_BYTES } from "../core/presence/relay-protocol.js";
|
|
2
|
+
/**
|
|
3
|
+
* `relay serve`: the Bun self-host of the presence relay (ADR 0016 phase 2).
|
|
4
|
+
* Same wire protocol as the Cloudflare Durable Objects host in relay/worker/ —
|
|
5
|
+
* one in-memory room per opaque room id, warm-join cache, fan-out, rate
|
|
6
|
+
* limits. For teams who'd rather run their own box than use the public
|
|
7
|
+
* relay.harnery.com; point clients at it via `.harnery/config.jsonc` →
|
|
8
|
+
* `{ "presence": { "relay": "ws://host:port" } }`.
|
|
9
|
+
*
|
|
10
|
+
* Bun-only (Bun.serve WebSockets), like `harn tunnel` — fails fast with a
|
|
11
|
+
* clear message on a Bun-free host.
|
|
12
|
+
*/
|
|
13
|
+
const MAX_SOCKETS_PER_ROOM = 64;
|
|
14
|
+
const RATE_BURST = 10;
|
|
15
|
+
const RATE_REFILL_MS = 2000;
|
|
16
|
+
const CACHE_TTL_MS = 15 * 60 * 1000;
|
|
17
|
+
const ROOM_ID_RE = /^[0-9a-f]{32}$/;
|
|
18
|
+
/** Start the relay server. Exported for in-process integration tests. */
|
|
19
|
+
export function startRelayServer(port) {
|
|
20
|
+
if (typeof Bun === "undefined" || typeof Bun.serve !== "function") {
|
|
21
|
+
throw new Error("relay serve requires Bun (Bun.serve WebSockets)");
|
|
22
|
+
}
|
|
23
|
+
const rooms = new Map();
|
|
24
|
+
const roomOf = (roomId) => {
|
|
25
|
+
let r = rooms.get(roomId);
|
|
26
|
+
if (!r) {
|
|
27
|
+
r = { sockets: new Set(), cache: new Map() };
|
|
28
|
+
rooms.set(roomId, r);
|
|
29
|
+
}
|
|
30
|
+
return r;
|
|
31
|
+
};
|
|
32
|
+
const server = Bun.serve({
|
|
33
|
+
port,
|
|
34
|
+
fetch(req, srv) {
|
|
35
|
+
const url = new URL(req.url);
|
|
36
|
+
if (url.pathname === "/" || url.pathname === "/healthz") {
|
|
37
|
+
return Response.json({ ok: true, service: "harnery-presence-relay", v: 1 });
|
|
38
|
+
}
|
|
39
|
+
const m = /^\/v1\/room\/([0-9a-f]{32})$/.exec(url.pathname);
|
|
40
|
+
if (!m || !ROOM_ID_RE.test(m[1]))
|
|
41
|
+
return new Response("not found", { status: 404 });
|
|
42
|
+
const room = roomOf(m[1]);
|
|
43
|
+
if (room.sockets.size >= MAX_SOCKETS_PER_ROOM) {
|
|
44
|
+
return new Response("room full", { status: 429 });
|
|
45
|
+
}
|
|
46
|
+
const ok = srv.upgrade(req, {
|
|
47
|
+
data: { roomId: m[1], bucketTokens: RATE_BURST, bucketAt: Date.now() },
|
|
48
|
+
});
|
|
49
|
+
return ok ? undefined : new Response("expected websocket", { status: 426 });
|
|
50
|
+
},
|
|
51
|
+
websocket: {
|
|
52
|
+
open(ws) {
|
|
53
|
+
const room = roomOf(ws.data.roomId);
|
|
54
|
+
room.sockets.add(ws);
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
for (const [sender, entry] of room.cache) {
|
|
57
|
+
if (now - entry.at > CACHE_TTL_MS) {
|
|
58
|
+
room.cache.delete(sender);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
ws.send(entry.raw);
|
|
62
|
+
}
|
|
63
|
+
ws.send(JSON.stringify({ t: "hello", peers: room.sockets.size }));
|
|
64
|
+
},
|
|
65
|
+
message(ws, message) {
|
|
66
|
+
if (typeof message !== "string" || message.length > MAX_FRAME_BYTES)
|
|
67
|
+
return;
|
|
68
|
+
// Rate limit (token bucket per socket).
|
|
69
|
+
const d = ws.data;
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const refill = Math.floor((now - d.bucketAt) / RATE_REFILL_MS);
|
|
72
|
+
if (refill > 0) {
|
|
73
|
+
d.bucketTokens = Math.min(RATE_BURST, d.bucketTokens + refill);
|
|
74
|
+
d.bucketAt = now;
|
|
75
|
+
}
|
|
76
|
+
if (d.bucketTokens <= 0)
|
|
77
|
+
return;
|
|
78
|
+
d.bucketTokens -= 1;
|
|
79
|
+
let frame;
|
|
80
|
+
try {
|
|
81
|
+
frame = JSON.parse(message);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (frame.t !== "pub" ||
|
|
87
|
+
typeof frame.sender !== "string" ||
|
|
88
|
+
!/^[0-9a-f]+$/.test(frame.sender) ||
|
|
89
|
+
frame.sender.length > 64) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const room = roomOf(d.roomId);
|
|
93
|
+
room.cache.set(frame.sender, { raw: message, at: now });
|
|
94
|
+
for (const peer of room.sockets) {
|
|
95
|
+
if (peer === ws)
|
|
96
|
+
continue;
|
|
97
|
+
try {
|
|
98
|
+
peer.send(message);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* dead socket */
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
close(ws) {
|
|
106
|
+
const room = rooms.get(ws.data.roomId);
|
|
107
|
+
if (!room)
|
|
108
|
+
return;
|
|
109
|
+
room.sockets.delete(ws);
|
|
110
|
+
if (room.sockets.size === 0 && room.cache.size === 0)
|
|
111
|
+
rooms.delete(ws.data.roomId);
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
port: server.port ?? port,
|
|
117
|
+
stop: () => server.stop(true),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export function registerRelayCommand(program, emit) {
|
|
121
|
+
const cmd = program
|
|
122
|
+
.command("relay")
|
|
123
|
+
.description("Self-host the presence relay (see also relay/worker/ for the Cloudflare host)");
|
|
124
|
+
cmd
|
|
125
|
+
.command("serve")
|
|
126
|
+
.description("Run the presence relay on this machine (Bun-only; same protocol as relay/worker/)")
|
|
127
|
+
.option("--port <n>", "Port to listen on", "8787")
|
|
128
|
+
.action((opts) => {
|
|
129
|
+
let server;
|
|
130
|
+
try {
|
|
131
|
+
server = startRelayServer(Number.parseInt(opts.port, 10));
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
emit.error({
|
|
135
|
+
code: "relay_serve_failed",
|
|
136
|
+
message: e instanceof Error ? e.message : String(e),
|
|
137
|
+
});
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
emit.log(`presence relay listening on :${server.port} (ws path: /v1/room/<room-id>)`, "info");
|
|
141
|
+
// Foreground service: run until interrupted.
|
|
142
|
+
});
|
|
143
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAoHnD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAuK7E"}
|
package/dist/commands/sync.js
CHANGED
|
@@ -22,6 +22,7 @@ import { spawnSync } from "node:child_process";
|
|
|
22
22
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
23
|
import os from "node:os";
|
|
24
24
|
import path from "node:path";
|
|
25
|
+
import { syncJsoncConfig } from "../core/config.js";
|
|
25
26
|
// What we sync. Anything matching one of these (relative to .harnery/) is
|
|
26
27
|
// considered part of the sync set. Everything else stays local-only.
|
|
27
28
|
const SYNC_PATHS = ["identities/", "scratch/archived/", "councils/"];
|
|
@@ -29,10 +30,14 @@ function configPath() {
|
|
|
29
30
|
return path.join(os.homedir(), ".config", "harnery", "sync.json");
|
|
30
31
|
}
|
|
31
32
|
function loadConfig() {
|
|
33
|
+
// Precedence: env → .harnery/config.jsonc `sync` → ~/.config/harnery/sync.json.
|
|
32
34
|
const envRemote = process.env.HARNERY_SYNC_REMOTE;
|
|
33
35
|
const envPrefix = process.env.HARNERY_SYNC_PREFIX ?? "harnery";
|
|
34
36
|
if (envRemote)
|
|
35
37
|
return { remote: envRemote, prefix: envPrefix };
|
|
38
|
+
const jsonc = syncJsoncConfig();
|
|
39
|
+
if (jsonc)
|
|
40
|
+
return jsonc;
|
|
36
41
|
const p = configPath();
|
|
37
42
|
if (!existsSync(p))
|
|
38
43
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA8BnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA+IjF"}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { workflowSubscriptionOnly } from "../core/config.js";
|
|
2
|
+
import { createBuiltinHarnessRegistry } from "../core/harnesses/index.js";
|
|
2
3
|
import { findCoordRoot } from "../core/hooks/resolve/coord-root.js";
|
|
3
|
-
const HARNESSES = ["claude-code", "codex", "cursor"];
|
|
4
4
|
export function registerWorkflowCommand(program, emit) {
|
|
5
|
+
const registry = createBuiltinHarnessRegistry();
|
|
6
|
+
const harnesses = registry.ids();
|
|
5
7
|
const workflow = program
|
|
6
8
|
.command("workflow")
|
|
7
9
|
.description("Run bounded, schema-gated multi-subagent workflow scripts.");
|
|
8
10
|
workflow
|
|
9
11
|
.command("run <script>")
|
|
10
|
-
.description("Execute a workflow script (plain JS: `export default async ({agent, parallel, stage, log}) => …`). " +
|
|
12
|
+
.description("Execute a workflow script (plain JS: `export default async ({agent, parallel, stage, log, evidence}) => …`). " +
|
|
11
13
|
"Subagents spawn as headless harness-CLI subprocesses, coordination-registered.")
|
|
12
14
|
.option("--max-agents <n>", "Total-agent ceiling for the run (default 50)")
|
|
13
15
|
.option("--concurrency <n>", "Concurrent-subagent cap (default 4)")
|
|
14
16
|
.option("--cwd <dir>", "Working directory children spawn in (default: coord root)")
|
|
15
|
-
.option("--harness <name>", `Default harness for agent() calls: ${
|
|
17
|
+
.option("--harness <name>", `Default harness for agent() calls: ${harnesses.join(" | ")} (default claude-code); scripts can override per agent via opts.harness`)
|
|
16
18
|
.option("--resume-from <run-id>", "Reuse completed agent results from a prior run's journal; only changed/failed calls re-run")
|
|
17
19
|
.option("--subscription-only", "Guarantee subscription billing: scrub API-key vars from child envs; fail loud when a " +
|
|
18
20
|
"harness has no stored login (repo default via config.jsonc workflow.subscriptionOnly)")
|
|
@@ -28,10 +30,10 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
28
30
|
});
|
|
29
31
|
process.exit(1);
|
|
30
32
|
}
|
|
31
|
-
if (opts.harness && !
|
|
33
|
+
if (opts.harness && !registry.get(opts.harness)) {
|
|
32
34
|
emit.error({
|
|
33
35
|
code: "bad_harness",
|
|
34
|
-
message: `unknown harness "${opts.harness}" (expected: ${
|
|
36
|
+
message: `unknown harness "${opts.harness}" (expected: ${harnesses.join(" | ")})`,
|
|
35
37
|
});
|
|
36
38
|
process.exit(1);
|
|
37
39
|
}
|
|
@@ -46,19 +48,10 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
46
48
|
process.exit(1);
|
|
47
49
|
}
|
|
48
50
|
try {
|
|
49
|
-
const
|
|
50
|
-
import("../core/workflow/engine.js"),
|
|
51
|
-
import("../core/workflow/spawn-claude.js"),
|
|
52
|
-
import("../core/workflow/spawn-codex.js"),
|
|
53
|
-
import("../core/workflow/spawn-cursor.js"),
|
|
54
|
-
]);
|
|
51
|
+
const { runWorkflow } = await import("../core/workflow/engine.js");
|
|
55
52
|
const report = await runWorkflow(script, {
|
|
56
53
|
coordRoot,
|
|
57
|
-
spawners:
|
|
58
|
-
"claude-code": claudeCodeSpawner,
|
|
59
|
-
codex: codexSpawner,
|
|
60
|
-
cursor: cursorSpawner,
|
|
61
|
-
},
|
|
54
|
+
spawners: registry.spawners(),
|
|
62
55
|
defaultHarness: opts.harness,
|
|
63
56
|
resumeFrom: opts.resumeFrom,
|
|
64
57
|
subscriptionOnly,
|
|
@@ -66,6 +59,12 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
66
59
|
maxAgents: opts.maxAgents ? Number.parseInt(opts.maxAgents, 10) : undefined,
|
|
67
60
|
concurrency: opts.concurrency ? Number.parseInt(opts.concurrency, 10) : undefined,
|
|
68
61
|
cwd: opts.cwd,
|
|
62
|
+
harnessEvidence: Object.fromEntries(registry
|
|
63
|
+
.list()
|
|
64
|
+
.map((adapter) => [
|
|
65
|
+
adapter.profile.id,
|
|
66
|
+
{ toolEvidence: adapter.profile.capabilities.toolEvidence },
|
|
67
|
+
])),
|
|
69
68
|
});
|
|
70
69
|
if (opts.json) {
|
|
71
70
|
emit.config({ format: "json" });
|
|
@@ -78,11 +77,48 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
78
77
|
: "";
|
|
79
78
|
emit.text(`run ${report.runId} (${report.name}) finished: ${report.agentsSpawned} agent(s)${cachedPart}, ` +
|
|
80
79
|
`$${report.costUsd.toFixed(4)}, ${Math.round(report.durationMs / 1000)}s\n${billingPart}` +
|
|
80
|
+
`acceptance: ${report.acceptance.satisfied} satisfied, ${report.acceptance.unsatisfied} unsatisfied, ` +
|
|
81
|
+
`${report.acceptance.unknown} unknown\n` +
|
|
81
82
|
`journal: ${report.journalPath}\n` +
|
|
83
|
+
`proof: ${report.proofPath}\n` +
|
|
82
84
|
`result: ${typeof report.result === "string" ? report.result : JSON.stringify(report.result, null, 2)}\n`);
|
|
83
85
|
}
|
|
84
86
|
catch (err) {
|
|
85
|
-
|
|
87
|
+
const proofPath = typeof err === "object" && err !== null && "proofPath" in err
|
|
88
|
+
? String(err.proofPath)
|
|
89
|
+
: undefined;
|
|
90
|
+
emit.error({
|
|
91
|
+
code: "workflow_failed",
|
|
92
|
+
message: `${err.message}${proofPath ? `\nproof: ${proofPath}` : ""}`,
|
|
93
|
+
});
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
workflow
|
|
98
|
+
.command("proof <run-id>")
|
|
99
|
+
.description("Show the bounded proof packet for a completed workflow run.")
|
|
100
|
+
.option("--json", "Emit the stored proof packet as JSON")
|
|
101
|
+
.action(async (runId, opts) => {
|
|
102
|
+
const coordRoot = findCoordRoot();
|
|
103
|
+
if (!coordRoot) {
|
|
104
|
+
emit.error({
|
|
105
|
+
code: "no_coord_root",
|
|
106
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
107
|
+
});
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const { readWorkflowProof, renderWorkflowProof } = await import("../core/workflow/proof.js");
|
|
112
|
+
const proof = readWorkflowProof(coordRoot, runId);
|
|
113
|
+
if (opts.json) {
|
|
114
|
+
emit.config({ format: "json" });
|
|
115
|
+
emit.data(proof);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
emit.text(renderWorkflowProof(proof));
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
emit.error({ code: "workflow_proof_failed", message: err.message });
|
|
86
122
|
process.exit(1);
|
|
87
123
|
}
|
|
88
124
|
});
|
|
@@ -18,6 +18,21 @@ export interface CanonicalEmitInput {
|
|
|
18
18
|
parentSessionId?: string;
|
|
19
19
|
parentTurnId?: string;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the coord root for a canonical emit.
|
|
23
|
+
*
|
|
24
|
+
* Order: explicit `HARNERY_COORD_ROOT_OVERRIDE` → git-superproject-aware
|
|
25
|
+
* `monorepoRoot()` (must actually carry `.harnery/`) → cwd walk (non-git
|
|
26
|
+
* hosts). The git-aware step is load-bearing: a shell cd'd into a nested
|
|
27
|
+
* directory that carries its own `.harnery/` (e.g. an embedded harnery
|
|
28
|
+
* checkout) made the old cwd-walk resolve the NESTED root, from which
|
|
29
|
+
* `<root>/harnery/bin/agent-coord` doesn't exist — so `agents status` /
|
|
30
|
+
* `set-task` emits silently vanished and the Stop-hook's rule 1/3
|
|
31
|
+
* (`state.status_checked` in-turn) blocked turns that had done the ritual.
|
|
32
|
+
* Same bug class as the coordHelperOpts root-pin fix; this closes the
|
|
33
|
+
* emitCanonical instance of it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveEmitRoot(start?: string): string | null;
|
|
21
36
|
export declare function emitCanonical(input: CanonicalEmitInput): void;
|
|
22
37
|
/**
|
|
23
38
|
* Normalize the heartbeat's `platform` field (which uses snake_case
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAW5E;AAID,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAsC7D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAIjG"}
|