@peakflo/agent-harness 0.2.8-rc.1 → 0.2.9-rc.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 +35 -0
- package/dist/cli.js +5 -0
- package/dist/harness-discovery.d.ts +142 -0
- package/dist/harness-discovery.js +727 -0
- package/dist/harness.d.ts +68 -1
- package/dist/harness.js +197 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +14 -2
- package/dist/options.js +17 -1
- package/dist/types.d.ts +75 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,41 @@ Optional flags include `--health-port <port>`, `--runtime <desktop|self-hosted>`
|
|
|
36
36
|
`--workspace-root <path>`, and `--github-proxy-port <port>`. Run
|
|
37
37
|
`npx @peakflo/agent-harness@rc --help` for the full list.
|
|
38
38
|
|
|
39
|
+
## Harnesses found on your machine
|
|
40
|
+
|
|
41
|
+
On a desktop or self-hosted machine the runner looks for Codex (`codex`),
|
|
42
|
+
Claude Code (`claude`), Cursor (`cursor-agent`), Pi (`pi`) and OpenCode
|
|
43
|
+
(`opencode`). For each one it reports whether it is installed, its version,
|
|
44
|
+
whether it is signed in, and the models it offers. Peakflo shows this list in
|
|
45
|
+
the agent editor and in the chat details.
|
|
46
|
+
|
|
47
|
+
Finding a harness does not mean the runner can use it. The runner runs turns
|
|
48
|
+
with Pi only; the other harnesses are shown as "Detected, not yet runnable by
|
|
49
|
+
this runner".
|
|
50
|
+
|
|
51
|
+
**When it looks.** Once after start, without delaying registration, and then
|
|
52
|
+
every five minutes while no turn is running. A harness you install or sign in
|
|
53
|
+
to later appears without a restart.
|
|
54
|
+
|
|
55
|
+
**What it runs.** To do this the runner starts those programs, as your user,
|
|
56
|
+
with `--version` and a status or model-list command. It looks in the absolute
|
|
57
|
+
directories of your `PATH` and in the usual install directories
|
|
58
|
+
(`~/.local/bin`, `~/.opencode/bin`, `/opt/homebrew/bin`, `/usr/local/bin`,
|
|
59
|
+
`~/.npm-global/bin`, `~/.volta/bin`, `~/.bun/bin`, and the directory of the
|
|
60
|
+
running `node`). Relative `PATH` entries are ignored. The programs are started
|
|
61
|
+
without the runner's own `PF_*` variables, so they do not receive the
|
|
62
|
+
enrolment token.
|
|
63
|
+
|
|
64
|
+
**What it sends.** Names, versions, a signed-in yes/no with a fixed reason,
|
|
65
|
+
and model names. It does not send file paths, account names, tokens, API keys
|
|
66
|
+
or the content of credential files. Model names that look like an email
|
|
67
|
+
address or a key are dropped; that check is a safety net, not a guarantee
|
|
68
|
+
about what a third-party program prints.
|
|
69
|
+
|
|
70
|
+
**Turning it off.** Start with `--no-harness-discovery`, or set
|
|
71
|
+
`PF_HARNESS_DISCOVERY=0`. The runner then starts none of these programs and
|
|
72
|
+
reports no list. Running turns with Pi is not affected.
|
|
73
|
+
|
|
39
74
|
## Releasing
|
|
40
75
|
|
|
41
76
|
`.github/scripts/agent-harness-release.mjs` publishes this package
|
package/dist/cli.js
CHANGED
|
@@ -75,6 +75,11 @@ Options:
|
|
|
75
75
|
--max-sessions <n> concurrent sessions (env PF_MAX_CONCURRENT_SESSIONS,
|
|
76
76
|
default 10 for desktop/self-hosted; sandbox is configured by control plane)
|
|
77
77
|
--labels <a,b> routing labels (env PF_RUNNER_LABELS)
|
|
78
|
+
--no-harness-discovery do not look for Codex, Claude Code, Cursor,
|
|
79
|
+
Pi and OpenCode on this machine, and report
|
|
80
|
+
none (env PF_HARNESS_DISCOVERY=0). On by
|
|
81
|
+
default for desktop/self-hosted; never runs
|
|
82
|
+
in a sandbox.
|
|
78
83
|
|
|
79
84
|
In a managed sandbox pf-bootstrap supplies these through the environment, so
|
|
80
85
|
the enrolment token never appears in /proc/<pid>/cmdline.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HARNESS DISCOVERY — which coding harnesses are installed on this machine.
|
|
3
|
+
*
|
|
4
|
+
* Modelled on 20x, where the harness code came from:
|
|
5
|
+
* - `src/main/agent-installer/detect.js` finds the binaries (`claude`,
|
|
6
|
+
* `codex`, `cursor-agent`, `opencode`, `pi`) with `--version`, after it
|
|
7
|
+
* puts the well-known install directories on PATH;
|
|
8
|
+
* - `src/renderer/src/types/index.ts` holds the static model lists the 20x
|
|
9
|
+
* agent form shows for Claude Code, Codex and Cursor.
|
|
10
|
+
*
|
|
11
|
+
* WHAT THIS IS NOT. It is not the list of backends this runner can run.
|
|
12
|
+
* `capabilities.backends` stays that list and the control plane routes on it.
|
|
13
|
+
* An entry here says "this CLI is on the machine", and `runnable` says whether
|
|
14
|
+
* the runner can also drive it. Detection never implies turn support.
|
|
15
|
+
*
|
|
16
|
+
* RULES THIS MODULE KEEPS:
|
|
17
|
+
*
|
|
18
|
+
* 1. NEVER THROWS, NEVER REJECTS. One broken CLI must not hide the others, so
|
|
19
|
+
* every harness is probed on its own, inside its own try/catch and its own
|
|
20
|
+
* deadline.
|
|
21
|
+
* 2. TIME-LIMITED. Each command has a timeout and each harness has a total
|
|
22
|
+
* deadline. A CLI that hangs is killed and reported as timed out.
|
|
23
|
+
* 3. NO SECRETS LEAVE THIS FILE. Sign-in is decided from an exit code, one
|
|
24
|
+
* boolean of a status reply, or the existence of an environment variable or
|
|
25
|
+
* a file. No status text is kept. Every `reason` is a constant from
|
|
26
|
+
* `REASONS` below, never something a CLI printed. Model ids and names are
|
|
27
|
+
* the only CLI text that is reported, and each one is checked first.
|
|
28
|
+
* 4. NO GLOBAL SIDE EFFECTS. `process.env.PATH` is not changed; the search path
|
|
29
|
+
* is local and is handed to each child process.
|
|
30
|
+
*/
|
|
31
|
+
import type { RunnerHarness, RunnerHarnessModel } from './types';
|
|
32
|
+
/** One command: `--version`, a status check, or a model listing. */
|
|
33
|
+
export declare const DISCOVERY_COMMAND_TIMEOUT_MS = 8000;
|
|
34
|
+
/** Everything for one harness. Harnesses run concurrently, so this is also the bound for the whole pass. */
|
|
35
|
+
export declare const DISCOVERY_HARNESS_TIMEOUT_MS = 20000;
|
|
36
|
+
/**
|
|
37
|
+
* Every sentence a `reason` can be. A closed set on purpose: text printed by a
|
|
38
|
+
* CLI can hold an account email or a key, and a constant cannot.
|
|
39
|
+
*/
|
|
40
|
+
export declare const REASONS: {
|
|
41
|
+
readonly notInstalled: "Not installed on this machine.";
|
|
42
|
+
readonly versionTimedOut: "The version check timed out.";
|
|
43
|
+
readonly versionFailed: "The version check failed.";
|
|
44
|
+
readonly discoveryTimedOut: "Discovery timed out.";
|
|
45
|
+
readonly discoveryFailed: "Discovery failed.";
|
|
46
|
+
readonly signInTimedOut: "The sign-in check timed out.";
|
|
47
|
+
readonly signInUnknown: "Sign-in could not be confirmed.";
|
|
48
|
+
readonly codexSignIn: "Not signed in. Run `codex login` on this machine.";
|
|
49
|
+
readonly claudeSignIn: "Not signed in. Run `claude auth login` on this machine.";
|
|
50
|
+
readonly cursorSignIn: "Not signed in. Run `cursor-agent login` on this machine.";
|
|
51
|
+
};
|
|
52
|
+
/** 20x `CLAUDE_MODELS`. Claude Code has no command that lists models. */
|
|
53
|
+
export declare const CLAUDE_STATIC_MODELS: ReadonlyArray<RunnerHarnessModel>;
|
|
54
|
+
/** 20x `CODEX_MODELS`. Used only when Codex's own model cache cannot be read. */
|
|
55
|
+
export declare const CODEX_STATIC_MODELS: ReadonlyArray<RunnerHarnessModel>;
|
|
56
|
+
/** 20x `CURSOR_MODELS`. Used only when `cursor-agent models` gives nothing. */
|
|
57
|
+
export declare const CURSOR_STATIC_MODELS: ReadonlyArray<RunnerHarnessModel>;
|
|
58
|
+
export interface DiscoveryExecResult {
|
|
59
|
+
/** Exit code, or null when the process did not exit by itself. */
|
|
60
|
+
code: number | null;
|
|
61
|
+
stdout: string;
|
|
62
|
+
stderr: string;
|
|
63
|
+
timedOut: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** Everything that touches the machine. Replaced in tests. */
|
|
66
|
+
export interface DiscoveryDeps {
|
|
67
|
+
/** Run one command. MUST NOT reject. */
|
|
68
|
+
exec(command: string, args: string[], timeoutMs: number): Promise<DiscoveryExecResult>;
|
|
69
|
+
/** Absolute path of a command, or null when it is not installed. */
|
|
70
|
+
resolveCommand(command: string): string | null;
|
|
71
|
+
realpath(path: string): string;
|
|
72
|
+
/** File text, or null when it cannot be read. */
|
|
73
|
+
readFile(path: string): string | null;
|
|
74
|
+
exists(path: string): boolean;
|
|
75
|
+
env: NodeJS.ProcessEnv;
|
|
76
|
+
home: string;
|
|
77
|
+
now(): Date;
|
|
78
|
+
}
|
|
79
|
+
export interface DiscoverHarnessesOptions {
|
|
80
|
+
/** Backend names this runner can run turns with (`capabilities.backends`). */
|
|
81
|
+
runnableBackends?: readonly string[];
|
|
82
|
+
commandTimeoutMs?: number;
|
|
83
|
+
harnessTimeoutMs?: number;
|
|
84
|
+
deps?: Partial<DiscoveryDeps>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Where to look for a harness binary: PATH first, then the directories the
|
|
88
|
+
* harness installers use. The well-known directories come from 20x
|
|
89
|
+
* `detect.js` (`ensureAgentPaths` and `getPiCommandCandidates`). The directory
|
|
90
|
+
* of the running `node` is added because a runner started by launchd or
|
|
91
|
+
* systemd has a short PATH, and an npm-installed CLI sits beside that `node`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function discoverySearchPath(env: NodeJS.ProcessEnv, home: string, platform?: NodeJS.Platform): string[];
|
|
94
|
+
/**
|
|
95
|
+
* The environment a harness CLI is started with.
|
|
96
|
+
*
|
|
97
|
+
* EVERY `PF_*` VARIABLE IS REMOVED. Those are this runner's own settings and
|
|
98
|
+
* include `PF_ENROLMENT_TOKEN`; a third-party CLI has no use for them and
|
|
99
|
+
* must not be handed them. The rest is kept, because each CLI finds its own
|
|
100
|
+
* login through it (`HOME`, `CODEX_HOME`, `ANTHROPIC_API_KEY`, ...).
|
|
101
|
+
*/
|
|
102
|
+
export declare function discoveryChildEnv(env: NodeJS.ProcessEnv, searchPath: readonly string[]): NodeJS.ProcessEnv;
|
|
103
|
+
/**
|
|
104
|
+
* Keep only entries that look like a model. Anything that looks like a secret
|
|
105
|
+
* is dropped whole: a model list has no reason to hold one, so the safe
|
|
106
|
+
* reading of such a line is that it is not a model.
|
|
107
|
+
*/
|
|
108
|
+
export declare function sanitizeHarnessModels(models: ReadonlyArray<{
|
|
109
|
+
id: unknown;
|
|
110
|
+
name?: unknown;
|
|
111
|
+
default?: unknown;
|
|
112
|
+
}>): RunnerHarnessModel[];
|
|
113
|
+
/** The first version-like token, as 20x `detect.js` reads it. */
|
|
114
|
+
export declare function parseVersion(output: string): string | null;
|
|
115
|
+
export declare function saysSignedOut(text: string): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Codex keeps the models its account can use in `models_cache.json`. This is a
|
|
118
|
+
* model cache, not a credential file; only `slug`, `display_name`,
|
|
119
|
+
* `visibility` and `priority` are read from it.
|
|
120
|
+
*/
|
|
121
|
+
export declare function parseCodexModelCache(text: string | null): RunnerHarnessModel[];
|
|
122
|
+
/** The top-level `model = "..."` of `config.toml`. Nothing else is read. */
|
|
123
|
+
export declare function parseCodexDefaultModel(text: string | null): string | null;
|
|
124
|
+
/**
|
|
125
|
+
* `cursor-agent models` prints one `id - Name` line per model and marks the
|
|
126
|
+
* default with `(current, default)` or `(default)`.
|
|
127
|
+
*/
|
|
128
|
+
export declare function parseCursorModels(output: string): RunnerHarnessModel[];
|
|
129
|
+
/** `opencode models` prints one `provider/model` id per line. */
|
|
130
|
+
export declare function parseOpencodeModels(output: string): RunnerHarnessModel[];
|
|
131
|
+
/** Every harness this build knows how to look for, in display order. */
|
|
132
|
+
export declare const KNOWN_HARNESSES: ReadonlyArray<{
|
|
133
|
+
name: string;
|
|
134
|
+
label: string;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Look for every known harness. ALWAYS resolves, with one entry per known
|
|
138
|
+
* harness in a fixed order — a harness that is missing is reported as
|
|
139
|
+
* `installed: false`, so the reader can tell "not installed" from "this runner
|
|
140
|
+
* does not report harnesses".
|
|
141
|
+
*/
|
|
142
|
+
export declare function discoverHarnesses(options?: DiscoverHarnessesOptions): Promise<RunnerHarness[]>;
|