@bridge4dev/runner 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/dist/adapters/claude.d.ts +19 -0
  4. package/dist/adapters/claude.js +631 -0
  5. package/dist/adapters/codex-home.d.ts +61 -0
  6. package/dist/adapters/codex-home.js +234 -0
  7. package/dist/adapters/codex-protocol.d.ts +59 -0
  8. package/dist/adapters/codex-protocol.js +204 -0
  9. package/dist/adapters/codex.d.ts +61 -0
  10. package/dist/adapters/codex.js +1406 -0
  11. package/dist/adapters/types.d.ts +183 -0
  12. package/dist/adapters/types.js +5 -0
  13. package/dist/async-queue.d.ts +11 -0
  14. package/dist/async-queue.js +50 -0
  15. package/dist/attachments.d.ts +72 -0
  16. package/dist/attachments.js +149 -0
  17. package/dist/auth-relay.d.ts +57 -0
  18. package/dist/auth-relay.js +289 -0
  19. package/dist/config.d.ts +96 -0
  20. package/dist/config.js +73 -0
  21. package/dist/fsview.d.ts +20 -0
  22. package/dist/fsview.js +122 -0
  23. package/dist/git.d.ts +54 -0
  24. package/dist/git.js +168 -0
  25. package/dist/gitops.d.ts +136 -0
  26. package/dist/gitops.js +596 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.js +352 -0
  29. package/dist/journal.d.ts +118 -0
  30. package/dist/journal.js +300 -0
  31. package/dist/log.d.ts +7 -0
  32. package/dist/log.js +19 -0
  33. package/dist/paths.d.ts +7 -0
  34. package/dist/paths.js +33 -0
  35. package/dist/policy.d.ts +17 -0
  36. package/dist/policy.js +272 -0
  37. package/dist/protocol.d.ts +754 -0
  38. package/dist/protocol.js +154 -0
  39. package/dist/self-update.d.ts +75 -0
  40. package/dist/self-update.js +221 -0
  41. package/dist/status-file.d.ts +14 -0
  42. package/dist/status-file.js +29 -0
  43. package/dist/supervisor.d.ts +216 -0
  44. package/dist/supervisor.js +1648 -0
  45. package/dist/version.d.ts +2 -0
  46. package/dist/version.js +3 -0
  47. package/dist/ws-client.d.ts +30 -0
  48. package/dist/ws-client.js +171 -0
  49. package/package.json +52 -0
