humanish 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/actor-registry.d.ts +6 -2
- package/dist/actor-registry.js +12 -0
- package/dist/actor-registry.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +3 -0
- package/dist/cua-actor-lab.js +75 -4
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-terminal-lab.js +44 -2
- package/dist/e2b-terminal-lab.js.map +1 -1
- package/dist/first-run-path.d.ts +39 -0
- package/dist/first-run-path.js +104 -0
- package/dist/first-run-path.js.map +1 -0
- package/dist/init-templates.d.ts +8 -0
- package/dist/init-templates.js +69 -0
- package/dist/init-templates.js.map +1 -1
- package/dist/init.d.ts +7 -0
- package/dist/init.js +62 -3
- package/dist/init.js.map +1 -1
- package/dist/lab-config.d.ts +19 -0
- package/dist/lab-config.js +17 -1
- package/dist/lab-config.js.map +1 -1
- package/dist/local-agent-appserver.d.ts +44 -0
- package/dist/local-agent-appserver.js +267 -0
- package/dist/local-agent-appserver.js.map +1 -0
- package/dist/local-agent-cli.d.ts +104 -0
- package/dist/local-agent-cli.js +337 -0
- package/dist/local-agent-cli.js.map +1 -0
- package/dist/program.js +21 -2
- package/dist/program.js.map +1 -1
- package/dist/run.js +9 -0
- package/dist/run.js.map +1 -1
- package/dist/tui-app.js +110 -110
- package/dist/tui-contract.d.ts +6 -0
- package/dist/tui-contract.js.map +1 -1
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +1 -1
- package/docs/ramp/README.md +29 -1
- package/package.json +3 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// A codex app-server THREAD as the computer-use brain, instead of a fresh `codex exec` per turn.
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS REPLACED THE ONE-SHOT VERSION, measured on this machine:
|
|
4
|
+
// `codex exec` per turn ~9s every turn, and every turn starts cold
|
|
5
|
+
// app-server thread ~500ms ONCE, then 2.7s / 5.8s / 8.9s per turn
|
|
6
|
+
//
|
|
7
|
+
// The speed is the smaller half. `codex exec` boots a CLI, loads config, agents and MCP servers on
|
|
8
|
+
// every single turn, and — the part that actually matters — hands the model a conversation that
|
|
9
|
+
// begins fresh each time. A participant driving a desktop that way has AMNESIA: it cannot remember
|
|
10
|
+
// that it already tried the menu, because nothing carried. A thread remembers, which is how the
|
|
11
|
+
// OpenAI computer-use provider has always worked (previous_response_id), and it is the difference
|
|
12
|
+
// between studying one participant and studying sixty strangers who each see one screenshot.
|
|
13
|
+
//
|
|
14
|
+
// The protocol is not guessed: `codex app-server generate-json-schema` emits it, and it gives us
|
|
15
|
+
// exactly what this loop needs — `turn/start` takes `input: [{type:"localImage", path}, ...]`,
|
|
16
|
+
// an `outputSchema` that constrains the reply (so no fence-scraping), and per-turn `effort` and
|
|
17
|
+
// `model` overrides described as applying "for this turn and subsequent turns".
|
|
18
|
+
import { spawn } from "node:child_process";
|
|
19
|
+
import readline from "node:readline";
|
|
20
|
+
import { toCuaActions } from "./local-agent-cli.js";
|
|
21
|
+
/** Newline-delimited JSON-RPC over the child's stdio — the framing codex-app-server.ts uses. */
|
|
22
|
+
export function stdioTransport(child) {
|
|
23
|
+
const rl = readline.createInterface({ input: child.stdout });
|
|
24
|
+
const pending = new Map();
|
|
25
|
+
const waiters = [];
|
|
26
|
+
let nextId = 0;
|
|
27
|
+
rl.on("line", (line) => {
|
|
28
|
+
let message;
|
|
29
|
+
try {
|
|
30
|
+
message = JSON.parse(line);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return; // the server also logs non-JSON; ignore rather than crash the run
|
|
34
|
+
}
|
|
35
|
+
const id = message.id;
|
|
36
|
+
if (typeof id === "number" && pending.has(id)) {
|
|
37
|
+
const entry = pending.get(id);
|
|
38
|
+
pending.delete(id);
|
|
39
|
+
if (message.error !== undefined) {
|
|
40
|
+
entry.reject(new Error(`codex app-server: ${JSON.stringify(message.error).slice(0, 300)}`));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
entry.resolve((message.result ?? {}));
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (typeof message.method === "string") {
|
|
48
|
+
for (let index = waiters.length - 1; index >= 0; index -= 1) {
|
|
49
|
+
if (waiters[index].method === message.method) {
|
|
50
|
+
waiters.splice(index, 1)[0].resolve(message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
request: async (method, params) => await new Promise((resolve, reject) => {
|
|
57
|
+
const id = (nextId += 1);
|
|
58
|
+
pending.set(id, { resolve, reject });
|
|
59
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
|
|
60
|
+
}),
|
|
61
|
+
notify: (method, params) => {
|
|
62
|
+
child.stdin.write(`${JSON.stringify({ method, params })}\n`);
|
|
63
|
+
},
|
|
64
|
+
awaitNotification: async (method, timeoutMs) => await new Promise((resolve, reject) => {
|
|
65
|
+
const timer = setTimeout(() => reject(new Error(`timed out waiting for ${method}`)), timeoutMs);
|
|
66
|
+
waiters.push({
|
|
67
|
+
method,
|
|
68
|
+
resolve: (value) => {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
resolve(value);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}),
|
|
74
|
+
close: () => {
|
|
75
|
+
rl.close();
|
|
76
|
+
child.kill();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export const APP_SERVER_CAPABILITIES = {
|
|
81
|
+
headless: true,
|
|
82
|
+
structuredTrace: true,
|
|
83
|
+
lanes: ["computer-use"],
|
|
84
|
+
producesScreenshots: true,
|
|
85
|
+
byoModel: true,
|
|
86
|
+
preGrantableApprovals: false,
|
|
87
|
+
inProcessTools: false,
|
|
88
|
+
license: "open"
|
|
89
|
+
};
|
|
90
|
+
/** The reply shape a turn is constrained to. Strict mode: every property in `required`. */
|
|
91
|
+
export function turnOutputSchema() {
|
|
92
|
+
return {
|
|
93
|
+
type: "object",
|
|
94
|
+
additionalProperties: false,
|
|
95
|
+
required: ["reasoning", "done", "message", "actions"],
|
|
96
|
+
properties: {
|
|
97
|
+
reasoning: { type: "string" },
|
|
98
|
+
done: { type: "boolean" },
|
|
99
|
+
message: { type: ["string", "null"] },
|
|
100
|
+
actions: {
|
|
101
|
+
type: "array",
|
|
102
|
+
items: {
|
|
103
|
+
type: "object",
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
required: ["kind", "x", "y", "text", "keys", "ms"],
|
|
106
|
+
properties: {
|
|
107
|
+
kind: { type: "string", enum: ["click", "double_click", "type", "keypress", "scroll", "wait", "done"] },
|
|
108
|
+
x: { type: ["integer", "null"] },
|
|
109
|
+
y: { type: ["integer", "null"] },
|
|
110
|
+
text: { type: ["string", "null"] },
|
|
111
|
+
keys: { type: ["array", "null"], items: { type: "string" } },
|
|
112
|
+
ms: { type: ["integer", "null"] }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function readNested(value, keys) {
|
|
120
|
+
let current = value;
|
|
121
|
+
for (const key of keys) {
|
|
122
|
+
if (typeof current !== "object" || current === null)
|
|
123
|
+
return undefined;
|
|
124
|
+
current = current[key];
|
|
125
|
+
}
|
|
126
|
+
return typeof current === "string" ? current : undefined;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Start a thread and return a provider that spends it, one `turn/start` per computer-use turn.
|
|
130
|
+
*
|
|
131
|
+
* The thread is started EAGERLY (before the first screenshot) so the ~500ms handshake is paid
|
|
132
|
+
* while the sandbox is still booting rather than inside turn one.
|
|
133
|
+
*/
|
|
134
|
+
export async function startAppServerSession(options = {}) {
|
|
135
|
+
const timeoutMs = options.timeoutMs ?? 180_000;
|
|
136
|
+
const cwd = options.cwd ?? process.cwd();
|
|
137
|
+
const effort = options.reasoningEffort ?? "low";
|
|
138
|
+
let child;
|
|
139
|
+
let transport = options.transport;
|
|
140
|
+
if (transport === undefined) {
|
|
141
|
+
child = spawn("codex", ["app-server"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
142
|
+
// The server logs to stderr; draining it keeps the pipe from filling and stalling the run.
|
|
143
|
+
child.stderr.resume();
|
|
144
|
+
transport = stdioTransport(child);
|
|
145
|
+
}
|
|
146
|
+
await transport.request("initialize", {
|
|
147
|
+
clientInfo: { name: "humanish_cli", title: "Humanish CLI", version: "0.1.0" },
|
|
148
|
+
capabilities: {}
|
|
149
|
+
});
|
|
150
|
+
transport.notify("initialized", {});
|
|
151
|
+
const thread = await transport.request("thread/start", {
|
|
152
|
+
cwd,
|
|
153
|
+
// The participant is here to look at a screen, not to touch this machine. read-only + never
|
|
154
|
+
// is the same bound the one-shot version had, declared once for the whole thread.
|
|
155
|
+
approvalPolicy: "never",
|
|
156
|
+
sandbox: "read-only",
|
|
157
|
+
serviceName: "humanish",
|
|
158
|
+
// Nothing about this study belongs in the operator's codex history.
|
|
159
|
+
ephemeral: true,
|
|
160
|
+
...(options.baseInstructions === undefined ? {} : { baseInstructions: options.baseInstructions }),
|
|
161
|
+
...(options.model === undefined ? {} : { model: options.model })
|
|
162
|
+
});
|
|
163
|
+
const threadId = readNested(thread, ["thread", "id"]);
|
|
164
|
+
if (threadId === undefined) {
|
|
165
|
+
transport.close();
|
|
166
|
+
throw new Error("codex app-server did not return a thread id");
|
|
167
|
+
}
|
|
168
|
+
const provider = {
|
|
169
|
+
id: "local-agent-codex-app-server",
|
|
170
|
+
version: options.model ?? "codex app-server (local, operator-authenticated)",
|
|
171
|
+
modelSettings: { reasoningEffort: effort },
|
|
172
|
+
capabilities: APP_SERVER_CAPABILITIES,
|
|
173
|
+
requiresFrame: true,
|
|
174
|
+
async nextTurn(request, signal) {
|
|
175
|
+
const frame = request.observation.screenshot;
|
|
176
|
+
if (frame === undefined) {
|
|
177
|
+
throw new Error("the codex app-server provider needs a screenshot and this observation has none");
|
|
178
|
+
}
|
|
179
|
+
const { mkdtemp, rm, writeFile } = await import("node:fs/promises");
|
|
180
|
+
const { tmpdir } = await import("node:os");
|
|
181
|
+
const path = await import("node:path");
|
|
182
|
+
const work = await mkdtemp(path.join(tmpdir(), "humanish-appserver-"));
|
|
183
|
+
try {
|
|
184
|
+
const shot = path.join(work, "screen.png");
|
|
185
|
+
await writeFile(shot, frame);
|
|
186
|
+
const hint = request.contextHint === undefined ? "" : `\n\nNote from the harness: ${request.contextHint}`;
|
|
187
|
+
const completed = transport.awaitNotification("turn/completed", timeoutMs);
|
|
188
|
+
await transport.request("turn/start", {
|
|
189
|
+
threadId,
|
|
190
|
+
cwd,
|
|
191
|
+
approvalPolicy: "never",
|
|
192
|
+
sandboxPolicy: { type: "readOnly" },
|
|
193
|
+
// Per-turn, because a lane declares it and the schema says it applies to this turn and
|
|
194
|
+
// the ones after — so a lane that raises effort raises it from that point on.
|
|
195
|
+
effort,
|
|
196
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
197
|
+
outputSchema: turnOutputSchema(),
|
|
198
|
+
input: [
|
|
199
|
+
{ type: "localImage", path: shot },
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
// The persona already lives in baseInstructions; this is only the turn's ask, which
|
|
203
|
+
// is why it stays this short.
|
|
204
|
+
text: `This is the current screen. What do you do next?${hint}`,
|
|
205
|
+
text_elements: []
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
});
|
|
209
|
+
const abort = signal === undefined
|
|
210
|
+
? undefined
|
|
211
|
+
: new Promise((_resolve, reject) => {
|
|
212
|
+
signal.addEventListener("abort", () => reject(new Error("run stopped")), { once: true });
|
|
213
|
+
});
|
|
214
|
+
const note = await (abort === undefined ? completed : Promise.race([completed, abort]));
|
|
215
|
+
return turnFromNotification(note);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
await rm(work, { recursive: true, force: true }).catch(() => undefined);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
return {
|
|
223
|
+
provider,
|
|
224
|
+
close: () => {
|
|
225
|
+
transport?.close();
|
|
226
|
+
child?.kill();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/** Read a `turn/completed` notification into a CuaTurn. Exported for tests. */
|
|
231
|
+
export function turnFromNotification(note) {
|
|
232
|
+
const params = (note.params ?? {});
|
|
233
|
+
const turn = (params.turn ?? {});
|
|
234
|
+
// Read the protocol, do not guess it: `turn/completed` carries `turn.items`, and the model's
|
|
235
|
+
// answer is the LAST `agentMessage` item, whose `text` is a plain string. The first cut of this
|
|
236
|
+
// looked for `turn.output`, found nothing, and silently produced an empty turn — which the loop
|
|
237
|
+
// read as "the participant is finished", ending a live study after one screenshot.
|
|
238
|
+
const items = Array.isArray(turn.items) ? turn.items : [];
|
|
239
|
+
const message = [...items].reverse().find((item) => item.type === "agentMessage");
|
|
240
|
+
const text = typeof message?.text === "string" ? message.text : "";
|
|
241
|
+
let parsed;
|
|
242
|
+
try {
|
|
243
|
+
// `outputSchema` constrains it to JSON, but a fence costs nothing to tolerate.
|
|
244
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text);
|
|
245
|
+
parsed = JSON.parse((fenced?.[1] ?? text).trim());
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// A turn whose constrained output did not arrive is a BROKEN turn, never an empty one: an
|
|
249
|
+
// empty turn reads to the loop as "the participant chose to do nothing".
|
|
250
|
+
throw new Error(`codex app-server returned no structured turn output (${text.slice(0, 160)})`);
|
|
251
|
+
}
|
|
252
|
+
const actions = toCuaActions(Array.isArray(parsed.actions) ? parsed.actions : []);
|
|
253
|
+
return {
|
|
254
|
+
actions,
|
|
255
|
+
pendingSafetyChecks: [],
|
|
256
|
+
// EXPLICIT only. The first version also inferred done from "no actions plus a message", and
|
|
257
|
+
// that inference ended a live study on turn one with a 7-second "goal_satisfied": the model
|
|
258
|
+
// narrated the screen, chose no action, and the provider called it finished. The schema
|
|
259
|
+
// guarantees a `done` boolean, so there is nothing to infer.
|
|
260
|
+
done: parsed.done === true,
|
|
261
|
+
...(typeof parsed.reasoning === "string" && parsed.reasoning.length > 0 ? { reasoning: parsed.reasoning } : {}),
|
|
262
|
+
...(typeof parsed.message === "string" && parsed.message.length > 0 ? { message: parsed.message } : {})
|
|
263
|
+
// No `usage`: a subscription thread reports nothing we could price, and a number invented here
|
|
264
|
+
// is what would make the run's cost line a lie.
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=local-agent-appserver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-agent-appserver.js","sourceRoot":"","sources":["../src/local-agent-appserver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { ActorCapabilities } from "./actor-contract.js";
|
|
2
|
+
import type { CuaAction, CuaProvider } from "./computer-use.js";
|
|
3
|
+
import type { ReasoningEffort } from "./reasoning-effort.js";
|
|
4
|
+
export type LocalAgentId = "codex" | "claude";
|
|
5
|
+
export interface LocalAgentDescriptor {
|
|
6
|
+
id: LocalAgentId;
|
|
7
|
+
/** The command a person types. */
|
|
8
|
+
bin: string;
|
|
9
|
+
/** For humans: "Codex (ChatGPT plan)". */
|
|
10
|
+
label: string;
|
|
11
|
+
/** Where its credentials live, so `doctor` can say "signed in" without reading the file. */
|
|
12
|
+
credentialPath: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const LOCAL_AGENTS: readonly LocalAgentDescriptor[];
|
|
15
|
+
/**
|
|
16
|
+
* OpenAI structured outputs run in STRICT mode: every property must appear in `required`, so
|
|
17
|
+
* "optional" is expressed as a nullable type. Getting this wrong is a 400 before any thinking
|
|
18
|
+
* happens, which is how this shape was arrived at.
|
|
19
|
+
*/
|
|
20
|
+
export declare function localAgentTurnSchema(): Record<string, unknown>;
|
|
21
|
+
interface RawAction {
|
|
22
|
+
kind?: string;
|
|
23
|
+
x?: number | null;
|
|
24
|
+
y?: number | null;
|
|
25
|
+
text?: string | null;
|
|
26
|
+
keys?: string[] | null;
|
|
27
|
+
ms?: number | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Map the agent's answer onto the harness action vocabulary. Anything unrecognized is DROPPED
|
|
31
|
+
* rather than guessed at: a coordinate we invented would be recorded as the participant's choice.
|
|
32
|
+
*/
|
|
33
|
+
export declare function toCuaActions(raw: readonly RawAction[]): CuaAction[];
|
|
34
|
+
/**
|
|
35
|
+
* Pull the JSON object out of whatever the CLI printed. Codex writes clean JSON to
|
|
36
|
+
* `--output-last-message`; Claude Code wraps it in a ```json fence. Both are handled here rather
|
|
37
|
+
* than in two places, and a response with no object at all is a turn error, never an empty turn —
|
|
38
|
+
* an empty turn would read to the loop as "the participant chose to do nothing".
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseAgentJson(text: string): Record<string, unknown>;
|
|
41
|
+
export interface SpawnResult {
|
|
42
|
+
code: number | null;
|
|
43
|
+
stdout: string;
|
|
44
|
+
stderr: string;
|
|
45
|
+
}
|
|
46
|
+
export type SpawnLike = (bin: string, args: readonly string[], options: {
|
|
47
|
+
cwd: string;
|
|
48
|
+
timeoutMs: number;
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
}) => Promise<SpawnResult>;
|
|
51
|
+
export interface LocalAgentProviderOptions {
|
|
52
|
+
agent: LocalAgentId;
|
|
53
|
+
/** Per-turn wall clock. A coding agent left to think can outlast the run. */
|
|
54
|
+
timeoutMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Codex defaults to HIGH effort, which timed out at 240s on a single action; `low` answered the
|
|
57
|
+
* same screenshot correctly in 9s. A computer-use run is sixty of these, so the default here is
|
|
58
|
+
* deliberately low and the lab can raise it.
|
|
59
|
+
*/
|
|
60
|
+
reasoningEffort?: ReasoningEffort;
|
|
61
|
+
/** Model override passed to the CLI (`--model`). Absent = the CLI's own default. */
|
|
62
|
+
model?: string;
|
|
63
|
+
spawnFn?: SpawnLike;
|
|
64
|
+
/** Scratch root for the screenshot and schema handed to the CLI. */
|
|
65
|
+
workRoot?: string;
|
|
66
|
+
}
|
|
67
|
+
export declare const LOCAL_AGENT_CAPABILITIES: ActorCapabilities;
|
|
68
|
+
/**
|
|
69
|
+
* A CuaProvider backed by a coding agent that is already signed in on this machine.
|
|
70
|
+
*
|
|
71
|
+
* Deliberately NOT a new lane: the loop, the executor, the trace, the affordance record and the
|
|
72
|
+
* Observer are all unchanged, because the only thing that differs is where the next action comes
|
|
73
|
+
* from. That is also why it is honest to compare a local-agent run against an API run.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createLocalAgentProvider(options: LocalAgentProviderOptions): CuaProvider;
|
|
76
|
+
export interface DetectedLocalAgent extends LocalAgentDescriptor {
|
|
77
|
+
/** Resolved path to the binary. */
|
|
78
|
+
binPath: string;
|
|
79
|
+
/**
|
|
80
|
+
* Whether a credential file exists for it. EXISTENCE ONLY — never read, never parsed, never
|
|
81
|
+
* reported beyond this boolean. "signed in somewhere" is all doctor needs to say, and it is all
|
|
82
|
+
* we are entitled to know.
|
|
83
|
+
*/
|
|
84
|
+
credentialsPresent: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface DetectLocalAgentsOptions {
|
|
87
|
+
/** Injected for tests: resolves a binary name to a path, or undefined. */
|
|
88
|
+
which?: (bin: string) => Promise<string | undefined>;
|
|
89
|
+
/** Injected for tests: does this path exist? */
|
|
90
|
+
exists?: (file: string) => Promise<boolean>;
|
|
91
|
+
home?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Which coding agents are installed and signed in on this machine.
|
|
95
|
+
*
|
|
96
|
+
* This is the whole point of the feature at the surface: someone new does not have to go and make
|
|
97
|
+
* an API key if the thing that can drive the study is already on their laptop. `doctor` says so,
|
|
98
|
+
* and says it as a capability rather than a gate — a machine with no local agent is not broken,
|
|
99
|
+
* it just needs a key.
|
|
100
|
+
*/
|
|
101
|
+
export declare function detectLocalAgents(options?: DetectLocalAgentsOptions): Promise<DetectedLocalAgent[]>;
|
|
102
|
+
/** One line for `doctor`, in the register the other rows use. */
|
|
103
|
+
export declare function localAgentDoctorMessage(found: readonly DetectedLocalAgent[]): string;
|
|
104
|
+
export {};
|