/** * The coordination helpers. * * Both surfaces (bash hooks + this TS module) write into the same * `.harnery/active/.json` heartbeat files and `.harnery/pid-map/` * ppid map, so a single coord state can be observed and mutated from * either side without divergence. */ /** * Instance IDs become coordination filenames, so only one portable basename * alphabet is accepted at every filesystem boundary. UUIDs, test IDs, and * legacy hex IDs all fit this contract; separators and dot segments do not. */ export declare function isSafeInstanceId(value: unknown): value is string; export declare function assertSafeInstanceId(value: unknown): asserts value is string; /** * Resolve one direct-child filename beneath a trusted coordination directory. * * This is a second boundary behind the instance-ID allowlist. Normalizing the * candidate and proving its directory prefix prevents traversal even if a new * caller constructs a filename from a different untrusted source. */ export declare function resolveContainedFile(directory: string, fileName: string): string; /** * Resolve the monorepo root for coord-state purposes. * * Thin alias for `resolveCoordRoot()`; kept because it is the name the CLI * command modules and the vendored downstream consumer already import. */ export declare function monorepoRoot(): string | null; /** * THE coordination-root resolution. Every surface — the hooks, the CLI's reads, * and the CLI's canonical emits — resolves through this one function, because a * root the two layers disagree about is a root that silently breaks the * end-of-turn rules: the hook evaluates `coord.status_observed` from the stream * it reads, so an emit into a different V3 ledger root is invisible * and rule 1/3 blocks a turn that did run `agents status`, with no sequence of * CLI commands able to satisfy it. * * Precedence: * 1. `HARNERY_COORD_ROOT_OVERRIDE` — explicit pin (tests, git hooks, and the * root every coord-helper spawn pins for its child). * 2. `CLAUDE_PROJECT_DIR` — the adapter stating which project it opened. Hook * processes inherit the session's *shell* cwd, which follows `cd` into a * subdirectory or submodule that may carry a `.harnery/` of its own (or * none at all), so the adapter's own statement outranks the cwd walk. * 3. The candidate root that already holds THIS session's heartbeat. * 4. The nearest enclosing `.harnery/`, then a git-derived root. * * Step 3 is what makes CLI/hook disagreement structurally impossible rather * than a coin flip. Choosing either root unconditionally is wrong in one * direction each: preferring the git superproject strands a session whose * adapter opened the submodule itself (its heartbeat lives in the submodule, * so status/set-task wrote events the hook never read), while walking up from * cwd alone strands the opposite case, a session opened on the superproject * whose shell has cd'd into a submodule that carries its own `.harnery/` * (regression-tested in tests/unit/coord-helper-root-pin.test.ts). The session's * own heartbeat settles it: whichever root the hook registered this session in * is the root the CLI must use, and the adapter-exported session id needed to * recognize it is available to a plain tool-call subprocess even though * `CLAUDE_PROJECT_DIR` is not. */ export declare function resolveCoordRoot(start?: string): string | null; /** Nearest enclosing directory carrying `.harnery/`, or null. */ export declare function nearestCoordRoot(start: string): string | null; /** * Does this root's `.harnery/` already know the process asking? * * Two discriminators, both genuinely about *this* session: the adapter-exported * session id matching live V3 producer state, and a pid-map row on our own ppid * chain. Deliberately NOT the single-live-agent fallback that owner resolution * ends with — a lone stranger in the wrong root is exactly how `whoami` came to * report another agent's name and task as its own. */ export declare function rootKnowsSession(root: string): boolean; /** Parse owner from a pid-map row (`owner` or `owner\tplatform`). */ export declare function parsePidmapRowOwner(row: string): string; /** Parse platform from a pid-map row; legacy rows default to `claude-code`. */ export declare function parsePidmapRowPlatform(row: string): string; /** Parse the start token from a pid-map row; rows written before it carry none. */ export declare function parsePidmapRowStartToken(row: string): string | undefined; /** * Is the process now holding `pid` the one this row was written for? * * A pid is a number the OS re-issues, and quickly: a `pid_max` of 99999 against * ~100 new processes a second recycles the whole space about every quarter * hour. Believing a row past that point resolves this session to whichever * agent last held the number — which is what made `whoami` report a stranger's * name and files. The start token settles it, since two processes may share a * pid but never a pid and a start instant. * * Deliberately inlined rather than imported from `state/proc-start.ts`: this * file is vendored verbatim into a downstream consumer and stays on node * builtins only. The token is a wire format shared with that module and with * the host's commit guard, so the copies must agree byte for byte; exported so * a test can hold this one against `processStartToken` and fail on drift. */ export declare function pidStartToken(pid: number): string | null; /** * Walk up the ppid chain looking for a pid-map entry. Returns * the resolved instance_id or null. * * Pid-map files are `instance_id` or `instance_id\tplatform` (Cursor Phase 1). * Prefer a row whose platform matches `HARNERY_AGENT_COORD_PLATFORM` (default * `claude-code`); otherwise return the first owner seen on the walk. * * Subagents intentionally do not write pid-map entries; a bare Bash-tool * ppid-walk from inside an unbridged child therefore resolves to the parent's * pid-map entry. Native hook bridges supersede this fallback through the * child session environment. */ export declare function resolveOwner(): string | null; /** * Like `resolveOwner` but also reports which resolution path matched. * Used by `harn agents whoami` to surface the path (`env` / `pidmap`) in * the diagnostic output. Operators trying to debug "why doesn't my * Codex session see itself?" need to know whether `HARNERY_AGENT_COORD_OWNER` * is propagating or the ppid-walk is the load-bearing path. */ export declare function resolveOwnerWithSource(): { owner: string | null; source: "env" | "pidmap" | "pidmap_fallback" | "session_env" | "active_singleton" | "none"; }; /** * The nearest pid-map anchor on our own ppid chain whose start token PROVES the * row still describes the process holding that pid, or null when the nearest * row is unverified, recycled, or absent. * * Nearest matters: in a nested-agent tree (session A spawns a headless session * B whose shell runs this code), both A's and B's adapter processes are * ancestors, and only the innermost one owns this process's tool environment. * Exported for unit testing with an injectable root. */ export declare function nearestVerifiedSessionAnchor(root: string): { owner: string; platform: string; } | null; /** * Walk our own ppid chain for a pid-map row in ONE given root. * * Root-parameterized (rather than resolving the root itself) so root resolution * can use it as a discriminator without recursing back into itself. */ export declare function resolveOwnerByPidmap(root: string): { owner: string | null; source: "pidmap" | "pidmap_fallback" | "none"; }; /** Read normalized candidates from the first non-empty adapter session-id env var. */ /** * First adapter/bridge-stamped session id from the environment, WITHOUT the * live-heartbeat validation resolveOwnerBySessionEnv applies. A fresh session * has no heartbeat until its first set-task, so heartbeat-validated resolution * returns null there by design; commands that REGISTER a session (set-task) * may use this id directly — it carries the same trust as an explicit * `--session-id` argument, because the adapter or connector stamped it. */ export declare function sessionIdentityFromEnv(): string | null; /** * Resolve the owner by joining adapter session environment to the live V3 * hook-producer state. A missing, terminal, or ambiguous generation fails * closed. The disposable cache is consulted only to recover the native * instance label bound to an already-authoritative canonical instance. * * Exported for unit testing with an injectable root. */ export declare function resolveOwnerBySessionEnv(root: string): string | null; /** * `resolveOwnerBySessionEnv` plus WHICH adapter's producer state the session id * joined. The caller uses the adapter to tell a same-adapter conflict (the env * var is stamped fresh per tool call and outranks a possibly-lagging pid-map * row) from a cross-adapter one (the var is inherited context from an outer * session and the verified row wins). */ export declare function resolveOwnerBySessionEnvDetailed(root: string): { owner: string; adapters: string[]; } | null; /** * Return the native instance label of the sole live V3 generation in this * coord root, or null if there are zero or more than one. * * Exported for unit testing with an injectable root (the caller in * `resolveOwnerWithSource` passes `monorepoRoot()`). */ export declare function resolveSingleActiveOwner(root: string): string | null; //# sourceMappingURL=coord-client.d.ts.map