@@ -0,0 +1,183 @@
1
+ import type { TrustMode } from '../policy.js';
2
+ export interface McpConfig {
3
+ url: string;
4
+ token: string;
5
+ }
6
+ /**
7
+ * Agent-agnostic interaction mode (session-5 plan §2).
8
+ * ask — ask before acting (Claude `default`, Codex `on-request`)
9
+ * plan — plan first, act after approval (Claude `plan`, Codex on-request + plan)
10
+ * auto — apply edits silently (Claude `acceptEdits`, Codex `on-failure`)
11
+ * full — never ask (Claude `bypassPermissions`, Codex `never`)
12
+ */
13
+ export type AgentMode = 'ask' | 'plan' | 'auto' | 'full';
14
+ export declare const AGENT_MODES: readonly AgentMode[];
15
+ export declare function isAgentMode(value: unknown): value is AgentMode;
16
+ /**
17
+ * A reasoning-effort level a model supports. Codex advertises these per model
18
+ * (`supportedReasoningEfforts`) and the set differs between models — the new
19
+ * Sol/Terra/Luna line adds `max`/`ultra` on top of low/medium/high/xhigh — so
20
+ * the list is never hard-coded, it always comes from the agent.
21
+ */
22
+ export interface EffortOption {
23
+ /** Identifier passed back to setEffort/startSession (e.g. `high`). */
24
+ id: string;
25
+ label: string;
26
+ description?: string;
27
+ }
28
+ export interface ModelOption {
29
+ /** Identifier passed back to setModel/startSession. */
30
+ id: string;
31
+ label: string;
32
+ description?: string;
33
+ isDefault?: boolean;
34
+ /** Empty/absent when the model has no reasoning-effort dial. */
35
+ efforts?: EffortOption[];
36
+ /** The level the agent uses when none is chosen. */
37
+ defaultEffort?: string;
38
+ }
39
+ export interface CommandOption {
40
+ /** Without the leading slash. */
41
+ name: string;
42
+ description?: string;
43
+ argumentHint?: string;
44
+ }
45
+ export interface AgentAccountInfo {
46
+ email?: string;
47
+ organization?: string;
48
+ plan?: string;
49
+ }
50
+ export interface McpServerStatusInfo {
51
+ name: string;
52
+ status: string;
53
+ }
54
+ /** Everything the dashboard needs to render agent controls, live from the agent. */
55
+ export interface AgentCapabilities {
56
+ models: ModelOption[];
57
+ modes: AgentMode[];
58
+ commands: CommandOption[];
59
+ currentModel?: string;
60
+ /** Reasoning effort in force, when the agent has that dial. */
61
+ currentEffort?: string;
62
+ currentMode: AgentMode;
63
+ account?: AgentAccountInfo;
64
+ mcpServers?: McpServerStatusInfo[];
65
+ /** Interrupting a running turn is supported. */
66
+ interrupt: boolean;
67
+ }
68
+ export interface SessionSpec {
69
+ sessionId: string;
70
+ /** Working directory for the agent — the session worktree. */
71
+ cwd: string;
72
+ /**
73
+ * Fully composed initial prompt (ticket context included by the supervisor).
74
+ * Empty/undefined for free CHAT sessions: the agent boots and waits for the
75
+ * first message instead of starting a turn.
76
+ */
77
+ prompt?: string;
78
+ trustMode: TrustMode;
79
+ mode?: AgentMode;
80
+ model?: string;
81
+ effort?: string;
82
+ resumeProviderSessionId?: string;
83
+ mcp?: McpConfig;
84
+ maxBudgetUsd?: number;
85
+ }
86
+ export type AgentEvent = {
87
+ type: 'message';
88
+ role: 'assistant' | 'user';
89
+ text: string;
90
+ } | {
91
+ type: 'thinking';
92
+ text: string;
93
+ }
94
+ /** The agent needs a decision from the user before it can continue. */
95
+ | {
96
+ type: 'question';
97
+ text: string;
98
+ options?: string[];
99
+ } | {
100
+ type: 'tool';
101
+ phase: 'use' | 'result';
102
+ name: string;
103
+ toolUseId?: string;
104
+ detail?: Record<string, unknown>;
105
+ text?: string;
106
+ } | {
107
+ type: 'permission';
108
+ requestId: string;
109
+ toolName: string;
110
+ title: string;
111
+ description?: string;
112
+ input: Record<string, unknown>;
113
+ /** Plan approval (Claude ExitPlanMode / Codex plan item) — rendered as a plan card. */
114
+ plan?: string;
115
+ } | {
116
+ type: 'permission_resolved';
117
+ requestId: string;
118
+ allow: boolean;
119
+ source: 'user' | 'policy' | 'abort';
120
+ reason?: string;
121
+ } | {
122
+ type: 'provider_session';
123
+ providerSessionId: string;
124
+ model?: string;
125
+ } | {
126
+ type: 'capabilities';
127
+ capabilities: AgentCapabilities;
128
+ } | {
129
+ type: 'settings';
130
+ model?: string;
131
+ mode?: AgentMode;
132
+ effort?: string | null;
133
+ } | {
134
+ type: 'context_usage';
135
+ usedTokens: number;
136
+ maxTokens: number;
137
+ } | {
138
+ type: 'notice';
139
+ level: 'info' | 'warn';
140
+ text: string;
141
+ } | {
142
+ type: 'cost';
143
+ costUsd: number;
144
+ numTurns?: number;
145
+ durationMs?: number;
146
+ } | {
147
+ type: 'turn_end';
148
+ ok: boolean;
149
+ errorMessage?: string;
150
+ } | {
151
+ type: 'error';
152
+ message: string;
153
+ /**
154
+ * `auth_missing` — no login on this server at all.
155
+ * `auth_expired` — a login exists but the provider rejected it.
156
+ * Kept apart because the remedies differ, and calling a server that was
157
+ * never signed in "expired" sent people looking for a problem that did
158
+ * not exist.
159
+ */
160
+ code?: 'resume_failed' | 'auth_expired' | 'auth_missing';
161
+ };
162
+ export interface AgentSession {
163
+ /** Ends when the underlying agent process is gone. */
164
+ events: AsyncIterable<AgentEvent>;
165
+ answerPermission(requestId: string, allow: boolean, note?: string): void;
166
+ /** Follow-up user input into the live session. */
167
+ send(text: string): void;
168
+ /** Switch model mid-session (VS-Code-extension parity). */
169
+ setModel(model: string): Promise<void>;
170
+ /** Switch reasoning effort mid-session; null = the model's own default. */
171
+ setEffort(effort: string | null): Promise<void>;
172
+ /** Switch interaction mode mid-session. */
173
+ setMode(mode: AgentMode): Promise<void>;
174
+ /** Interrupt the current turn (session stays resumable). */
175
+ interrupt(): Promise<void>;
176
+ /** Tear the session down (kills the agent process). */
177
+ stop(): void;
178
+ }
179
+ export interface AgentAdapter {
180
+ readonly id: 'claude' | 'codex';
181
+ startSession(spec: SessionSpec): AgentSession;
182
+ }
183
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,5 @@
1
+ export const AGENT_MODES = ['ask', 'plan', 'auto', 'full'];
2
+ export function isAgentMode(value) {
3
+ return typeof value === 'string' && AGENT_MODES.includes(value);
4
+ }
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,11 @@
1
+ export declare class AsyncQueue<T> implements AsyncIterable<T> {
2
+ private readonly values;
3
+ private readonly waiters;
4
+ private ended;
5
+ /** Returns false when the queue has ended and the value was dropped. */
6
+ push(value: T): boolean;
7
+ end(): void;
8
+ get isEnded(): boolean;
9
+ [Symbol.asyncIterator](): AsyncIterator<T>;
10
+ }
11
+ //# sourceMappingURL=async-queue.d.ts.map
@@ -0,0 +1,50 @@
1
+ // Minimal push-based async queue — bridges callback-style producers (WS
2
+ // frames, SDK messages) into AsyncIterable consumers.
3
+ export class AsyncQueue {
4
+ values = [];
5
+ waiters = [];
6
+ ended = false;
7
+ /** Returns false when the queue has ended and the value was dropped. */
8
+ push(value) {
9
+ if (this.ended)
10
+ return false;
11
+ const waiter = this.waiters.shift();
12
+ if (waiter) {
13
+ waiter({ value, done: false });
14
+ }
15
+ else {
16
+ this.values.push(value);
17
+ }
18
+ return true;
19
+ }
20
+ end() {
21
+ if (this.ended)
22
+ return;
23
+ this.ended = true;
24
+ for (const waiter of this.waiters.splice(0)) {
25
+ waiter({ value: undefined, done: true });
26
+ }
27
+ }
28
+ get isEnded() {
29
+ return this.ended;
30
+ }
31
+ [Symbol.asyncIterator]() {
32
+ return {
33
+ next: () => {
34
+ const value = this.values.shift();
35
+ if (value !== undefined) {
36
+ return Promise.resolve({ value, done: false });
37
+ }
38
+ if (this.ended) {
39
+ return Promise.resolve({ value: undefined, done: true });
40
+ }
41
+ return new Promise((resolve) => this.waiters.push(resolve));
42
+ },
43
+ return: () => {
44
+ this.end();
45
+ return Promise.resolve({ value: undefined, done: true });
46
+ },
47
+ };
48
+ }
49
+ }
50
+ //# sourceMappingURL=async-queue.js.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Files the user attached to a message, brought onto their own machine
3
+ * (session 10).
4
+ *
5
+ * Why they land INSIDE the session worktree: layer-1 policy lets the agent read
6
+ * only within its worktree (policy.ts `isInsideWorktree`), and Claude Code and
7
+ * Codex both sandbox to their working directory. A file in `<state>/…` would
8
+ * cost a permission card on every single read — or be refused outright.
9
+ *
10
+ * Why that does not pollute the git panel: `.devbridge/` is added once to the
11
+ * repository's `info/exclude`, so `git status` and the diff never see it.
12
+ */
13
+ /** Directory inside the worktree; also the line written to info/exclude. */
14
+ export declare const ATTACHMENT_DIR = ".devbridge/attachments";
15
+ export interface RemoteAttachment {
16
+ id: string;
17
+ fileName: string;
18
+ mimeType: string;
19
+ fileSize: number;
20
+ }
21
+ export interface SavedAttachment {
22
+ /** Path relative to the worktree — what the agent is told to open. */
23
+ relativePath: string;
24
+ fileName: string;
25
+ mimeType: string;
26
+ fileSize: number;
27
+ }
28
+ /**
29
+ * A file name that is safe to write and unambiguous to read.
30
+ *
31
+ * The name comes from the user's own machine and travels through our API, so it
32
+ * is treated as hostile: no directory separators, no leading dots, no control
33
+ * characters, bounded length. The attachment id prefix keeps two files called
34
+ * `screenshot.png` apart without inventing a counter.
35
+ */
36
+ export declare function safeAttachmentName(id: string, fileName: string): string;
37
+ /**
38
+ * Keep `.devbridge/` out of git for this repository.
39
+ *
40
+ * Uses `info/exclude` rather than `.gitignore`: it is local-only, so we never
41
+ * add a line to a file the user commits. `--git-common-dir` matters because a
42
+ * linked worktree's `.git` is a file, and the shared exclude list is what both
43
+ * the worktree and the main checkout consult.
44
+ */
45
+ export declare function ensureGitExclude(worktreePath: string): Promise<void>;
46
+ /**
47
+ * Download one message's attachments into the session worktree.
48
+ *
49
+ * Returns what actually landed — a file that could not be fetched is reported
50
+ * and skipped rather than failing the whole message, because the text the user
51
+ * typed is usually still worth delivering.
52
+ */
53
+ export declare function saveAttachments(input: {
54
+ worktreePath: string;
55
+ apiUrl: string;
56
+ token: string;
57
+ attachments: RemoteAttachment[];
58
+ fetchImpl?: typeof fetch;
59
+ }): Promise<{
60
+ saved: SavedAttachment[];
61
+ failed: string[];
62
+ }>;
63
+ /**
64
+ * Turn the user's message plus the files into one prompt.
65
+ *
66
+ * Plain paths and an explicit instruction, deliberately: both agents read local
67
+ * files with their own tools, and an agent-specific encoding (image blocks for
68
+ * Claude, `localImage` items for Codex) would be two code paths that drift.
69
+ * The user's own words stay first — the files are context, not the request.
70
+ */
71
+ export declare function composeMessageWithAttachments(text: string, saved: SavedAttachment[]): string;
72
+ //# sourceMappingURL=attachments.d.ts.map
@@ -0,0 +1,149 @@
1
+ import { execFile } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { log } from './log.js';
6
+ const execFileAsync = promisify(execFile);
7
+ /**
8
+ * Files the user attached to a message, brought onto their own machine
9
+ * (session 10).
10
+ *
11
+ * Why they land INSIDE the session worktree: layer-1 policy lets the agent read
12
+ * only within its worktree (policy.ts `isInsideWorktree`), and Claude Code and
13
+ * Codex both sandbox to their working directory. A file in `<state>/…` would
14
+ * cost a permission card on every single read — or be refused outright.
15
+ *
16
+ * Why that does not pollute the git panel: `.devbridge/` is added once to the
17
+ * repository's `info/exclude`, so `git status` and the diff never see it.
18
+ */
19
+ /** Directory inside the worktree; also the line written to info/exclude. */
20
+ export const ATTACHMENT_DIR = '.devbridge/attachments';
21
+ /** Hard ceiling per file; the API allows 5 MB for images and 25 MB for documents. */
22
+ const MAX_ATTACHMENT_BYTES = 26 * 1024 * 1024;
23
+ const DOWNLOAD_TIMEOUT_MS = 60_000;
24
+ /**
25
+ * A file name that is safe to write and unambiguous to read.
26
+ *
27
+ * The name comes from the user's own machine and travels through our API, so it
28
+ * is treated as hostile: no directory separators, no leading dots, no control
29
+ * characters, bounded length. The attachment id prefix keeps two files called
30
+ * `screenshot.png` apart without inventing a counter.
31
+ */
32
+ export function safeAttachmentName(id, fileName) {
33
+ const base = path
34
+ .basename(fileName)
35
+ .replace(/[^A-Za-z0-9._-]+/g, '-')
36
+ .replace(/^\.+/, '');
37
+ const trimmed = base.slice(-80) || 'file';
38
+ return `${id.slice(0, 8)}-${trimmed}`;
39
+ }
40
+ /**
41
+ * Keep `.devbridge/` out of git for this repository.
42
+ *
43
+ * Uses `info/exclude` rather than `.gitignore`: it is local-only, so we never
44
+ * add a line to a file the user commits. `--git-common-dir` matters because a
45
+ * linked worktree's `.git` is a file, and the shared exclude list is what both
46
+ * the worktree and the main checkout consult.
47
+ */
48
+ export async function ensureGitExclude(worktreePath) {
49
+ try {
50
+ const { stdout } = await execFileAsync('git', ['rev-parse', '--git-common-dir'], {
51
+ cwd: worktreePath,
52
+ timeout: 10_000,
53
+ });
54
+ const commonDir = path.resolve(worktreePath, stdout.trim());
55
+ const infoDir = path.join(commonDir, 'info');
56
+ const excludeFile = path.join(infoDir, 'exclude');
57
+ const line = '/.devbridge/';
58
+ let current = '';
59
+ try {
60
+ current = fs.readFileSync(excludeFile, 'utf8');
61
+ }
62
+ catch {
63
+ /* no exclude file yet */
64
+ }
65
+ if (current.split('\n').some((entry) => entry.trim() === line))
66
+ return;
67
+ fs.mkdirSync(infoDir, { recursive: true });
68
+ const prefix = current.length === 0 || current.endsWith('\n') ? '' : '\n';
69
+ fs.appendFileSync(excludeFile, `${prefix}# DevBridge: files you attach to a session live here\n${line}\n`);
70
+ }
71
+ catch (error) {
72
+ // Not fatal: the worst case is that attachments show up as untracked files.
73
+ log.warn('attachments: could not update info/exclude', { error: String(error) });
74
+ }
75
+ }
76
+ /**
77
+ * Download one message's attachments into the session worktree.
78
+ *
79
+ * Returns what actually landed — a file that could not be fetched is reported
80
+ * and skipped rather than failing the whole message, because the text the user
81
+ * typed is usually still worth delivering.
82
+ */
83
+ export async function saveAttachments(input) {
84
+ const doFetch = input.fetchImpl ?? fetch;
85
+ const dir = path.join(input.worktreePath, ATTACHMENT_DIR);
86
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
87
+ await ensureGitExclude(input.worktreePath);
88
+ const saved = [];
89
+ const failed = [];
90
+ const base = input.apiUrl.replace(/\/$/, '');
91
+ for (const attachment of input.attachments) {
92
+ try {
93
+ const response = await doFetch(`${base}/api/v1/dev/runner/attachments/${encodeURIComponent(attachment.id)}`, {
94
+ headers: { Authorization: `Bearer ${input.token}` },
95
+ signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
96
+ });
97
+ if (!response.ok) {
98
+ throw new Error(`HTTP ${response.status}`);
99
+ }
100
+ const buffer = Buffer.from(await response.arrayBuffer());
101
+ if (buffer.length > MAX_ATTACHMENT_BYTES) {
102
+ throw new Error(`file is larger than ${Math.round(MAX_ATTACHMENT_BYTES / 1024 / 1024)}MB`);
103
+ }
104
+ const name = safeAttachmentName(attachment.id, attachment.fileName);
105
+ fs.writeFileSync(path.join(dir, name), buffer, { mode: 0o600 });
106
+ saved.push({
107
+ relativePath: `${ATTACHMENT_DIR}/${name}`,
108
+ fileName: attachment.fileName,
109
+ mimeType: attachment.mimeType,
110
+ fileSize: buffer.length,
111
+ });
112
+ }
113
+ catch (error) {
114
+ log.warn('attachments: download failed', {
115
+ attachmentId: attachment.id,
116
+ error: String(error),
117
+ });
118
+ failed.push(attachment.fileName);
119
+ }
120
+ }
121
+ return { saved, failed };
122
+ }
123
+ /**
124
+ * Turn the user's message plus the files into one prompt.
125
+ *
126
+ * Plain paths and an explicit instruction, deliberately: both agents read local
127
+ * files with their own tools, and an agent-specific encoding (image blocks for
128
+ * Claude, `localImage` items for Codex) would be two code paths that drift.
129
+ * The user's own words stay first — the files are context, not the request.
130
+ */
131
+ export function composeMessageWithAttachments(text, saved) {
132
+ if (saved.length === 0)
133
+ return text;
134
+ const lines = saved.map((file) => `- ${file.relativePath} — ${file.fileName} (${file.mimeType}, ${sizeLabel(file.fileSize)})`);
135
+ const header = saved.length === 1
136
+ ? 'The user attached a file. It is already saved in this workspace:'
137
+ : 'The user attached files. They are already saved in this workspace:';
138
+ return [text.trim(), '', header, ...lines, '', 'Open them before answering — images included.']
139
+ .join('\n')
140
+ .trim();
141
+ }
142
+ function sizeLabel(bytes) {
143
+ if (bytes < 1024)
144
+ return `${bytes} B`;
145
+ if (bytes < 1024 * 1024)
146
+ return `${Math.round(bytes / 1024)} KB`;
147
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
148
+ }
149
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1,57 @@
1
+ export type RelayAgent = 'claude' | 'codex';
2
+ export interface LoginStartResult {
3
+ url: string;
4
+ /** Device-auth style user code to enter on the provider page (Codex). */
5
+ code?: string;
6
+ /** true → the flow finishes by pasting a confirmation code back (Claude). */
7
+ expectsCode: boolean;
8
+ }
9
+ export interface LoginCodeResult {
10
+ ok: boolean;
11
+ detail?: string;
12
+ }
13
+ export interface AgentAuthStatus {
14
+ /**
15
+ * `missing` — never signed in here (or the CLI is not installed).
16
+ * `expired` — we hold a credential and it is past its expiry.
17
+ * `unknown` — the probe itself failed; say so instead of guessing.
18
+ *
19
+ * `expired` used to be the catch-all, which is how a server that had simply
20
+ * never signed in — and one whose probe merely timed out — both ended up
21
+ * telling the user their login had expired.
22
+ */
23
+ status: 'ok' | 'expired' | 'missing' | 'unknown';
24
+ expiresAt?: string;
25
+ detail?: string;
26
+ }
27
+ /** Strip ANSI/OSC control sequences so text matching sees plain output. */
28
+ export declare function stripControl(raw: string): string;
29
+ export declare function extractLoginUrl(agent: RelayAgent, raw: string): string | null;
30
+ export declare function extractDeviceCode(raw: string): string | null;
31
+ export declare class AuthRelay {
32
+ private readonly commands;
33
+ private active;
34
+ constructor(commands?: Record<RelayAgent, string>);
35
+ /** Start (or restart) a login flow and wait until the sign-in URL appears. */
36
+ start(agent: RelayAgent): Promise<LoginStartResult>;
37
+ /** Paste the confirmation code back into the waiting CLI (Claude flow). */
38
+ submitCode(agent: RelayAgent, code: string): Promise<LoginCodeResult>;
39
+ cancel(): void;
40
+ }
41
+ /**
42
+ * Claude: the subscription token's expiry is recorded in the CLI's own
43
+ * credentials file. The RUNNER reads it (its own host user's file) — the
44
+ * agent itself is still denied this path by layer-1 policy.
45
+ */
46
+ export declare function claudeAuthStatus(homedir?: string): Promise<AgentAuthStatus>;
47
+ /**
48
+ * Codex reports its own login state via an exit code (0 signed in / 1 not).
49
+ * Probed against the RUNNER's home: the host user can be signed in while our
50
+ * isolated home is not, and it is ours that sessions use.
51
+ */
52
+ export declare function codexAuthStatus(): Promise<AgentAuthStatus>;
53
+ export declare function agentAuthStatuses(): Promise<{
54
+ claude: AgentAuthStatus;
55
+ codex: AgentAuthStatus;
56
+ }>;
57
+ //# sourceMappingURL=auth-relay.d.ts.map