@haven_ai/connect 0.0.0-dev.202609031523.fd49e1a
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 +406 -0
- package/dist/cli.cjs +5164 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +15 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +5156 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +5203 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +902 -0
- package/dist/index.d.ts +902 -0
- package/dist/index.js +5166 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
export { CliIo, runCli } from './cli.cjs';
|
|
2
|
+
|
|
3
|
+
interface ConnectApiClient {
|
|
4
|
+
resolveSetup(input: ResolveSetupInput): Promise<ResolvedSetup>;
|
|
5
|
+
registerSetup(input: RegisterSetupInput): Promise<RegisterSetupResponse>;
|
|
6
|
+
updateInstallStatus(setupId: string, apiKey: string, input: UpdateInstallStatusInput): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* #1377 D: the narrow status read the connector polls after registering,
|
|
9
|
+
* authenticated with the agent API key it just minted (works while the
|
|
10
|
+
* agent is still `setup_pending` — the endpoint exists for exactly that
|
|
11
|
+
* window). Returns status plus the approved budget once approval lands;
|
|
12
|
+
* never any secret material.
|
|
13
|
+
*/
|
|
14
|
+
getConnectorStatus(setupId: string, apiKey: string): Promise<ConnectorStatusResponse>;
|
|
15
|
+
/**
|
|
16
|
+
* #1700: the agent behind an API key. Used by the re-key flow on both sides
|
|
17
|
+
* of the dashboard hand-off — see {@link AgentIdentity}.
|
|
18
|
+
*/
|
|
19
|
+
getAgentIdentity(apiKey: string): Promise<AgentIdentity>;
|
|
20
|
+
}
|
|
21
|
+
interface ConnectorStatusResponse {
|
|
22
|
+
status: string;
|
|
23
|
+
approved_budget: {
|
|
24
|
+
token_symbol: string;
|
|
25
|
+
token_address: string;
|
|
26
|
+
amount: string;
|
|
27
|
+
reset_period_min: number;
|
|
28
|
+
} | null;
|
|
29
|
+
}
|
|
30
|
+
interface ResolveSetupInput {
|
|
31
|
+
setupToken: string;
|
|
32
|
+
connectorVersion: string;
|
|
33
|
+
runtime?: string;
|
|
34
|
+
}
|
|
35
|
+
interface RegisterSetupInput extends ResolveSetupInput {
|
|
36
|
+
challengeId: string;
|
|
37
|
+
delegateAddress: string;
|
|
38
|
+
proofSignature: string;
|
|
39
|
+
apiKeyHash: string;
|
|
40
|
+
apiKeyPrefix: string;
|
|
41
|
+
/**
|
|
42
|
+
* #1878: the RESOLVED hosted MCP server name this agent is being wired as —
|
|
43
|
+
* `haven` for the bare pair, `haven-<slug>` for a named one. Sent so the
|
|
44
|
+
* dashboard can tell two agents in one harness apart; it is a display aid
|
|
45
|
+
* and Haven keys nothing off it.
|
|
46
|
+
*
|
|
47
|
+
* Always sent by a connector that supports it, bare pair included. Omitting
|
|
48
|
+
* it for the bare pair would make "absent" ambiguous between *this is the
|
|
49
|
+
* unnamed pair* and *an older connector said nothing*, and only the second
|
|
50
|
+
* of those may render as unknown.
|
|
51
|
+
*/
|
|
52
|
+
mcpServerName?: string;
|
|
53
|
+
connectorContext?: ConnectorContext;
|
|
54
|
+
installCapabilities?: {
|
|
55
|
+
canWriteRuntimeConfig?: boolean;
|
|
56
|
+
restartRequired?: boolean;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
interface UpdateInstallStatusInput {
|
|
60
|
+
runtime?: string;
|
|
61
|
+
connectorVersion: string;
|
|
62
|
+
runtimeMcpMode?: string;
|
|
63
|
+
hostedMcpConfigured: boolean;
|
|
64
|
+
localSignerConfigured: boolean;
|
|
65
|
+
localMcpConfigured?: boolean;
|
|
66
|
+
credentialFilesWritten?: boolean;
|
|
67
|
+
signerAcknowledged?: boolean;
|
|
68
|
+
localMcpAcknowledged?: boolean;
|
|
69
|
+
activationCommandAvailable?: boolean;
|
|
70
|
+
skillInstalled?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Omitted on the #1543 early config-written report — no probe has run yet,
|
|
73
|
+
* and the backend merge keeps the key absent until the final report sets it.
|
|
74
|
+
*/
|
|
75
|
+
probeResult?: string;
|
|
76
|
+
restartRequired: boolean;
|
|
77
|
+
nextUserAction: string;
|
|
78
|
+
errorCode?: string | null;
|
|
79
|
+
environmentLabel?: string;
|
|
80
|
+
}
|
|
81
|
+
interface ConnectorContext {
|
|
82
|
+
environment_label?: string;
|
|
83
|
+
runtime_version?: string;
|
|
84
|
+
config_target?: string;
|
|
85
|
+
}
|
|
86
|
+
interface ResolvedSetup {
|
|
87
|
+
setup_id: string;
|
|
88
|
+
status: string;
|
|
89
|
+
agent: {
|
|
90
|
+
name: string;
|
|
91
|
+
description?: string | null;
|
|
92
|
+
};
|
|
93
|
+
haven_wallet: {
|
|
94
|
+
id: string;
|
|
95
|
+
name: string;
|
|
96
|
+
address: string;
|
|
97
|
+
chain_id: number;
|
|
98
|
+
network: string;
|
|
99
|
+
};
|
|
100
|
+
agent_budget: Array<{
|
|
101
|
+
token_address: string;
|
|
102
|
+
token_symbol: string;
|
|
103
|
+
allowance_amount: string;
|
|
104
|
+
reset_period_min: number;
|
|
105
|
+
}>;
|
|
106
|
+
hosted_mcp_url: string;
|
|
107
|
+
x402_binding_signer?: string | null;
|
|
108
|
+
challenge: {
|
|
109
|
+
id: string;
|
|
110
|
+
message: string;
|
|
111
|
+
expires_at: string;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
interface RegisterSetupResponse {
|
|
115
|
+
setup_id: string;
|
|
116
|
+
agent_id: string;
|
|
117
|
+
status: string;
|
|
118
|
+
agent_status: string;
|
|
119
|
+
api_key_prefix: string;
|
|
120
|
+
api_key_scope: string;
|
|
121
|
+
delegate_address: string;
|
|
122
|
+
hosted_mcp_url: string;
|
|
123
|
+
next_action: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The agent identity behind an API key (#1700).
|
|
127
|
+
*
|
|
128
|
+
* Read from `GET /machine-payments/agent`, the same agent-authenticated
|
|
129
|
+
* endpoint the SDK's `getAgent()` uses. Re-key needs it twice and for two
|
|
130
|
+
* different questions: BEFORE, to refuse a legacy-rail account the way the
|
|
131
|
+
* backend would rather than let the owner discover it five signed steps later;
|
|
132
|
+
* and AFTER, to prove the API key the owner pasted really belongs to this
|
|
133
|
+
* agent and really names the key this machine just generated.
|
|
134
|
+
*/
|
|
135
|
+
interface AgentIdentity {
|
|
136
|
+
id: string;
|
|
137
|
+
name: string;
|
|
138
|
+
status: string;
|
|
139
|
+
safe_address: string | null;
|
|
140
|
+
delegate_address: string | null;
|
|
141
|
+
chain_id: number | null;
|
|
142
|
+
execution_rail: 'legacy' | 'delegation' | string;
|
|
143
|
+
}
|
|
144
|
+
declare function createConnectApiClient(baseUrl: string, fetchImpl?: typeof fetch): ConnectApiClient;
|
|
145
|
+
|
|
146
|
+
interface LocalDelegateKey {
|
|
147
|
+
privateKey: string;
|
|
148
|
+
address: string;
|
|
149
|
+
signChallenge(message: string): Promise<string>;
|
|
150
|
+
}
|
|
151
|
+
declare function generateDelegateKey(): LocalDelegateKey;
|
|
152
|
+
declare function delegateKeyFromPrivateKey(privateKey: string): LocalDelegateKey;
|
|
153
|
+
|
|
154
|
+
interface StoredCredentialPaths {
|
|
155
|
+
directory: string;
|
|
156
|
+
identityPath: string;
|
|
157
|
+
signerPath: string;
|
|
158
|
+
/** Non-secret orientation file (identity + configured budget, no keys). */
|
|
159
|
+
agentPath: string;
|
|
160
|
+
}
|
|
161
|
+
interface WriteCredentialInput {
|
|
162
|
+
baseDir?: string;
|
|
163
|
+
agentId: string;
|
|
164
|
+
/**
|
|
165
|
+
* #1696: wiring slug. Named agents live at ~/.haven/agents/<slug>/ (stable
|
|
166
|
+
* across a re-key by construction — the slug never rotates); unnamed keep
|
|
167
|
+
* the historical ~/.haven/agents/<agent-uuid>/. Both schemes coexist.
|
|
168
|
+
*/
|
|
169
|
+
serverName?: string;
|
|
170
|
+
apiKey: string;
|
|
171
|
+
delegateKey: string;
|
|
172
|
+
delegateAddress: string;
|
|
173
|
+
safeAddress?: string;
|
|
174
|
+
chainId?: number;
|
|
175
|
+
network?: string;
|
|
176
|
+
agentBudget?: Array<{
|
|
177
|
+
token_symbol: string;
|
|
178
|
+
allowance_amount: string;
|
|
179
|
+
reset_period_min: number;
|
|
180
|
+
}>;
|
|
181
|
+
apiUrl: string;
|
|
182
|
+
hostedMcpUrl: string;
|
|
183
|
+
x402BindingSigner?: string;
|
|
184
|
+
warn?: (message: string) => void;
|
|
185
|
+
}
|
|
186
|
+
interface PreflightCredentialStorageInput {
|
|
187
|
+
baseDir?: string;
|
|
188
|
+
warn?: (message: string) => void;
|
|
189
|
+
}
|
|
190
|
+
declare function preflightCredentialStorage(input?: PreflightCredentialStorageInput): Promise<string>;
|
|
191
|
+
declare function writeCredentialFiles(input: WriteCredentialInput): Promise<StoredCredentialPaths>;
|
|
192
|
+
declare function defaultAgentDirectory(agentId: string, baseDir?: string): string;
|
|
193
|
+
/**
|
|
194
|
+
* Where the terminal `ConnectOutcome` is parked so a caller that lost the
|
|
195
|
+
* stream can still read the verdict.
|
|
196
|
+
*
|
|
197
|
+
* The field failure this exists for (2026-08-28, connector 0.1.31-alpha.0):
|
|
198
|
+
* setup SUCCEEDED, but the agent driving it stopped watching during the cold
|
|
199
|
+
* signer install, so the connector's final `--json` object — the only place
|
|
200
|
+
* the restart instruction and the read-only verification sequence live — was
|
|
201
|
+
* emitted into a stream nobody was reading. The agent reverse-engineered
|
|
202
|
+
* completion from `codex mcp list` instead.
|
|
203
|
+
*/
|
|
204
|
+
declare const CONNECT_OUTCOME_FILENAME = "last-connect-outcome.json";
|
|
205
|
+
/**
|
|
206
|
+
* Persist the terminal outcome next to the credentials it describes.
|
|
207
|
+
*
|
|
208
|
+
* Non-secret by construction: the content is exactly the object already
|
|
209
|
+
* emitted on stdout, whose secret-freedom is pinned by its own tests. This
|
|
210
|
+
* function never reads or embeds credential-file contents — it serializes what
|
|
211
|
+
* it is handed and nothing else. Written 0o600 anyway, because it lives inside
|
|
212
|
+
* a 0o700 credential directory and there is no reason to widen it.
|
|
213
|
+
*
|
|
214
|
+
* Overwrites, unlike every other write in this file: "the last outcome" is a
|
|
215
|
+
* single slot, not a credential, so `assertDoesNotExist` would be wrong here —
|
|
216
|
+
* a stale verdict left in place is exactly the failure the file exists to
|
|
217
|
+
* prevent.
|
|
218
|
+
*
|
|
219
|
+
* Throws on failure. Best-effort is the CALLER's contract (`runConnect` wraps
|
|
220
|
+
* both call sites), so that a test can inject a writer that fails and still
|
|
221
|
+
* assert the run completes.
|
|
222
|
+
*/
|
|
223
|
+
declare function writeConnectOutcomeRecord(directory: string, outcome: unknown, warn?: (message: string) => void): Promise<string>;
|
|
224
|
+
|
|
225
|
+
type RuntimeId = 'claude-code' | 'codex-desktop' | 'codex-cli' | 'cursor' | 'vscode' | 'vscode-insiders' | 'claude-desktop' | 'hermes' | 'other';
|
|
226
|
+
type RestartMode = 'restart-session' | 'restart-app' | 'hot-reload' | 'manual';
|
|
227
|
+
interface RuntimeProfile {
|
|
228
|
+
id: RuntimeId;
|
|
229
|
+
label: string;
|
|
230
|
+
restartMode: RestartMode;
|
|
231
|
+
canWriteRuntimeConfig: boolean;
|
|
232
|
+
/** Precise, runtime-owned instruction for loading a freshly written MCP entry. */
|
|
233
|
+
activationInstruction: string;
|
|
234
|
+
}
|
|
235
|
+
declare function runtimeProfile(runtime: string | undefined, env?: NodeJS.ProcessEnv): RuntimeProfile;
|
|
236
|
+
declare function normalizeRuntime(runtime: string | undefined, env?: NodeJS.ProcessEnv): RuntimeId;
|
|
237
|
+
/** The same values as refusal-message prose. */
|
|
238
|
+
declare const RUNTIME_FLAG_VALUES: string;
|
|
239
|
+
interface RuntimeSelection {
|
|
240
|
+
runtime: RuntimeId | null;
|
|
241
|
+
source: 'force' | 'detected' | 'explicit' | 'prompted' | 'none';
|
|
242
|
+
/** Set when a confident environment detection overrode a contradicting explicit hint (#1672). */
|
|
243
|
+
overrodeHint?: RuntimeId;
|
|
244
|
+
/** Set when a supplied hint was not a runtime name at all and detection carried the run (#1719). */
|
|
245
|
+
discardedHint?: string;
|
|
246
|
+
}
|
|
247
|
+
interface RuntimeResolutionOptions {
|
|
248
|
+
env?: NodeJS.ProcessEnv;
|
|
249
|
+
/**
|
|
250
|
+
* Rung 3b (#1719): the harness an AGENT executing this command reported for
|
|
251
|
+
* ITSELF, when detection found nothing to go on.
|
|
252
|
+
*
|
|
253
|
+
* It deliberately enters at the same precedence as `--runtime`, which is
|
|
254
|
+
* what makes it safe: a hint can only ever fill a vacuum, and loses to a
|
|
255
|
+
* confident detection exactly as #1672 made it. In practice the agent
|
|
256
|
+
* supplies it BY re-running with `--runtime <name>`; this field exists so a
|
|
257
|
+
* programmatic caller can pass a self-report without pretending to be a
|
|
258
|
+
* command-line flag, and so the ladder names the rung it has.
|
|
259
|
+
*/
|
|
260
|
+
selfReported?: string;
|
|
261
|
+
/**
|
|
262
|
+
* Rung 4 (#1719): ask a human at an interactive terminal which of the
|
|
263
|
+
* clients installed on this machine to configure. Injected as a thunk so
|
|
264
|
+
* the ladder stays a policy function — the scan, the readline prompt, and
|
|
265
|
+
* the "nothing writable is installed" refusal all live in
|
|
266
|
+
* `installed-clients.ts`. Omitted (`--json`, no TTY, library callers) means
|
|
267
|
+
* the rung is skipped entirely, not answered with a guess.
|
|
268
|
+
*/
|
|
269
|
+
promptForRuntime?: () => Promise<RuntimeId>;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Runtime resolution, detection-first (#1672) and self-resolving (#1719).
|
|
273
|
+
*
|
|
274
|
+
* The setup command carries no `--runtime`; the connector works out the
|
|
275
|
+
* runtime it is executing inside. Precedence:
|
|
276
|
+
*
|
|
277
|
+
* 1. `--runtime-force <name>` — always wins (unknown name refuses).
|
|
278
|
+
* 2. Environment detection over a CONTRADICTING hint. Detection only fires
|
|
279
|
+
* inside a real agent shell, where writing a different client's config is
|
|
280
|
+
* almost surely wrong — the claude-desktop-hint-in-Claude-Code dead end
|
|
281
|
+
* this exists to close.
|
|
282
|
+
* 3. An explicit `--runtime`, or an agent's self-report, with no contradicting
|
|
283
|
+
* detection (the legit plain-terminal "configure Claude Desktop by hand"
|
|
284
|
+
* case — unchanged).
|
|
285
|
+
* 4. Detection alone.
|
|
286
|
+
* 5. An interactive pick among the clients actually installed here, when a
|
|
287
|
+
* human is at a TTY. The scan populates the choices; it never selects.
|
|
288
|
+
* 6. Nothing known → `runtime: null`; the caller refuses BEFORE side effects
|
|
289
|
+
* rather than guessing a config location.
|
|
290
|
+
*
|
|
291
|
+
* A hint that is not a runtime name at all is not a hint — it is a mistake, and
|
|
292
|
+
* the one thing it must never do is fall through to a config location nobody
|
|
293
|
+
* asked for. With no detection to fall back on it refuses (`runtime_unrecognized`).
|
|
294
|
+
* With a detection it loses to it, loudly, exactly like a contradicting hint:
|
|
295
|
+
* the detected client is the right write either way, and refusing there would
|
|
296
|
+
* turn every rollout window in which the dashboard learns an id before the
|
|
297
|
+
* published connector does into a hard failure.
|
|
298
|
+
*/
|
|
299
|
+
declare function resolveRuntimeSelection(explicit: string | undefined, force: string | undefined, options?: RuntimeResolutionOptions): Promise<RuntimeSelection>;
|
|
300
|
+
|
|
301
|
+
type RuntimeMcpMode = 'local_stdio' | 'hosted_plus_signer' | 'manual';
|
|
302
|
+
|
|
303
|
+
type LocalMcpProbeStatus = 'ok' | 'timeout' | 'process_error' | 'bad_response' | 'missing_tools';
|
|
304
|
+
interface LocalMcpProbeResult {
|
|
305
|
+
status: LocalMcpProbeStatus;
|
|
306
|
+
/** #1589: the initialize response's serverInfo/capabilities, when reached —
|
|
307
|
+
* the doctor reports the signer's advertised compat versions from here
|
|
308
|
+
* instead of a second probe call. */
|
|
309
|
+
serverInfo?: {
|
|
310
|
+
name?: string;
|
|
311
|
+
version?: string;
|
|
312
|
+
};
|
|
313
|
+
capabilities?: Record<string, unknown>;
|
|
314
|
+
toolNames?: string[];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
interface PrepareLocalMcpRuntimeInput {
|
|
318
|
+
credentialDirectory: string;
|
|
319
|
+
identityPath: string;
|
|
320
|
+
signerPath: string;
|
|
321
|
+
homeDir?: string;
|
|
322
|
+
nodeVersion?: string;
|
|
323
|
+
/** #1696: wiring slug, recorded in the sidecar for per-agent inventory (#1697). */
|
|
324
|
+
serverName?: string;
|
|
325
|
+
}
|
|
326
|
+
interface PreparedLocalMcpRuntime {
|
|
327
|
+
command: string;
|
|
328
|
+
args: string[];
|
|
329
|
+
wrapperPath: string;
|
|
330
|
+
runtimeDirectory: string;
|
|
331
|
+
npmCacheDirectory: string;
|
|
332
|
+
cliPath: string;
|
|
333
|
+
messages: string[];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
interface PrepareSignerRuntimeInput {
|
|
337
|
+
credentialDirectory: string;
|
|
338
|
+
signerPath: string;
|
|
339
|
+
homeDir?: string;
|
|
340
|
+
/** #1696: wiring slug, recorded in the sidecar for per-agent inventory (#1697). */
|
|
341
|
+
serverName?: string;
|
|
342
|
+
}
|
|
343
|
+
interface PreparedSignerRuntime {
|
|
344
|
+
/** Absolute command to register as the signer MCP `command`. */
|
|
345
|
+
command: string;
|
|
346
|
+
/** Args to register alongside `command` (credentials are baked into the wrapper). */
|
|
347
|
+
args: string[];
|
|
348
|
+
wrapperPath: string;
|
|
349
|
+
runtimeDirectory: string;
|
|
350
|
+
npmCacheDirectory: string;
|
|
351
|
+
cliPath: string;
|
|
352
|
+
messages: string[];
|
|
353
|
+
}
|
|
354
|
+
interface SignerRuntimeDeps {
|
|
355
|
+
runCommand?: (command: string, args: string[]) => Promise<void>;
|
|
356
|
+
/** Heartbeats during the (possibly minutes-long) npm install (#1586). */
|
|
357
|
+
onProgress?: (message: string) => void;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Pre-install the edge signer into a version-pinned, connector-managed
|
|
361
|
+
* directory and write a stable wrapper that launches it with an absolute Node
|
|
362
|
+
* path. The signer MCP is then registered as `command: <wrapper>` instead of a
|
|
363
|
+
* runtime `npx -y @haven_ai/signer@…` invocation.
|
|
364
|
+
*
|
|
365
|
+
* Why: launching the signer via bare `npx` at every MCP spawn made it depend on
|
|
366
|
+
* the PATH/environment the agent runtime hands the stdio subprocess, which is
|
|
367
|
+
* not the user's interactive shell — the failure mode that left `haven-signer`
|
|
368
|
+
* stuck at "Failed to connect" while the hosted HTTP server connected fine. The
|
|
369
|
+
* local MCP topology already avoids this with the same pre-install + wrapper
|
|
370
|
+
* pattern (see prepareLocalMcpRuntime); this brings the default hosted+signer
|
|
371
|
+
* topology to parity. Version stays pinned (no unpinned `npm i -g`), and the
|
|
372
|
+
* wrapper lives under ~/.haven so the reset flow already cleans it up.
|
|
373
|
+
*/
|
|
374
|
+
declare function prepareSignerRuntime(input: PrepareSignerRuntimeInput, deps?: SignerRuntimeDeps): Promise<PreparedSignerRuntime>;
|
|
375
|
+
|
|
376
|
+
interface RuntimeInstallInput {
|
|
377
|
+
runtime?: string;
|
|
378
|
+
hostedMcpUrl: string;
|
|
379
|
+
apiKey: string;
|
|
380
|
+
signerPath: string;
|
|
381
|
+
identityPath: string;
|
|
382
|
+
credentialDirectory: string;
|
|
383
|
+
environmentLabel?: string;
|
|
384
|
+
ackSigner?: boolean;
|
|
385
|
+
ackLocalTools?: boolean;
|
|
386
|
+
/**
|
|
387
|
+
* Explicit opt-in to the local-stdio MCP topology (zero hosted dependency).
|
|
388
|
+
* Default is hosted MCP + local signer for every runtime; local MCP is only
|
|
389
|
+
* used when this is true and the runtime supports it.
|
|
390
|
+
*/
|
|
391
|
+
localMcp?: boolean;
|
|
392
|
+
/**
|
|
393
|
+
* #1695: wiring slug for a NAMED MCP pair (haven-<slug> /
|
|
394
|
+
* haven-signer-<slug>). Absent = the bare pair, unchanged from today.
|
|
395
|
+
*/
|
|
396
|
+
serverName?: string;
|
|
397
|
+
}
|
|
398
|
+
interface RuntimeInstallResult {
|
|
399
|
+
runtime: RuntimeId;
|
|
400
|
+
runtimeMcpMode: RuntimeMcpMode;
|
|
401
|
+
hostedMcpConfigured: boolean;
|
|
402
|
+
localSignerConfigured: boolean;
|
|
403
|
+
localMcpConfigured: boolean;
|
|
404
|
+
probeResult: string;
|
|
405
|
+
restartRequired: boolean;
|
|
406
|
+
nextUserAction: string;
|
|
407
|
+
errorCode?: string;
|
|
408
|
+
configTarget?: string;
|
|
409
|
+
runtimeVersion?: string;
|
|
410
|
+
signerAcknowledged?: boolean;
|
|
411
|
+
localMcpAcknowledged?: boolean;
|
|
412
|
+
activationCommand?: string;
|
|
413
|
+
skillInstalled?: boolean;
|
|
414
|
+
/**
|
|
415
|
+
* Hosted topology only: true when the signer was pre-installed and registered
|
|
416
|
+
* via its absolute wrapper, false when prep failed and it fell back to the
|
|
417
|
+
* fragile runtime-`npx` launch. Undefined for local/manual topologies that
|
|
418
|
+
* do not run the signer as a separate MCP. Lets callers/tests detect the
|
|
419
|
+
* silent npx fallback instead of treating every write as fully healthy.
|
|
420
|
+
*/
|
|
421
|
+
signerRuntimePrepared?: boolean;
|
|
422
|
+
messages: string[];
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* The config-write snapshot handed to `onRuntimeConfigured` (#1543). The
|
|
426
|
+
* booleans mirror the semantics of the FINAL report's unlock keys minus the
|
|
427
|
+
* probe verdicts: "configured" here means the config write succeeded, the
|
|
428
|
+
* required consent is acknowledged, and the signer credential exists on disk
|
|
429
|
+
* (a local fs check) — not that a handshake has verified it. A later probe
|
|
430
|
+
* failure refines the final report and sets its errorCode; the dashboard's
|
|
431
|
+
* unlock condition accepts either state.
|
|
432
|
+
*/
|
|
433
|
+
interface EarlyRuntimeConfigReport {
|
|
434
|
+
runtime: RuntimeId;
|
|
435
|
+
runtimeMcpMode: RuntimeMcpMode;
|
|
436
|
+
hostedMcpConfigured: boolean;
|
|
437
|
+
localSignerConfigured: boolean;
|
|
438
|
+
localMcpConfigured: boolean;
|
|
439
|
+
signerAcknowledged?: boolean;
|
|
440
|
+
localMcpAcknowledged?: boolean;
|
|
441
|
+
restartRequired: boolean;
|
|
442
|
+
nextUserAction: string;
|
|
443
|
+
errorCode?: string;
|
|
444
|
+
}
|
|
445
|
+
interface RuntimeInstallDeps {
|
|
446
|
+
env?: NodeJS.ProcessEnv;
|
|
447
|
+
homeDir?: string;
|
|
448
|
+
fetch?: typeof fetch;
|
|
449
|
+
/**
|
|
450
|
+
* Optional live progress callback. installRuntime's slowest steps (signer
|
|
451
|
+
* pre-install, runtime config write, MCP handshake probes) otherwise emit
|
|
452
|
+
* nothing until the caller flushes the collected `messages` after this
|
|
453
|
+
* returns — leaving the console silent through the longest part of setup.
|
|
454
|
+
* When provided, a few lightweight heartbeat lines are emitted as they run.
|
|
455
|
+
*/
|
|
456
|
+
onProgress?: (message: string) => void;
|
|
457
|
+
/**
|
|
458
|
+
* #1543: invoked once, the moment the runtime MCP config write has settled —
|
|
459
|
+
* BEFORE the network probes and the skill install. The dashboard withholds
|
|
460
|
+
* its budget-approval controls until an install-status report shows the
|
|
461
|
+
* runtime configured, and the final report only lands after the entire
|
|
462
|
+
* install; gating approval on that tail made the user wait on work approval
|
|
463
|
+
* does not depend on. The snapshot carries the config-write facts (no probe
|
|
464
|
+
* verdicts, no skill state); the caller's complete report remains
|
|
465
|
+
* authoritative and overwrites these keys. Best-effort by contract: a
|
|
466
|
+
* throwing callback is swallowed and never fails the install.
|
|
467
|
+
*/
|
|
468
|
+
onRuntimeConfigured?: (report: EarlyRuntimeConfigReport) => Promise<void> | void;
|
|
469
|
+
runCommand?: (command: string, args: string[]) => Promise<void>;
|
|
470
|
+
prepareLocalMcpRuntime?: (input: PrepareLocalMcpRuntimeInput) => Promise<PreparedLocalMcpRuntime>;
|
|
471
|
+
prepareSignerRuntime?: (input: PrepareSignerRuntimeInput) => Promise<PreparedSignerRuntime>;
|
|
472
|
+
probeSignerTools?: (command: string, args: string[], requiredTools: readonly string[]) => Promise<LocalMcpProbeResult>;
|
|
473
|
+
probeLocalMcpTools?: (command: string, args: string[], requiredTools: readonly string[]) => Promise<LocalMcpProbeResult>;
|
|
474
|
+
}
|
|
475
|
+
declare function installRuntime(input: RuntimeInstallInput, deps?: RuntimeInstallDeps): Promise<RuntimeInstallResult>;
|
|
476
|
+
declare function runtimeInstallCapabilities(runtime: string | undefined, env?: NodeJS.ProcessEnv): {
|
|
477
|
+
canWriteRuntimeConfig: boolean;
|
|
478
|
+
restartRequired: boolean;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Installed-client scan + interactive pick (#1719).
|
|
483
|
+
*
|
|
484
|
+
* The rung that answers "which runtime am I configuring?" for a HUMAN in a
|
|
485
|
+
* plain terminal, where there is no agent shell to detect. Two properties hold
|
|
486
|
+
* it up, and both are load-bearing:
|
|
487
|
+
*
|
|
488
|
+
* 1. **Only clients the connector can actually write.** A row that cannot be
|
|
489
|
+
* configured is not a choice, it is a dead end wearing a choice's clothes.
|
|
490
|
+
* 2. **The scan populates the choices; it never selects.** Finding exactly one
|
|
491
|
+
* installed app tells you what EXISTS, not where the user wants their agent
|
|
492
|
+
* to run — and the cost of being wrong is an API key and a delegate key
|
|
493
|
+
* written into an app the user does not use. `scanInstalledClients` returns
|
|
494
|
+
* candidates and nothing else; only an answer typed at the prompt resolves
|
|
495
|
+
* a runtime.
|
|
496
|
+
*/
|
|
497
|
+
interface InstalledClientCandidate {
|
|
498
|
+
runtime: RuntimeId;
|
|
499
|
+
label: string;
|
|
500
|
+
/** What made this a candidate — shown at the prompt so the pick is informed. */
|
|
501
|
+
detail: string;
|
|
502
|
+
/**
|
|
503
|
+
* The config file Haven would write for this client, when it owns one.
|
|
504
|
+
* `null` for Claude Code, which is configured through its own CLI. Set
|
|
505
|
+
* regardless of which evidence found the client — `evidence` is what says
|
|
506
|
+
* whether that file exists today.
|
|
507
|
+
*/
|
|
508
|
+
configPath: string | null;
|
|
509
|
+
evidence: 'config-file' | 'client-directory';
|
|
510
|
+
}
|
|
511
|
+
interface ScanInstalledClientsOptions {
|
|
512
|
+
homeDir?: string;
|
|
513
|
+
/** Workspace root, for the project-local `.vscode/` marker. */
|
|
514
|
+
cwd?: string;
|
|
515
|
+
env?: NodeJS.ProcessEnv;
|
|
516
|
+
/** Injectable so the scan is testable without a populated home directory. */
|
|
517
|
+
exists?: (path: string) => Promise<boolean>;
|
|
518
|
+
}
|
|
519
|
+
declare function scanInstalledClients(options?: ScanInstalledClientsOptions): Promise<InstalledClientCandidate[]>;
|
|
520
|
+
/**
|
|
521
|
+
* The scan's findings as DATA, for the `--json` refusal (#2174).
|
|
522
|
+
*
|
|
523
|
+
* The interactive prompt is deliberately omitted under `--json`, which threw
|
|
524
|
+
* this signal away exactly where it was most useful: an agent retrying a
|
|
525
|
+
* `runtime_undetermined` refusal picked from a nine-value menu on
|
|
526
|
+
* self-knowledge alone, while the connector already knew which client configs
|
|
527
|
+
* exist on the machine.
|
|
528
|
+
*
|
|
529
|
+
* Property 2 above is preserved verbatim and is the reason this returns a
|
|
530
|
+
* HINT rather than a runtime: the caller still has to refuse. Finding exactly
|
|
531
|
+
* one installed app tells you what exists, not where the user wants their
|
|
532
|
+
* agent to run, and the cost of being wrong is an API key and a delegate key
|
|
533
|
+
* written into an app they do not use.
|
|
534
|
+
*/
|
|
535
|
+
interface InstalledClientHint {
|
|
536
|
+
/** Runtime ids the scan found, likeliest first. */
|
|
537
|
+
installedClients: readonly RuntimeId[];
|
|
538
|
+
/**
|
|
539
|
+
* The top hit, and only when it is unambiguously top — see
|
|
540
|
+
* `installedClientHint`. A value an agent may echo back as `--runtime`;
|
|
541
|
+
* never a selection the connector makes for it.
|
|
542
|
+
*/
|
|
543
|
+
suggestedRuntime?: RuntimeId;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* A suggestion is offered only when one candidate is CLEARLY first: a lone
|
|
547
|
+
* candidate, or a single live MCP config file among bare client directories —
|
|
548
|
+
* the one tier difference the scan treats as evidence. Candidates within a
|
|
549
|
+
* tier are separated only by `SCAN_ORDER`, a fixed preference rather than a
|
|
550
|
+
* fact about this machine, so suggesting the winner of that tiebreak would
|
|
551
|
+
* dress an arbitrary choice as a finding.
|
|
552
|
+
*
|
|
553
|
+
* The suggestion is derived from the WHOLE array rather than from the first
|
|
554
|
+
* two entries, so it does not depend on the caller having sorted anything.
|
|
555
|
+
* This is exported, and an unsorted list reaching a positional rule would
|
|
556
|
+
* yield a quietly wrong suggestion — never a selection, since only the caller
|
|
557
|
+
* can act on it, but wrong is still wrong. `installedClients` preserves the
|
|
558
|
+
* order it was given, which for `scanInstalledClients` is likeliest-first.
|
|
559
|
+
*/
|
|
560
|
+
declare function installedClientHint(candidates: readonly InstalledClientCandidate[]): InstalledClientHint;
|
|
561
|
+
interface PromptIo {
|
|
562
|
+
write: (text: string) => void;
|
|
563
|
+
/** Resolves the typed line, or `null` on EOF / Ctrl-C. */
|
|
564
|
+
question: (query: string) => Promise<string | null>;
|
|
565
|
+
}
|
|
566
|
+
declare function promptForInstalledClient(candidates: readonly InstalledClientCandidate[], io?: PromptIo): Promise<RuntimeId>;
|
|
567
|
+
/**
|
|
568
|
+
* The whole rung as one thunk: scan, refuse if nothing writable is installed,
|
|
569
|
+
* otherwise prompt. This is what `resolveRuntimeSelection` calls, which is why
|
|
570
|
+
* the registry needs no knowledge of the filesystem or of readline.
|
|
571
|
+
*/
|
|
572
|
+
declare function resolveRuntimeByInstalledClientPrompt(options?: ScanInstalledClientsOptions & {
|
|
573
|
+
io?: PromptIo;
|
|
574
|
+
}): Promise<RuntimeId>;
|
|
575
|
+
|
|
576
|
+
declare const CONNECTOR_VERSION = "0.0.0-dev.202609031523.fd49e1a";
|
|
577
|
+
interface ConnectOptions {
|
|
578
|
+
setupToken: string;
|
|
579
|
+
apiBaseUrl: string;
|
|
580
|
+
runtime?: string;
|
|
581
|
+
/** #1672 escape hatch: use exactly this runtime, ignoring environment detection. */
|
|
582
|
+
runtimeForce?: string;
|
|
583
|
+
/**
|
|
584
|
+
* #1719: the harness an AGENT running this command reported for itself. Enters
|
|
585
|
+
* at the same precedence as `runtime`, so it can only fill a vacuum — never
|
|
586
|
+
* override a detected environment.
|
|
587
|
+
*/
|
|
588
|
+
runtimeSelfReport?: string;
|
|
589
|
+
/**
|
|
590
|
+
* #1719: this run may ask a human which installed client to configure. The
|
|
591
|
+
* CLI sets it for a non-`--json` run; a library caller must opt in. Combined
|
|
592
|
+
* with `deps.isTty`, it is what makes the prompt rung SKIPPED rather than
|
|
593
|
+
* answered in CI, in `--json` automation, and in library embeddings.
|
|
594
|
+
*/
|
|
595
|
+
interactive?: boolean;
|
|
596
|
+
credentialsDir?: string;
|
|
597
|
+
environmentLabel?: string;
|
|
598
|
+
/** #1696: wiring slug for a named MCP pair + slug-keyed credential dir. */
|
|
599
|
+
serverName?: string;
|
|
600
|
+
connectorVersion?: string;
|
|
601
|
+
ackSigner?: boolean;
|
|
602
|
+
ackLocalTools?: boolean;
|
|
603
|
+
localMcp?: boolean;
|
|
604
|
+
/**
|
|
605
|
+
* #1377 D: keep the process alive after registering and poll for the
|
|
606
|
+
* user's budget approval (default). Set false for structured/automation
|
|
607
|
+
* runs (--json) where prompt output emission matters more than narration.
|
|
608
|
+
*/
|
|
609
|
+
waitForApproval?: boolean;
|
|
610
|
+
/** Test/injection overrides for the approval poll cadence and clock. */
|
|
611
|
+
approvalWait?: ApprovalWaitOptions;
|
|
612
|
+
}
|
|
613
|
+
/** The stable machine-readable result emitted by `haven-connect --json`. */
|
|
614
|
+
declare const CONNECT_OUTCOME_SCHEMA_VERSION: 1;
|
|
615
|
+
type ConnectOutcomeStatus = 'complete' | 'action_required' | 'failed';
|
|
616
|
+
interface ConnectOutcome {
|
|
617
|
+
schema_version: typeof CONNECT_OUTCOME_SCHEMA_VERSION;
|
|
618
|
+
outcome: ConnectOutcomeStatus;
|
|
619
|
+
runtime: string;
|
|
620
|
+
topology: string;
|
|
621
|
+
configuration: {
|
|
622
|
+
hosted_mcp: boolean;
|
|
623
|
+
local_signer: boolean;
|
|
624
|
+
local_mcp: boolean;
|
|
625
|
+
};
|
|
626
|
+
probe: {
|
|
627
|
+
result: string;
|
|
628
|
+
};
|
|
629
|
+
activation: {
|
|
630
|
+
restart_required: boolean;
|
|
631
|
+
instruction: string;
|
|
632
|
+
};
|
|
633
|
+
next_action: string;
|
|
634
|
+
approval: {
|
|
635
|
+
required: boolean;
|
|
636
|
+
expires_at: string | null;
|
|
637
|
+
};
|
|
638
|
+
verification: {
|
|
639
|
+
tools: readonly ['haven_get_agent', 'haven_get_allowances'];
|
|
640
|
+
instruction: string;
|
|
641
|
+
};
|
|
642
|
+
delegate_address?: string;
|
|
643
|
+
setup_challenge_expires_at?: string;
|
|
644
|
+
/**
|
|
645
|
+
* #2173, additive within schema_version 1. Both are present on a completed
|
|
646
|
+
* run and absent on `failedConnectOutcome`, which by construction has
|
|
647
|
+
* neither a registration nor a credential scan behind it.
|
|
648
|
+
*
|
|
649
|
+
* `hosted_mcp_url` is the endpoint Connect wired this run up to — written
|
|
650
|
+
* into the runtime's MCP config, or, on a manual runtime that Connect cannot
|
|
651
|
+
* configure, printed as the endpoint to enter by hand. It is deliberately
|
|
652
|
+
* NOT the `--api` backend URL: the hosted MCP
|
|
653
|
+
* server is a separate deployment, and an automation caller comparing the
|
|
654
|
+
* two used to read that intentional topology as an environment mismatch. It
|
|
655
|
+
* is non-secret — the same string already sits in the user's own config file
|
|
656
|
+
* — and carries no credential; the API key travels beside it in a header.
|
|
657
|
+
*
|
|
658
|
+
* `superseded_agent_ids` is the #1688 heads-up made structural: previously
|
|
659
|
+
* it was prose on stderr, so a `--json` caller could not see that the run it
|
|
660
|
+
* just completed left older agents alive with their own keys. Empty on a
|
|
661
|
+
* clean first run. An empty list is NOT proof of a clean machine — a scan
|
|
662
|
+
* that cannot read the credential root also yields an empty list, and the
|
|
663
|
+
* connector prefers that to failing a completed setup.
|
|
664
|
+
*/
|
|
665
|
+
hosted_mcp_url?: string;
|
|
666
|
+
superseded_agent_ids?: readonly string[];
|
|
667
|
+
/**
|
|
668
|
+
* #2091, additive within schema_version 1: `message` is the redacted human
|
|
669
|
+
* refusal (automation used to get code + next_action and nothing to act
|
|
670
|
+
* on), and `allowed_runtimes` carries the valid `--runtime` retry values on
|
|
671
|
+
* runtime-selection refusals — the list the backend's setup prompt requires
|
|
672
|
+
* a retry to be drawn from, which `--json` previously discarded with the
|
|
673
|
+
* prose.
|
|
674
|
+
*/
|
|
675
|
+
error?: {
|
|
676
|
+
code: string;
|
|
677
|
+
next_action: string;
|
|
678
|
+
message?: string;
|
|
679
|
+
allowed_runtimes?: readonly string[];
|
|
680
|
+
/**
|
|
681
|
+
* #2174, additive within schema_version 1. What the installed-client scan
|
|
682
|
+
* found on THIS machine, narrowing a `runtime_undetermined` retry from the
|
|
683
|
+
* nine-value `allowed_runtimes` menu to what is actually here.
|
|
684
|
+
*
|
|
685
|
+
* A hint an agent may echo back as `--runtime`, never a selection: the
|
|
686
|
+
* outcome stays `failed` with `rerun_connect_with_explicit_runtime`, and
|
|
687
|
+
* the connector never proceeds on it (#1719's populates-never-selects
|
|
688
|
+
* invariant). Absent when the scan found nothing OR could not run —
|
|
689
|
+
* both are honestly "no finding"; neither is a claim the machine is bare.
|
|
690
|
+
* `suggested_runtime` appears only when one candidate is unambiguously
|
|
691
|
+
* top, so it is never the winner of an arbitrary tiebreak.
|
|
692
|
+
*/
|
|
693
|
+
installed_clients?: readonly string[];
|
|
694
|
+
suggested_runtime?: string;
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
interface ConnectDeps {
|
|
698
|
+
api?: ConnectApiClient;
|
|
699
|
+
generateKey?: () => LocalDelegateKey;
|
|
700
|
+
generateApiKey?: () => string;
|
|
701
|
+
preflightStorage?: typeof preflightCredentialStorage;
|
|
702
|
+
writeCredentials?: typeof writeCredentialFiles;
|
|
703
|
+
installRuntime?: typeof installRuntime;
|
|
704
|
+
log?: (message: string) => void;
|
|
705
|
+
/** JSON CLI mode must not expose owner-only credential file locations. */
|
|
706
|
+
redactPaths?: boolean;
|
|
707
|
+
/** Overridable so the Node-floor refusal is testable without spawning a Node. */
|
|
708
|
+
nodeVersion?: string;
|
|
709
|
+
/** Overridable so runtime detection (#1672) is testable without faking process.env. */
|
|
710
|
+
env?: NodeJS.ProcessEnv;
|
|
711
|
+
/** Overridable so the #1719 TTY gate is testable without faking process.stdin. */
|
|
712
|
+
isTty?: boolean;
|
|
713
|
+
/** Overridable so the #1719 installed-client prompt is testable without readline. */
|
|
714
|
+
promptRuntime?: () => Promise<RuntimeId>;
|
|
715
|
+
/**
|
|
716
|
+
* Overridable so the #2173 recovery-record write is testable without a real
|
|
717
|
+
* credential directory — and so a FAILING writer can be injected to prove
|
|
718
|
+
* that a broken record write never fails a completed setup.
|
|
719
|
+
*/
|
|
720
|
+
writeOutcomeRecord?: typeof writeConnectOutcomeRecord;
|
|
721
|
+
/**
|
|
722
|
+
* Overridable so the #2174 refusal hint is testable without depending on
|
|
723
|
+
* which agent clients happen to be installed on the machine running the
|
|
724
|
+
* suite — and so a THROWING scan can be injected to prove the refusal
|
|
725
|
+
* degrades to its un-hinted shape.
|
|
726
|
+
*/
|
|
727
|
+
scanInstalledClients?: typeof scanInstalledClients;
|
|
728
|
+
}
|
|
729
|
+
interface ConnectResult {
|
|
730
|
+
setupId: string;
|
|
731
|
+
agentId: string;
|
|
732
|
+
delegateAddress: string;
|
|
733
|
+
credentialPaths: StoredCredentialPaths;
|
|
734
|
+
/** Additive, secret-free completion contract shared by library callers and --json. */
|
|
735
|
+
outcome: ConnectOutcome;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* The failure record for an error thrown by `runConnect` — the run's own, when
|
|
739
|
+
* it built one, and a freshly derived record otherwise (a rejection that never
|
|
740
|
+
* entered the run at all, such as an argument-parse refusal, or an injected
|
|
741
|
+
* one in a test).
|
|
742
|
+
*/
|
|
743
|
+
declare function failureOutcomeFor(runtimeHint: string | undefined, error: unknown): ConnectOutcome;
|
|
744
|
+
declare function runConnect(options: ConnectOptions, deps?: ConnectDeps): Promise<ConnectResult>;
|
|
745
|
+
declare function completionOutcome(input: {
|
|
746
|
+
runtimeInstall: RuntimeInstallResult;
|
|
747
|
+
delegateAddress: string;
|
|
748
|
+
hostedMcpUrl?: string;
|
|
749
|
+
supersededAgentIds?: readonly string[];
|
|
750
|
+
setupChallengeExpiresAt?: string;
|
|
751
|
+
approvalRequired: boolean;
|
|
752
|
+
}): ConnectOutcome;
|
|
753
|
+
/**
|
|
754
|
+
* The failure record for the `--json` contract.
|
|
755
|
+
*
|
|
756
|
+
* #1719: a `ConnectError` carries its own code and next action, so it is read
|
|
757
|
+
* rather than guessed. The regex ladder below survives only for the refusals
|
|
758
|
+
* that are still plain `Error`s — every new failure mode joins the vocabulary
|
|
759
|
+
* instead of joining that ladder.
|
|
760
|
+
*
|
|
761
|
+
* #2091: the record now carries the redacted message rather than omitting it.
|
|
762
|
+
* The old stance — terse because "messages can contain server or filesystem
|
|
763
|
+
* detail" — left automation with a code and nothing to act on; the field
|
|
764
|
+
* failure was a Codex agent staring at `connect_failed` with, in its own
|
|
765
|
+
* words, "no additional safe error detail". Sanitize, don't silence.
|
|
766
|
+
*/
|
|
767
|
+
declare function failedConnectOutcome(runtimeHint: string | undefined, error: unknown): ConnectOutcome;
|
|
768
|
+
/**
|
|
769
|
+
* #1377 D: after registering, the connector no longer goes dead while the
|
|
770
|
+
* user approves in Haven — it polls the narrow connector-status endpoint
|
|
771
|
+
* (agent-API-key auth, works during `setup_pending`) and narrates progress in
|
|
772
|
+
* the flow's voice, ending in a concrete celebration naming the granted
|
|
773
|
+
* authority. Bounds (stated per the issue): poll every 5 s, give up after
|
|
774
|
+
* 3 minutes (36 polls; with the immediate first check below the last one
|
|
775
|
+
* lands at ~175 s) — the connector ALWAYS terminates on its own; a timeout
|
|
776
|
+
* is a clean exit with guidance, never a hang. Injectable clock/cadence for
|
|
777
|
+
* tests.
|
|
778
|
+
*
|
|
779
|
+
* #1542: the first check happens BEFORE the "waiting for you to approve" line
|
|
780
|
+
* is printed. Users routinely approve in the dashboard while the runtime
|
|
781
|
+
* install is still running, and announcing a wait that is already over made
|
|
782
|
+
* the very next line ("Budget approved 🎉") read as a contradiction — the
|
|
783
|
+
* field-test agent relayed the pair verbatim as a bug.
|
|
784
|
+
*/
|
|
785
|
+
interface ApprovalWaitOptions {
|
|
786
|
+
intervalMs?: number;
|
|
787
|
+
timeoutMs?: number;
|
|
788
|
+
sleep?: (ms: number) => Promise<void>;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
interface ParsedCli {
|
|
792
|
+
options: ConnectOptions;
|
|
793
|
+
help: boolean;
|
|
794
|
+
json: boolean;
|
|
795
|
+
/** #1589: diagnosis mode — no setup token required. */
|
|
796
|
+
doctor: boolean;
|
|
797
|
+
repair: boolean;
|
|
798
|
+
/** #1681: retire an agent credential directory in place — no token required. */
|
|
799
|
+
tombstone?: {
|
|
800
|
+
directory: string;
|
|
801
|
+
reason?: string;
|
|
802
|
+
replacedBy?: string;
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* #2169: unwire one agent — tombstone it, then remove its MCP pair and
|
|
806
|
+
* Hermes dotenv key from every runtime config. No token required; refuses
|
|
807
|
+
* rather than guess when a bare pair is owned by a different agent.
|
|
808
|
+
*/
|
|
809
|
+
unwire?: {
|
|
810
|
+
reason?: string;
|
|
811
|
+
replacedBy?: string;
|
|
812
|
+
};
|
|
813
|
+
/** Optional positional value of --unwire <dir> (else --name / --credentials-dir resolve it). */
|
|
814
|
+
unwireDir?: string;
|
|
815
|
+
/**
|
|
816
|
+
* #1700: replace an agent's signing key on this machine. Two phases, because
|
|
817
|
+
* the dashboard sits between them — `start` generates the key and prints its
|
|
818
|
+
* address, `finish` writes the key the owner brings back. No setup token: the
|
|
819
|
+
* re-key API is owner-authenticated and this connector never calls it.
|
|
820
|
+
*/
|
|
821
|
+
rekey?: {
|
|
822
|
+
phase: 'start' | 'finish';
|
|
823
|
+
newApiKey?: string;
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
declare function parseArgs(argv: string[], env?: NodeJS.ProcessEnv): ParsedCli;
|
|
827
|
+
declare function helpText(): string;
|
|
828
|
+
|
|
829
|
+
declare function redactSecrets(value: string): string;
|
|
830
|
+
declare function shortAddress(address: string): string;
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* The connector's machine-readable failure vocabulary (#1719).
|
|
834
|
+
*
|
|
835
|
+
* Before this, a refusal was a bare `throw new Error(...)` and the automation
|
|
836
|
+
* contract recovered a code by regex-matching the message in
|
|
837
|
+
* `failedConnectOutcome`. That works only for as long as nobody rewords a
|
|
838
|
+
* sentence, and it gives the caller nothing to branch on that the wording did
|
|
839
|
+
* not accidentally provide. Every refusal this module fronts carries:
|
|
840
|
+
*
|
|
841
|
+
* - `code` — stable, snake_case, safe to switch on;
|
|
842
|
+
* - `nextAction` — the one thing to do next, in the same snake_case shape the
|
|
843
|
+
* install-status contract already uses for `next_action`;
|
|
844
|
+
* - `message` — human/agent prose, still the thing printed to a terminal.
|
|
845
|
+
*
|
|
846
|
+
* Codes are additive and never renamed: a consumer pinned to an older
|
|
847
|
+
* connector must keep recognising the ones it already knows.
|
|
848
|
+
*/
|
|
849
|
+
interface ConnectErrorDetails {
|
|
850
|
+
/**
|
|
851
|
+
* The `--runtime` values a retry may use, when the refusal is about runtime
|
|
852
|
+
* selection (#2091). The values lived only in `message` prose, which the
|
|
853
|
+
* `--json` contract discards entirely — while the backend's setup prompt
|
|
854
|
+
* instructs an agent to retry only with "one of the values that refusal
|
|
855
|
+
* lists". Carrying them structurally is what makes that retry reachable
|
|
856
|
+
* from automation.
|
|
857
|
+
*/
|
|
858
|
+
allowedRuntimes?: readonly string[];
|
|
859
|
+
/**
|
|
860
|
+
* What the #1719 installed-client scan found on this machine (#2174), when
|
|
861
|
+
* the refusal is `runtime_undetermined`. `installedClients` is ordered
|
|
862
|
+
* likeliest-first; `suggestedRuntime` appears only when one candidate is
|
|
863
|
+
* unambiguously top.
|
|
864
|
+
*
|
|
865
|
+
* A HINT, never a decision. The scan populates choices and never selects —
|
|
866
|
+
* carrying it here does not let the connector proceed on it, and the retry
|
|
867
|
+
* stays the agent's explicit `--runtime <name>`.
|
|
868
|
+
*/
|
|
869
|
+
installedClients?: readonly string[];
|
|
870
|
+
suggestedRuntime?: string;
|
|
871
|
+
}
|
|
872
|
+
declare class ConnectError extends Error {
|
|
873
|
+
readonly code: string;
|
|
874
|
+
readonly nextAction: string;
|
|
875
|
+
readonly details: ConnectErrorDetails;
|
|
876
|
+
constructor(code: string, message: string, nextAction: string, details?: ConnectErrorDetails);
|
|
877
|
+
}
|
|
878
|
+
declare function isConnectError(err: unknown): err is ConnectError;
|
|
879
|
+
|
|
880
|
+
declare const MCP_RUNTIME_MANIFEST: {
|
|
881
|
+
readonly mcpPackage: "@haven_ai/mcp";
|
|
882
|
+
readonly mcpVersion: "0.0.0-dev.202609031523.fd49e1a";
|
|
883
|
+
readonly sdkPackage: "@haven_ai/sdk";
|
|
884
|
+
readonly sdkVersion: "0.0.0-dev.202609031523.fd49e1a";
|
|
885
|
+
readonly signerPackage: "@haven_ai/signer";
|
|
886
|
+
readonly signerVersion: "0.0.0-dev.202609031523.fd49e1a";
|
|
887
|
+
readonly minimumNodeVersion: "22.0.0";
|
|
888
|
+
readonly supportedClients: readonly ["codex-cli", "codex-desktop", "claude-code"];
|
|
889
|
+
readonly requiredTools: readonly string[];
|
|
890
|
+
/**
|
|
891
|
+
* The signer MCP's tool surface, DERIVED from the pinned @haven_ai/signer
|
|
892
|
+
* package (#1587) — same anti-drift rule as `requiredTools` above: a
|
|
893
|
+
* literal list here would rot the first time the signer gains a tool.
|
|
894
|
+
* The handshake probe requires all of them.
|
|
895
|
+
*/
|
|
896
|
+
readonly requiredSignerTools: readonly string[];
|
|
897
|
+
};
|
|
898
|
+
declare function mcpPackageSpec(): string;
|
|
899
|
+
declare function sdkPackageSpec(): string;
|
|
900
|
+
declare function signerPackageSpec(): string;
|
|
901
|
+
|
|
902
|
+
export { CONNECTOR_VERSION, CONNECT_OUTCOME_FILENAME, CONNECT_OUTCOME_SCHEMA_VERSION, type ConnectApiClient, type ConnectDeps, ConnectError, type ConnectOptions, type ConnectOutcome, type ConnectOutcomeStatus, type ConnectResult, type InstalledClientCandidate, type InstalledClientHint, type LocalDelegateKey, MCP_RUNTIME_MANIFEST, type ParsedCli, type PrepareSignerRuntimeInput, type PreparedSignerRuntime, type PromptIo, RUNTIME_FLAG_VALUES, type RegisterSetupInput, type RegisterSetupResponse, type ResolveSetupInput, type ResolvedSetup, type RuntimeId, type RuntimeInstallInput, type RuntimeInstallResult, type RuntimeProfile, type RuntimeResolutionOptions, type RuntimeSelection, type ScanInstalledClientsOptions, type StoredCredentialPaths, type UpdateInstallStatusInput, type WriteCredentialInput, completionOutcome, createConnectApiClient, defaultAgentDirectory, delegateKeyFromPrivateKey, failedConnectOutcome, failureOutcomeFor, generateDelegateKey, helpText, installRuntime, installedClientHint, isConnectError, mcpPackageSpec, normalizeRuntime, parseArgs, prepareSignerRuntime, promptForInstalledClient, redactSecrets, resolveRuntimeByInstalledClientPrompt, resolveRuntimeSelection, runConnect, runtimeInstallCapabilities, runtimeProfile, scanInstalledClients, sdkPackageSpec, shortAddress, signerPackageSpec, writeConnectOutcomeRecord, writeCredentialFiles };
|