@yul-labs/agent-relay 0.1.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/LICENSE +21 -0
- package/README.md +395 -0
- package/agent-relay.config.example.json +38 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2938 -0
- package/dist/index.d.ts +1698 -0
- package/dist/index.js +2571 -0
- package/package.json +83 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1698 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deciders answer the approval / choice / input prompts that a coding agent
|
|
5
|
+
* raises during an INTERACTIVE run. This is the heart of the project's core
|
|
6
|
+
* feature: instead of suppressing prompts (the old non-interactive approach),
|
|
7
|
+
* agent-relay lets the agent ask, detects the question, and a Decider — a rule
|
|
8
|
+
* policy, an LLM (via a CLI like `claude -p` / `codex exec`), or any plugged-in
|
|
9
|
+
* function/API — produces the answer that is fed back into the session.
|
|
10
|
+
*
|
|
11
|
+
* Deciders are pluggable and vendor-agnostic; the interactive adapters call
|
|
12
|
+
* `decide()` and translate the result into keystrokes / protocol responses.
|
|
13
|
+
*/
|
|
14
|
+
type InteractionKind = "approval" | "choice" | "input";
|
|
15
|
+
/** A prompt the agent is waiting on, surfaced to the Decider. */
|
|
16
|
+
interface InteractionRequest {
|
|
17
|
+
kind: InteractionKind;
|
|
18
|
+
/** The cleaned prompt text the agent is showing. */
|
|
19
|
+
prompt: string;
|
|
20
|
+
/** The original task/goal the agent is working on — lets a decider judge
|
|
21
|
+
* whether a risky action is ON-task (allow) or OFF-task (redirect). */
|
|
22
|
+
task?: string;
|
|
23
|
+
/** For `choice`: the visible options/menu items, in display order. */
|
|
24
|
+
options?: string[];
|
|
25
|
+
/** For a MULTI-select choice: true, and `checked` carries each option's
|
|
26
|
+
* current state. The decider returns the DESIRED set in `optionIndexes`. */
|
|
27
|
+
multiSelect?: boolean;
|
|
28
|
+
/** For a multi-select: current checked state per option (aligned to `options`). */
|
|
29
|
+
checked?: boolean[];
|
|
30
|
+
/** Recent transcript / surrounding context to inform the decision. */
|
|
31
|
+
context?: string;
|
|
32
|
+
/** Working directory of the run. */
|
|
33
|
+
cwd?: string;
|
|
34
|
+
/** Which agent is asking (`claude` / `codex` / ...). */
|
|
35
|
+
agent?: string;
|
|
36
|
+
}
|
|
37
|
+
type DecisionAction = "approve" | "deny" | "select" | "answer" | "abort";
|
|
38
|
+
/** The Decider's answer to an {@link InteractionRequest}. */
|
|
39
|
+
interface InteractionDecision {
|
|
40
|
+
action: DecisionAction;
|
|
41
|
+
/** For `select`: index into `request.options`. */
|
|
42
|
+
optionIndex?: number;
|
|
43
|
+
/** For a MULTI-select `select`: the DESIRED set of checked indices. The keymap
|
|
44
|
+
* toggles only the options whose current `checked` state differs, then submits. */
|
|
45
|
+
optionIndexes?: number[];
|
|
46
|
+
/** For `answer`: free-text response. */
|
|
47
|
+
text?: string;
|
|
48
|
+
/** Why this decision was made (for logging/observability). */
|
|
49
|
+
reason?: string;
|
|
50
|
+
/** Which decider produced this. */
|
|
51
|
+
by?: string;
|
|
52
|
+
}
|
|
53
|
+
/** The pluggable contract every decider implements. */
|
|
54
|
+
interface Decider {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
decide(req: InteractionRequest): Promise<InteractionDecision>;
|
|
57
|
+
}
|
|
58
|
+
interface RuleDeciderOptions {
|
|
59
|
+
/** If the prompt/command matches any, deny (or pick a negative choice). */
|
|
60
|
+
denyPatterns?: RegExp[];
|
|
61
|
+
/** Default stance for approvals that match no deny pattern. */
|
|
62
|
+
defaultApproval?: "approve" | "deny";
|
|
63
|
+
/** Recognizes an affirmative option ("Yes", "Proceed", "Allow", ...). */
|
|
64
|
+
affirmativePattern?: RegExp;
|
|
65
|
+
/** Recognizes a negative option ("No", "Cancel", "Reject", ...). */
|
|
66
|
+
negativePattern?: RegExp;
|
|
67
|
+
/** Default free-text answer for `input` prompts. */
|
|
68
|
+
defaultAnswer?: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
}
|
|
71
|
+
/** Conservative default patterns for genuinely dangerous actions. */
|
|
72
|
+
declare const DEFAULT_DENY_PATTERNS: RegExp[];
|
|
73
|
+
declare class RuleDecider implements Decider {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
private readonly opts;
|
|
76
|
+
constructor(options?: RuleDeciderOptions);
|
|
77
|
+
private isDangerous;
|
|
78
|
+
decide(req: InteractionRequest): Promise<InteractionDecision>;
|
|
79
|
+
}
|
|
80
|
+
/** Approve everything (mirror of the old AutoApprovalGate, as a Decider). */
|
|
81
|
+
declare class AlwaysApproveDecider implements Decider {
|
|
82
|
+
readonly name = "always-approve";
|
|
83
|
+
decide(req: InteractionRequest): Promise<InteractionDecision>;
|
|
84
|
+
}
|
|
85
|
+
declare class FunctionDecider implements Decider {
|
|
86
|
+
private readonly fn;
|
|
87
|
+
readonly name: string;
|
|
88
|
+
constructor(fn: (req: InteractionRequest) => InteractionDecision | Promise<InteractionDecision>, name?: string);
|
|
89
|
+
decide(req: InteractionRequest): Promise<InteractionDecision>;
|
|
90
|
+
}
|
|
91
|
+
interface CommandDeciderOptions {
|
|
92
|
+
/** The decider model command, e.g. "claude" or "codex". */
|
|
93
|
+
command: string;
|
|
94
|
+
/** Args, e.g. ["-p","--output-format","json"] for claude print mode. */
|
|
95
|
+
args?: string[];
|
|
96
|
+
env?: Record<string, string>;
|
|
97
|
+
/** Build the model prompt from a request. */
|
|
98
|
+
renderPrompt?: (req: InteractionRequest) => string;
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
/** Decision used if the model errors / times out / is unparseable. */
|
|
101
|
+
fallback?: InteractionDecision;
|
|
102
|
+
name?: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Default rendering. The policy biases STRONGLY toward letting the agent work:
|
|
106
|
+
* approve by default so the task progresses, and only deny when an action is
|
|
107
|
+
* both dangerous AND unrelated to the task — in which case the model supplies a
|
|
108
|
+
* short redirect comment. (This judgment needs the task, which is why it cannot
|
|
109
|
+
* be a regex rule.)
|
|
110
|
+
*/
|
|
111
|
+
declare function renderDecisionPrompt(req: InteractionRequest): string;
|
|
112
|
+
/** Lenient parse of a model's textual reply into an InteractionDecision. */
|
|
113
|
+
declare function parseDecisionReply(reply: string): InteractionDecision | undefined;
|
|
114
|
+
declare class CommandDecider implements Decider {
|
|
115
|
+
readonly name: string;
|
|
116
|
+
private readonly opts;
|
|
117
|
+
constructor(options: CommandDeciderOptions);
|
|
118
|
+
decide(req: InteractionRequest): Promise<InteractionDecision>;
|
|
119
|
+
}
|
|
120
|
+
interface ApiDeciderOptions {
|
|
121
|
+
/** Full chat-completions URL, e.g. http://localhost:9090/v1/chat/completions */
|
|
122
|
+
url: string;
|
|
123
|
+
model?: string;
|
|
124
|
+
apiKey?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Cap on tokens the model may generate per decision (NOT a target — the model
|
|
127
|
+
* stops at its stop token, so a normal decision still costs only what it needs).
|
|
128
|
+
* Default 2048. Reasoning models emit a long chain-of-thought before the JSON
|
|
129
|
+
* answer, so a too-small cap (e.g. a few hundred) truncates them mid-thought,
|
|
130
|
+
* leaving an empty `content` and an unparseable reply → safe-deny. Raise it
|
|
131
|
+
* (not lower) for verbose reasoning models.
|
|
132
|
+
*/
|
|
133
|
+
maxTokens?: number;
|
|
134
|
+
temperature?: number;
|
|
135
|
+
timeoutMs?: number;
|
|
136
|
+
renderPrompt?: (req: InteractionRequest) => string;
|
|
137
|
+
fallback?: InteractionDecision;
|
|
138
|
+
name?: string;
|
|
139
|
+
}
|
|
140
|
+
declare class ApiDecider implements Decider {
|
|
141
|
+
private readonly opts;
|
|
142
|
+
readonly name: string;
|
|
143
|
+
constructor(opts: ApiDeciderOptions);
|
|
144
|
+
decide(req: InteractionRequest): Promise<InteractionDecision>;
|
|
145
|
+
}
|
|
146
|
+
type DeciderType = "rule" | "always-approve" | "command" | "api";
|
|
147
|
+
/** Plain config object describing which decider to use (mirrors the zod schema). */
|
|
148
|
+
interface DeciderConfig {
|
|
149
|
+
type: DeciderType;
|
|
150
|
+
/** rule: regex strings that trigger a deny / negative choice. */
|
|
151
|
+
denyPatterns?: string[];
|
|
152
|
+
/** rule: stance for non-dangerous approvals. */
|
|
153
|
+
defaultApproval?: "approve" | "deny";
|
|
154
|
+
/** rule: default text for `input` prompts. */
|
|
155
|
+
defaultAnswer?: string;
|
|
156
|
+
/** command: the model CLI to invoke and its args. */
|
|
157
|
+
command?: string;
|
|
158
|
+
args?: string[];
|
|
159
|
+
env?: Record<string, string>;
|
|
160
|
+
timeoutMs?: number;
|
|
161
|
+
/** api: OpenAI-compatible chat-completions endpoint + model. */
|
|
162
|
+
url?: string;
|
|
163
|
+
model?: string;
|
|
164
|
+
apiKey?: string;
|
|
165
|
+
maxTokens?: number;
|
|
166
|
+
}
|
|
167
|
+
/** Build a {@link Decider} from config. Defaults to a {@link RuleDecider}. */
|
|
168
|
+
declare function createDecider(config?: DeciderConfig): Decider;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Core type contracts for agent-relay.
|
|
172
|
+
*
|
|
173
|
+
* IMPORTANT: This module is the seam that keeps the core vendor-agnostic.
|
|
174
|
+
* Nothing in `core/` may import Claude/Codex-specific code; everything talks
|
|
175
|
+
* to the {@link AgentAdapter} interface defined here. Concrete adapters live
|
|
176
|
+
* under `src/adapters/` and depend on these types, never the other way around.
|
|
177
|
+
*/
|
|
178
|
+
/**
|
|
179
|
+
* How an adapter talks to its underlying agent.
|
|
180
|
+
*
|
|
181
|
+
* - `pty` : drive the agent's real interactive TUI through a pseudo-terminal.
|
|
182
|
+
* This is the primary mode for Claude/Codex.
|
|
183
|
+
* - `test` : in-process deterministic adapter used for tests / dry runs.
|
|
184
|
+
*/
|
|
185
|
+
type AdapterMode = "pty" | "test";
|
|
186
|
+
/**
|
|
187
|
+
* How much autonomy the underlying agent is granted for a run.
|
|
188
|
+
*
|
|
189
|
+
* - `auto` : the agent acts without asking — the orchestrator auto-approves
|
|
190
|
+
* every action (the default for "run while I sleep" automation).
|
|
191
|
+
* - `gated` : risky actions are deferred to an {@link ApprovalGate} handler;
|
|
192
|
+
* without one they pause the run (status `waiting_approval`).
|
|
193
|
+
* - `readonly` : the agent may inspect but not mutate (plan / read-only sandbox).
|
|
194
|
+
*/
|
|
195
|
+
type ApprovalMode = "auto" | "gated" | "readonly";
|
|
196
|
+
/** Sandbox strength for process-spawning agents (maps to vendor flags). */
|
|
197
|
+
type SandboxLevel = "read-only" | "workspace-write" | "danger-full-access";
|
|
198
|
+
/**
|
|
199
|
+
* The normalized event types every adapter emits, regardless of vendor.
|
|
200
|
+
* Adapters translate their native event streams into these.
|
|
201
|
+
*/
|
|
202
|
+
type AgentEventType = "start" | "stdout" | "stderr" | "json" | "assistant_message" | "tool_call" | "tool_result" | "status" | "error" | "complete";
|
|
203
|
+
/** A single normalized event in an agent run. */
|
|
204
|
+
interface AgentEvent {
|
|
205
|
+
type: AgentEventType;
|
|
206
|
+
/** ISO-8601 timestamp of when the event was observed. */
|
|
207
|
+
timestamp: string;
|
|
208
|
+
/** Human-readable text payload, when applicable (assistant text, log line). */
|
|
209
|
+
text?: string;
|
|
210
|
+
/** Structured payload (parsed JSON, tool input/output, usage, etc). */
|
|
211
|
+
data?: unknown;
|
|
212
|
+
/** The raw, unparsed source line, preserved for debugging/logging. */
|
|
213
|
+
raw?: string;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* A pointer to an underlying agent session so it can later be resumed.
|
|
217
|
+
* `nativeSessionId` is the vendor's own id (Codex thread id, Claude session id).
|
|
218
|
+
*/
|
|
219
|
+
interface AgentSessionRef {
|
|
220
|
+
adapter: string;
|
|
221
|
+
nativeSessionId?: string;
|
|
222
|
+
resumable: boolean;
|
|
223
|
+
}
|
|
224
|
+
/** Everything an adapter needs to perform a single run. */
|
|
225
|
+
interface AgentRunInput {
|
|
226
|
+
/** The task prompt to hand to the agent. */
|
|
227
|
+
prompt: string;
|
|
228
|
+
/** Working directory the agent should operate in. */
|
|
229
|
+
cwd: string;
|
|
230
|
+
/** Safety cap on the number of assistant turns. */
|
|
231
|
+
maxTurns: number;
|
|
232
|
+
/** Hard wall-clock timeout in milliseconds. */
|
|
233
|
+
timeoutMs: number;
|
|
234
|
+
/** Idle timeout: abort if no event is observed for this long. */
|
|
235
|
+
idleTimeoutMs: number;
|
|
236
|
+
/** When true, no real process is spawned; only a command preview is produced. */
|
|
237
|
+
dryRun?: boolean;
|
|
238
|
+
/** When resuming, the native session reference to continue from. */
|
|
239
|
+
resume?: AgentSessionRef;
|
|
240
|
+
/** Extra args appended to the underlying command (escape hatch). */
|
|
241
|
+
extraArgs?: string[];
|
|
242
|
+
/** Autonomy level: how the agent's risky actions are approved. */
|
|
243
|
+
approvalMode?: ApprovalMode;
|
|
244
|
+
/** Sandbox strength for process-spawning agents. */
|
|
245
|
+
sandbox?: SandboxLevel;
|
|
246
|
+
/**
|
|
247
|
+
* Interactive (PTY) only: how long the agent must produce no output and show
|
|
248
|
+
* no prompt before the session decides the task is complete and quits the
|
|
249
|
+
* TUI. Lower = faster termination but more risk of quitting mid-think.
|
|
250
|
+
*/
|
|
251
|
+
completionIdleMs?: number;
|
|
252
|
+
/**
|
|
253
|
+
* Claude only: get the full "ultracode" preset (xhigh effort + dynamic workflow
|
|
254
|
+
* orchestration) on an UNATTENDED run. The adapter uses an xhigh-capable model
|
|
255
|
+
* (`--model opus`, required) and drives `/effort ultracode` in the TUI before
|
|
256
|
+
* the task. (Plain `--effort xhigh` is the default and needs no model change.)
|
|
257
|
+
*/
|
|
258
|
+
ultracode?: boolean;
|
|
259
|
+
/** Arbitrary extra options interpreted by specific adapters (e.g. fake). */
|
|
260
|
+
options?: Record<string, unknown>;
|
|
261
|
+
}
|
|
262
|
+
/** The terminal result an adapter returns from {@link AgentAdapter.run}. */
|
|
263
|
+
interface AgentRunResult {
|
|
264
|
+
/** Whether the adapter believes the run finished successfully. */
|
|
265
|
+
success: boolean;
|
|
266
|
+
/** Process exit code, or null for non-process adapters / aborted runs. */
|
|
267
|
+
exitCode: number | null;
|
|
268
|
+
/** Native session reference for resume, if the adapter produced one. */
|
|
269
|
+
sessionRef?: AgentSessionRef;
|
|
270
|
+
/** The agent's final assistant message, if captured. */
|
|
271
|
+
finalMessage?: string;
|
|
272
|
+
/** True if the run was aborted via the provided AbortSignal. */
|
|
273
|
+
aborted?: boolean;
|
|
274
|
+
/** Structured error info when the run failed (friendly, not a raw stack). */
|
|
275
|
+
error?: AgentErrorInfo;
|
|
276
|
+
/** Free-form metadata (token usage, cost, turn counts, ...). */
|
|
277
|
+
meta?: Record<string, unknown>;
|
|
278
|
+
}
|
|
279
|
+
/** Friendly, structured error info surfaced by adapters (never a raw throw). */
|
|
280
|
+
interface AgentErrorInfo {
|
|
281
|
+
message: string;
|
|
282
|
+
/** Stable code, e.g. `COMMAND_NOT_FOUND`, `SPAWN_FAILED`, `NON_ZERO_EXIT`. */
|
|
283
|
+
code?: string;
|
|
284
|
+
/** Optional remediation hint shown to the user. */
|
|
285
|
+
hint?: string;
|
|
286
|
+
}
|
|
287
|
+
/** Result of an adapter availability probe. */
|
|
288
|
+
interface AdapterAvailability {
|
|
289
|
+
ok: boolean;
|
|
290
|
+
/** Why the adapter is unavailable (friendly message). */
|
|
291
|
+
reason?: string;
|
|
292
|
+
/** Detected version string, when discoverable. */
|
|
293
|
+
version?: string;
|
|
294
|
+
/** Remediation hint, e.g. install instructions. */
|
|
295
|
+
hint?: string;
|
|
296
|
+
}
|
|
297
|
+
/** A human-facing preview of the command an adapter would execute. */
|
|
298
|
+
interface CommandPreview {
|
|
299
|
+
mode: AdapterMode;
|
|
300
|
+
command: string;
|
|
301
|
+
args: string[];
|
|
302
|
+
/** Whether the prompt is delivered over stdin rather than as an arg. */
|
|
303
|
+
promptViaStdin?: boolean;
|
|
304
|
+
/** Display string, e.g. `codex exec --json "<prompt>"`. */
|
|
305
|
+
display: string;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* The runtime context the runner passes to an adapter. The adapter pushes
|
|
309
|
+
* normalized events via {@link onEvent} and must honor {@link signal} for
|
|
310
|
+
* cancellation (timeout / idle / Ctrl-C). Keeping this callback-based (rather
|
|
311
|
+
* than returning an async iterator) makes the control flow easy to test and to
|
|
312
|
+
* race against timers in the runner.
|
|
313
|
+
*/
|
|
314
|
+
interface AdapterRunContext {
|
|
315
|
+
/** Aborts when the runner decides to stop (timeout, idle, signal, maxTurns). */
|
|
316
|
+
signal: AbortSignal;
|
|
317
|
+
/** Push a normalized event to the runner. */
|
|
318
|
+
onEvent: (event: AgentEvent) => void;
|
|
319
|
+
/** Working directory (mirror of input.cwd, provided for convenience). */
|
|
320
|
+
cwd: string;
|
|
321
|
+
/**
|
|
322
|
+
* Answers the approval / choice / input prompts an INTERACTIVE agent raises.
|
|
323
|
+
* Interactive (PTY) adapters consult this for every detected prompt — it is
|
|
324
|
+
* the runtime hook that actually handles user-approval situations.
|
|
325
|
+
*/
|
|
326
|
+
decider?: Decider;
|
|
327
|
+
}
|
|
328
|
+
/** Static description of an adapter, used by the registry and `adapters` cmd. */
|
|
329
|
+
interface AgentAdapterDefinition {
|
|
330
|
+
/** Registry key / adapter name as used on the CLI (`claude`, `codex`, ...). */
|
|
331
|
+
name: string;
|
|
332
|
+
/** Adapter family/type (from config; usually equals `name`). */
|
|
333
|
+
type: string;
|
|
334
|
+
/** Execution mode. */
|
|
335
|
+
mode: AdapterMode;
|
|
336
|
+
/** One-line human description. */
|
|
337
|
+
description: string;
|
|
338
|
+
/** Whether this adapter can resume previous sessions. */
|
|
339
|
+
supportsResume: boolean;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* The single interface every agent integration must implement. The core only
|
|
343
|
+
* ever depends on this; vendor specifics stay inside implementations.
|
|
344
|
+
*/
|
|
345
|
+
interface AgentAdapter {
|
|
346
|
+
readonly definition: AgentAdapterDefinition;
|
|
347
|
+
/** Probe whether the adapter can actually run (command present, key set...). */
|
|
348
|
+
isAvailable(): Promise<AdapterAvailability>;
|
|
349
|
+
/** Describe (but do not execute) the command for a given input. */
|
|
350
|
+
describeCommand(input: AgentRunInput): CommandPreview;
|
|
351
|
+
/** Execute a run, emitting events via ctx.onEvent and resolving a result. */
|
|
352
|
+
run(input: AgentRunInput, ctx: AdapterRunContext): Promise<AgentRunResult>;
|
|
353
|
+
/**
|
|
354
|
+
* Resume a previous session with a follow-up prompt. Optional: adapters that
|
|
355
|
+
* cannot resume omit this. Implementations should honor the same context
|
|
356
|
+
* contract as {@link run}.
|
|
357
|
+
*/
|
|
358
|
+
resume?(ref: AgentSessionRef, input: AgentRunInput, ctx: AdapterRunContext): Promise<AgentRunResult>;
|
|
359
|
+
}
|
|
360
|
+
/** Lifecycle status of a relay session. */
|
|
361
|
+
type SessionStatus = "created" | "running" | "completed" | "failed" | "timeout" | "cancelled" | "waiting_approval";
|
|
362
|
+
/** Persisted metadata for a single session. */
|
|
363
|
+
interface SessionMetadata {
|
|
364
|
+
sessionId: string;
|
|
365
|
+
prompt: string;
|
|
366
|
+
adapter: string;
|
|
367
|
+
mode: AdapterMode;
|
|
368
|
+
status: SessionStatus;
|
|
369
|
+
cwd: string;
|
|
370
|
+
dryRun: boolean;
|
|
371
|
+
startedAt: string;
|
|
372
|
+
endedAt?: string;
|
|
373
|
+
logFile: string;
|
|
374
|
+
exitCode?: number | null;
|
|
375
|
+
error?: AgentErrorInfo;
|
|
376
|
+
/** Native session reference enabling resume. */
|
|
377
|
+
sessionRef?: AgentSessionRef;
|
|
378
|
+
/** Free-form metadata captured from the adapter result. */
|
|
379
|
+
meta?: Record<string, unknown>;
|
|
380
|
+
}
|
|
381
|
+
/** Reason the runner aborted a run before the adapter finished naturally. */
|
|
382
|
+
type AbortReason = "timeout" | "idle" | "cancel" | "maxTurns";
|
|
383
|
+
|
|
384
|
+
/** Config schema (zod), defaults, and load/save helpers. */
|
|
385
|
+
|
|
386
|
+
/** The canonical config file name created by `agent-relay init`. */
|
|
387
|
+
declare const CONFIG_FILENAME = "agent-relay.config.json";
|
|
388
|
+
declare const approvalPolicySchema: z.ZodEnum<["auto", "gated", "readonly"]>;
|
|
389
|
+
declare const sandboxSchema: z.ZodEnum<["read-only", "workspace-write", "danger-full-access"]>;
|
|
390
|
+
/**
|
|
391
|
+
* Per-adapter configuration. `command`/`args` are only meaningful for adapters
|
|
392
|
+
* that spawn a process; the in-process `fake`/`test` adapter omits them.
|
|
393
|
+
*/
|
|
394
|
+
declare const adapterConfigSchema: z.ZodObject<{
|
|
395
|
+
type: z.ZodString;
|
|
396
|
+
mode: z.ZodEnum<["pty", "test"]>;
|
|
397
|
+
command: z.ZodOptional<z.ZodString>;
|
|
398
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
399
|
+
/** Extra env vars to inject when spawning. */
|
|
400
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
401
|
+
/** Per-adapter approval override (falls back to defaults). */
|
|
402
|
+
approvalPolicy: z.ZodOptional<z.ZodEnum<["auto", "gated", "readonly"]>>;
|
|
403
|
+
/** Per-adapter sandbox override (falls back to defaults). */
|
|
404
|
+
sandbox: z.ZodOptional<z.ZodEnum<["read-only", "workspace-write", "danger-full-access"]>>;
|
|
405
|
+
}, "strict", z.ZodTypeAny, {
|
|
406
|
+
type: string;
|
|
407
|
+
mode: "pty" | "test";
|
|
408
|
+
args: string[];
|
|
409
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
410
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
411
|
+
command?: string | undefined;
|
|
412
|
+
env?: Record<string, string> | undefined;
|
|
413
|
+
}, {
|
|
414
|
+
type: string;
|
|
415
|
+
mode: "pty" | "test";
|
|
416
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
417
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
418
|
+
command?: string | undefined;
|
|
419
|
+
args?: string[] | undefined;
|
|
420
|
+
env?: Record<string, string> | undefined;
|
|
421
|
+
}>;
|
|
422
|
+
type AdapterConfig = z.infer<typeof adapterConfigSchema>;
|
|
423
|
+
declare const defaultsSchema: z.ZodObject<{
|
|
424
|
+
maxTurns: z.ZodDefault<z.ZodNumber>;
|
|
425
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
426
|
+
idleTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
427
|
+
/**
|
|
428
|
+
* Default autonomy level. `auto` (the default) makes runs fully
|
|
429
|
+
* unattended — the orchestrator auto-approves and the agent CLIs are
|
|
430
|
+
* launched with their non-interactive "don't ask" flags.
|
|
431
|
+
*/
|
|
432
|
+
approvalPolicy: z.ZodOptional<z.ZodEnum<["auto", "gated", "readonly"]>>;
|
|
433
|
+
/** Default sandbox strength for process-spawning agents. */
|
|
434
|
+
sandbox: z.ZodOptional<z.ZodEnum<["read-only", "workspace-write", "danger-full-access"]>>;
|
|
435
|
+
/** Interactive: idle window before a finished agent's TUI is quit (ms). */
|
|
436
|
+
completionIdleMs: z.ZodOptional<z.ZodNumber>;
|
|
437
|
+
/**
|
|
438
|
+
* Legacy flag. When `approvalPolicy` is unset, `true` -> `gated` and
|
|
439
|
+
* `false` -> `auto`. Kept for back-compat; prefer `approvalPolicy`.
|
|
440
|
+
*/
|
|
441
|
+
requireApprovalOnRiskyActions: z.ZodDefault<z.ZodBoolean>;
|
|
442
|
+
}, "strict", z.ZodTypeAny, {
|
|
443
|
+
maxTurns: number;
|
|
444
|
+
timeoutMs: number;
|
|
445
|
+
idleTimeoutMs: number;
|
|
446
|
+
requireApprovalOnRiskyActions: boolean;
|
|
447
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
448
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
449
|
+
completionIdleMs?: number | undefined;
|
|
450
|
+
}, {
|
|
451
|
+
maxTurns?: number | undefined;
|
|
452
|
+
timeoutMs?: number | undefined;
|
|
453
|
+
idleTimeoutMs?: number | undefined;
|
|
454
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
455
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
456
|
+
completionIdleMs?: number | undefined;
|
|
457
|
+
requireApprovalOnRiskyActions?: boolean | undefined;
|
|
458
|
+
}>;
|
|
459
|
+
type RelayDefaults = z.infer<typeof defaultsSchema>;
|
|
460
|
+
/** Resolve the effective approval mode (adapter override > defaults > legacy). */
|
|
461
|
+
declare function resolveApprovalMode(defaults: RelayDefaults, adapter?: AdapterConfig): ApprovalMode;
|
|
462
|
+
/** Resolve the effective sandbox level (adapter override > defaults > default). */
|
|
463
|
+
declare function resolveSandbox(defaults: RelayDefaults, adapter?: AdapterConfig): SandboxLevel;
|
|
464
|
+
declare const hooksSchema: z.ZodObject<{
|
|
465
|
+
/** Shell command run just before the agent starts. */
|
|
466
|
+
onStart: z.ZodOptional<z.ZodString>;
|
|
467
|
+
/** Shell command run after the run reaches a terminal status. */
|
|
468
|
+
onComplete: z.ZodOptional<z.ZodString>;
|
|
469
|
+
}, "strict", z.ZodTypeAny, {
|
|
470
|
+
onStart?: string | undefined;
|
|
471
|
+
onComplete?: string | undefined;
|
|
472
|
+
}, {
|
|
473
|
+
onStart?: string | undefined;
|
|
474
|
+
onComplete?: string | undefined;
|
|
475
|
+
}>;
|
|
476
|
+
type HooksConfig = z.infer<typeof hooksSchema>;
|
|
477
|
+
/** Which decider answers interactive approval/choice prompts. */
|
|
478
|
+
declare const deciderSchema: z.ZodObject<{
|
|
479
|
+
type: z.ZodDefault<z.ZodEnum<["rule", "always-approve", "command", "api"]>>;
|
|
480
|
+
/** rule: regex strings that trigger deny / a negative choice. */
|
|
481
|
+
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
482
|
+
/** rule: stance for non-dangerous approvals. */
|
|
483
|
+
defaultApproval: z.ZodOptional<z.ZodEnum<["approve", "deny"]>>;
|
|
484
|
+
/** rule: default answer for free-text input prompts. */
|
|
485
|
+
defaultAnswer: z.ZodOptional<z.ZodString>;
|
|
486
|
+
/** command: the decider model CLI + args (e.g. claude -p / codex exec). */
|
|
487
|
+
command: z.ZodOptional<z.ZodString>;
|
|
488
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
489
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
490
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
491
|
+
/** api: OpenAI-compatible chat-completions endpoint + model. */
|
|
492
|
+
url: z.ZodOptional<z.ZodString>;
|
|
493
|
+
model: z.ZodOptional<z.ZodString>;
|
|
494
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
495
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
496
|
+
}, "strict", z.ZodTypeAny, {
|
|
497
|
+
type: "command" | "rule" | "always-approve" | "api";
|
|
498
|
+
timeoutMs?: number | undefined;
|
|
499
|
+
command?: string | undefined;
|
|
500
|
+
args?: string[] | undefined;
|
|
501
|
+
env?: Record<string, string> | undefined;
|
|
502
|
+
denyPatterns?: string[] | undefined;
|
|
503
|
+
defaultApproval?: "approve" | "deny" | undefined;
|
|
504
|
+
defaultAnswer?: string | undefined;
|
|
505
|
+
url?: string | undefined;
|
|
506
|
+
model?: string | undefined;
|
|
507
|
+
apiKey?: string | undefined;
|
|
508
|
+
maxTokens?: number | undefined;
|
|
509
|
+
}, {
|
|
510
|
+
timeoutMs?: number | undefined;
|
|
511
|
+
type?: "command" | "rule" | "always-approve" | "api" | undefined;
|
|
512
|
+
command?: string | undefined;
|
|
513
|
+
args?: string[] | undefined;
|
|
514
|
+
env?: Record<string, string> | undefined;
|
|
515
|
+
denyPatterns?: string[] | undefined;
|
|
516
|
+
defaultApproval?: "approve" | "deny" | undefined;
|
|
517
|
+
defaultAnswer?: string | undefined;
|
|
518
|
+
url?: string | undefined;
|
|
519
|
+
model?: string | undefined;
|
|
520
|
+
apiKey?: string | undefined;
|
|
521
|
+
maxTokens?: number | undefined;
|
|
522
|
+
}>;
|
|
523
|
+
type DeciderConfigSchema = z.infer<typeof deciderSchema>;
|
|
524
|
+
declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
525
|
+
defaultAdapter: z.ZodString;
|
|
526
|
+
sessionsDir: z.ZodDefault<z.ZodString>;
|
|
527
|
+
logsDir: z.ZodDefault<z.ZodString>;
|
|
528
|
+
defaults: z.ZodDefault<z.ZodObject<{
|
|
529
|
+
maxTurns: z.ZodDefault<z.ZodNumber>;
|
|
530
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
531
|
+
idleTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
532
|
+
/**
|
|
533
|
+
* Default autonomy level. `auto` (the default) makes runs fully
|
|
534
|
+
* unattended — the orchestrator auto-approves and the agent CLIs are
|
|
535
|
+
* launched with their non-interactive "don't ask" flags.
|
|
536
|
+
*/
|
|
537
|
+
approvalPolicy: z.ZodOptional<z.ZodEnum<["auto", "gated", "readonly"]>>;
|
|
538
|
+
/** Default sandbox strength for process-spawning agents. */
|
|
539
|
+
sandbox: z.ZodOptional<z.ZodEnum<["read-only", "workspace-write", "danger-full-access"]>>;
|
|
540
|
+
/** Interactive: idle window before a finished agent's TUI is quit (ms). */
|
|
541
|
+
completionIdleMs: z.ZodOptional<z.ZodNumber>;
|
|
542
|
+
/**
|
|
543
|
+
* Legacy flag. When `approvalPolicy` is unset, `true` -> `gated` and
|
|
544
|
+
* `false` -> `auto`. Kept for back-compat; prefer `approvalPolicy`.
|
|
545
|
+
*/
|
|
546
|
+
requireApprovalOnRiskyActions: z.ZodDefault<z.ZodBoolean>;
|
|
547
|
+
}, "strict", z.ZodTypeAny, {
|
|
548
|
+
maxTurns: number;
|
|
549
|
+
timeoutMs: number;
|
|
550
|
+
idleTimeoutMs: number;
|
|
551
|
+
requireApprovalOnRiskyActions: boolean;
|
|
552
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
553
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
554
|
+
completionIdleMs?: number | undefined;
|
|
555
|
+
}, {
|
|
556
|
+
maxTurns?: number | undefined;
|
|
557
|
+
timeoutMs?: number | undefined;
|
|
558
|
+
idleTimeoutMs?: number | undefined;
|
|
559
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
560
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
561
|
+
completionIdleMs?: number | undefined;
|
|
562
|
+
requireApprovalOnRiskyActions?: boolean | undefined;
|
|
563
|
+
}>>;
|
|
564
|
+
adapters: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
565
|
+
type: z.ZodString;
|
|
566
|
+
mode: z.ZodEnum<["pty", "test"]>;
|
|
567
|
+
command: z.ZodOptional<z.ZodString>;
|
|
568
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
569
|
+
/** Extra env vars to inject when spawning. */
|
|
570
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
571
|
+
/** Per-adapter approval override (falls back to defaults). */
|
|
572
|
+
approvalPolicy: z.ZodOptional<z.ZodEnum<["auto", "gated", "readonly"]>>;
|
|
573
|
+
/** Per-adapter sandbox override (falls back to defaults). */
|
|
574
|
+
sandbox: z.ZodOptional<z.ZodEnum<["read-only", "workspace-write", "danger-full-access"]>>;
|
|
575
|
+
}, "strict", z.ZodTypeAny, {
|
|
576
|
+
type: string;
|
|
577
|
+
mode: "pty" | "test";
|
|
578
|
+
args: string[];
|
|
579
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
580
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
581
|
+
command?: string | undefined;
|
|
582
|
+
env?: Record<string, string> | undefined;
|
|
583
|
+
}, {
|
|
584
|
+
type: string;
|
|
585
|
+
mode: "pty" | "test";
|
|
586
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
587
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
588
|
+
command?: string | undefined;
|
|
589
|
+
args?: string[] | undefined;
|
|
590
|
+
env?: Record<string, string> | undefined;
|
|
591
|
+
}>>;
|
|
592
|
+
/** Which decider answers interactive prompts. */
|
|
593
|
+
decider: z.ZodOptional<z.ZodObject<{
|
|
594
|
+
type: z.ZodDefault<z.ZodEnum<["rule", "always-approve", "command", "api"]>>;
|
|
595
|
+
/** rule: regex strings that trigger deny / a negative choice. */
|
|
596
|
+
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
597
|
+
/** rule: stance for non-dangerous approvals. */
|
|
598
|
+
defaultApproval: z.ZodOptional<z.ZodEnum<["approve", "deny"]>>;
|
|
599
|
+
/** rule: default answer for free-text input prompts. */
|
|
600
|
+
defaultAnswer: z.ZodOptional<z.ZodString>;
|
|
601
|
+
/** command: the decider model CLI + args (e.g. claude -p / codex exec). */
|
|
602
|
+
command: z.ZodOptional<z.ZodString>;
|
|
603
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
604
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
605
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
606
|
+
/** api: OpenAI-compatible chat-completions endpoint + model. */
|
|
607
|
+
url: z.ZodOptional<z.ZodString>;
|
|
608
|
+
model: z.ZodOptional<z.ZodString>;
|
|
609
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
610
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
611
|
+
}, "strict", z.ZodTypeAny, {
|
|
612
|
+
type: "command" | "rule" | "always-approve" | "api";
|
|
613
|
+
timeoutMs?: number | undefined;
|
|
614
|
+
command?: string | undefined;
|
|
615
|
+
args?: string[] | undefined;
|
|
616
|
+
env?: Record<string, string> | undefined;
|
|
617
|
+
denyPatterns?: string[] | undefined;
|
|
618
|
+
defaultApproval?: "approve" | "deny" | undefined;
|
|
619
|
+
defaultAnswer?: string | undefined;
|
|
620
|
+
url?: string | undefined;
|
|
621
|
+
model?: string | undefined;
|
|
622
|
+
apiKey?: string | undefined;
|
|
623
|
+
maxTokens?: number | undefined;
|
|
624
|
+
}, {
|
|
625
|
+
timeoutMs?: number | undefined;
|
|
626
|
+
type?: "command" | "rule" | "always-approve" | "api" | undefined;
|
|
627
|
+
command?: string | undefined;
|
|
628
|
+
args?: string[] | undefined;
|
|
629
|
+
env?: Record<string, string> | undefined;
|
|
630
|
+
denyPatterns?: string[] | undefined;
|
|
631
|
+
defaultApproval?: "approve" | "deny" | undefined;
|
|
632
|
+
defaultAnswer?: string | undefined;
|
|
633
|
+
url?: string | undefined;
|
|
634
|
+
model?: string | undefined;
|
|
635
|
+
apiKey?: string | undefined;
|
|
636
|
+
maxTokens?: number | undefined;
|
|
637
|
+
}>>;
|
|
638
|
+
/** Optional shell-command lifecycle hooks. */
|
|
639
|
+
hooks: z.ZodOptional<z.ZodObject<{
|
|
640
|
+
/** Shell command run just before the agent starts. */
|
|
641
|
+
onStart: z.ZodOptional<z.ZodString>;
|
|
642
|
+
/** Shell command run after the run reaches a terminal status. */
|
|
643
|
+
onComplete: z.ZodOptional<z.ZodString>;
|
|
644
|
+
}, "strict", z.ZodTypeAny, {
|
|
645
|
+
onStart?: string | undefined;
|
|
646
|
+
onComplete?: string | undefined;
|
|
647
|
+
}, {
|
|
648
|
+
onStart?: string | undefined;
|
|
649
|
+
onComplete?: string | undefined;
|
|
650
|
+
}>>;
|
|
651
|
+
}, "strict", z.ZodTypeAny, {
|
|
652
|
+
adapters: Record<string, {
|
|
653
|
+
type: string;
|
|
654
|
+
mode: "pty" | "test";
|
|
655
|
+
args: string[];
|
|
656
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
657
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
658
|
+
command?: string | undefined;
|
|
659
|
+
env?: Record<string, string> | undefined;
|
|
660
|
+
}>;
|
|
661
|
+
defaultAdapter: string;
|
|
662
|
+
sessionsDir: string;
|
|
663
|
+
logsDir: string;
|
|
664
|
+
defaults: {
|
|
665
|
+
maxTurns: number;
|
|
666
|
+
timeoutMs: number;
|
|
667
|
+
idleTimeoutMs: number;
|
|
668
|
+
requireApprovalOnRiskyActions: boolean;
|
|
669
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
670
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
671
|
+
completionIdleMs?: number | undefined;
|
|
672
|
+
};
|
|
673
|
+
decider?: {
|
|
674
|
+
type: "command" | "rule" | "always-approve" | "api";
|
|
675
|
+
timeoutMs?: number | undefined;
|
|
676
|
+
command?: string | undefined;
|
|
677
|
+
args?: string[] | undefined;
|
|
678
|
+
env?: Record<string, string> | undefined;
|
|
679
|
+
denyPatterns?: string[] | undefined;
|
|
680
|
+
defaultApproval?: "approve" | "deny" | undefined;
|
|
681
|
+
defaultAnswer?: string | undefined;
|
|
682
|
+
url?: string | undefined;
|
|
683
|
+
model?: string | undefined;
|
|
684
|
+
apiKey?: string | undefined;
|
|
685
|
+
maxTokens?: number | undefined;
|
|
686
|
+
} | undefined;
|
|
687
|
+
hooks?: {
|
|
688
|
+
onStart?: string | undefined;
|
|
689
|
+
onComplete?: string | undefined;
|
|
690
|
+
} | undefined;
|
|
691
|
+
}, {
|
|
692
|
+
adapters: Record<string, {
|
|
693
|
+
type: string;
|
|
694
|
+
mode: "pty" | "test";
|
|
695
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
696
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
697
|
+
command?: string | undefined;
|
|
698
|
+
args?: string[] | undefined;
|
|
699
|
+
env?: Record<string, string> | undefined;
|
|
700
|
+
}>;
|
|
701
|
+
defaultAdapter: string;
|
|
702
|
+
sessionsDir?: string | undefined;
|
|
703
|
+
logsDir?: string | undefined;
|
|
704
|
+
defaults?: {
|
|
705
|
+
maxTurns?: number | undefined;
|
|
706
|
+
timeoutMs?: number | undefined;
|
|
707
|
+
idleTimeoutMs?: number | undefined;
|
|
708
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
709
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
710
|
+
completionIdleMs?: number | undefined;
|
|
711
|
+
requireApprovalOnRiskyActions?: boolean | undefined;
|
|
712
|
+
} | undefined;
|
|
713
|
+
decider?: {
|
|
714
|
+
timeoutMs?: number | undefined;
|
|
715
|
+
type?: "command" | "rule" | "always-approve" | "api" | undefined;
|
|
716
|
+
command?: string | undefined;
|
|
717
|
+
args?: string[] | undefined;
|
|
718
|
+
env?: Record<string, string> | undefined;
|
|
719
|
+
denyPatterns?: string[] | undefined;
|
|
720
|
+
defaultApproval?: "approve" | "deny" | undefined;
|
|
721
|
+
defaultAnswer?: string | undefined;
|
|
722
|
+
url?: string | undefined;
|
|
723
|
+
model?: string | undefined;
|
|
724
|
+
apiKey?: string | undefined;
|
|
725
|
+
maxTokens?: number | undefined;
|
|
726
|
+
} | undefined;
|
|
727
|
+
hooks?: {
|
|
728
|
+
onStart?: string | undefined;
|
|
729
|
+
onComplete?: string | undefined;
|
|
730
|
+
} | undefined;
|
|
731
|
+
}>, {
|
|
732
|
+
adapters: Record<string, {
|
|
733
|
+
type: string;
|
|
734
|
+
mode: "pty" | "test";
|
|
735
|
+
args: string[];
|
|
736
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
737
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
738
|
+
command?: string | undefined;
|
|
739
|
+
env?: Record<string, string> | undefined;
|
|
740
|
+
}>;
|
|
741
|
+
defaultAdapter: string;
|
|
742
|
+
sessionsDir: string;
|
|
743
|
+
logsDir: string;
|
|
744
|
+
defaults: {
|
|
745
|
+
maxTurns: number;
|
|
746
|
+
timeoutMs: number;
|
|
747
|
+
idleTimeoutMs: number;
|
|
748
|
+
requireApprovalOnRiskyActions: boolean;
|
|
749
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
750
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
751
|
+
completionIdleMs?: number | undefined;
|
|
752
|
+
};
|
|
753
|
+
decider?: {
|
|
754
|
+
type: "command" | "rule" | "always-approve" | "api";
|
|
755
|
+
timeoutMs?: number | undefined;
|
|
756
|
+
command?: string | undefined;
|
|
757
|
+
args?: string[] | undefined;
|
|
758
|
+
env?: Record<string, string> | undefined;
|
|
759
|
+
denyPatterns?: string[] | undefined;
|
|
760
|
+
defaultApproval?: "approve" | "deny" | undefined;
|
|
761
|
+
defaultAnswer?: string | undefined;
|
|
762
|
+
url?: string | undefined;
|
|
763
|
+
model?: string | undefined;
|
|
764
|
+
apiKey?: string | undefined;
|
|
765
|
+
maxTokens?: number | undefined;
|
|
766
|
+
} | undefined;
|
|
767
|
+
hooks?: {
|
|
768
|
+
onStart?: string | undefined;
|
|
769
|
+
onComplete?: string | undefined;
|
|
770
|
+
} | undefined;
|
|
771
|
+
}, {
|
|
772
|
+
adapters: Record<string, {
|
|
773
|
+
type: string;
|
|
774
|
+
mode: "pty" | "test";
|
|
775
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
776
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
777
|
+
command?: string | undefined;
|
|
778
|
+
args?: string[] | undefined;
|
|
779
|
+
env?: Record<string, string> | undefined;
|
|
780
|
+
}>;
|
|
781
|
+
defaultAdapter: string;
|
|
782
|
+
sessionsDir?: string | undefined;
|
|
783
|
+
logsDir?: string | undefined;
|
|
784
|
+
defaults?: {
|
|
785
|
+
maxTurns?: number | undefined;
|
|
786
|
+
timeoutMs?: number | undefined;
|
|
787
|
+
idleTimeoutMs?: number | undefined;
|
|
788
|
+
approvalPolicy?: "auto" | "gated" | "readonly" | undefined;
|
|
789
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
|
|
790
|
+
completionIdleMs?: number | undefined;
|
|
791
|
+
requireApprovalOnRiskyActions?: boolean | undefined;
|
|
792
|
+
} | undefined;
|
|
793
|
+
decider?: {
|
|
794
|
+
timeoutMs?: number | undefined;
|
|
795
|
+
type?: "command" | "rule" | "always-approve" | "api" | undefined;
|
|
796
|
+
command?: string | undefined;
|
|
797
|
+
args?: string[] | undefined;
|
|
798
|
+
env?: Record<string, string> | undefined;
|
|
799
|
+
denyPatterns?: string[] | undefined;
|
|
800
|
+
defaultApproval?: "approve" | "deny" | undefined;
|
|
801
|
+
defaultAnswer?: string | undefined;
|
|
802
|
+
url?: string | undefined;
|
|
803
|
+
model?: string | undefined;
|
|
804
|
+
apiKey?: string | undefined;
|
|
805
|
+
maxTokens?: number | undefined;
|
|
806
|
+
} | undefined;
|
|
807
|
+
hooks?: {
|
|
808
|
+
onStart?: string | undefined;
|
|
809
|
+
onComplete?: string | undefined;
|
|
810
|
+
} | undefined;
|
|
811
|
+
}>;
|
|
812
|
+
type RelayConfig = z.infer<typeof configSchema>;
|
|
813
|
+
/** The default config object written by `init`. */
|
|
814
|
+
declare function createDefaultConfig(): RelayConfig;
|
|
815
|
+
/** Validate an unknown value as a RelayConfig, throwing a friendly error. */
|
|
816
|
+
declare function parseConfig(value: unknown): RelayConfig;
|
|
817
|
+
/** Resolve the absolute path to the config file for a given root dir. */
|
|
818
|
+
declare function configPath(rootDir: string): string;
|
|
819
|
+
/** Load and validate the config from disk. */
|
|
820
|
+
declare function loadConfig(rootDir: string): Promise<RelayConfig>;
|
|
821
|
+
/**
|
|
822
|
+
* Load the config, or fall back to the built-in defaults when **no config file
|
|
823
|
+
* exists** — so the CLI works zero-config (e.g. `npx agent-relay run --adapter
|
|
824
|
+
* claude -p "..."`) without requiring `agent-relay init` first. The session/log
|
|
825
|
+
* directories are created lazily on the first run, so nothing else is needed.
|
|
826
|
+
*
|
|
827
|
+
* A config file that IS present but malformed (bad JSON or failing validation)
|
|
828
|
+
* still throws — we never silently ignore a config the user actually wrote.
|
|
829
|
+
* `onDefault` fires only when the fallback is used, letting callers surface a
|
|
830
|
+
* one-line "using built-in defaults" hint.
|
|
831
|
+
*/
|
|
832
|
+
declare function loadConfigOrDefault(rootDir: string, onDefault?: () => void): Promise<RelayConfig>;
|
|
833
|
+
/** Serialize a config to pretty JSON (stable key order via schema parse). */
|
|
834
|
+
declare function stringifyConfig(config: RelayConfig): string;
|
|
835
|
+
/** Write a config to disk, creating parent dirs as needed. */
|
|
836
|
+
declare function saveConfig(rootDir: string, config: RelayConfig): Promise<string>;
|
|
837
|
+
|
|
838
|
+
/** Typed errors used across agent-relay so callers can branch on `code`. */
|
|
839
|
+
declare class AgentRelayError extends Error {
|
|
840
|
+
readonly code: string;
|
|
841
|
+
readonly hint?: string;
|
|
842
|
+
constructor(message: string, code: string, hint?: string);
|
|
843
|
+
}
|
|
844
|
+
/** Raised when a config file is missing or invalid. */
|
|
845
|
+
declare class ConfigError extends AgentRelayError {
|
|
846
|
+
constructor(message: string, hint?: string);
|
|
847
|
+
}
|
|
848
|
+
/** Raised when an adapter name is not registered / not in config. */
|
|
849
|
+
declare class UnknownAdapterError extends AgentRelayError {
|
|
850
|
+
constructor(name: string, available: string[]);
|
|
851
|
+
}
|
|
852
|
+
/** Raised when a session cannot be found on disk. */
|
|
853
|
+
declare class SessionNotFoundError extends AgentRelayError {
|
|
854
|
+
constructor(sessionId: string);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** Session metadata persistence: create, save, load, list. */
|
|
858
|
+
|
|
859
|
+
interface CreateSessionInput {
|
|
860
|
+
prompt: string;
|
|
861
|
+
adapter: string;
|
|
862
|
+
mode: AdapterMode;
|
|
863
|
+
cwd: string;
|
|
864
|
+
logFile: string;
|
|
865
|
+
dryRun?: boolean;
|
|
866
|
+
/** Override the generated id (used by tests / explicit session ids). */
|
|
867
|
+
sessionId?: string;
|
|
868
|
+
/** Override the creation timestamp (used by tests for determinism). */
|
|
869
|
+
startedAt?: string;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Manages session metadata files under `sessionsDir`. The manager is given an
|
|
873
|
+
* already-resolved absolute directory; the runner/commands compute that from
|
|
874
|
+
* config + root dir.
|
|
875
|
+
*/
|
|
876
|
+
declare class SessionManager {
|
|
877
|
+
private readonly sessionsDir;
|
|
878
|
+
constructor(sessionsDir: string);
|
|
879
|
+
/** Absolute path to a session's JSON metadata file. */
|
|
880
|
+
filePath(sessionId: string): string;
|
|
881
|
+
/** Build a fresh session record in the `created` state (not yet persisted). */
|
|
882
|
+
create(input: CreateSessionInput): SessionMetadata;
|
|
883
|
+
/** Persist (create or overwrite) a session's metadata. */
|
|
884
|
+
save(meta: SessionMetadata): Promise<string>;
|
|
885
|
+
/** Load a session's metadata, throwing {@link SessionNotFoundError}. */
|
|
886
|
+
load(sessionId: string): Promise<SessionMetadata>;
|
|
887
|
+
/** Apply a partial update and persist; returns the updated record. */
|
|
888
|
+
update(sessionId: string, patch: Partial<SessionMetadata>): Promise<SessionMetadata>;
|
|
889
|
+
/** Convenience helper to set status (and optionally endedAt). */
|
|
890
|
+
setStatus(sessionId: string, status: SessionStatus, extra?: Partial<SessionMetadata>): Promise<SessionMetadata>;
|
|
891
|
+
/** List all sessions, newest first. Returns [] if the dir does not exist. */
|
|
892
|
+
list(): Promise<SessionMetadata[]>;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Append-only run logger. Writes a human-readable header, one line per event,
|
|
897
|
+
* and a footer with the final status. Uses synchronous appends so the log is
|
|
898
|
+
* always flushed even if the process is killed mid-run.
|
|
899
|
+
*/
|
|
900
|
+
|
|
901
|
+
interface RunLoggerHeader {
|
|
902
|
+
sessionId: string;
|
|
903
|
+
adapter: string;
|
|
904
|
+
mode: string;
|
|
905
|
+
prompt: string;
|
|
906
|
+
cwd: string;
|
|
907
|
+
command?: CommandPreview;
|
|
908
|
+
startedAt: string;
|
|
909
|
+
}
|
|
910
|
+
interface RunLoggerOptions {
|
|
911
|
+
/**
|
|
912
|
+
* When false (the default), raw `stdout`/`stderr` events are NOT written to the
|
|
913
|
+
* log — only the meaningful ones (start, prompt/status, decision, complete,
|
|
914
|
+
* error). Interactive TUIs redraw constantly, so the raw stream is ~98% noise
|
|
915
|
+
* and bloats logs; keep it only when debugging via `verbose: true`.
|
|
916
|
+
*/
|
|
917
|
+
verbose?: boolean;
|
|
918
|
+
/** Optional cap on log bytes; appends stop (with a one-time notice) past it. */
|
|
919
|
+
maxBytes?: number;
|
|
920
|
+
}
|
|
921
|
+
declare class RunLogger {
|
|
922
|
+
private readonly logFile;
|
|
923
|
+
private readonly opts;
|
|
924
|
+
private bytes;
|
|
925
|
+
private suppressed;
|
|
926
|
+
private truncated;
|
|
927
|
+
constructor(logFile: string, opts?: RunLoggerOptions);
|
|
928
|
+
private append;
|
|
929
|
+
/** Ensure the log directory exists and write the run header. */
|
|
930
|
+
start(header: RunLoggerHeader): void;
|
|
931
|
+
/** Record a single normalized event. */
|
|
932
|
+
event(event: AgentEvent): void;
|
|
933
|
+
/** Write the run footer with terminal status and optional error. */
|
|
934
|
+
finish(status: SessionStatus, endedAt: string, error?: AgentErrorInfo): void;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Completion detection: maps a finished (or aborted) run to a terminal
|
|
939
|
+
* {@link SessionStatus}. Designed to be extensible — today the default detector
|
|
940
|
+
* uses adapter success / exit code / abort reason, but new detectors can layer
|
|
941
|
+
* in test-result parsing, output-pattern matching, or changed-file analysis.
|
|
942
|
+
*/
|
|
943
|
+
|
|
944
|
+
/** Everything a detector needs to render a verdict. */
|
|
945
|
+
interface CompletionContext {
|
|
946
|
+
/** Adapter result (may be partial when aborted). */
|
|
947
|
+
result?: AgentRunResult;
|
|
948
|
+
/** All normalized events observed during the run. */
|
|
949
|
+
events: AgentEvent[];
|
|
950
|
+
/** Why the runner aborted, if it did. */
|
|
951
|
+
abortReason?: AbortReason;
|
|
952
|
+
/** An error thrown by the adapter's run(), if any. */
|
|
953
|
+
error?: Error;
|
|
954
|
+
}
|
|
955
|
+
/** A pluggable completion strategy. */
|
|
956
|
+
interface CompletionDetector {
|
|
957
|
+
readonly name: string;
|
|
958
|
+
/**
|
|
959
|
+
* Return a terminal status, or `undefined` to defer to the next detector in
|
|
960
|
+
* a chain (lets specialized detectors only handle cases they understand).
|
|
961
|
+
*/
|
|
962
|
+
detect(ctx: CompletionContext): SessionStatus | undefined;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Default detector. Order of precedence:
|
|
966
|
+
* 1. abort reasons (timeout / idle -> timeout, cancel -> cancelled,
|
|
967
|
+
* maxTurns -> failed),
|
|
968
|
+
* 2. thrown error -> failed,
|
|
969
|
+
* 3. adapter success or exit code 0 -> completed,
|
|
970
|
+
* 4. otherwise -> failed.
|
|
971
|
+
*/
|
|
972
|
+
declare class DefaultCompletionDetector implements CompletionDetector {
|
|
973
|
+
readonly name = "default";
|
|
974
|
+
detect(ctx: CompletionContext): SessionStatus;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Runs detectors in order, returning the first defined verdict, and falling
|
|
978
|
+
* back to {@link DefaultCompletionDetector}. This is the extension seam: append
|
|
979
|
+
* a `TestsPassedDetector`, `OutputPatternDetector`, or `ChangedFilesDetector`
|
|
980
|
+
* before the default to refine the verdict without touching the runner.
|
|
981
|
+
*/
|
|
982
|
+
declare class CompositeCompletionDetector implements CompletionDetector {
|
|
983
|
+
readonly name = "composite";
|
|
984
|
+
private readonly detectors;
|
|
985
|
+
private readonly fallback;
|
|
986
|
+
constructor(detectors?: CompletionDetector[]);
|
|
987
|
+
detect(ctx: CompletionContext): SessionStatus;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* --- Future extension hooks (intentionally not wired into the runner yet) ---
|
|
991
|
+
*
|
|
992
|
+
* These illustrate how richer signals plug into the same interface. They are
|
|
993
|
+
* exported so they can be unit-tested and adopted incrementally.
|
|
994
|
+
*/
|
|
995
|
+
/** Detect "tests passed" from a configurable success/failure regex. */
|
|
996
|
+
interface PatternDetectorOptions {
|
|
997
|
+
/** If this matches any assistant/stdout text, the run is `completed`. */
|
|
998
|
+
successPattern?: RegExp;
|
|
999
|
+
/** If this matches, the run is `failed`. Evaluated before successPattern. */
|
|
1000
|
+
failurePattern?: RegExp;
|
|
1001
|
+
}
|
|
1002
|
+
declare class OutputPatternDetector implements CompletionDetector {
|
|
1003
|
+
private readonly options;
|
|
1004
|
+
readonly name = "output-pattern";
|
|
1005
|
+
constructor(options: PatternDetectorOptions);
|
|
1006
|
+
detect(ctx: CompletionContext): SessionStatus | undefined;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Run orchestration. Owns the lifecycle of a single agent run:
|
|
1011
|
+
* config -> adapter selection -> session create/save -> prompt dispatch ->
|
|
1012
|
+
* event collection -> timeout/idle/maxTurns enforcement -> logging ->
|
|
1013
|
+
* signal-based cleanup -> completion detection -> final status persistence.
|
|
1014
|
+
*
|
|
1015
|
+
* The runner depends ONLY on the {@link AgentAdapter} interface, resolved
|
|
1016
|
+
* through an injected {@link AdapterFactory}. It never imports a concrete
|
|
1017
|
+
* vendor adapter, which keeps `core/` decoupled from Claude/Codex.
|
|
1018
|
+
*/
|
|
1019
|
+
|
|
1020
|
+
/** Builds an {@link AgentAdapter} from its name + the active config. */
|
|
1021
|
+
type AdapterFactory = (name: string, config: RelayConfig) => AgentAdapter;
|
|
1022
|
+
interface RunnerOptions {
|
|
1023
|
+
config: RelayConfig;
|
|
1024
|
+
/** Base directory used to resolve sessionsDir/logsDir and default cwd. */
|
|
1025
|
+
rootDir: string;
|
|
1026
|
+
/** Adapter to use; defaults to `config.defaultAdapter`. */
|
|
1027
|
+
adapterName?: string;
|
|
1028
|
+
prompt: string;
|
|
1029
|
+
/** Working directory for the agent; defaults to `rootDir`. */
|
|
1030
|
+
cwd?: string;
|
|
1031
|
+
maxTurns?: number;
|
|
1032
|
+
timeoutMs?: number;
|
|
1033
|
+
idleTimeoutMs?: number;
|
|
1034
|
+
dryRun?: boolean;
|
|
1035
|
+
extraArgs?: string[];
|
|
1036
|
+
/** Override the resolved approval mode for this run. */
|
|
1037
|
+
approvalMode?: ApprovalMode;
|
|
1038
|
+
/** Override the resolved sandbox level for this run. */
|
|
1039
|
+
sandbox?: SandboxLevel;
|
|
1040
|
+
/** Interactive: idle window before a finished agent's TUI is quit (ms). */
|
|
1041
|
+
completionIdleMs?: number;
|
|
1042
|
+
/** Claude: drive `/effort ultracode` (+ Opus) in the TUI before the task. */
|
|
1043
|
+
ultracode?: boolean;
|
|
1044
|
+
/** Decider that answers interactive prompts (overrides config/hooks). */
|
|
1045
|
+
decider?: Decider;
|
|
1046
|
+
/** Programmatic lifecycle hooks. */
|
|
1047
|
+
hooks?: RunHooks;
|
|
1048
|
+
/** Resume target (native session ref) for resume runs. */
|
|
1049
|
+
resume?: AgentSessionRef;
|
|
1050
|
+
/** Force a specific session id (otherwise generated). */
|
|
1051
|
+
sessionId?: string;
|
|
1052
|
+
/** DI seam: resolves an adapter instance. */
|
|
1053
|
+
resolveAdapter: AdapterFactory;
|
|
1054
|
+
/** Optional completion detector; defaults to the composite/default chain. */
|
|
1055
|
+
detector?: CompletionDetector;
|
|
1056
|
+
/** Observe normalized events live (in addition to logging). */
|
|
1057
|
+
onEvent?: (event: AgentEvent) => void;
|
|
1058
|
+
/** Log the raw stdout/stderr stream too (default false → small logs). */
|
|
1059
|
+
verbose?: boolean;
|
|
1060
|
+
/** Optional cap on log file bytes. */
|
|
1061
|
+
maxLogBytes?: number;
|
|
1062
|
+
/** Install SIGINT/SIGTERM handlers (default true; tests pass false). */
|
|
1063
|
+
installSignalHandlers?: boolean;
|
|
1064
|
+
/**
|
|
1065
|
+
* External cancellation signal. Aborting it cancels the run (mapped to the
|
|
1066
|
+
* `cancel` reason -> `cancelled` status), independent of OS signals. Enables
|
|
1067
|
+
* programmatic cancellation and deterministic cancel tests.
|
|
1068
|
+
*/
|
|
1069
|
+
signal?: AbortSignal;
|
|
1070
|
+
/** Injectable clock for deterministic timestamps in tests. */
|
|
1071
|
+
now?: () => Date;
|
|
1072
|
+
}
|
|
1073
|
+
interface RunOutcome {
|
|
1074
|
+
session: SessionMetadata;
|
|
1075
|
+
status: SessionStatus;
|
|
1076
|
+
result?: AgentRunResult;
|
|
1077
|
+
events: AgentEvent[];
|
|
1078
|
+
abortReason?: AbortReason;
|
|
1079
|
+
logFile: string;
|
|
1080
|
+
}
|
|
1081
|
+
declare function runAgent(options: RunnerOptions): Promise<RunOutcome>;
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Run lifecycle hooks — agent-relay's extensibility harness.
|
|
1085
|
+
*
|
|
1086
|
+
* Two flavors:
|
|
1087
|
+
* - **Programmatic** {@link RunHooks}: callbacks for embedders (onSessionStart,
|
|
1088
|
+
* onEvent, onComplete, onApprovalRequest).
|
|
1089
|
+
* - **Shell** {@link ShellHooks}: shell commands defined in the config file,
|
|
1090
|
+
* run with `AGENT_RELAY_*` env vars (think git hooks, but for agent runs) —
|
|
1091
|
+
* e.g. run the test suite or send a notification when a run completes.
|
|
1092
|
+
*/
|
|
1093
|
+
|
|
1094
|
+
/** Programmatic lifecycle callbacks supplied by an embedder. */
|
|
1095
|
+
interface RunHooks {
|
|
1096
|
+
onSessionStart?: (session: SessionMetadata) => void | Promise<void>;
|
|
1097
|
+
onEvent?: (event: AgentEvent, session: SessionMetadata) => void | Promise<void>;
|
|
1098
|
+
onComplete?: (outcome: RunOutcome) => void | Promise<void>;
|
|
1099
|
+
/**
|
|
1100
|
+
* Act as the Decider for interactive prompts: given the agent's prompt,
|
|
1101
|
+
* return the decision. When provided, this takes precedence over the
|
|
1102
|
+
* configured decider — a programmatic human-in-the-loop / policy hook.
|
|
1103
|
+
*/
|
|
1104
|
+
onApprovalRequest?: (req: InteractionRequest) => InteractionDecision | Promise<InteractionDecision>;
|
|
1105
|
+
}
|
|
1106
|
+
/** Shell-command hooks defined in the config file. */
|
|
1107
|
+
interface ShellHooks {
|
|
1108
|
+
/** Runs just before the agent starts (status: running). */
|
|
1109
|
+
onStart?: string;
|
|
1110
|
+
/** Runs after the run reaches a terminal status. */
|
|
1111
|
+
onComplete?: string;
|
|
1112
|
+
}
|
|
1113
|
+
interface ShellHookContext {
|
|
1114
|
+
sessionId: string;
|
|
1115
|
+
adapter: string;
|
|
1116
|
+
status: string;
|
|
1117
|
+
cwd: string;
|
|
1118
|
+
logFile: string;
|
|
1119
|
+
exitCode?: number | null;
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Run a shell-command hook. Failures never throw (they must not break a run);
|
|
1123
|
+
* the command runs in a shell with `AGENT_RELAY_*` env vars describing the run.
|
|
1124
|
+
*/
|
|
1125
|
+
declare function runShellHook(command: string, ctx: ShellHookContext): Promise<{
|
|
1126
|
+
ok: boolean;
|
|
1127
|
+
exitCode: number | null;
|
|
1128
|
+
}>;
|
|
1129
|
+
|
|
1130
|
+
type FakeScenario = "success" | "failure" | "timeout" | "auto";
|
|
1131
|
+
interface FakeAdapterOptions {
|
|
1132
|
+
scenario?: FakeScenario;
|
|
1133
|
+
/** Assistant messages to emit (one `assistant_message` event each). */
|
|
1134
|
+
assistantMessages?: string[];
|
|
1135
|
+
/** Emit a tool_call / tool_result pair before finishing. */
|
|
1136
|
+
emitTool?: boolean;
|
|
1137
|
+
/** Delay (ms) before the run resolves in success/failure scenarios. */
|
|
1138
|
+
delayMs?: number;
|
|
1139
|
+
/** Exit code reported on failure. */
|
|
1140
|
+
failureExitCode?: number;
|
|
1141
|
+
/** Safety cap (ms) so a `timeout` scenario can never truly hang forever. */
|
|
1142
|
+
hangSafetyMs?: number;
|
|
1143
|
+
/** Injectable clock for deterministic timestamps. */
|
|
1144
|
+
now?: () => Date;
|
|
1145
|
+
}
|
|
1146
|
+
declare class FakeAgentAdapter implements AgentAdapter {
|
|
1147
|
+
readonly definition: AgentAdapterDefinition;
|
|
1148
|
+
private readonly options;
|
|
1149
|
+
constructor(options?: FakeAdapterOptions);
|
|
1150
|
+
isAvailable(): Promise<AdapterAvailability>;
|
|
1151
|
+
describeCommand(input: AgentRunInput): {
|
|
1152
|
+
mode: AdapterMode;
|
|
1153
|
+
command: string;
|
|
1154
|
+
args: never[];
|
|
1155
|
+
display: string;
|
|
1156
|
+
};
|
|
1157
|
+
private resolveScenario;
|
|
1158
|
+
private emit;
|
|
1159
|
+
run(input: AgentRunInput, ctx: AdapterRunContext): Promise<AgentRunResult>;
|
|
1160
|
+
resume(ref: {
|
|
1161
|
+
adapter: string;
|
|
1162
|
+
nativeSessionId?: string;
|
|
1163
|
+
resumable: boolean;
|
|
1164
|
+
}, input: AgentRunInput, ctx: AdapterRunContext): Promise<AgentRunResult>;
|
|
1165
|
+
private hangUntilAborted;
|
|
1166
|
+
private sleep;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Heuristic detector for "the agent is now waiting on a prompt" in interactive
|
|
1171
|
+
* (PTY) terminal output. Detection is necessarily heuristic — TUIs redraw,
|
|
1172
|
+
* paginate, and vary — so patterns are configurable per adapter, and the PTY
|
|
1173
|
+
* session only runs detection once output has gone idle (settled).
|
|
1174
|
+
*
|
|
1175
|
+
* Two robust shapes are recognized out of the box:
|
|
1176
|
+
* - approval : a yes/no style confirmation (e.g. "... [y/n]", "Proceed?")
|
|
1177
|
+
* - choice : a numbered menu ("1. Continue", "2. Stop", optionally "> 1.")
|
|
1178
|
+
* Adapters can supply their own patterns for vendor-specific TUIs.
|
|
1179
|
+
*/
|
|
1180
|
+
|
|
1181
|
+
interface DetectedPrompt {
|
|
1182
|
+
kind: InteractionKind;
|
|
1183
|
+
/** Cleaned prompt text shown to the decider. */
|
|
1184
|
+
prompt: string;
|
|
1185
|
+
/** For `choice`: option labels in display order (checkbox marker stripped). */
|
|
1186
|
+
options?: string[];
|
|
1187
|
+
/** For `choice`: the display number for each option (for keystroke mapping). */
|
|
1188
|
+
optionNumbers?: number[];
|
|
1189
|
+
/** True for a MULTI-select menu (each option has a `[ ]`/`[x]`/`◯`/`◉` box). */
|
|
1190
|
+
multiSelect?: boolean;
|
|
1191
|
+
/** For a multi-select: current checked state per option (aligned to `options`). */
|
|
1192
|
+
checked?: boolean[];
|
|
1193
|
+
/** For a multi-select: index of the row the cursor (`>`/`❯`) is on (default 0). */
|
|
1194
|
+
cursorIndex?: number;
|
|
1195
|
+
/** Stable signature of the settled prompt, for de-duplication. */
|
|
1196
|
+
signature: string;
|
|
1197
|
+
}
|
|
1198
|
+
/** Recognize a leading checkbox marker on an option label; returns its state +
|
|
1199
|
+
* the remaining label, or null when the label has no checkbox. */
|
|
1200
|
+
declare function parseCheckbox(label: string): {
|
|
1201
|
+
checked: boolean;
|
|
1202
|
+
label: string;
|
|
1203
|
+
} | null;
|
|
1204
|
+
interface PromptDetectorOptions {
|
|
1205
|
+
/** Recognizes a yes/no approval prompt. */
|
|
1206
|
+
approvalPattern?: RegExp;
|
|
1207
|
+
/** Matches a single numbered menu option; groups: 1=number, 2=label. */
|
|
1208
|
+
optionLine?: RegExp;
|
|
1209
|
+
/** Recognizes a free-text input prompt (optional; off by default). */
|
|
1210
|
+
inputPattern?: RegExp;
|
|
1211
|
+
/** Minimum option lines to treat the tail as a choice menu. */
|
|
1212
|
+
minOptions?: number;
|
|
1213
|
+
/** How many trailing lines to inspect. */
|
|
1214
|
+
tailLines?: number;
|
|
1215
|
+
}
|
|
1216
|
+
declare class PromptDetector {
|
|
1217
|
+
private readonly opts;
|
|
1218
|
+
constructor(options?: PromptDetectorOptions);
|
|
1219
|
+
/** Detect a settled prompt from raw terminal output, or null. */
|
|
1220
|
+
detect(rawOutput: string): DetectedPrompt | null;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* The interactive PTY session driver — agent-relay's core run loop.
|
|
1225
|
+
*
|
|
1226
|
+
* It spawns a coding agent in a real pseudo-terminal, watches the output, and
|
|
1227
|
+
* once the terminal SETTLES (no new bytes for `idleMs`) it checks whether the
|
|
1228
|
+
* agent is:
|
|
1229
|
+
* - waiting on a prompt -> ask the {@link Decider}, translate the decision to
|
|
1230
|
+
* keystrokes, and write them back into the PTY, or
|
|
1231
|
+
* - done with the task -> (a `completionPattern` settled) send the quit keys
|
|
1232
|
+
* and let the process exit.
|
|
1233
|
+
*
|
|
1234
|
+
* This is what lets agent-relay run agents *interactively* while an LLM/policy
|
|
1235
|
+
* answers the approvals and choices that come up — instead of suppressing them.
|
|
1236
|
+
*/
|
|
1237
|
+
|
|
1238
|
+
/** Maps a decision + detected prompt to the bytes to write into the PTY. */
|
|
1239
|
+
interface PtyKeymap {
|
|
1240
|
+
keysFor(decision: InteractionDecision, prompt: DetectedPrompt): string | null;
|
|
1241
|
+
}
|
|
1242
|
+
/** Sensible default keystrokes for yes/no prompts and numbered menus. */
|
|
1243
|
+
declare class DefaultKeymap implements PtyKeymap {
|
|
1244
|
+
private readonly multiSelectSubmit;
|
|
1245
|
+
constructor(multiSelectSubmit?: string);
|
|
1246
|
+
keysFor(decision: InteractionDecision, prompt: DetectedPrompt): string | null;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* A pre-task step run once the agent is idle (e.g. set a session option via a
|
|
1250
|
+
* slash command) BEFORE the task prompt is typed. `send` is typed (Enter
|
|
1251
|
+
* appended), then we wait `waitMs` for any animated widget it opens to finish and
|
|
1252
|
+
* the screen to revert (e.g. claude `/effort ultracode`), before the next step /
|
|
1253
|
+
* the task prompt. Time-based because such widgets redraw continuously and never
|
|
1254
|
+
* "settle" for detection.
|
|
1255
|
+
*/
|
|
1256
|
+
interface SetupStep {
|
|
1257
|
+
send: string;
|
|
1258
|
+
waitMs?: number;
|
|
1259
|
+
}
|
|
1260
|
+
interface PtySessionOptions {
|
|
1261
|
+
command: string;
|
|
1262
|
+
args: string[];
|
|
1263
|
+
cwd: string;
|
|
1264
|
+
env?: Record<string, string>;
|
|
1265
|
+
decider: Decider;
|
|
1266
|
+
detector: PromptDetector;
|
|
1267
|
+
keymap?: PtyKeymap;
|
|
1268
|
+
/** No-output settle window before running detection (ms). */
|
|
1269
|
+
idleMs?: number;
|
|
1270
|
+
cols?: number;
|
|
1271
|
+
rows?: number;
|
|
1272
|
+
agent?: string;
|
|
1273
|
+
/** The original task/goal, passed to the decider for relevance judgment. */
|
|
1274
|
+
task?: string;
|
|
1275
|
+
/** Cap on prompts handled before aborting (maps from maxTurns). */
|
|
1276
|
+
maxInteractions?: number;
|
|
1277
|
+
/** When this matches the settled tail, the task is considered complete. */
|
|
1278
|
+
completionPattern?: RegExp;
|
|
1279
|
+
/**
|
|
1280
|
+
* If the agent produces no output and shows no prompt for this long, treat
|
|
1281
|
+
* the task as complete and quit the TUI (so the run ends `completed` rather
|
|
1282
|
+
* than waiting for the overall idle timeout). Disabled when unset.
|
|
1283
|
+
*/
|
|
1284
|
+
completionIdleMs?: number;
|
|
1285
|
+
/**
|
|
1286
|
+
* If the visible screen matches this, the agent is still WORKING (e.g. a
|
|
1287
|
+
* "Thinking… esc to interrupt" indicator) — the completion-idle countdown is
|
|
1288
|
+
* suppressed no matter how long the agent stays silent, so a long
|
|
1289
|
+
* think/build is never mistaken for "done".
|
|
1290
|
+
*/
|
|
1291
|
+
workingPattern?: RegExp;
|
|
1292
|
+
/** Keys to send to exit the TUI on completion (e.g. double Ctrl-C). */
|
|
1293
|
+
quitKeys?: string;
|
|
1294
|
+
/** Optional text to type once the UI is ready (for TUIs needing typed input). */
|
|
1295
|
+
initialInput?: string;
|
|
1296
|
+
/** Delay before sending initialInput (ms). */
|
|
1297
|
+
initialDelayMs?: number;
|
|
1298
|
+
/**
|
|
1299
|
+
* Pre-task setup steps (slash-commands + menu choices) to run once the agent
|
|
1300
|
+
* is idle, before the task. With this set, the prompt is NOT a launch arg — it
|
|
1301
|
+
* is typed via `promptAfterSetup` after the steps complete.
|
|
1302
|
+
*/
|
|
1303
|
+
setup?: SetupStep[];
|
|
1304
|
+
/** Task prompt to TYPE after `setup` completes. */
|
|
1305
|
+
promptAfterSetup?: string;
|
|
1306
|
+
/** Grace period to wait for exit after sending quit keys (ms). */
|
|
1307
|
+
exitGraceMs?: number;
|
|
1308
|
+
now?: () => Date;
|
|
1309
|
+
/** Friendly install hint for a missing command. */
|
|
1310
|
+
installHint?: string;
|
|
1311
|
+
}
|
|
1312
|
+
declare function runPtySession(opts: PtySessionOptions, ctx: AdapterRunContext): Promise<AgentRunResult>;
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Base adapter for driving a coding agent INTERACTIVELY through a PTY. Concrete
|
|
1316
|
+
* adapters (Claude / Codex) supply the command, the pre-prompt args (sandbox /
|
|
1317
|
+
* approval flags that make the agent *ask*), the prompt-detection patterns, the
|
|
1318
|
+
* keystroke map, and how to detect completion. The detect -> decide -> respond
|
|
1319
|
+
* loop lives in {@link runPtySession}; the {@link Decider} comes from the run
|
|
1320
|
+
* context so the same adapter works with any decision backend.
|
|
1321
|
+
*/
|
|
1322
|
+
|
|
1323
|
+
interface InteractiveAdapterConfig {
|
|
1324
|
+
definition: AgentAdapterDefinition;
|
|
1325
|
+
command: string;
|
|
1326
|
+
installHint: string;
|
|
1327
|
+
/** Args placed BEFORE the prompt (sandbox / approval flags, model, ...). */
|
|
1328
|
+
preArgs: (input: AgentRunInput) => string[];
|
|
1329
|
+
/**
|
|
1330
|
+
* Args that make the agent RESUME a prior conversation (before the follow-up
|
|
1331
|
+
* prompt). When set, the adapter advertises `supportsResume` and `resume()`
|
|
1332
|
+
* spawns with these instead of `preArgs` — e.g. claude `--continue`, codex
|
|
1333
|
+
* `resume --last`. The native session id is rarely captured in PTY mode, so
|
|
1334
|
+
* these usually resume the agent's MOST RECENT session in the cwd.
|
|
1335
|
+
*/
|
|
1336
|
+
resumeArgs?: (input: AgentRunInput, ref: AgentSessionRef) => string[];
|
|
1337
|
+
/**
|
|
1338
|
+
* On resume, TYPE the follow-up prompt once the agent is idle instead of
|
|
1339
|
+
* passing it as a launch arg. May be a predicate so the adapter can decide
|
|
1340
|
+
* per-resume: codex types the prompt ONLY on the `resume --last` fallback
|
|
1341
|
+
* (where `codex resume --last "<prompt>"` misparses the prompt as the optional
|
|
1342
|
+
* `[SESSION_ID]` positional), but passes it as an arg when a native SESSION_ID
|
|
1343
|
+
* is present (`codex resume <id> "<prompt>"` parses cleanly).
|
|
1344
|
+
*/
|
|
1345
|
+
resumeTypesPrompt?: boolean | ((input: AgentRunInput, ref: AgentSessionRef) => boolean);
|
|
1346
|
+
/** With `resumeTypesPrompt`, warm up this many ms after the agent first goes
|
|
1347
|
+
* idle before typing the prompt (lets a slow startup, e.g. codex loading MCP
|
|
1348
|
+
* servers, finish so the prompt isn't typed into a not-ready input). */
|
|
1349
|
+
resumeWarmupMs?: number;
|
|
1350
|
+
/**
|
|
1351
|
+
* Pre-task setup steps to run before the prompt (e.g. claude `/effort` →
|
|
1352
|
+
* "ultracode"). When this returns steps, the agent is launched WITHOUT the
|
|
1353
|
+
* prompt as an arg; the steps run once it is idle, then the prompt is typed.
|
|
1354
|
+
*/
|
|
1355
|
+
setup?: (input: AgentRunInput) => SetupStep[] | undefined;
|
|
1356
|
+
detector?: PromptDetectorOptions;
|
|
1357
|
+
keymap?: PtyKeymap;
|
|
1358
|
+
completionPattern?: RegExp;
|
|
1359
|
+
completionIdleMs?: number;
|
|
1360
|
+
/** "Agent is working" indicator that suppresses completion (see PtySession). */
|
|
1361
|
+
workingPattern?: RegExp;
|
|
1362
|
+
quitKeys?: string;
|
|
1363
|
+
env?: Record<string, string>;
|
|
1364
|
+
/** Deliver the prompt as a CLI arg (default) or typed into the TUI. */
|
|
1365
|
+
promptMode?: "arg" | "type";
|
|
1366
|
+
idleMs?: number;
|
|
1367
|
+
now?: () => Date;
|
|
1368
|
+
}
|
|
1369
|
+
declare class InteractivePtyAdapter implements AgentAdapter {
|
|
1370
|
+
readonly definition: AgentAdapterDefinition;
|
|
1371
|
+
protected readonly cfg: InteractiveAdapterConfig;
|
|
1372
|
+
private readonly detector;
|
|
1373
|
+
constructor(cfg: InteractiveAdapterConfig);
|
|
1374
|
+
isAvailable(): Promise<AdapterAvailability>;
|
|
1375
|
+
protected buildArgs(input: AgentRunInput): {
|
|
1376
|
+
args: string[];
|
|
1377
|
+
initialInput?: string;
|
|
1378
|
+
};
|
|
1379
|
+
describeCommand(input: AgentRunInput): CommandPreview;
|
|
1380
|
+
run(input: AgentRunInput, ctx: AdapterRunContext): Promise<AgentRunResult>;
|
|
1381
|
+
resume(ref: AgentSessionRef, input: AgentRunInput, ctx: AdapterRunContext): Promise<AgentRunResult>;
|
|
1382
|
+
private spawn;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Claude Code driven interactively in a PTY. The project's concept is PURE
|
|
1387
|
+
* AUTONOMY: by default Claude runs with `--dangerously-skip-permissions` so it
|
|
1388
|
+
* acts without per-action prompts. The {@link Decider} still handles the prompts
|
|
1389
|
+
* that appear anyway (the directory-trust menu, etc.). `approvalPolicy: "gated"`
|
|
1390
|
+
* uses `--permission-mode acceptEdits` so Claude asks more and the decider sees
|
|
1391
|
+
* those; `"readonly"` uses `--permission-mode plan`. The prompt is a positional
|
|
1392
|
+
* arg so the session starts immediately.
|
|
1393
|
+
*/
|
|
1394
|
+
|
|
1395
|
+
interface ClaudeInteractiveOptions {
|
|
1396
|
+
command?: string;
|
|
1397
|
+
env?: Record<string, string>;
|
|
1398
|
+
now?: () => Date;
|
|
1399
|
+
}
|
|
1400
|
+
declare class ClaudeInteractiveAdapter extends InteractivePtyAdapter {
|
|
1401
|
+
constructor(opts?: ClaudeInteractiveOptions);
|
|
1402
|
+
static fromConfig(config: AdapterConfig): ClaudeInteractiveAdapter;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* Codex driven interactively in a PTY. The project's concept is PURE AUTONOMY:
|
|
1407
|
+
* by default Codex runs with `-a never` (never ask) within the chosen sandbox,
|
|
1408
|
+
* so it just works. The {@link Decider} still handles the prompts that appear
|
|
1409
|
+
* anyway (the directory-trust dialog, etc.). `approvalPolicy: "gated"` switches
|
|
1410
|
+
* Codex to `-a on-request` so the decider sees each action; `"readonly"` runs it
|
|
1411
|
+
* read-only. The prompt is a positional arg so the TUI starts immediately.
|
|
1412
|
+
*/
|
|
1413
|
+
|
|
1414
|
+
interface CodexInteractiveOptions {
|
|
1415
|
+
command?: string;
|
|
1416
|
+
env?: Record<string, string>;
|
|
1417
|
+
now?: () => Date;
|
|
1418
|
+
/** Override Codex's sessions root (~/.codex/sessions) — for tests. */
|
|
1419
|
+
sessionsDir?: string;
|
|
1420
|
+
}
|
|
1421
|
+
declare class CodexInteractiveAdapter extends InteractivePtyAdapter {
|
|
1422
|
+
private readonly clock;
|
|
1423
|
+
/** Override the sessions root (~/.codex/sessions) for tests. */
|
|
1424
|
+
private readonly sessionsDir?;
|
|
1425
|
+
constructor(opts?: CodexInteractiveOptions);
|
|
1426
|
+
/**
|
|
1427
|
+
* Run Codex, then capture its NATIVE session id (the rollout UUID) for this
|
|
1428
|
+
* cwd and attach it to the result's `sessionRef` so the runner persists it and
|
|
1429
|
+
* a later resume can use `codex resume <id> "<prompt>"`. Capture is best-effort:
|
|
1430
|
+
* if no rollout matches (or any I/O fails) the result is returned unchanged, so
|
|
1431
|
+
* the run still resumes via the `--last` fallback.
|
|
1432
|
+
*/
|
|
1433
|
+
run(input: AgentRunInput, ctx: AdapterRunContext): Promise<AgentRunResult>;
|
|
1434
|
+
static fromConfig(config: AdapterConfig): CodexInteractiveAdapter;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* Adapter registry. Maps an adapter config entry (by `type`) to a concrete
|
|
1439
|
+
* {@link AgentAdapter} instance. This is the only place that knows about all
|
|
1440
|
+
* vendor adapters; `core/` depends solely on the interface and receives a
|
|
1441
|
+
* factory from here.
|
|
1442
|
+
*
|
|
1443
|
+
* The built-in agents (claude, codex) are driven INTERACTIVELY through a PTY.
|
|
1444
|
+
*/
|
|
1445
|
+
|
|
1446
|
+
/** A builder that turns a config entry into an adapter instance. */
|
|
1447
|
+
type AdapterBuilder = (config: AdapterConfig) => AgentAdapter;
|
|
1448
|
+
/** Static descriptions of the built-in adapters (for `agent-relay adapters`). */
|
|
1449
|
+
declare const BUILTIN_ADAPTER_DEFINITIONS: AgentAdapterDefinition[];
|
|
1450
|
+
/** A registry mapping adapter `type` -> builder. */
|
|
1451
|
+
declare class AdapterRegistry {
|
|
1452
|
+
private readonly builders;
|
|
1453
|
+
constructor();
|
|
1454
|
+
register(type: string, builder: AdapterBuilder): void;
|
|
1455
|
+
has(type: string): boolean;
|
|
1456
|
+
/** List the adapter types this registry can build. */
|
|
1457
|
+
types(): string[];
|
|
1458
|
+
/** Build an adapter from a config entry, by its `type`. */
|
|
1459
|
+
build(config: AdapterConfig): AgentAdapter;
|
|
1460
|
+
/** Resolve an adapter by its config name within a {@link RelayConfig}. */
|
|
1461
|
+
resolveByName(name: string, config: RelayConfig): AgentAdapter;
|
|
1462
|
+
}
|
|
1463
|
+
/** Shared default registry instance. */
|
|
1464
|
+
declare const defaultRegistry: AdapterRegistry;
|
|
1465
|
+
/**
|
|
1466
|
+
* The {@link AdapterFactory} the runner expects. Wires the default registry so
|
|
1467
|
+
* commands can simply pass `createAdapterFactory()`.
|
|
1468
|
+
*/
|
|
1469
|
+
declare function createAdapterFactory(registry?: AdapterRegistry): (name: string, config: RelayConfig) => AgentAdapter;
|
|
1470
|
+
|
|
1471
|
+
/** `agent-relay init`: create config + session/log directories. */
|
|
1472
|
+
|
|
1473
|
+
interface InitOptions {
|
|
1474
|
+
rootDir: string;
|
|
1475
|
+
/** Overwrite an existing config file. */
|
|
1476
|
+
force?: boolean;
|
|
1477
|
+
}
|
|
1478
|
+
interface InitResult {
|
|
1479
|
+
configPath: string;
|
|
1480
|
+
/** True if a fresh config file was written. */
|
|
1481
|
+
configCreated: boolean;
|
|
1482
|
+
sessionsDir: string;
|
|
1483
|
+
logsDir: string;
|
|
1484
|
+
/** Directories that were created (absolute paths). */
|
|
1485
|
+
createdDirs: string[];
|
|
1486
|
+
}
|
|
1487
|
+
declare function runInit(options: InitOptions): Promise<InitResult>;
|
|
1488
|
+
|
|
1489
|
+
/** `agent-relay adapters`: list registered/built-in adapters. */
|
|
1490
|
+
|
|
1491
|
+
interface AdapterListItem {
|
|
1492
|
+
/** The name used on the CLI (`--adapter <name>`). */
|
|
1493
|
+
name: string;
|
|
1494
|
+
type: string;
|
|
1495
|
+
mode: string;
|
|
1496
|
+
description: string;
|
|
1497
|
+
supportsResume: boolean;
|
|
1498
|
+
/** True when present in the active config. */
|
|
1499
|
+
configured: boolean;
|
|
1500
|
+
/** The configured command, if any. */
|
|
1501
|
+
command?: string;
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Produce the adapter list. When `config` is provided, entries reflect the
|
|
1505
|
+
* configured adapters (name = config key) augmented with built-in descriptions;
|
|
1506
|
+
* built-ins not present in config are appended as `configured: false`.
|
|
1507
|
+
*/
|
|
1508
|
+
declare function listAdapters(config?: RelayConfig): AdapterListItem[];
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* `agent-relay doctor`: environment diagnostics. Missing agent CLIs are
|
|
1512
|
+
* reported as warnings (not errors) so this never fails CI just because Claude
|
|
1513
|
+
* or Codex isn't installed.
|
|
1514
|
+
*/
|
|
1515
|
+
type CheckLevel = "ok" | "warn" | "error";
|
|
1516
|
+
interface DoctorCheck {
|
|
1517
|
+
name: string;
|
|
1518
|
+
level: CheckLevel;
|
|
1519
|
+
detail: string;
|
|
1520
|
+
hint?: string;
|
|
1521
|
+
}
|
|
1522
|
+
interface DoctorReport {
|
|
1523
|
+
checks: DoctorCheck[];
|
|
1524
|
+
/** True when there are no `error`-level checks. */
|
|
1525
|
+
ok: boolean;
|
|
1526
|
+
}
|
|
1527
|
+
interface DoctorOptions {
|
|
1528
|
+
rootDir: string;
|
|
1529
|
+
}
|
|
1530
|
+
declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
|
|
1531
|
+
|
|
1532
|
+
/** `agent-relay run`: resolve prompt + config and orchestrate a run. */
|
|
1533
|
+
|
|
1534
|
+
/** Raw decider flags (as collected by the CLI) to build a decider override. */
|
|
1535
|
+
interface DeciderFlags {
|
|
1536
|
+
/** rule | always-approve | command | api */
|
|
1537
|
+
decider?: string;
|
|
1538
|
+
/** command decider: full command line, whitespace-split (e.g. "codex exec -s read-only"). */
|
|
1539
|
+
deciderCommand?: string;
|
|
1540
|
+
/** api decider: OpenAI-compatible chat-completions URL. */
|
|
1541
|
+
deciderUrl?: string;
|
|
1542
|
+
deciderModel?: string;
|
|
1543
|
+
deciderKey?: string;
|
|
1544
|
+
deciderMaxTokens?: number;
|
|
1545
|
+
deciderTimeoutMs?: number;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Build a validated decider override from CLI flags, or `undefined` when no
|
|
1549
|
+
* decider flag was given (so the configured/default decider is kept). Lets users
|
|
1550
|
+
* pick a decider without writing a config file — e.g.
|
|
1551
|
+
* --decider api --decider-url http://localhost:9090/v1/chat/completions --decider-model default
|
|
1552
|
+
* --decider command --decider-command "codex exec --skip-git-repo-check -s read-only"
|
|
1553
|
+
*/
|
|
1554
|
+
declare function deciderConfigFromFlags(flags: DeciderFlags): DeciderConfig | undefined;
|
|
1555
|
+
interface RunCommandOptions {
|
|
1556
|
+
rootDir: string;
|
|
1557
|
+
adapter?: string;
|
|
1558
|
+
prompt?: string;
|
|
1559
|
+
promptFile?: string;
|
|
1560
|
+
maxTurns?: number;
|
|
1561
|
+
timeoutMs?: number;
|
|
1562
|
+
idleTimeoutMs?: number;
|
|
1563
|
+
completionIdleMs?: number;
|
|
1564
|
+
/** Approval mode: auto (autonomy) | gated (agent asks more) | readonly (sandbox). */
|
|
1565
|
+
approvalMode?: ApprovalMode;
|
|
1566
|
+
/** Claude: drive `/effort ultracode` (+ Opus) in the TUI before the task. */
|
|
1567
|
+
ultracode?: boolean;
|
|
1568
|
+
dryRun?: boolean;
|
|
1569
|
+
cwd?: string;
|
|
1570
|
+
extraArgs?: string[];
|
|
1571
|
+
/** Pre-loaded config (skips disk read; used by tests). */
|
|
1572
|
+
config?: RelayConfig;
|
|
1573
|
+
/** Called when no config file is found and built-in defaults are used. */
|
|
1574
|
+
onDefaultConfig?: () => void;
|
|
1575
|
+
/** Decider override (e.g. from --decider* flags); replaces config.decider. */
|
|
1576
|
+
deciderConfig?: DeciderConfig;
|
|
1577
|
+
/** Log the raw stdout/stderr stream too (default false → small logs). */
|
|
1578
|
+
verbose?: boolean;
|
|
1579
|
+
/** Optional cap on log file bytes. */
|
|
1580
|
+
maxLogBytes?: number;
|
|
1581
|
+
/** Override adapter resolution (used by tests / custom registries). */
|
|
1582
|
+
resolveAdapter?: AdapterFactory;
|
|
1583
|
+
onEvent?: (event: AgentEvent) => void;
|
|
1584
|
+
installSignalHandlers?: boolean;
|
|
1585
|
+
now?: () => Date;
|
|
1586
|
+
}
|
|
1587
|
+
/** Resolve the prompt text from `prompt` or `promptFile`. */
|
|
1588
|
+
declare function resolvePrompt(options: Pick<RunCommandOptions, "prompt" | "promptFile" | "rootDir">): Promise<string>;
|
|
1589
|
+
declare function runCommand(options: RunCommandOptions): Promise<RunOutcome>;
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* `agent-relay resume`: look up a prior session's metadata and, if a follow-up
|
|
1593
|
+
* prompt is given and the adapter supports resume, dispatch a resume run.
|
|
1594
|
+
*
|
|
1595
|
+
* In this initial version the focus is structural: load + surface metadata, and
|
|
1596
|
+
* thread a follow-up prompt through the adapter's resume path when possible.
|
|
1597
|
+
*/
|
|
1598
|
+
|
|
1599
|
+
interface ResumeCommandOptions {
|
|
1600
|
+
rootDir: string;
|
|
1601
|
+
sessionId: string;
|
|
1602
|
+
/** Optional follow-up prompt; without it, only metadata is returned. */
|
|
1603
|
+
prompt?: string;
|
|
1604
|
+
/** Run-tuning (parity with `run`); fall back to config defaults when unset. */
|
|
1605
|
+
maxTurns?: number;
|
|
1606
|
+
timeoutMs?: number;
|
|
1607
|
+
idleTimeoutMs?: number;
|
|
1608
|
+
completionIdleMs?: number;
|
|
1609
|
+
config?: RelayConfig;
|
|
1610
|
+
/** Called when no config file is found and built-in defaults are used. */
|
|
1611
|
+
onDefaultConfig?: () => void;
|
|
1612
|
+
/** Decider override (e.g. from --decider* flags); replaces config.decider. */
|
|
1613
|
+
deciderConfig?: DeciderConfig;
|
|
1614
|
+
/** Log the raw stdout/stderr stream too (default false → small logs). */
|
|
1615
|
+
verbose?: boolean;
|
|
1616
|
+
/** Optional cap on log file bytes. */
|
|
1617
|
+
maxLogBytes?: number;
|
|
1618
|
+
resolveAdapter?: AdapterFactory;
|
|
1619
|
+
onEvent?: (event: AgentEvent) => void;
|
|
1620
|
+
installSignalHandlers?: boolean;
|
|
1621
|
+
now?: () => Date;
|
|
1622
|
+
}
|
|
1623
|
+
interface ResumeCommandResult {
|
|
1624
|
+
/** The previously stored session metadata. */
|
|
1625
|
+
session: SessionMetadata;
|
|
1626
|
+
/** Whether the adapter is able to resume this session. */
|
|
1627
|
+
resumable: boolean;
|
|
1628
|
+
/** True when a resume run was actually dispatched. */
|
|
1629
|
+
resumed: boolean;
|
|
1630
|
+
/** Reason a resume was not attempted, when applicable. */
|
|
1631
|
+
reason?: string;
|
|
1632
|
+
/** Outcome of the resume run, when one was dispatched. */
|
|
1633
|
+
outcome?: RunOutcome;
|
|
1634
|
+
}
|
|
1635
|
+
declare function resumeCommand(options: ResumeCommandOptions): Promise<ResumeCommandResult>;
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* `agent-relay sessions`: list recorded sessions (with log sizes) and prune old
|
|
1639
|
+
* ones + their logs. Sessions/logs accumulate forever otherwise — this is the
|
|
1640
|
+
* retention/cleanup surface.
|
|
1641
|
+
*/
|
|
1642
|
+
|
|
1643
|
+
interface SessionsBaseOptions {
|
|
1644
|
+
rootDir: string;
|
|
1645
|
+
/** Called when no config file is found and built-in defaults are used. */
|
|
1646
|
+
onDefaultConfig?: () => void;
|
|
1647
|
+
}
|
|
1648
|
+
interface SessionListItem extends SessionMetadata {
|
|
1649
|
+
/** Size of the session's log file in bytes (0 if missing). */
|
|
1650
|
+
logBytes: number;
|
|
1651
|
+
}
|
|
1652
|
+
/** List sessions (newest first) with each log's size and the grand total. */
|
|
1653
|
+
declare function listSessions(opts: SessionsBaseOptions): Promise<{
|
|
1654
|
+
items: SessionListItem[];
|
|
1655
|
+
totalLogBytes: number;
|
|
1656
|
+
}>;
|
|
1657
|
+
interface PruneOptions extends SessionsBaseOptions {
|
|
1658
|
+
/** Keep the N newest sessions; delete the rest. */
|
|
1659
|
+
keep?: number;
|
|
1660
|
+
/** Delete sessions older than this many days (by endedAt, else startedAt). */
|
|
1661
|
+
olderThanDays?: number;
|
|
1662
|
+
/** Delete ALL sessions. */
|
|
1663
|
+
all?: boolean;
|
|
1664
|
+
now?: () => Date;
|
|
1665
|
+
}
|
|
1666
|
+
interface PruneResult {
|
|
1667
|
+
deleted: number;
|
|
1668
|
+
freedBytes: number;
|
|
1669
|
+
kept: number;
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Delete sessions (JSON + log) matching the filter. `keep` and `olderThanDays`
|
|
1673
|
+
* combine as OR (a session goes if EITHER rule selects it). At least one of
|
|
1674
|
+
* `keep` / `olderThanDays` / `all` must be set.
|
|
1675
|
+
*/
|
|
1676
|
+
declare function pruneSessions(opts: PruneOptions): Promise<PruneResult>;
|
|
1677
|
+
|
|
1678
|
+
/**
|
|
1679
|
+
* Terminal output cleanup helpers for parsing interactive TUI output.
|
|
1680
|
+
* Strips ANSI escape sequences (colors, cursor moves, OSC) and normalizes
|
|
1681
|
+
* whitespace so prompt detection works on readable text.
|
|
1682
|
+
*/
|
|
1683
|
+
/** Remove ANSI escape sequences from a string. */
|
|
1684
|
+
declare function stripAnsi(input: string): string;
|
|
1685
|
+
/**
|
|
1686
|
+
* Clean terminal output for prompt detection: strip ANSI, turn carriage
|
|
1687
|
+
* returns into line breaks, and drop other C0 control chars (keeping tab).
|
|
1688
|
+
*
|
|
1689
|
+
* Full-screen TUIs (e.g. Claude Code) position the cursor with escape
|
|
1690
|
+
* sequences and use `\r` to redraw, so collapsing each line to "the text after
|
|
1691
|
+
* the last CR" would wipe real content. Converting `\r` to `\n` preserves every
|
|
1692
|
+
* rendered fragment as its own line; prompt detection then works on the tail.
|
|
1693
|
+
*/
|
|
1694
|
+
declare function cleanTerminalText(input: string): string;
|
|
1695
|
+
/** Return the last `n` non-empty lines of cleaned text. */
|
|
1696
|
+
declare function tailLines(text: string, n: number): string[];
|
|
1697
|
+
|
|
1698
|
+
export { type AbortReason, type AdapterAvailability, type AdapterConfig, type AdapterFactory, type AdapterListItem, type AdapterMode, AdapterRegistry, type AdapterRunContext, type AgentAdapter, type AgentAdapterDefinition, type AgentErrorInfo, type AgentEvent, type AgentEventType, AgentRelayError, type AgentRunInput, type AgentRunResult, type AgentSessionRef, AlwaysApproveDecider, ApiDecider, type ApprovalMode, BUILTIN_ADAPTER_DEFINITIONS, CONFIG_FILENAME, ClaudeInteractiveAdapter, CodexInteractiveAdapter, CommandDecider, type CommandPreview, type CompletionContext, type CompletionDetector, CompositeCompletionDetector, ConfigError, type CreateSessionInput, DEFAULT_DENY_PATTERNS, type Decider, type DeciderConfig, type DeciderConfigSchema, type DeciderFlags, type DecisionAction, DefaultCompletionDetector, DefaultKeymap, type DetectedPrompt, type DoctorReport, type FakeAdapterOptions, FakeAgentAdapter, FunctionDecider, type HooksConfig, type InitResult, type InteractionDecision, type InteractionKind, type InteractionRequest, type InteractiveAdapterConfig, InteractivePtyAdapter, OutputPatternDetector, PromptDetector, type PromptDetectorOptions, type PruneOptions, type PruneResult, type PtyKeymap, type PtySessionOptions, type RelayConfig, type RelayDefaults, type ResumeCommandResult, RuleDecider, type RunHooks, RunLogger, type RunLoggerOptions, type RunOutcome, type RunnerOptions, type SandboxLevel, type SessionListItem, SessionManager, type SessionMetadata, SessionNotFoundError, type SessionStatus, type ShellHookContext, type ShellHooks, UnknownAdapterError, adapterConfigSchema, approvalPolicySchema, cleanTerminalText, configPath, configSchema, createAdapterFactory, createDecider, createDefaultConfig, deciderConfigFromFlags, deciderSchema, defaultRegistry, defaultsSchema, hooksSchema, listAdapters, listSessions, loadConfig, loadConfigOrDefault, parseCheckbox, parseConfig, parseDecisionReply, pruneSessions, renderDecisionPrompt, resolveApprovalMode, resolvePrompt, resolveSandbox, resumeCommand, runAgent, runCommand, runDoctor, runInit, runPtySession, runShellHook, sandboxSchema, saveConfig, stringifyConfig, stripAnsi, tailLines };
|