harnery 0.31.0 → 0.31.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +124 -14
- package/dist/commands/grep.d.ts +1 -1
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +19 -2
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +20 -4
- package/dist/core/agents/canonical-emit.d.ts +5 -10
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +10 -39
- package/dist/core/agents/coord-bin.d.ts +33 -0
- package/dist/core/agents/coord-bin.d.ts.map +1 -0
- package/dist/core/agents/coord-bin.js +89 -0
- package/dist/core/agents/coord-client.d.ts +57 -6
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +181 -48
- package/dist/core/agents/paths.d.ts +5 -2
- package/dist/core/agents/paths.d.ts.map +1 -1
- package/dist/core/agents/paths.js +7 -13
- package/dist/core/hooks/adapter/detect.d.ts +4 -2
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +9 -4
- package/dist/core/hooks/cli.js +14 -13
- package/dist/core/hooks/effects/index.d.ts.map +1 -1
- package/dist/core/hooks/effects/index.js +2 -2
- package/dist/core/hooks/resolve/coord-root.d.ts +16 -11
- package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -1
- package/dist/core/hooks/resolve/coord-root.js +18 -43
- package/dist/lib/context/index.js +9 -2
- package/package.json +1 -1
- package/src/commands/agents.ts +129 -14
- package/src/commands/grep.ts +19 -2
- package/src/commands/tunnel.ts +19 -4
- package/src/core/agents/canonical-emit.ts +10 -37
- package/src/core/agents/coord-bin.ts +92 -0
- package/src/core/agents/coord-client.ts +183 -50
- package/src/core/agents/paths.ts +7 -11
- package/src/core/hooks/adapter/detect.ts +9 -4
- package/src/core/hooks/cli.ts +14 -13
- package/src/core/hooks/effects/index.ts +2 -2
- package/src/core/hooks/resolve/coord-root.ts +18 -40
- package/src/lib/context/index.ts +11 -1
|
@@ -50,14 +50,55 @@ export interface Heartbeat {
|
|
|
50
50
|
/**
|
|
51
51
|
* Resolve the monorepo root for coord-state purposes.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* `<superproject>/.git/modules/<name>/` to recover the superproject for
|
|
56
|
-
* submodule worktrees. Without that fallback, a TS coord caller running
|
|
57
|
-
* inside a `harn worktree add-submodule` checkout would resolve a different
|
|
58
|
-
* `.harnery/` than the bash hooks and the two layers would silently diverge.
|
|
53
|
+
* Thin alias for `resolveCoordRoot()`; kept because it is the name the CLI
|
|
54
|
+
* command modules and the vendored downstream consumer already import.
|
|
59
55
|
*/
|
|
60
56
|
export declare function monorepoRoot(): string | null;
|
|
57
|
+
/**
|
|
58
|
+
* THE coordination-root resolution. Every surface — the hooks, the CLI's reads,
|
|
59
|
+
* and the CLI's canonical emits — resolves through this one function, because a
|
|
60
|
+
* root the two layers disagree about is a root that silently breaks the
|
|
61
|
+
* end-of-turn rules: the hook evaluates `state.status_checked` from the stream
|
|
62
|
+
* it reads, so an emit into a different `.harnery/events.ndjson` is invisible
|
|
63
|
+
* and rule 1/3 blocks a turn that did run `agents status`, with no sequence of
|
|
64
|
+
* CLI commands able to satisfy it.
|
|
65
|
+
*
|
|
66
|
+
* Precedence:
|
|
67
|
+
* 1. `HARNERY_COORD_ROOT_OVERRIDE` — explicit pin (tests, git hooks, and the
|
|
68
|
+
* root every coord-helper spawn pins for its child).
|
|
69
|
+
* 2. `CLAUDE_PROJECT_DIR` — the adapter stating which project it opened. Hook
|
|
70
|
+
* processes inherit the session's *shell* cwd, which follows `cd` into a
|
|
71
|
+
* subdirectory or submodule that may carry a `.harnery/` of its own (or
|
|
72
|
+
* none at all), so the adapter's own statement outranks the cwd walk.
|
|
73
|
+
* 3. The candidate root that already holds THIS session's heartbeat.
|
|
74
|
+
* 4. The nearest enclosing `.harnery/`, then a git-derived root.
|
|
75
|
+
*
|
|
76
|
+
* Step 3 is what makes CLI/hook disagreement structurally impossible rather
|
|
77
|
+
* than a coin flip. Choosing either root unconditionally is wrong in one
|
|
78
|
+
* direction each: preferring the git superproject strands a session whose
|
|
79
|
+
* adapter opened the submodule itself (its heartbeat lives in the submodule,
|
|
80
|
+
* so status/set-task wrote events the hook never read), while walking up from
|
|
81
|
+
* cwd alone strands the opposite case, a session opened on the superproject
|
|
82
|
+
* whose shell has cd'd into a submodule that carries its own `.harnery/`
|
|
83
|
+
* (regression-tested in tests/unit/coord-helper-root-pin.test.ts). The session's
|
|
84
|
+
* own heartbeat settles it: whichever root the hook registered this session in
|
|
85
|
+
* is the root the CLI must use, and the adapter-exported session id needed to
|
|
86
|
+
* recognize it is available to a plain tool-call subprocess even though
|
|
87
|
+
* `CLAUDE_PROJECT_DIR` is not.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolveCoordRoot(start?: string): string | null;
|
|
90
|
+
/** Nearest enclosing directory carrying `.harnery/`, or null. */
|
|
91
|
+
export declare function nearestCoordRoot(start: string): string | null;
|
|
92
|
+
/**
|
|
93
|
+
* Does this root's `.harnery/` already know the process asking?
|
|
94
|
+
*
|
|
95
|
+
* Two discriminators, both genuinely about *this* session: the adapter-exported
|
|
96
|
+
* session id matching a live heartbeat, and a pid-map row on our own ppid
|
|
97
|
+
* chain. Deliberately NOT the single-live-agent fallback that owner resolution
|
|
98
|
+
* ends with — a lone stranger in the wrong root is exactly how `whoami` came to
|
|
99
|
+
* report another agent's name and task as its own.
|
|
100
|
+
*/
|
|
101
|
+
export declare function rootKnowsSession(root: string): boolean;
|
|
61
102
|
/** Parse owner from a pid-map row (`owner` or `owner\tplatform`). */
|
|
62
103
|
export declare function parsePidmapRowOwner(row: string): string;
|
|
63
104
|
/** Parse platform from a pid-map row; legacy rows default to `claude_code`. */
|
|
@@ -105,6 +146,16 @@ export declare function resolveOwnerWithSource(): {
|
|
|
105
146
|
owner: string | null;
|
|
106
147
|
source: "env" | "pidmap" | "pidmap_fallback" | "session_env" | "active_singleton" | "none";
|
|
107
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* Walk our own ppid chain for a pid-map row in ONE given root.
|
|
151
|
+
*
|
|
152
|
+
* Root-parameterized (rather than resolving the root itself) so root resolution
|
|
153
|
+
* can use it as a discriminator without recursing back into itself.
|
|
154
|
+
*/
|
|
155
|
+
export declare function resolveOwnerByPidmap(root: string): {
|
|
156
|
+
owner: string | null;
|
|
157
|
+
source: "pidmap" | "pidmap_fallback" | "none";
|
|
158
|
+
};
|
|
108
159
|
/**
|
|
109
160
|
* Resolve the owner by matching the adapter session-id env var against the
|
|
110
161
|
* `session_id` of a live heartbeat in this coord root. Returns the matching
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAWhE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAI5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yIAAyI;IACzI,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mEAAmE;IACnE,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED
|
|
1
|
+
{"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
10
10
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
11
|
-
import { dirname, resolve, sep } from "node:path";
|
|
11
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
12
12
|
// NOTE: kept dependency-free (node builtins only); this file is vendored verbatim into
|
|
13
13
|
// a downstream consumer, so it cannot import the coordEnv helper.
|
|
14
14
|
const MAX_INSTANCE_ID_LENGTH = 128;
|
|
@@ -53,52 +53,171 @@ export function resolveContainedFile(directory, fileName) {
|
|
|
53
53
|
/**
|
|
54
54
|
* Resolve the monorepo root for coord-state purposes.
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `<superproject>/.git/modules/<name>/` to recover the superproject for
|
|
59
|
-
* submodule worktrees. Without that fallback, a TS coord caller running
|
|
60
|
-
* inside a `harn worktree add-submodule` checkout would resolve a different
|
|
61
|
-
* `.harnery/` than the bash hooks and the two layers would silently diverge.
|
|
56
|
+
* Thin alias for `resolveCoordRoot()`; kept because it is the name the CLI
|
|
57
|
+
* command modules and the vendored downstream consumer already import.
|
|
62
58
|
*/
|
|
63
59
|
export function monorepoRoot() {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
return resolveCoordRoot();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* THE coordination-root resolution. Every surface — the hooks, the CLI's reads,
|
|
64
|
+
* and the CLI's canonical emits — resolves through this one function, because a
|
|
65
|
+
* root the two layers disagree about is a root that silently breaks the
|
|
66
|
+
* end-of-turn rules: the hook evaluates `state.status_checked` from the stream
|
|
67
|
+
* it reads, so an emit into a different `.harnery/events.ndjson` is invisible
|
|
68
|
+
* and rule 1/3 blocks a turn that did run `agents status`, with no sequence of
|
|
69
|
+
* CLI commands able to satisfy it.
|
|
70
|
+
*
|
|
71
|
+
* Precedence:
|
|
72
|
+
* 1. `HARNERY_COORD_ROOT_OVERRIDE` — explicit pin (tests, git hooks, and the
|
|
73
|
+
* root every coord-helper spawn pins for its child).
|
|
74
|
+
* 2. `CLAUDE_PROJECT_DIR` — the adapter stating which project it opened. Hook
|
|
75
|
+
* processes inherit the session's *shell* cwd, which follows `cd` into a
|
|
76
|
+
* subdirectory or submodule that may carry a `.harnery/` of its own (or
|
|
77
|
+
* none at all), so the adapter's own statement outranks the cwd walk.
|
|
78
|
+
* 3. The candidate root that already holds THIS session's heartbeat.
|
|
79
|
+
* 4. The nearest enclosing `.harnery/`, then a git-derived root.
|
|
80
|
+
*
|
|
81
|
+
* Step 3 is what makes CLI/hook disagreement structurally impossible rather
|
|
82
|
+
* than a coin flip. Choosing either root unconditionally is wrong in one
|
|
83
|
+
* direction each: preferring the git superproject strands a session whose
|
|
84
|
+
* adapter opened the submodule itself (its heartbeat lives in the submodule,
|
|
85
|
+
* so status/set-task wrote events the hook never read), while walking up from
|
|
86
|
+
* cwd alone strands the opposite case, a session opened on the superproject
|
|
87
|
+
* whose shell has cd'd into a submodule that carries its own `.harnery/`
|
|
88
|
+
* (regression-tested in tests/unit/coord-helper-root-pin.test.ts). The session's
|
|
89
|
+
* own heartbeat settles it: whichever root the hook registered this session in
|
|
90
|
+
* is the root the CLI must use, and the adapter-exported session id needed to
|
|
91
|
+
* recognize it is available to a plain tool-call subprocess even though
|
|
92
|
+
* `CLAUDE_PROJECT_DIR` is not.
|
|
93
|
+
*/
|
|
94
|
+
export function resolveCoordRoot(start = process.cwd()) {
|
|
67
95
|
const rootOverride = process.env.HARNERY_COORD_ROOT_OVERRIDE;
|
|
68
|
-
if (rootOverride)
|
|
96
|
+
if (rootOverride)
|
|
69
97
|
return rootOverride;
|
|
98
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR;
|
|
99
|
+
if (projectDir) {
|
|
100
|
+
const fromProject = nearestCoordRoot(projectDir);
|
|
101
|
+
if (fromProject)
|
|
102
|
+
return fromProject;
|
|
103
|
+
}
|
|
104
|
+
// Nearest-first, so the fallback at the end keeps the historical cwd-walk
|
|
105
|
+
// behavior for a shell that has no registered session at all.
|
|
106
|
+
const ancestors = ancestorCoordRoots(start);
|
|
107
|
+
for (const candidate of ancestors) {
|
|
108
|
+
if (rootKnowsSession(candidate))
|
|
109
|
+
return candidate;
|
|
110
|
+
}
|
|
111
|
+
// Only reached when no enclosing root knows this session: a submodule
|
|
112
|
+
// worktree (`harn worktree add-submodule`) has no ancestor relationship with
|
|
113
|
+
// the superproject that registered the session, so ask git for it.
|
|
114
|
+
const gitRoots = gitCoordRoots(start).filter((r) => !ancestors.includes(r));
|
|
115
|
+
for (const candidate of gitRoots) {
|
|
116
|
+
if (rootKnowsSession(candidate))
|
|
117
|
+
return candidate;
|
|
118
|
+
}
|
|
119
|
+
if (ancestors.length > 0)
|
|
120
|
+
return ancestors[0];
|
|
121
|
+
if (gitRoots.length > 0)
|
|
122
|
+
return gitRoots[0];
|
|
123
|
+
// Nothing carries `.harnery/` yet. Hand back the enclosing checkout so a
|
|
124
|
+
// first run (`harn init`, agent-hook's session.start) has somewhere to
|
|
125
|
+
// create it.
|
|
126
|
+
return gitToplevel(start);
|
|
127
|
+
}
|
|
128
|
+
/** Nearest enclosing directory carrying `.harnery/`, or null. */
|
|
129
|
+
export function nearestCoordRoot(start) {
|
|
130
|
+
return ancestorCoordRoots(start)[0] ?? null;
|
|
131
|
+
}
|
|
132
|
+
/** Every enclosing directory carrying `.harnery/`, nearest first. */
|
|
133
|
+
function ancestorCoordRoots(start) {
|
|
134
|
+
const found = [];
|
|
135
|
+
let dir = resolve(start);
|
|
136
|
+
while (true) {
|
|
137
|
+
if (existsSync(join(dir, ".harnery")))
|
|
138
|
+
found.push(dir);
|
|
139
|
+
const parent = dirname(dir);
|
|
140
|
+
if (parent === dir)
|
|
141
|
+
break;
|
|
142
|
+
dir = parent;
|
|
70
143
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
144
|
+
return found;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Does this root's `.harnery/` already know the process asking?
|
|
148
|
+
*
|
|
149
|
+
* Two discriminators, both genuinely about *this* session: the adapter-exported
|
|
150
|
+
* session id matching a live heartbeat, and a pid-map row on our own ppid
|
|
151
|
+
* chain. Deliberately NOT the single-live-agent fallback that owner resolution
|
|
152
|
+
* ends with — a lone stranger in the wrong root is exactly how `whoami` came to
|
|
153
|
+
* report another agent's name and task as its own.
|
|
154
|
+
*/
|
|
155
|
+
export function rootKnowsSession(root) {
|
|
156
|
+
if (resolveOwnerBySessionEnv(root))
|
|
157
|
+
return true;
|
|
158
|
+
return resolveOwnerByPidmap(root).owner !== null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Git-derived candidate roots for `start`, in preference order, filtered to
|
|
162
|
+
* those that actually carry `.harnery/`.
|
|
163
|
+
*
|
|
164
|
+
* Spawns run with `cwd: start` rather than the process cwd so an injected
|
|
165
|
+
* start dir resolves its own repository — without that, a call about some
|
|
166
|
+
* unrelated directory inherits this process's repo and can resolve a root that
|
|
167
|
+
* has nothing to do with the question asked.
|
|
168
|
+
*/
|
|
169
|
+
function gitCoordRoots(start) {
|
|
170
|
+
const roots = [];
|
|
171
|
+
const push = (value) => {
|
|
172
|
+
if (value && existsSync(join(value, ".harnery")) && !roots.includes(value))
|
|
173
|
+
roots.push(value);
|
|
174
|
+
};
|
|
175
|
+
// Superproject working tree (running from inside a submodule).
|
|
176
|
+
push(gitRevParse(start, "--show-superproject-working-tree"));
|
|
177
|
+
// `--git-common-dir` fallback for submodule worktrees: `git worktree add`
|
|
178
|
+
// inside a submodule produces a worktree whose superproject working tree is
|
|
179
|
+
// empty (the worktree has no submodule relationship of its own), but the
|
|
180
|
+
// common dir points at `<superproject>/.git/modules/<name>/`, so the
|
|
181
|
+
// superproject is recoverable by stripping that suffix.
|
|
182
|
+
const common = gitRevParse(start, "--git-common-dir");
|
|
183
|
+
if (common) {
|
|
184
|
+
const idx = common.indexOf("/.git/modules/");
|
|
185
|
+
if (idx !== -1)
|
|
186
|
+
push(common.substring(0, idx));
|
|
187
|
+
}
|
|
188
|
+
// Top-level (regular checkout).
|
|
189
|
+
push(gitRevParse(start, "--show-toplevel"));
|
|
190
|
+
return roots;
|
|
191
|
+
}
|
|
192
|
+
function gitToplevel(start) {
|
|
193
|
+
return gitRevParse(start, "--show-toplevel");
|
|
194
|
+
}
|
|
195
|
+
function gitRevParse(cwd, flag) {
|
|
196
|
+
const key = `${cwd}${flag}`;
|
|
197
|
+
const cached = gitRevParseCache.get(key);
|
|
198
|
+
if (cached !== undefined)
|
|
199
|
+
return cached;
|
|
200
|
+
let value = null;
|
|
201
|
+
try {
|
|
202
|
+
// `cwd` must exist or the spawn itself fails; callers pass paths that may
|
|
203
|
+
// not (a project dir from a stale env var), so treat any failure as "no
|
|
204
|
+
// answer" rather than letting it throw.
|
|
205
|
+
const r = spawnSync("git", ["rev-parse", flag], { encoding: "utf8", cwd });
|
|
206
|
+
if (r.status === 0)
|
|
207
|
+
value = r.stdout.trim() || null;
|
|
92
208
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
encoding: "utf8",
|
|
96
|
-
});
|
|
97
|
-
if (top.status === 0 && top.stdout.trim() !== "") {
|
|
98
|
-
return top.stdout.trim();
|
|
209
|
+
catch {
|
|
210
|
+
value = null;
|
|
99
211
|
}
|
|
100
|
-
|
|
212
|
+
gitRevParseCache.set(key, value);
|
|
213
|
+
return value;
|
|
101
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Memoized because git spawns are the expensive part of resolution and every
|
|
217
|
+
* CLI command resolves the root many times per invocation. Keyed by (cwd, flag);
|
|
218
|
+
* a repository's identity does not change under a running process.
|
|
219
|
+
*/
|
|
220
|
+
const gitRevParseCache = new Map();
|
|
102
221
|
/** Parse owner from a pid-map row (`owner` or `owner\tplatform`). */
|
|
103
222
|
export function parsePidmapRowOwner(row) {
|
|
104
223
|
const trimmed = row.trim();
|
|
@@ -255,6 +374,29 @@ export function resolveOwnerWithSource() {
|
|
|
255
374
|
return { owner: bySession, source: "session_env" };
|
|
256
375
|
}
|
|
257
376
|
}
|
|
377
|
+
if (!existsSync(resolve(root, ".harnery", "pid-map")))
|
|
378
|
+
return { owner: null, source: "none" };
|
|
379
|
+
const byPidmap = resolveOwnerByPidmap(root);
|
|
380
|
+
if (byPidmap.owner)
|
|
381
|
+
return byPidmap;
|
|
382
|
+
// Last resort: if exactly one agent is live in this coord root, it's
|
|
383
|
+
// unambiguously us — resolve to it. This is what lets the bare `agents
|
|
384
|
+
// status` / `set-task` the stop hook recommends work without a `--session-id`
|
|
385
|
+
// flag in the common single-agent case. With 0 or 2+ live agents it would be
|
|
386
|
+
// a guess, so we stay null and require the explicit flag.
|
|
387
|
+
const singleton = resolveSingleActiveOwner(root);
|
|
388
|
+
if (singleton) {
|
|
389
|
+
return { owner: singleton, source: "active_singleton" };
|
|
390
|
+
}
|
|
391
|
+
return { owner: null, source: "none" };
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Walk our own ppid chain for a pid-map row in ONE given root.
|
|
395
|
+
*
|
|
396
|
+
* Root-parameterized (rather than resolving the root itself) so root resolution
|
|
397
|
+
* can use it as a discriminator without recursing back into itself.
|
|
398
|
+
*/
|
|
399
|
+
export function resolveOwnerByPidmap(root) {
|
|
258
400
|
const pidmapDir = resolve(root, ".harnery", "pid-map");
|
|
259
401
|
if (!existsSync(pidmapDir))
|
|
260
402
|
return { owner: null, source: "none" };
|
|
@@ -279,15 +421,6 @@ export function resolveOwnerWithSource() {
|
|
|
279
421
|
if (fallbackOwner) {
|
|
280
422
|
return { owner: fallbackOwner, source: "pidmap_fallback" };
|
|
281
423
|
}
|
|
282
|
-
// Last resort: if exactly one agent is live in this coord root, it's
|
|
283
|
-
// unambiguously us — resolve to it. This is what lets the bare `agents
|
|
284
|
-
// status` / `set-task` the stop hook recommends work without a `--session-id`
|
|
285
|
-
// flag in the common single-agent case. With 0 or 2+ live agents it would be
|
|
286
|
-
// a guess, so we stay null and require the explicit flag.
|
|
287
|
-
const singleton = resolveSingleActiveOwner(root);
|
|
288
|
-
if (singleton) {
|
|
289
|
-
return { owner: singleton, source: "active_singleton" };
|
|
290
|
-
}
|
|
291
424
|
return { owner: null, source: "none" };
|
|
292
425
|
}
|
|
293
426
|
/**
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Resolve the coordination root.
|
|
3
|
+
*
|
|
4
|
+
* Delegates to `resolveCoordRoot()`. This was a third, independent copy of the
|
|
5
|
+
* walk-up — the kind of duplication that let the hooks and the CLI drift onto
|
|
6
|
+
* different roots in the first place; keep resolution in one place.
|
|
4
7
|
*/
|
|
5
8
|
export declare function findCoordRoot(start?: string): string | null;
|
|
6
9
|
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/core/agents/paths.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/core/agents/paths.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE1E"}
|
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
1
|
+
import { resolveCoordRoot } from "./coord-client.js";
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Resolve the coordination root.
|
|
4
|
+
*
|
|
5
|
+
* Delegates to `resolveCoordRoot()`. This was a third, independent copy of the
|
|
6
|
+
* walk-up — the kind of duplication that let the hooks and the CLI drift onto
|
|
7
|
+
* different roots in the first place; keep resolution in one place.
|
|
6
8
|
*/
|
|
7
9
|
export function findCoordRoot(start = process.cwd()) {
|
|
8
|
-
|
|
9
|
-
while (true) {
|
|
10
|
-
if (existsSync(join(dir, ".harnery")))
|
|
11
|
-
return dir;
|
|
12
|
-
const parent = dirname(dir);
|
|
13
|
-
if (parent === dir)
|
|
14
|
-
return null;
|
|
15
|
-
dir = parent;
|
|
16
|
-
}
|
|
10
|
+
return resolveCoordRoot(start);
|
|
17
11
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Adapter } from "../events/schema.js";
|
|
2
2
|
/**
|
|
3
3
|
* Resolve the adapter firing the hook. The dispatcher binary is always
|
|
4
|
-
* invoked with `--adapter <name>` per the wiring in each adapter's
|
|
5
|
-
* file
|
|
4
|
+
* invoked with `--adapter <name>` per the current wiring in each adapter's
|
|
5
|
+
* settings file. `--harness` remains an accepted alias because settings files
|
|
6
|
+
* written by older releases survive a package upgrade until the consumer
|
|
7
|
+
* re-runs `harn init`. Returns null when neither flag is present; caller falls
|
|
6
8
|
* through to the env-based fallback or skips emission.
|
|
7
9
|
*/
|
|
8
10
|
export declare function detectAdapter(argv: readonly string[]): Adapter | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/detect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/detect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,GAAG,IAAI,CAcrE"}
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { coordEnv } from "../../../lib/env.js";
|
|
2
2
|
/**
|
|
3
3
|
* Resolve the adapter firing the hook. The dispatcher binary is always
|
|
4
|
-
* invoked with `--adapter <name>` per the wiring in each adapter's
|
|
5
|
-
* file
|
|
4
|
+
* invoked with `--adapter <name>` per the current wiring in each adapter's
|
|
5
|
+
* settings file. `--harness` remains an accepted alias because settings files
|
|
6
|
+
* written by older releases survive a package upgrade until the consumer
|
|
7
|
+
* re-runs `harn init`. Returns null when neither flag is present; caller falls
|
|
6
8
|
* through to the env-based fallback or skips emission.
|
|
7
9
|
*/
|
|
8
10
|
export function detectAdapter(argv) {
|
|
9
11
|
for (let i = 0; i < argv.length; i++) {
|
|
10
12
|
const a = argv[i];
|
|
11
|
-
if (a === "--adapter") {
|
|
13
|
+
if (a === "--adapter" || a === "--harness") {
|
|
12
14
|
return validate(argv[i + 1]);
|
|
13
15
|
}
|
|
14
16
|
if (a.startsWith("--adapter=")) {
|
|
15
17
|
return validate(a.slice("--adapter=".length));
|
|
16
18
|
}
|
|
19
|
+
if (a.startsWith("--harness=")) {
|
|
20
|
+
return validate(a.slice("--harness=".length));
|
|
21
|
+
}
|
|
17
22
|
}
|
|
18
|
-
return validate(coordEnv("AGENT_COORD_ADAPTER"));
|
|
23
|
+
return validate(coordEnv("AGENT_COORD_ADAPTER") ?? coordEnv("AGENT_COORD_HARNESS"));
|
|
19
24
|
}
|
|
20
25
|
function validate(v) {
|
|
21
26
|
if (v === "claude-code" || v === "cursor" || v === "codex")
|
package/dist/core/hooks/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
22
22
|
import { dirname, join } from "node:path";
|
|
23
23
|
import { coordEnv } from "../../lib/env.js";
|
|
24
24
|
import { replayCodexJsonl } from "../agents/codex-replay.js";
|
|
25
|
+
import { coordBinPath } from "../agents/coord-bin.js";
|
|
25
26
|
import { consumeSince, writeCursor } from "../agents/events/consume.js";
|
|
26
27
|
import { evaluateStopHook } from "../agents/rules/stop-hook.js";
|
|
27
28
|
import { projectHeartbeats } from "../agents/state/heartbeat-projector.js";
|
|
@@ -43,11 +44,11 @@ function parseArgv(argv) {
|
|
|
43
44
|
const out = { eventName: null, extra: [] };
|
|
44
45
|
for (let i = 0; i < argv.length; i++) {
|
|
45
46
|
const arg = argv[i];
|
|
46
|
-
if (arg === "--adapter") {
|
|
47
|
+
if (arg === "--adapter" || arg === "--harness") {
|
|
47
48
|
i++; // detectAdapter will re-parse; just consume the value here.
|
|
48
49
|
continue;
|
|
49
50
|
}
|
|
50
|
-
if (arg.startsWith("--adapter="))
|
|
51
|
+
if (arg.startsWith("--adapter=") || arg.startsWith("--harness="))
|
|
51
52
|
continue;
|
|
52
53
|
if (!out.eventName && !arg.startsWith("--")) {
|
|
53
54
|
out.eventName = arg;
|
|
@@ -118,7 +119,7 @@ function logError(coordRoot, err, context) {
|
|
|
118
119
|
* separation: we spawn rather than import.
|
|
119
120
|
*/
|
|
120
121
|
function assignNameViaAgentCoord(coordRoot, instanceId, kind) {
|
|
121
|
-
const binary =
|
|
122
|
+
const binary = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
122
123
|
if (!existsSync(binary))
|
|
123
124
|
return null;
|
|
124
125
|
try {
|
|
@@ -620,7 +621,7 @@ async function main() {
|
|
|
620
621
|
// subagent (claude-code + cursor; codex doesn't fan out subagents today).
|
|
621
622
|
if (norm.event_type === "subagent.start") {
|
|
622
623
|
try {
|
|
623
|
-
const agentCoordBin =
|
|
624
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
624
625
|
if (existsSync(agentCoordBin)) {
|
|
625
626
|
spawnSync(agentCoordBin, ["project"], {
|
|
626
627
|
encoding: "utf8",
|
|
@@ -644,7 +645,7 @@ async function main() {
|
|
|
644
645
|
if (norm.event_type === "subagent.stop") {
|
|
645
646
|
try {
|
|
646
647
|
cleanupSessionEnd(coordRoot, owner.instance_id, data.reason ?? "unknown");
|
|
647
|
-
const agentCoordBin =
|
|
648
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
648
649
|
if (existsSync(agentCoordBin)) {
|
|
649
650
|
spawnSync(agentCoordBin, [
|
|
650
651
|
"log",
|
|
@@ -907,7 +908,7 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
|
|
|
907
908
|
.filter((p) => p !== null);
|
|
908
909
|
if (targets.length === 0)
|
|
909
910
|
return;
|
|
910
|
-
const agentCoordBin =
|
|
911
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
911
912
|
if (!existsSync(agentCoordBin))
|
|
912
913
|
return;
|
|
913
914
|
// For apply_patch (multi-file), collect siblings so the deny reason names
|
|
@@ -1008,7 +1009,7 @@ function parseApplyPatchPaths(data) {
|
|
|
1008
1009
|
return out;
|
|
1009
1010
|
}
|
|
1010
1011
|
function healHeartbeatViaCli(coordRoot, instanceId, sessionId, adapter) {
|
|
1011
|
-
const agentCoordBin =
|
|
1012
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1012
1013
|
if (!existsSync(agentCoordBin))
|
|
1013
1014
|
return;
|
|
1014
1015
|
// Pass the detected adapter so a pruned Cursor/Codex heartbeat is recreated
|
|
@@ -1118,7 +1119,7 @@ function refreshPidmap(coordRoot, instanceId, adapter, payloadPid) {
|
|
|
1118
1119
|
writePidmapViaAgentCoord(coordRoot, pid, instanceId, adapterPlatform(adapter));
|
|
1119
1120
|
}
|
|
1120
1121
|
function stampToolActivity(coordRoot, instanceId, data) {
|
|
1121
|
-
const agentCoordBin =
|
|
1122
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1122
1123
|
if (!existsSync(agentCoordBin))
|
|
1123
1124
|
return;
|
|
1124
1125
|
const toolName = data.tool_name ?? "";
|
|
@@ -1188,7 +1189,7 @@ function releaseClaimOnFailure(coordRoot, instanceId, data, rawPayload) {
|
|
|
1188
1189
|
? filePath.slice(coordRoot.length + 1)
|
|
1189
1190
|
: filePath;
|
|
1190
1191
|
}
|
|
1191
|
-
const agentCoordBin =
|
|
1192
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1192
1193
|
if (!existsSync(agentCoordBin))
|
|
1193
1194
|
return;
|
|
1194
1195
|
spawnSync(agentCoordBin, ["release-claim", instanceId, canonical], {
|
|
@@ -1232,13 +1233,13 @@ function cleanupSessionEnd(coordRoot, instanceId, reason) {
|
|
|
1232
1233
|
}
|
|
1233
1234
|
}
|
|
1234
1235
|
// Activity log
|
|
1235
|
-
const agentCoordBin =
|
|
1236
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1236
1237
|
if (existsSync(agentCoordBin)) {
|
|
1237
1238
|
spawnSync(agentCoordBin, ["log", `SESSION_END reason=${reason}`, "--instance", instanceId], { encoding: "utf8", timeout: 2000 });
|
|
1238
1239
|
}
|
|
1239
1240
|
}
|
|
1240
1241
|
async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionId, adapter, recoveryBriefing = "") {
|
|
1241
|
-
const agentCoordBin =
|
|
1242
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1242
1243
|
let additionalContext = "";
|
|
1243
1244
|
if (existsSync(agentCoordBin)) {
|
|
1244
1245
|
// Look up the agent's name from its heartbeat (for council pending rendering).
|
|
@@ -1293,7 +1294,7 @@ function emitSubagentStartContext(coordRoot, instanceId, sessionId, data, adapte
|
|
|
1293
1294
|
// Render peer table inline since the subagent might want to know who else
|
|
1294
1295
|
// is around. Reuse prompt-context (which dedups against the per-owner hash);
|
|
1295
1296
|
// first call will always emit.
|
|
1296
|
-
const agentCoordBin =
|
|
1297
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1297
1298
|
let combined = message;
|
|
1298
1299
|
if (existsSync(agentCoordBin)) {
|
|
1299
1300
|
const result = spawnSync(agentCoordBin, ["prompt-context", "--instance", instanceId, "--session", sessionId, "--name", subagentName], { encoding: "utf8", timeout: 3000 });
|
|
@@ -1313,7 +1314,7 @@ function adapterPlatform(adapter) {
|
|
|
1313
1314
|
return adapter;
|
|
1314
1315
|
}
|
|
1315
1316
|
async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, emittedData, adapter, recoveryBriefing = "") {
|
|
1316
|
-
const agentCoordBin =
|
|
1317
|
+
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1317
1318
|
let additionalContext = "";
|
|
1318
1319
|
if (existsSync(agentCoordBin)) {
|
|
1319
1320
|
// Sync-project so the heartbeat exists for downstream readers (peer table,
|
|
@@ -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;AAMD,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAYrD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAa3D;AAED,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAYpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAqB9E;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 { coordBinPath } from "../../agents/coord-bin.js";
|
|
19
20
|
export { captureImages, imageJanitor } from "./image-capture.js";
|
|
20
21
|
/**
|
|
21
22
|
* Play a notification sound via the cross-platform utility
|
|
@@ -53,8 +54,7 @@ export function soundForEvent(eventName) {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
function harnBin(repoRoot) {
|
|
56
|
-
|
|
57
|
-
return existsSync(bin) ? bin : null;
|
|
57
|
+
return coordBinPath("harn", repoRoot);
|
|
58
58
|
}
|
|
59
59
|
/** Prune stale journal archives + sweep orphans (global, fast). Fire-and-forget. */
|
|
60
60
|
export function journalJanitor(repoRoot) {
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* single coord-root resolution so every adapter agrees on the same root.
|
|
2
|
+
* The hooks-side name for THE coordination-root resolution.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* Delegates to `resolveCoordRoot()` so the hooks and the CLI cannot resolve
|
|
5
|
+
* different roots. They used to: this walked up from `CLAUDE_PROJECT_DIR` or
|
|
6
|
+
* cwd while the CLI's `monorepoRoot()` asked git for the superproject first, so
|
|
7
|
+
* with a shell inside a submodule that carries its own `.harnery/`, the CLI's
|
|
8
|
+
* `state.status_checked` landed in a stream this side never read and rule 1/3
|
|
9
|
+
* blocked every turn.
|
|
10
|
+
*
|
|
11
|
+
* Resolution precedence (see `resolveCoordRoot` for the full rationale):
|
|
6
12
|
* 1. HARNERY_COORD_ROOT_OVERRIDE — explicit pin (tests, git hooks).
|
|
7
|
-
* 2. The adapter's project dir (CLAUDE_PROJECT_DIR) — hook processes
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 3. Walk up from `start` (default cwd).
|
|
13
|
+
* 2. The adapter's project dir (CLAUDE_PROJECT_DIR) — hook processes inherit
|
|
14
|
+
* the session's *shell* cwd, which follows `cd` into subdirectories or
|
|
15
|
+
* submodules that may carry a `.harnery/` of their own (or none at all,
|
|
16
|
+
* e.g. a journal dir under /tmp). The session's coordination home is the
|
|
17
|
+
* project the adapter opened, not wherever the shell happens to sit.
|
|
18
|
+
* 3. The root that already holds this session's heartbeat.
|
|
19
|
+
* 4. The nearest enclosing `.harnery/`, then a git-derived root.
|
|
15
20
|
*/
|
|
16
21
|
export declare function findCoordRoot(start?: string): string | null;
|
|
17
22
|
//# sourceMappingURL=coord-root.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coord-root.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/coord-root.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"coord-root.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/coord-root.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE1E"}
|