@oh-hai/cli 0.2.0 → 0.2.1
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 +51 -8
- package/dist/auth/resolve-token.d.ts +2 -4
- package/dist/auth/resolve-token.js +4 -11
- package/dist/auth/resolve-token.js.map +1 -1
- package/dist/bindings.d.ts +65 -0
- package/dist/bindings.js +242 -0
- package/dist/bindings.js.map +1 -0
- package/dist/cli.js +171 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/ask.js +21 -17
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/binary-update.d.ts +61 -0
- package/dist/commands/binary-update.js +160 -0
- package/dist/commands/binary-update.js.map +1 -0
- package/dist/commands/context.d.ts +123 -0
- package/dist/commands/doctor.js +30 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/flags.d.ts +14 -2
- package/dist/commands/flags.js +34 -6
- package/dist/commands/flags.js.map +1 -1
- package/dist/commands/handlers.js +6 -0
- package/dist/commands/handlers.js.map +1 -1
- package/dist/commands/inbox.js +129 -33
- package/dist/commands/inbox.js.map +1 -1
- package/dist/commands/messaging/identity.d.ts +9 -0
- package/dist/commands/messaging/identity.js +21 -0
- package/dist/commands/messaging/identity.js.map +1 -1
- package/dist/commands/messaging/validate.d.ts +6 -5
- package/dist/commands/messaging/validate.js +6 -5
- package/dist/commands/messaging/validate.js.map +1 -1
- package/dist/commands/registry.js +71 -8
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/setup.d.ts +17 -0
- package/dist/commands/setup.js +103 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/task.js +21 -24
- package/dist/commands/task.js.map +1 -1
- package/dist/commands/teach.d.ts +13 -0
- package/dist/commands/teach.js +317 -0
- package/dist/commands/teach.js.map +1 -0
- package/dist/commands/update-check.d.ts +49 -0
- package/dist/commands/update-check.js +203 -0
- package/dist/commands/update-check.js.map +1 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +178 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/use.d.ts +2 -0
- package/dist/commands/use.js +99 -0
- package/dist/commands/use.js.map +1 -0
- package/dist/commands/whoami.js +113 -27
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config-file.js +4 -20
- package/dist/config-file.js.map +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.js +39 -3
- package/dist/config.js.map +1 -1
- package/dist/exit-codes.d.ts +6 -0
- package/dist/exit-codes.js +6 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/help.js +4 -1
- package/dist/help.js.map +1 -1
- package/dist/os.d.ts +16 -0
- package/dist/os.js +32 -0
- package/dist/os.js.map +1 -0
- package/dist/url.d.ts +7 -0
- package/dist/url.js +14 -0
- package/dist/url.js.map +1 -1
- package/dist/version.d.ts +11 -3
- package/dist/version.js +20 -3
- package/dist/version.js.map +1 -1
- package/dist/watch-lock.d.ts +85 -0
- package/dist/watch-lock.js +358 -0
- package/dist/watch-lock.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binary-update.js","sourceRoot":"","sources":["../../src/commands/binary-update.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ResolvedConfig } from "../config.js";
|
|
2
2
|
import type { LoadedConfigFiles } from "../config-file.js";
|
|
3
|
+
import type { Binding, BindingsIo } from "../bindings.js";
|
|
3
4
|
import type { CredentialStore, OpenStoreOptions } from "../auth/token-store.js";
|
|
5
|
+
import type { WatchLockIo } from "../watch-lock.js";
|
|
4
6
|
/** Output sink — the same shape `cli.ts` uses, so tests capture stdout/stderr in-process. */
|
|
5
7
|
export interface Io {
|
|
6
8
|
log(line: string): void;
|
|
@@ -19,6 +21,13 @@ export interface HttpResponse {
|
|
|
19
21
|
headers?: {
|
|
20
22
|
get(name: string): string | null;
|
|
21
23
|
};
|
|
24
|
+
/** The raw response body as bytes — read only by the binary self-update (#358), to download the
|
|
25
|
+
* release binary. Optional so a status-only / JSON fake still satisfies the type; the DOM
|
|
26
|
+
* `Response.arrayBuffer` is structurally assignable, so production `fetch` provides it. */
|
|
27
|
+
arrayBuffer?(): Promise<ArrayBuffer>;
|
|
28
|
+
/** The response body as text — read only by the binary self-update (#358), to download the
|
|
29
|
+
* `SHA256SUMS` checksum file. Optional for the same reason as `arrayBuffer`. */
|
|
30
|
+
text?(): Promise<string>;
|
|
22
31
|
}
|
|
23
32
|
/** The subset of `fetch` the CLI uses. `body` carries a JSON request payload (the device-code
|
|
24
33
|
* POSTs); it's optional so a bodyless GET (`whoami --check`) is unaffected. Real `fetch`'s return
|
|
@@ -29,6 +38,84 @@ export type FetchLike = (url: string, init: {
|
|
|
29
38
|
body?: string;
|
|
30
39
|
signal?: AbortSignal;
|
|
31
40
|
}) => Promise<HttpResponse>;
|
|
41
|
+
/** How the running CLI was installed (drives `oh-hai upgrade`, §4 / #343). `dev` is a source or
|
|
42
|
+
* local-dist checkout; `binary` is a compiled standalone; `npm-global` is a global npm install;
|
|
43
|
+
* `unknown` is the safe fallback (generic guidance, no self-mutation). */
|
|
44
|
+
export type InstallMethod = "npm-global" | "binary" | "dev" | "unknown";
|
|
45
|
+
/** The facts `classifyInstall` (update-check.ts) reduces to an `InstallMethod`. Injected via
|
|
46
|
+
* `RuntimeDeps.installFacts` so the classification stays a pure, unit-tested function and the CLI
|
|
47
|
+
* never touches the real process/module layout in a test. */
|
|
48
|
+
export interface InstallFacts {
|
|
49
|
+
/** `process.execPath` — the executable running the CLI: `node`/`bun` when an interpreter runs a
|
|
50
|
+
* built script, or the compiled standalone binary itself (which is what `binary` self-update
|
|
51
|
+
* replaces). Its basename is the signal that distinguishes the two (see `classifyInstall`). */
|
|
52
|
+
execPath: string;
|
|
53
|
+
/** The real (symlink-resolved) on-disk path of the CLI module — npm installs the bin as a symlink. */
|
|
54
|
+
modulePath: string;
|
|
55
|
+
}
|
|
56
|
+
/** Host + filesystem seam for `oh-hai upgrade`'s in-place binary self-update (§4 / #358). The
|
|
57
|
+
* command downloads the release asset via the existing `fetchImpl`; THIS seam owns the host
|
|
58
|
+
* identity (`platform`/`arch` → asset name) and the stage→verify-run→atomic-swap that
|
|
59
|
+
* `packaging/install.sh` performs in shell. Injected so the binary case is unit-testable with zero
|
|
60
|
+
* disk, network, or subprocess — a test passes a fake that records `stage`/`finalize` and returns a
|
|
61
|
+
* canned `verifyRuns`. Production (cli.ts) wires it over `node:fs`/`node:child_process` against
|
|
62
|
+
* `realpath(process.execPath)` (the running standalone). The current binary path is INTERNAL to the
|
|
63
|
+
* production impl — the orchestrator never learns where the binary lives, only that it can stage,
|
|
64
|
+
* verify, finalize, and clean up. */
|
|
65
|
+
export interface BinaryUpdateIo {
|
|
66
|
+
/** `process.platform` — mapped to the asset OS token (`win32`→`windows`, else as-is). */
|
|
67
|
+
platform: NodeJS.Platform;
|
|
68
|
+
/** `process.arch` — mapped to the asset arch token (`x64`/`arm64`; anything else is unsupported). */
|
|
69
|
+
arch: string;
|
|
70
|
+
/** Write `bytes` as an executable file (mode 0o755) BESIDE the running binary — same filesystem, so
|
|
71
|
+
* the later `finalize` rename is atomic — and return the staged path. Throws on a write failure. */
|
|
72
|
+
stage(bytes: Uint8Array): string;
|
|
73
|
+
/** Run `<stagedPath> --version`; true iff it exits 0. Guards against an authentic-but-unrunnable
|
|
74
|
+
* binary replacing a working install (e.g. a glibc build on musl) — the checksum proves the bytes
|
|
75
|
+
* are genuine, not that they execute here. Only ever called AFTER checksum verification. */
|
|
76
|
+
verifyRuns(stagedPath: string): boolean;
|
|
77
|
+
/** Atomically replace the running binary with the staged file (a same-dir rename on POSIX; on
|
|
78
|
+
* Windows the live `.exe` is locked, so rename the current binary aside then move the staged one
|
|
79
|
+
* into place). Throws on failure, leaving the current install untouched. */
|
|
80
|
+
finalize(stagedPath: string): void;
|
|
81
|
+
/** Best-effort delete of the staged file on an aborted update (checksum mismatch, unrunnable
|
|
82
|
+
* binary, or a finalize failure). Never throws. */
|
|
83
|
+
cleanup(stagedPath: string): void;
|
|
84
|
+
}
|
|
85
|
+
/** The captured result of a subprocess run (mirrors auth/keychain.ts's ProcResult). `spawnError` is
|
|
86
|
+
* set (e.g. `"ENOENT"`) when the binary is missing; `code` is null on a spawn failure. */
|
|
87
|
+
export interface CommandResult {
|
|
88
|
+
code: number | null;
|
|
89
|
+
stdout: string;
|
|
90
|
+
stderr: string;
|
|
91
|
+
spawnError?: string;
|
|
92
|
+
}
|
|
93
|
+
/** The result of inspecting a teach target. `absent` = no such file (safe to create); `file` = a regular
|
|
94
|
+
* file we read (safe to update); `unsafe` = it exists but must NOT be read or written through — a symlink
|
|
95
|
+
* (writing would follow it OUTSIDE the repo), a non-regular file (FIFO/device/dir — reading could hang),
|
|
96
|
+
* or an existing file that failed to read for a non-ENOENT reason (treating it as absent would let a later
|
|
97
|
+
* write TRUNCATE it). The three states are distinguished so teach never conflates "unreadable" with
|
|
98
|
+
* "missing" — the difference is the line between a safe create and silent data loss. */
|
|
99
|
+
export type TeachRead = {
|
|
100
|
+
kind: "absent";
|
|
101
|
+
} | {
|
|
102
|
+
kind: "file";
|
|
103
|
+
content: string;
|
|
104
|
+
} | {
|
|
105
|
+
kind: "unsafe";
|
|
106
|
+
reason: string;
|
|
107
|
+
};
|
|
108
|
+
/** Filesystem seam for `oh-hai teach` (§4.9) — the only command that writes arbitrary files (the
|
|
109
|
+
* agent-instruction files it manages). Injected so the teach tests run against an in-memory tree with
|
|
110
|
+
* zero disk access, mirroring `ConfigFileIo` (config-file.ts). `read` classifies a target (see
|
|
111
|
+
* `TeachRead`); `writeFile` creates any missing parent directories. `cwd`/`homedir` are the fixed anchors
|
|
112
|
+
* targets are joined onto — the project root (cwd) and the user's home (`--global`). */
|
|
113
|
+
export interface TeachFs {
|
|
114
|
+
read(path: string): TeachRead;
|
|
115
|
+
writeFile(path: string, content: string): void;
|
|
116
|
+
cwd: string;
|
|
117
|
+
homedir: string;
|
|
118
|
+
}
|
|
32
119
|
/** Side-effecting dependencies, injected so command tests stay hermetic. */
|
|
33
120
|
export interface RuntimeDeps {
|
|
34
121
|
/** Open the credential store (selects keychain vs `0600` file fallback). */
|
|
@@ -43,6 +130,15 @@ export interface RuntimeDeps {
|
|
|
43
130
|
fetchImpl: FetchLike;
|
|
44
131
|
/** Load the user-global + per-project config files (§6). Injected so tests never read real disk. */
|
|
45
132
|
loadConfigFiles: () => LoadedConfigFiles;
|
|
133
|
+
/** Load the user-side directory→agent bindings (bindings.ts, §6). The entrypoint feeds the result
|
|
134
|
+
* into `resolveConfig` for account resolution. Optional so an existing hermetic caller that never
|
|
135
|
+
* touches bindings (and doesn't set it) resolves as if there were none — the entrypoint treats an
|
|
136
|
+
* absent loader as an empty binding set. */
|
|
137
|
+
loadBindings?: () => Binding[];
|
|
138
|
+
/** Filesystem seam for `oh-hai use` — the command that READS/WRITES bindings and needs the process
|
|
139
|
+
* cwd (the directory it binds). Optional so no existing hermetic caller needs the field; production
|
|
140
|
+
* wires `nodeBindingsIo()`, and the `use` handler errors clearly if it's absent when `use` runs. */
|
|
141
|
+
bindingsIo?: BindingsIo;
|
|
46
142
|
/** Sleep `ms` milliseconds between device-code polls (§4.3). Injected so tests don't actually
|
|
47
143
|
* wait — a no-op keeps the happy path instant, and a recording sleep lets a test assert the
|
|
48
144
|
* `slow_down` backoff grew the interval. */
|
|
@@ -64,6 +160,33 @@ export interface RuntimeDeps {
|
|
|
64
160
|
* ever blocks on a prompt by accident. Distinct from `readLine`, which merely READS a line — its
|
|
65
161
|
* presence says nothing about whether a human is there to answer. */
|
|
66
162
|
isInteractive?: () => boolean;
|
|
163
|
+
/** Filesystem seam for `oh-hai teach` (§4.9). Optional so no existing hermetic caller needs the field;
|
|
164
|
+
* production wires `nodeTeachFs()` (teach.ts) and the handler throws a clear internal error if it's
|
|
165
|
+
* absent when teach actually runs. */
|
|
166
|
+
teachFs?: TeachFs;
|
|
167
|
+
/** Lock seam for the `inbox watch` concurrent-drain guard (#323). Optional so existing hermetic
|
|
168
|
+
* callers compile unchanged — when absent, the guard is skipped entirely (today's behavior).
|
|
169
|
+
* Production wires `nodeWatchLockIo()` (watch-lock.ts) so the guard is active in the shipped CLI. */
|
|
170
|
+
watchLockIo?: WatchLockIo;
|
|
171
|
+
/** How the CLI was installed, for `oh-hai upgrade` (#343). Optional so hermetic callers compile
|
|
172
|
+
* unchanged — when absent, `upgrade` classifies the install as `unknown` and gives generic
|
|
173
|
+
* guidance. Production wires the real process/module facts (cli.ts). */
|
|
174
|
+
installFacts?: InstallFacts;
|
|
175
|
+
/** Run a subprocess and capture its result, for `oh-hai upgrade` → `npm i -g @oh-hai/cli@latest`
|
|
176
|
+
* (#343). Optional so hermetic callers compile unchanged — when absent (or under a pipe / --json)
|
|
177
|
+
* `upgrade` PRINTS the command instead of spawning. Production wires a `spawn`-based runner (cli.ts). */
|
|
178
|
+
runCommand?: (file: string, args: string[]) => Promise<CommandResult>;
|
|
179
|
+
/** Whether stdout is a TTY, for `oh-hai upgrade`'s auto-run gate (#343/#349). `isInteractive` (stdin
|
|
180
|
+
* TTY) alone isn't enough: `oh-hai upgrade | tee log` has a TTY stdin but a piped stdout, and the
|
|
181
|
+
* spec promises a pipe PRINTS the command rather than mutating a global install. Optional — an
|
|
182
|
+
* omitting caller is treated as a TTY stdout (hermetic tests that only set isInteractive assume a
|
|
183
|
+
* terminal); production wires `process.stdout.isTTY`. */
|
|
184
|
+
isStdoutTty?: () => boolean;
|
|
185
|
+
/** Host + fs seam for `oh-hai upgrade`'s in-place binary self-update (#358). Optional so hermetic
|
|
186
|
+
* callers compile unchanged — when absent (or under `--json`), the `binary` case emits installer
|
|
187
|
+
* guidance instead of mutating (machine mode / an unwired caller must not swap the binary).
|
|
188
|
+
* Production wires the real download-and-replace over node fs/child_process (cli.ts). */
|
|
189
|
+
binaryUpdateIo?: BinaryUpdateIo;
|
|
67
190
|
}
|
|
68
191
|
export interface CommandContext {
|
|
69
192
|
io: Io;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -17,11 +17,13 @@ import { resolveIdentity } from "../auth/resolve-token.js";
|
|
|
17
17
|
import { buildOk, serializeEnvelope } from "../envelope.js";
|
|
18
18
|
import { ExitCode } from "../exit-codes.js";
|
|
19
19
|
import { sanitizeForTerminal } from "../terminal.js";
|
|
20
|
+
import { cliVersion } from "../version.js";
|
|
20
21
|
import { flagOn, parseCommandArgs } from "./flags.js";
|
|
21
22
|
import { buildNotify } from "./messaging/build.js";
|
|
22
23
|
import { submitEnvelope } from "./messaging/http.js";
|
|
23
24
|
import { resolveSubmitIdentity } from "./messaging/identity.js";
|
|
24
25
|
import { resolveRunId } from "./messaging/shared.js";
|
|
26
|
+
import { checkForUpdate } from "./update-check.js";
|
|
25
27
|
/** Default per-request bound for the network probes when no --timeout/MA2H_TIMEOUT_MS is set —
|
|
26
28
|
* §9 promises offline is an explicit state, never a silent hang. Mirrors whoami's checkToken. */
|
|
27
29
|
const DEFAULT_PROBE_TIMEOUT_MS = 10_000;
|
|
@@ -96,6 +98,24 @@ async function testNotifyCheck(ctx) {
|
|
|
96
98
|
return { name: "test-notify", status: "warn", detail: `could not send test notify — ${message}` };
|
|
97
99
|
}
|
|
98
100
|
}
|
|
101
|
+
/** Advisory "update available" check (§4.6, #343). Best-effort and network-based: a `warn` when a
|
|
102
|
+
* newer @oh-hai/cli is published, a `pass` when current. Returns `undefined` on ANY registry
|
|
103
|
+
* failure so doctor omits the line entirely — connectivity already reports offline, so a second
|
|
104
|
+
* network warn would be noise — and the exit code is never touched (advisory carries no exitCode). */
|
|
105
|
+
async function updateCheck(ctx) {
|
|
106
|
+
const current = cliVersion();
|
|
107
|
+
const result = await checkForUpdate(current, ctx.runtime.fetchImpl, probeTimeoutMs(ctx));
|
|
108
|
+
if (result.error !== undefined)
|
|
109
|
+
return undefined;
|
|
110
|
+
if (result.updateAvailable) {
|
|
111
|
+
return {
|
|
112
|
+
name: "update",
|
|
113
|
+
status: "warn",
|
|
114
|
+
detail: `update available: v${current} → v${result.latest ?? "?"} — run \`oh-hai upgrade\``,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return { name: "update", status: "pass", detail: `up to date (v${current})` };
|
|
118
|
+
}
|
|
99
119
|
/** Emit the collected checks: the uniform --json envelope (data.checks, exactly name/status/detail),
|
|
100
120
|
* or a human table with a per-status marker. Details are sanitized on BOTH paths (§5.4): some carry
|
|
101
121
|
* Hub-supplied strings (a device-code-login discovered agent id, a --test-notify ack id), and
|
|
@@ -222,9 +242,16 @@ export async function doctorCommand(ctx) {
|
|
|
222
242
|
: "OS keychain unavailable — the 0600 file fallback would be used (§5.2)";
|
|
223
243
|
checks.push({ name: "keychain", status: "warn", detail });
|
|
224
244
|
}
|
|
225
|
-
// 4. Hub connectivity (ADVISORY
|
|
226
|
-
|
|
227
|
-
//
|
|
245
|
+
// 4-5. Hub connectivity + the update check (both ADVISORY, and hitting independent hosts — the Hub
|
|
246
|
+
// vs registry.npmjs.org) run concurrently, so a slow registry never serializes behind a slow
|
|
247
|
+
// Hub on every doctor run. Order is preserved by pushing connectivity first; the update line is
|
|
248
|
+
// omitted entirely on any registry failure (#343).
|
|
249
|
+
const [connectivity, update] = await Promise.all([probeConnectivity(ctx, token), updateCheck(ctx)]);
|
|
250
|
+
checks.push(connectivity);
|
|
251
|
+
if (update !== undefined) {
|
|
252
|
+
checks.push(update);
|
|
253
|
+
}
|
|
254
|
+
// 6. test-notify (ADVISORY, opt-in).
|
|
228
255
|
if (testNotify) {
|
|
229
256
|
checks.push(await testNotifyCheck(ctx));
|
|
230
257
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,2FAA2F;AAC3F,6FAA6F;AAC7F,kGAAkG;AAClG,+FAA+F;AAC/F,yFAAyF;AACzF,+FAA+F;AAC/F,gGAAgG;AAChG,2DAA2D;AAC3D,EAAE;AACF,mGAAmG;AACnG,8FAA8F;AAC9F,oGAAoG;AACpG,mGAAmG;AACnG,6CAA6C;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/flags.d.ts
CHANGED
|
@@ -10,8 +10,20 @@ export declare const GLOBAL_OPTIONS: OptionsConfig;
|
|
|
10
10
|
* are on; `--json=false` / `0` / `no` are off. A repeated flag becomes an array — last wins.
|
|
11
11
|
*/
|
|
12
12
|
export declare function flagOn(value: unknown): boolean;
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Like `flagOn`, but REJECTS an unrecognized value (usage error) instead of silently treating it as
|
|
15
|
+
* off. For a SAFETY flag where a typo'd value must not fall through to the unsafe path — e.g.
|
|
16
|
+
* `oh-hai upgrade --check=treu` must fail loudly, not run the live `npm i -g` (#349 review). Bare
|
|
17
|
+
* presence and clean truthy values are on; explicit `false`/`0`/`no`/`off` are off; anything else
|
|
18
|
+
* throws. The raw value is not echoed (it's user argv, and the message stays control-char-free).
|
|
19
|
+
*/
|
|
20
|
+
export declare function flagOnStrict(value: unknown, flag: string): boolean;
|
|
21
|
+
/** Parse argv for a command with no subcommand operand (the auth commands). `maxOperands` (default 0)
|
|
22
|
+
* allows a command to accept that many positional operands BEYOND the command token — `oh-hai use
|
|
23
|
+
* <agent>` passes 1 so a single free-form operand is accepted while a second stray one still errors. */
|
|
24
|
+
export declare function parseCommandArgs(argv: string[], extra?: OptionsConfig, opts?: {
|
|
25
|
+
maxOperands?: number;
|
|
26
|
+
}): ReturnType<typeof parseArgs>;
|
|
15
27
|
/** Parse argv for a command that takes a SUBCOMMAND operand (`ask`/`task` → submit | await).
|
|
16
28
|
* Like `parseCommandArgs` (global + `extra` options, `strict:false` so the `--flag=value`
|
|
17
29
|
* boolean form isn't rejected, then reject any unknown flag), but `positionals[0]` is the
|
package/dist/commands/flags.js
CHANGED
|
@@ -36,6 +36,30 @@ export function flagOn(value) {
|
|
|
36
36
|
}
|
|
37
37
|
return false;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Like `flagOn`, but REJECTS an unrecognized value (usage error) instead of silently treating it as
|
|
41
|
+
* off. For a SAFETY flag where a typo'd value must not fall through to the unsafe path — e.g.
|
|
42
|
+
* `oh-hai upgrade --check=treu` must fail loudly, not run the live `npm i -g` (#349 review). Bare
|
|
43
|
+
* presence and clean truthy values are on; explicit `false`/`0`/`no`/`off` are off; anything else
|
|
44
|
+
* throws. The raw value is not echoed (it's user argv, and the message stays control-char-free).
|
|
45
|
+
*/
|
|
46
|
+
export function flagOnStrict(value, flag) {
|
|
47
|
+
if (value === true) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === "string") {
|
|
51
|
+
const v = value.trim().toLowerCase();
|
|
52
|
+
if (v === "" || v === "true" || v === "1" || v === "yes" || v === "on")
|
|
53
|
+
return true;
|
|
54
|
+
if (v === "false" || v === "0" || v === "no" || v === "off")
|
|
55
|
+
return false;
|
|
56
|
+
throw new CliError("usage", `--${flag} is a boolean flag: pass it bare or as --${flag}=false (unrecognized value).`);
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
return value.length > 0 && flagOnStrict(value[value.length - 1], flag);
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
39
63
|
/** Parse argv against the global flags plus the command's own `extra` options with `strict: false`
|
|
40
64
|
* (so the `--flag=value` boolean form the CLI supports isn't rejected), then reject any flag NOT
|
|
41
65
|
* in the known set — so a typo like `oh-hai whoami --chek` is a usage error (exit 2), not silently
|
|
@@ -67,13 +91,17 @@ function parseKnownFlags(argv, extra) {
|
|
|
67
91
|
}
|
|
68
92
|
return parsed;
|
|
69
93
|
}
|
|
70
|
-
/** Parse argv for a command with no subcommand operand (the auth commands).
|
|
71
|
-
|
|
94
|
+
/** Parse argv for a command with no subcommand operand (the auth commands). `maxOperands` (default 0)
|
|
95
|
+
* allows a command to accept that many positional operands BEYOND the command token — `oh-hai use
|
|
96
|
+
* <agent>` passes 1 so a single free-form operand is accepted while a second stray one still errors. */
|
|
97
|
+
export function parseCommandArgs(argv, extra = {}, opts = {}) {
|
|
72
98
|
const parsed = parseKnownFlags(argv, extra);
|
|
73
|
-
// positionals[0] is the command token;
|
|
74
|
-
// stray one (`oh-hai logout all`, `oh-hai whoami check`) is a typo, not a silently-ignored arg.
|
|
75
|
-
|
|
76
|
-
|
|
99
|
+
// positionals[0] is the command token; most auth commands take no operands (maxOperands 0), so a
|
|
100
|
+
// stray one (`oh-hai logout all`, `oh-hai whoami check`) is a typo, not a silently-ignored arg. A
|
|
101
|
+
// command that DOES take an operand (`use <agent>`) raises the ceiling.
|
|
102
|
+
const ceiling = 1 + (opts.maxOperands ?? 0);
|
|
103
|
+
if (parsed.positionals.length > ceiling) {
|
|
104
|
+
throw new CliError("usage", `unexpected argument: ${parsed.positionals[ceiling]}. Run 'oh-hai <command> --help'.`);
|
|
77
105
|
}
|
|
78
106
|
return parsed;
|
|
79
107
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flags.js","sourceRoot":"","sources":["../../src/commands/flags.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"flags.js","sourceRoot":"","sources":["../../src/commands/flags.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,17 +12,23 @@ import { logoutCommand } from "./logout.js";
|
|
|
12
12
|
import { notifyCommand } from "./notify.js";
|
|
13
13
|
import { setupCommand } from "./setup.js";
|
|
14
14
|
import { taskCommand } from "./task.js";
|
|
15
|
+
import { teachCommand } from "./teach.js";
|
|
16
|
+
import { upgradeCommand } from "./upgrade.js";
|
|
17
|
+
import { useCommand } from "./use.js";
|
|
15
18
|
import { whoamiCommand } from "./whoami.js";
|
|
16
19
|
export const COMMAND_HANDLERS = {
|
|
17
20
|
setup: setupCommand,
|
|
18
21
|
login: loginCommand,
|
|
19
22
|
logout: logoutCommand,
|
|
20
23
|
whoami: whoamiCommand,
|
|
24
|
+
use: useCommand,
|
|
21
25
|
notify: notifyCommand,
|
|
22
26
|
ask: askCommand,
|
|
23
27
|
task: taskCommand,
|
|
24
28
|
agents: agentsCommand,
|
|
25
29
|
inbox: inboxCommand,
|
|
26
30
|
doctor: doctorCommand,
|
|
31
|
+
teach: teachCommand,
|
|
32
|
+
upgrade: upgradeCommand,
|
|
27
33
|
};
|
|
28
34
|
//# sourceMappingURL=handlers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/commands/handlers.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,4FAA4F;AAC5F,2FAA2F;AAC3F,uFAAuF;AACvF,2FAA2F;AAE3F,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,MAAM,gBAAgB,GAAmC;IAC9D,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/commands/handlers.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,4FAA4F;AAC5F,2FAA2F;AAC3F,uFAAuF;AACvF,2FAA2F;AAE3F,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,MAAM,gBAAgB,GAAmC;IAC9D,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,UAAU;IACf,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,OAAO,EAAE,cAAc;CACxB,CAAC"}
|
package/dist/commands/inbox.js
CHANGED
|
@@ -12,20 +12,27 @@
|
|
|
12
12
|
// loop's. The signed delivery `{ directive, signature }` is forwarded VERBATIM so a runtime that
|
|
13
13
|
// wants defense-in-depth can verify it against the Hub key. In-session `id` dedup (§13.4) IS handled
|
|
14
14
|
// here (see the loop). Sibling subcommands (`inbox list` / `inbox ack`) and long-poll are out of scope.
|
|
15
|
-
import {
|
|
15
|
+
import { originOf } from "../auth/resolve-token.js";
|
|
16
|
+
import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
|
|
17
|
+
import { acquireWatchLock } from "../watch-lock.js";
|
|
16
18
|
import { parseSubcommandArgs } from "./flags.js";
|
|
17
19
|
import { ackInbox, drainInbox, redactToken } from "./messaging/http.js";
|
|
18
|
-
import {
|
|
20
|
+
import { requireWatchIdentity } from "./messaging/identity.js";
|
|
19
21
|
import { positiveInt, sanitizeForTerminal, strictBoolFlag, stringFlag } from "./messaging/shared.js";
|
|
20
22
|
const SUBCOMMANDS = ["watch"];
|
|
21
23
|
const INBOX_WATCH_OPTIONS = {
|
|
22
24
|
interval: { type: "string" },
|
|
23
25
|
max: { type: "string" },
|
|
24
26
|
once: { type: "boolean" },
|
|
27
|
+
force: { type: "boolean" },
|
|
25
28
|
};
|
|
26
29
|
/** Default poll cadence (ms). Well under the Hub's 90s presence-freshness window so the agent stays
|
|
27
30
|
* `online` between polls, and responsive to arriving directives without hammering the Hub. */
|
|
28
31
|
const DEFAULT_INTERVAL_MS = 5000;
|
|
32
|
+
/** Consecutive failed heartbeats before the guard warns (#323). Past a few misses the lock's heartbeat
|
|
33
|
+
* is frozen and a peer may reclaim it while this watcher keeps draining — a silent double-drain we
|
|
34
|
+
* surface once. Above 1 so a single transient write blip doesn't cry wolf. */
|
|
35
|
+
const HEARTBEAT_FAILURE_WARN_THRESHOLD = 3;
|
|
29
36
|
/** A sane upper bound for `--max` (a drain batch size, not a timer): generous enough to never reject a
|
|
30
37
|
* realistic mailbox drain, small enough to reject an absurd `?max=` — the Hub clamps to its own cap
|
|
31
38
|
* regardless. Distinct from the timer ceiling `--interval` uses. */
|
|
@@ -42,9 +49,17 @@ async function inboxWatch(ctx, flags) {
|
|
|
42
49
|
// Strict boolean: a mistyped `--once=treu` is a usage error, not a silent fall-through to the
|
|
43
50
|
// infinite watch loop (it would keep draining + acking every interval instead of a one-shot).
|
|
44
51
|
const once = strictBoolFlag(flags.once, "--once");
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
// `--force` takes over a lock held by another watcher for this identity (see the guard below).
|
|
53
|
+
const force = strictBoolFlag(flags.force, "--force");
|
|
54
|
+
// The mailbox is selected by the bearer (the Hub derives the agent from the token), so the drain
|
|
55
|
+
// needs only a token — like `await`. But the concurrent-drain guard keys on the AGENT ID (the mailbox
|
|
56
|
+
// is per-agent, not per-token: one agent can hold several bearers), so resolve both here.
|
|
57
|
+
const { token, agentId } = await requireWatchIdentity(ctx);
|
|
58
|
+
// Concurrent-drain guard (#323): `inbox watch` is a DESTRUCTIVE drain+ack, so two watchers sharing
|
|
59
|
+
// one identity race to consume the same directive. Acquire an agent-scoped lock before the loop,
|
|
60
|
+
// refuse a live second watcher (unless --force), and release on exit. Skipped when no lock seam is
|
|
61
|
+
// wired (hermetic callers) — production always wires one, so the guard is active in the shipped CLI.
|
|
62
|
+
const lock = acquireWatchGuard(ctx, agentId, intervalMs, force);
|
|
48
63
|
// Cross-poll dedup (§13.4): once a directive id has been emitted AND its batch acked, a later
|
|
49
64
|
// redelivery of that same id (fresh t/jti, after a crash or the server's visibility timeout) is
|
|
50
65
|
// suppressed from re-emission — but still acked so the Hub consumes it. `seen` commits only AFTER a
|
|
@@ -54,36 +69,117 @@ async function inboxWatch(ctx, flags) {
|
|
|
54
69
|
// set (dp-007), the runtime's concern, not this loop's. A long-lived watch grows `seen` with
|
|
55
70
|
// directive volume; acceptable for v1.
|
|
56
71
|
const seen = new Set();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
// Track sustained heartbeat failure so a frozen-heartbeat watcher warns instead of silently letting a
|
|
73
|
+
// peer reclaim its identity mid-drain (#323). Reset on any successful heartbeat.
|
|
74
|
+
let heartbeatFailures = 0;
|
|
75
|
+
let heartbeatWarned = false;
|
|
76
|
+
try {
|
|
77
|
+
for (;;) {
|
|
78
|
+
// Refresh the heartbeat each poll so a live watcher isn't seen as stale, while a crashed one
|
|
79
|
+
// ages out and becomes reclaimable. Best-effort (a failed write never crashes the loop) — but a
|
|
80
|
+
// SUSTAINED failure means our heartbeat is frozen; warn once so the operator knows the guard may
|
|
81
|
+
// have silently degraded and a second watcher could now be draining too.
|
|
82
|
+
if (lock !== undefined && !lock.heartbeat()) {
|
|
83
|
+
heartbeatFailures += 1;
|
|
84
|
+
if (!heartbeatWarned && heartbeatFailures >= HEARTBEAT_FAILURE_WARN_THRESHOLD) {
|
|
85
|
+
heartbeatWarned = true;
|
|
86
|
+
ctx.io.err(`⚠️ oh-hai: the watch-lock heartbeat has failed ${heartbeatFailures}× in a row (state dir ` +
|
|
87
|
+
`unwritable?) — another watcher may reclaim this identity while this one keeps draining.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
heartbeatFailures = 0;
|
|
92
|
+
heartbeatWarned = false;
|
|
93
|
+
}
|
|
94
|
+
const { directives } = await drainInbox(ctx, token, max);
|
|
95
|
+
// The unique ids in THIS drain — both the ack list and the within-batch emit guard (a Hub could
|
|
96
|
+
// repeat an id inside one batch; emit it once and ack it once, not twice).
|
|
97
|
+
const batchIds = new Set();
|
|
98
|
+
for (const delivery of directives) {
|
|
99
|
+
const id = delivery.directive.id;
|
|
100
|
+
if (!seen.has(id) && !batchIds.has(id))
|
|
101
|
+
emitDirective(ctx, delivery, token);
|
|
102
|
+
batchIds.add(id);
|
|
103
|
+
}
|
|
104
|
+
if (batchIds.size > 0) {
|
|
105
|
+
// Flush stdout BEFORE acking so the emit-then-ack handoff is genuinely at-least-once: `io.log`
|
|
106
|
+
// only queues to stdout, so when `watch` is piped to a runtime under backpressure the bytes may
|
|
107
|
+
// still be buffered in this process. Acking first would let the Hub consume a directive that a
|
|
108
|
+
// crash/EPIPE could still lose before it reaches the consumer. `flushOutput` waits for the
|
|
109
|
+
// queued writes to complete (a write-callback barrier, not just the drain flag); THEN ack.
|
|
110
|
+
await ctx.runtime.flushOutput?.();
|
|
111
|
+
// The single batched consume-ack (§14) — one call per drained batch, deduped ids.
|
|
112
|
+
await ackInbox(ctx, token, [...batchIds]);
|
|
113
|
+
// Commit to the cross-poll dedup set only now that the batch is durably consumed.
|
|
114
|
+
for (const id of batchIds)
|
|
115
|
+
seen.add(id);
|
|
116
|
+
}
|
|
117
|
+
// --once: a single drain→emit→ack pass (cron / scripting / deterministic tests). Otherwise loop;
|
|
118
|
+
// the poll itself IS the presence heartbeat, so keep polling on the interval even on an empty drain.
|
|
119
|
+
if (once)
|
|
120
|
+
return;
|
|
121
|
+
await ctx.runtime.sleep(intervalMs);
|
|
67
122
|
}
|
|
68
|
-
if (batchIds.size > 0) {
|
|
69
|
-
// Flush stdout BEFORE acking so the emit-then-ack handoff is genuinely at-least-once: `io.log`
|
|
70
|
-
// only queues to stdout, so when `watch` is piped to a runtime under backpressure the bytes may
|
|
71
|
-
// still be buffered in this process. Acking first would let the Hub consume a directive that a
|
|
72
|
-
// crash/EPIPE could still lose before it reaches the consumer. `flushOutput` waits for the
|
|
73
|
-
// queued writes to complete (a write-callback barrier, not just the drain flag); THEN ack.
|
|
74
|
-
await ctx.runtime.flushOutput?.();
|
|
75
|
-
// The single batched consume-ack (§14) — one call per drained batch, deduped ids.
|
|
76
|
-
await ackInbox(ctx, token, [...batchIds]);
|
|
77
|
-
// Commit to the cross-poll dedup set only now that the batch is durably consumed.
|
|
78
|
-
for (const id of batchIds)
|
|
79
|
-
seen.add(id);
|
|
80
|
-
}
|
|
81
|
-
// --once: a single drain→emit→ack pass (cron / scripting / deterministic tests). Otherwise loop;
|
|
82
|
-
// the poll itself IS the presence heartbeat, so keep polling on the interval even on an empty drain.
|
|
83
|
-
if (once)
|
|
84
|
-
return;
|
|
85
|
-
await ctx.runtime.sleep(intervalMs);
|
|
86
123
|
}
|
|
124
|
+
finally {
|
|
125
|
+
// Release on every exit path (--once return, a thrown drain/ack error, the loop's stop). Deletes
|
|
126
|
+
// the lockfile only if it is still ours, so a reclaimer's lock is never removed. An abrupt SIGKILL
|
|
127
|
+
// skips this, but the dead pid then makes the lock reclaimable — the stale mechanism is the net.
|
|
128
|
+
lock?.release();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Acquire the per-agent watch lock (#323) and translate the outcome into user-facing behavior:
|
|
133
|
+
* refuse a live second watcher (`already_running`, exit 10), a `--force` steal warning, a
|
|
134
|
+
* stale-reclaim note, or an "unavailable" warning when no lock dir resolves — or when the agent id
|
|
135
|
+
* can't be resolved (the guard keys on the agent id, never the bearer). Returns the live handle to
|
|
136
|
+
* heartbeat/release, or undefined when there is no handle to manage (guard unavailable, or no lock
|
|
137
|
+
* seam wired — the hermetic-test path). All human notes go to stderr so the `--json` NDJSON stdout
|
|
138
|
+
* stream stays clean.
|
|
139
|
+
*/
|
|
140
|
+
function acquireWatchGuard(ctx, agentId, intervalMs, force) {
|
|
141
|
+
const io = ctx.runtime.watchLockIo;
|
|
142
|
+
if (io === undefined)
|
|
143
|
+
return undefined; // no seam wired → guard skipped (existing behavior)
|
|
144
|
+
if (agentId === undefined) {
|
|
145
|
+
// The mailbox is per-agent, so without a resolved agent id there's no correct key (the bearer is
|
|
146
|
+
// wrong — one agent can hold several). Degrade honestly rather than key on the token.
|
|
147
|
+
ctx.io.err("⚠️ oh-hai: concurrent-watch protection is unavailable (no resolvable agent id — set --account / " +
|
|
148
|
+
"MA2H_AGENT_ID, or log in). Proceeding without a lock — do not start a second watcher for this identity.");
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const origin = originOf(ctx.config.baseUrl);
|
|
152
|
+
const outcome = acquireWatchLock(io, { origin, agentId, intervalMs, force });
|
|
153
|
+
if (outcome.kind === "held") {
|
|
154
|
+
const h = outcome.holder;
|
|
155
|
+
// `already_running` (exit 10), NOT `conflict`/exit 8: this is a LOCAL precondition conflict with no
|
|
156
|
+
// Hub call, distinct from a Hub 409, so a caller branching on the exit code never treats "stop the
|
|
157
|
+
// other watcher" (non-retryable) as a retryable idempotency conflict. See docs/specs/cli.md §7/§8.
|
|
158
|
+
// `holder` is undefined when a peer just won the create race and its record isn't readable yet.
|
|
159
|
+
const who = h !== undefined ? `(pid ${h.pid} on ${h.hostname}, watching ${h.origin} since ${new Date(h.startedAt).toISOString()})` : "(it just started; its lock is being written)";
|
|
160
|
+
throw new CliError("already_running", `another 'oh-hai inbox watch' is already draining this identity ${who}. Two watchers on one ` +
|
|
161
|
+
`identity race to drain the same mailbox — a directive delivered once is consumed by whichever ` +
|
|
162
|
+
`drains first, so the other agent can silently lose it. Stop the other watcher, or re-run with ` +
|
|
163
|
+
`--force to take over.`);
|
|
164
|
+
}
|
|
165
|
+
if (outcome.kind === "unavailable") {
|
|
166
|
+
ctx.io.err(`⚠️ oh-hai: concurrent-watch protection is unavailable (${outcome.reason}). Proceeding without a ` +
|
|
167
|
+
`lock — do not start a second 'oh-hai inbox watch' for this identity.`);
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
if (outcome.reclaimed?.wasLive) {
|
|
171
|
+
// Only reachable under --force: we stole a lock that still looked live.
|
|
172
|
+
const h = outcome.reclaimed.holder;
|
|
173
|
+
ctx.io.err(`⚠️ oh-hai: --force took over the watch lock held by pid ${h.pid} on ${h.hostname} (held since ` +
|
|
174
|
+
`${new Date(h.startedAt).toISOString()}). If that watcher is still running you now BOTH drain ` +
|
|
175
|
+
`this mailbox and will race for directives.`);
|
|
176
|
+
}
|
|
177
|
+
else if (outcome.reclaimed) {
|
|
178
|
+
const h = outcome.reclaimed.holder;
|
|
179
|
+
ctx.io.err(`oh-hai: reclaimed a stale watch lock from pid ${h.pid} on ${h.hostname} (last active ` +
|
|
180
|
+
`${new Date(h.heartbeatAt).toISOString()}).`);
|
|
181
|
+
}
|
|
182
|
+
return outcome.lock;
|
|
87
183
|
}
|
|
88
184
|
/** Hand one directive to the agent runtime. stdout is the directive STREAM.
|
|
89
185
|
*
|