@bridge4dev/runner 0.65.1 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account-commands.d.ts +43 -0
- package/dist/account-commands.js +142 -0
- package/dist/adapters/claude-usage.d.ts +79 -15
- package/dist/adapters/claude-usage.js +143 -67
- package/dist/adapters/claude.d.ts +30 -1
- package/dist/adapters/claude.js +171 -11
- package/dist/adapters/codex-home.d.ts +143 -21
- package/dist/adapters/codex-home.js +623 -77
- package/dist/adapters/codex.d.ts +25 -3
- package/dist/adapters/codex.js +149 -11
- package/dist/adapters/types.d.ts +22 -0
- package/dist/agent-auth.d.ts +100 -0
- package/dist/agent-auth.js +273 -15
- package/dist/auth-relay.d.ts +114 -26
- package/dist/auth-relay.js +464 -278
- package/dist/claude-homes.d.ts +433 -0
- package/dist/claude-homes.js +1550 -0
- package/dist/codex-accounts.d.ts +79 -0
- package/dist/codex-accounts.js +386 -0
- package/dist/commit-message.js +4 -1
- package/dist/config.d.ts +16 -16
- package/dist/config.js +4 -1
- package/dist/index.js +78 -10
- package/dist/login-marks.d.ts +42 -0
- package/dist/login-marks.js +70 -0
- package/dist/protocol.d.ts +38 -38
- package/dist/protocol.js +3 -1
- package/dist/supervisor.js +17 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { type CodexHome } from './codex-home.js';
|
|
2
2
|
import { AppServerClient, type ServerRequest } from './codex-protocol.js';
|
|
3
3
|
import { type AgentAdapter, type AgentQuestion, type AgentSession, type EffortOption, type SessionSpec } from './types.js';
|
|
4
|
+
/** One entry of `/`, as `writableRootsFor` needs it. */
|
|
5
|
+
export interface RootEntry {
|
|
6
|
+
path: string;
|
|
7
|
+
/** Where a link leads; null when it leads nowhere. */
|
|
8
|
+
realPath: string | null;
|
|
9
|
+
isDirectory: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Lists `/`; throws when it cannot. Replaced only by tests. */
|
|
12
|
+
export type RootLister = () => RootEntry[];
|
|
13
|
+
/**
|
|
14
|
+
* The top-level directories a widened sandbox may write to, or null when `/`
|
|
15
|
+
* could not be read.
|
|
16
|
+
*
|
|
17
|
+
* Links are followed to where they lead and counted once (`/bin` → `/usr/bin`
|
|
18
|
+
* adds nothing `/usr` does not); a directory inside another root is dropped for
|
|
19
|
+
* the same reason; files (a swap file) and dangling links are skipped.
|
|
20
|
+
*/
|
|
21
|
+
export declare function topLevelWritableRoots(listRoot?: RootLister): string[] | null;
|
|
4
22
|
export interface CodexAdapterDeps {
|
|
5
23
|
/** Injected in tests to drive a scripted app-server. */
|
|
6
24
|
spawnClient?: (options: {
|
|
@@ -28,11 +46,15 @@ export interface CodexAdapterDeps {
|
|
|
28
46
|
repairHome?: () => CodexHome;
|
|
29
47
|
/**
|
|
30
48
|
* How this runner's CODEX_HOME is authenticated (`[codex] auth` in
|
|
31
|
-
* config.toml)
|
|
32
|
-
* repair that
|
|
33
|
-
* runner's credential separate
|
|
49
|
+
* config.toml) – ONLY when the owner wrote it. Every repair honours it, and a
|
|
50
|
+
* repair that defaulted to `link` would undo both an owner's decision to keep
|
|
51
|
+
* the runner's credential separate and a switch to a saved login (#422 S4 item
|
|
52
|
+
* 2). Production leaves it unset: the daemon records the configured mode once
|
|
53
|
+
* (`configureCodexAuth`), and the home reads it from there.
|
|
34
54
|
*/
|
|
35
55
|
authMode?: 'link' | 'own';
|
|
56
|
+
/** What `/` holds, for the widened sandbox roots – a test seam (`codex-auto-dev-null`). */
|
|
57
|
+
listRoot?: RootLister;
|
|
36
58
|
/** #382: how often live helpers are re-checked against Codex – a test seam. */
|
|
37
59
|
subagentReconcileMs?: number;
|
|
38
60
|
/** #382: how long a just-started helper's `idle` is disbelieved – a test seam. */
|
package/dist/adapters/codex.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { AsyncQueue } from '../async-queue.js';
|
|
2
4
|
import { log } from '../log.js';
|
|
3
5
|
import { evaluateToolUse, maskSecrets, maskString, } from '../policy.js';
|
|
4
6
|
import { memoryDeathSentence, sessionMemoryEnv, sessionMemoryPromptLine } from '../session-cage.js';
|
|
5
7
|
import { RUNNER_VERSION } from '../version.js';
|
|
6
8
|
import { repairCodexAuth } from './codex-home.js';
|
|
9
|
+
import { codexSessionAccount, noteCodexSessionAccount, noteCodexUsage, releaseCodexSessionAccount, } from '../codex-accounts.js';
|
|
7
10
|
import { AppServerClient, asRecord, num, RpcError, RpcTimeoutError, str, } from './codex-protocol.js';
|
|
8
11
|
import { truncate } from './claude.js';
|
|
9
12
|
import { AgentTaskTray } from './agent-tasks.js';
|
|
@@ -87,11 +90,79 @@ const MODE_POLICY = {
|
|
|
87
90
|
*
|
|
88
91
|
* Read on every turn rather than captured at launch, because the setting can be
|
|
89
92
|
* switched while the session runs.
|
|
93
|
+
*
|
|
94
|
+
* **Never `/` itself** (gotcha 541, plan `codex-auto-dev-null`). Codex mounts a
|
|
95
|
+
* writable root without devices, and `/` laid over its own `/dev` left
|
|
96
|
+
* `/dev/null` visible but unopenable: every `bash -lc` printed «Permission
|
|
97
|
+
* denied» and `git status` exited 128. The widened roots are the machine's real
|
|
98
|
+
* top-level directories instead, minus the kernel's own (`/dev`, `/proc`,
|
|
99
|
+
* `/sys`) – checked on codex-cli 0.154.0 through `command/exec`: `/dev/null`
|
|
100
|
+
* opens, `git status` passes, a write outside the project is allowed, and, unlike
|
|
101
|
+
* `/`, a list of directories leaves no empty `.git`/`.codex`/`.agents` behind
|
|
102
|
+
* (17.09.2026, on throwaway folders – never on this production host's `/`).
|
|
90
103
|
*/
|
|
91
|
-
function writableRootsFor(spec) {
|
|
104
|
+
function writableRootsFor(spec, listRoot = listRootEntries) {
|
|
92
105
|
if (spec.trustMode === 'STRICT')
|
|
93
106
|
return [spec.cwd];
|
|
94
|
-
|
|
107
|
+
if (spec.gitPolicy?.agentAllowOutsideFolder !== true)
|
|
108
|
+
return [spec.cwd];
|
|
109
|
+
const roots = topLevelWritableRoots(listRoot);
|
|
110
|
+
if (roots === null || roots.length === 0) {
|
|
111
|
+
// Unknown means confined (ADR 0007, «polarity»): a step outside asks, as it
|
|
112
|
+
// did before the permission existed.
|
|
113
|
+
return [spec.cwd];
|
|
114
|
+
}
|
|
115
|
+
return roots;
|
|
116
|
+
}
|
|
117
|
+
/** The kernel's own file systems – never a place a sandbox should bind over. */
|
|
118
|
+
const NEVER_WRITABLE_ROOTS = ['/dev', '/proc', '/sys'];
|
|
119
|
+
function listRootEntries() {
|
|
120
|
+
// `withFileTypes`, so a top-level entry is judged by what `readdir` already
|
|
121
|
+
// knows. A `stat` per entry would reach INTO every mount point, and a dead
|
|
122
|
+
// network mount at the top level would then block the daemon's only thread on
|
|
123
|
+
// every turn (found by the independent check of S4). Only a symbolic link is
|
|
124
|
+
// resolved, and those are few (`/bin`, `/lib`, …).
|
|
125
|
+
return fs.readdirSync('/', { withFileTypes: true }).map((entry) => {
|
|
126
|
+
const full = path.join('/', entry.name);
|
|
127
|
+
if (!entry.isSymbolicLink()) {
|
|
128
|
+
return { path: full, realPath: full, isDirectory: entry.isDirectory() };
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const realPath = fs.realpathSync(full);
|
|
132
|
+
return { path: full, realPath, isDirectory: fs.statSync(realPath).isDirectory() };
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return { path: full, realPath: null, isDirectory: false };
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The top-level directories a widened sandbox may write to, or null when `/`
|
|
141
|
+
* could not be read.
|
|
142
|
+
*
|
|
143
|
+
* Links are followed to where they lead and counted once (`/bin` → `/usr/bin`
|
|
144
|
+
* adds nothing `/usr` does not); a directory inside another root is dropped for
|
|
145
|
+
* the same reason; files (a swap file) and dangling links are skipped.
|
|
146
|
+
*/
|
|
147
|
+
export function topLevelWritableRoots(listRoot = listRootEntries) {
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = listRoot();
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
log.warn('codex: could not read the top-level folders – the sandbox stays on the project folder', {
|
|
154
|
+
error: String(error).slice(0, 200),
|
|
155
|
+
});
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const kernel = (dir) => NEVER_WRITABLE_ROOTS.some((root) => dir === root || dir.startsWith(`${root}/`));
|
|
159
|
+
const unique = [
|
|
160
|
+
...new Set(entries
|
|
161
|
+
.filter((entry) => entry.isDirectory && entry.realPath !== null)
|
|
162
|
+
.map((entry) => path.resolve(entry.realPath))
|
|
163
|
+
.filter((dir) => dir !== '/' && !kernel(dir))),
|
|
164
|
+
].sort();
|
|
165
|
+
return unique.filter((dir) => !unique.some((other) => other !== dir && dir.startsWith(`${other}/`)));
|
|
95
166
|
}
|
|
96
167
|
/**
|
|
97
168
|
* DevBridge's own rules — composed per session since session 18, and kept
|
|
@@ -214,6 +285,15 @@ class CodexSession {
|
|
|
214
285
|
queuedInput = [];
|
|
215
286
|
/** Null when the home was injected — an injected home is the whole truth. */
|
|
216
287
|
repairHome;
|
|
288
|
+
/**
|
|
289
|
+
* The login this process started under (#422 S4), kept to its end: the machine
|
|
290
|
+
* may be switched while it runs, and its limits, its card and a refusal it meets
|
|
291
|
+
* all belong to THIS account (R14). Codex itself keeps a running session on the
|
|
292
|
+
* account it started with – it will not refresh into another account's file.
|
|
293
|
+
*/
|
|
294
|
+
account;
|
|
295
|
+
accountId;
|
|
296
|
+
listRoot;
|
|
217
297
|
/** Open elicitations, oldest first — Codex may have several in flight. */
|
|
218
298
|
questions = new Map();
|
|
219
299
|
threadId = null;
|
|
@@ -279,6 +359,12 @@ class CodexSession {
|
|
|
279
359
|
this.model = spec.model;
|
|
280
360
|
this.effort = spec.effort;
|
|
281
361
|
this.repairHome = deps.repairHome ?? (deps.codexHome ? null : repairCodexAuth);
|
|
362
|
+
this.listRoot = deps.listRoot;
|
|
363
|
+
this.account = codexSessionAccount(home);
|
|
364
|
+
this.accountId = this.account.id;
|
|
365
|
+
// Released in `finish`, and only by THIS session: «forget» refuses a login a
|
|
366
|
+
// live process refreshes into.
|
|
367
|
+
noteCodexSessionAccount(spec.sessionId, this.accountId, this);
|
|
282
368
|
const wiring = {
|
|
283
369
|
// The cage's two numbers ride along (#398 S5). Built here rather than
|
|
284
370
|
// inside `scrubbedEnv`, which has no access to the session — and merged
|
|
@@ -346,6 +432,13 @@ class CodexSession {
|
|
|
346
432
|
throw new Error(`codex is using an unexpected CODEX_HOME (${reportedHome ?? 'not reported'}) — refusing to start the session`);
|
|
347
433
|
}
|
|
348
434
|
this.client.notify('initialized', {});
|
|
435
|
+
// The app-server reads `auth.json` while it starts, so the login it ended
|
|
436
|
+
// up with is the one the link points at NOW, not the one this object read
|
|
437
|
+
// before the process existed. A switch in that window is rare and would
|
|
438
|
+
// otherwise put the previous account's name on this session's card and its
|
|
439
|
+
// limits (found by the independent check of S4). The row it is noted under
|
|
440
|
+
// moves with it, so «forget» refuses the right one.
|
|
441
|
+
this.rereadAccount();
|
|
349
442
|
// The helper set is per PROCESS (#113, #382): a session relaunched after a
|
|
350
443
|
// runner restart must not go on showing the helpers of its previous life,
|
|
351
444
|
// and nothing else would say so until the next one starts or ends.
|
|
@@ -675,7 +768,7 @@ class CodexSession {
|
|
|
675
768
|
input: [{ type: 'text', text, text_elements: [] }],
|
|
676
769
|
approvalPolicy: policy.approvalPolicy,
|
|
677
770
|
// #418: the roots live here and only here — see `writableRootsFor`.
|
|
678
|
-
sandboxPolicy: sandboxPolicyFor(policy.sandbox, this.spec),
|
|
771
|
+
sandboxPolicy: sandboxPolicyFor(policy.sandbox, this.spec, this.listRoot),
|
|
679
772
|
...(this.model ? { model: this.model } : {}),
|
|
680
773
|
// "Override the reasoning effort for this turn and subsequent turns" —
|
|
681
774
|
// the same sticky-override channel the model uses (there is still no
|
|
@@ -1835,6 +1928,13 @@ class CodexSession {
|
|
|
1835
1928
|
this.emitRateLimits();
|
|
1836
1929
|
}
|
|
1837
1930
|
emitRateLimits(blocked = null) {
|
|
1931
|
+
// Whose figures these are (#422 §8): the subscription this session started
|
|
1932
|
+
// under. Codex reports the windows of the account its process is signed in
|
|
1933
|
+
// with, and that is the account captured at the start – so the signature is
|
|
1934
|
+
// true for the whole life of the session. No key (an API-key login) – no
|
|
1935
|
+
// signature, and the panel draws as before #422 (R12).
|
|
1936
|
+
const orgId = this.account.orgId;
|
|
1937
|
+
noteCodexUsage(orgId, this.rateLimitWindows);
|
|
1838
1938
|
// An empty snapshot is «no plan», not «nothing happened»: Codex sends this
|
|
1839
1939
|
// for API-key accounts too, and the popup has to be able to say so.
|
|
1840
1940
|
this.emit({
|
|
@@ -1845,6 +1945,7 @@ class CodexSession {
|
|
|
1845
1945
|
planType: this.ratePlanType,
|
|
1846
1946
|
measuredAt: new Date().toISOString(),
|
|
1847
1947
|
windows: this.rateLimitWindows,
|
|
1948
|
+
...(orgId ? { orgId } : {}),
|
|
1848
1949
|
},
|
|
1849
1950
|
});
|
|
1850
1951
|
}
|
|
@@ -2194,6 +2295,27 @@ class CodexSession {
|
|
|
2194
2295
|
this.capabilitiesInFlight = false;
|
|
2195
2296
|
});
|
|
2196
2297
|
}
|
|
2298
|
+
/** Who the home belongs to now – after the app-server has read its credential. */
|
|
2299
|
+
rereadAccount() {
|
|
2300
|
+
if (!this.repairHome)
|
|
2301
|
+
return;
|
|
2302
|
+
try {
|
|
2303
|
+
const now = codexSessionAccount(this.repairHome());
|
|
2304
|
+
if (now.id === this.account.id)
|
|
2305
|
+
return;
|
|
2306
|
+
log.info('codex: the machine changed its Codex login while this session was starting', {
|
|
2307
|
+
sessionId: this.spec.sessionId,
|
|
2308
|
+
from: this.account.id,
|
|
2309
|
+
to: now.id,
|
|
2310
|
+
});
|
|
2311
|
+
this.account = now;
|
|
2312
|
+
this.accountId = now.id;
|
|
2313
|
+
noteCodexSessionAccount(this.spec.sessionId, now.id, this);
|
|
2314
|
+
}
|
|
2315
|
+
catch (error) {
|
|
2316
|
+
log.warn('codex: could not re-read the account after start', { error: describe(error) });
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2197
2319
|
async publishCapabilities() {
|
|
2198
2320
|
// Every probe carries its own catch: an unknown method on a newer or older
|
|
2199
2321
|
// codex must degrade a picker, not kill the session.
|
|
@@ -2218,7 +2340,7 @@ class CodexSession {
|
|
|
2218
2340
|
currentMode: this.mode,
|
|
2219
2341
|
...(currentModel ? { currentModel } : {}),
|
|
2220
2342
|
...(currentEffort ? { currentEffort } : {}),
|
|
2221
|
-
|
|
2343
|
+
account: this.sessionAccountCard(account),
|
|
2222
2344
|
mcpServers,
|
|
2223
2345
|
interrupt: true,
|
|
2224
2346
|
};
|
|
@@ -2358,6 +2480,21 @@ class CodexSession {
|
|
|
2358
2480
|
}
|
|
2359
2481
|
return options;
|
|
2360
2482
|
}
|
|
2483
|
+
/**
|
|
2484
|
+
* The card's account: what the live process says about itself (`account/read`),
|
|
2485
|
+
* under the row and the subscription the session started with (#422 §8) – the
|
|
2486
|
+
* `id` a «forget» is refused for, the `orgId` the limits panel matches against.
|
|
2487
|
+
*/
|
|
2488
|
+
sessionAccountCard(live) {
|
|
2489
|
+
const plan = live?.plan ?? (this.account.plan ? `ChatGPT ${this.account.plan}` : undefined);
|
|
2490
|
+
const email = live?.email ?? this.account.email;
|
|
2491
|
+
return {
|
|
2492
|
+
id: this.account.id,
|
|
2493
|
+
...(email ? { email } : {}),
|
|
2494
|
+
...(plan ? { plan } : {}),
|
|
2495
|
+
...(this.account.orgId ? { orgId: this.account.orgId } : {}),
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2361
2498
|
async readAccount() {
|
|
2362
2499
|
const result = asRecord(await this.client.request('account/read', {}, 15_000));
|
|
2363
2500
|
const account = asRecord(result['account']);
|
|
@@ -2505,6 +2642,7 @@ class CodexSession {
|
|
|
2505
2642
|
return home.auth === 'missing' ? 'missing' : 'expired';
|
|
2506
2643
|
}
|
|
2507
2644
|
finish() {
|
|
2645
|
+
releaseCodexSessionAccount(this.spec.sessionId, this);
|
|
2508
2646
|
this.stopped = true;
|
|
2509
2647
|
this.clearLimitSettle();
|
|
2510
2648
|
this.subagents.close();
|
|
@@ -2514,7 +2652,7 @@ class CodexSession {
|
|
|
2514
2652
|
}
|
|
2515
2653
|
class ResumeFailed extends Error {
|
|
2516
2654
|
}
|
|
2517
|
-
function sandboxPolicyFor(mode, spec) {
|
|
2655
|
+
function sandboxPolicyFor(mode, spec, listRoot) {
|
|
2518
2656
|
// turn/start takes the structured SandboxPolicy, while thread/start takes the
|
|
2519
2657
|
// CLI-style SandboxMode string. Same intent, two shapes — and only this one
|
|
2520
2658
|
// can name the writable roots, which is why #418 lives here.
|
|
@@ -2526,7 +2664,7 @@ function sandboxPolicyFor(mode, spec) {
|
|
|
2526
2664
|
default:
|
|
2527
2665
|
return {
|
|
2528
2666
|
type: 'workspaceWrite',
|
|
2529
|
-
writableRoots: writableRootsFor(spec),
|
|
2667
|
+
writableRoots: writableRootsFor(spec, listRoot),
|
|
2530
2668
|
// Unchanged by #418, on purpose: the permission is about the file
|
|
2531
2669
|
// system, and letting the network out with it would be a second
|
|
2532
2670
|
// boundary nobody asked to move. A network command still fails in the
|
|
@@ -2796,11 +2934,11 @@ export class CodexAdapter {
|
|
|
2796
2934
|
// Re-assert rather than reuse: `repairCodexAuth` is a couple of lstat calls
|
|
2797
2935
|
// and it puts a credential link back if something removed it since boot.
|
|
2798
2936
|
//
|
|
2799
|
-
// The configured mode has to travel with it
|
|
2800
|
-
// would silently re-link the host user's credential
|
|
2801
|
-
// explicitly asked to keep isolated
|
|
2802
|
-
//
|
|
2803
|
-
const repair = () => repairCodexAuth({ auth: this.deps.authMode
|
|
2937
|
+
// The configured mode has to travel with it – and only a configured one.
|
|
2938
|
+
// Repairing with a default would silently re-link the host user's credential
|
|
2939
|
+
// into a home the owner explicitly asked to keep isolated, and undo a switch
|
|
2940
|
+
// to a saved login at every session start (#422 S4 item 2).
|
|
2941
|
+
const repair = () => repairCodexAuth(this.deps.authMode ? { auth: this.deps.authMode } : {});
|
|
2804
2942
|
const home = this.deps.codexHome ?? repair();
|
|
2805
2943
|
return new CodexSession(spec, home, {
|
|
2806
2944
|
...this.deps,
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -87,9 +87,17 @@ export interface CommandOption {
|
|
|
87
87
|
argumentHint?: string;
|
|
88
88
|
}
|
|
89
89
|
export interface AgentAccountInfo {
|
|
90
|
+
/**
|
|
91
|
+
* Which of the machine's accounts THIS session started under (#422 §8) –
|
|
92
|
+
* `machine` or a saved account's id. Kept to the session's end: switching the
|
|
93
|
+
* machine does not move a running session.
|
|
94
|
+
*/
|
|
95
|
+
id?: string;
|
|
90
96
|
email?: string;
|
|
91
97
|
organization?: string;
|
|
92
98
|
plan?: string;
|
|
99
|
+
/** The subscription key (D19) – what the limits panel matches figures against. */
|
|
100
|
+
orgId?: string;
|
|
93
101
|
}
|
|
94
102
|
export interface McpServerStatusInfo {
|
|
95
103
|
name: string;
|
|
@@ -197,6 +205,15 @@ export interface AgentRateLimits {
|
|
|
197
205
|
key: AgentRateLimitWindow['key'];
|
|
198
206
|
resetsAt: string | null;
|
|
199
207
|
} | null;
|
|
208
|
+
/**
|
|
209
|
+
* Whose figures these are (#422 §8) – the account the session runs under.
|
|
210
|
+
* `orgId` is the subscription key (`claude auth status`), `accountUuid` the
|
|
211
|
+
* signature the CLI itself writes next to a `/usage` reading. Both optional:
|
|
212
|
+
* a session nobody could identify sends none, and so does every runner from
|
|
213
|
+
* before #422 – the panel then draws exactly as it did (R12).
|
|
214
|
+
*/
|
|
215
|
+
accountUuid?: string;
|
|
216
|
+
orgId?: string;
|
|
200
217
|
}
|
|
201
218
|
/** Everything the dashboard needs to render agent controls, live from the agent. */
|
|
202
219
|
export interface AgentCapabilities {
|
|
@@ -727,6 +744,11 @@ export type InterruptOutcome =
|
|
|
727
744
|
export interface AgentSession {
|
|
728
745
|
/** Ends when the underlying agent process is gone. */
|
|
729
746
|
events: AsyncIterable<AgentEvent>;
|
|
747
|
+
/**
|
|
748
|
+
* The account this session's process runs under (#422) – what a refusal or a
|
|
749
|
+
* successful turn is attributed to (R14). Absent: the machine login.
|
|
750
|
+
*/
|
|
751
|
+
readonly accountId?: string;
|
|
730
752
|
answerPermission(requestId: string, allow: boolean, note?: string): void;
|
|
731
753
|
/**
|
|
732
754
|
* Answer (or discuss) an open question. Returns false when the ask is no
|
package/dist/agent-auth.d.ts
CHANGED
|
@@ -1,7 +1,96 @@
|
|
|
1
1
|
export declare function agentAuthPath(): string;
|
|
2
|
+
/**
|
|
3
|
+
* Who a Claude home last said it was – the answer of `claude auth status` in it.
|
|
4
|
+
*
|
|
5
|
+
* Kept on disk (#422 R14) so a restarted runner does not forget whose numbers a
|
|
6
|
+
* usage reading is and whom `auth_status` names as active. Not a verdict on the
|
|
7
|
+
* login: `auth status` says `loggedIn: true` over a dead credential, which is why
|
|
8
|
+
* the verdict stays with the credentials file.
|
|
9
|
+
*/
|
|
10
|
+
export interface ClaudeIdentity {
|
|
11
|
+
email?: string;
|
|
12
|
+
/** The subscription key (D19, R1): one subscription – one row. */
|
|
13
|
+
orgId?: string;
|
|
14
|
+
orgName?: string;
|
|
15
|
+
/** `subscriptionType` – `max`, `pro`, … */
|
|
16
|
+
plan?: string;
|
|
17
|
+
/** When this identity was read, ISO. */
|
|
18
|
+
at: string;
|
|
19
|
+
}
|
|
20
|
+
/** One saved Claude login of this machine (#422). Files live in its home, not here. */
|
|
21
|
+
export interface ClaudeAccountRecord {
|
|
22
|
+
id: string;
|
|
23
|
+
addedAt: string;
|
|
24
|
+
lastSeenIdentity?: ClaudeIdentity;
|
|
25
|
+
/**
|
|
26
|
+
* A session under this account was refused (D2): the row is marked, never
|
|
27
|
+
* removed. Read against the credentials file – a file written after the mark
|
|
28
|
+
* (the CLI refreshed, somebody signed in again) outranks it.
|
|
29
|
+
*/
|
|
30
|
+
loginExpiredAt?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* One saved Codex login of this machine (#422 S4) – the same shape as a Claude
|
|
34
|
+
* record. `lastSeenIdentity.orgId` carries `tokens.account_id` of its `auth.json`:
|
|
35
|
+
* the key of a Codex subscription (plan §8), one subscription – one row.
|
|
36
|
+
*/
|
|
37
|
+
export type CodexAccountRecord = ClaudeAccountRecord;
|
|
38
|
+
/** What the runner remembers about the machine row itself – it has no record of its own. */
|
|
39
|
+
export interface ClaudeMachineRecord {
|
|
40
|
+
lastSeenIdentity?: ClaudeIdentity;
|
|
41
|
+
/**
|
|
42
|
+
* A refusal held against the token this runner captured (`claudeOauthToken`)
|
|
43
|
+
* – set only when the refused session actually ran with that token. It
|
|
44
|
+
* replaces the old «discard the token after a refusal», which erased the whole
|
|
45
|
+
* file and with it every saved account (#422 S1 item 3).
|
|
46
|
+
*/
|
|
47
|
+
loginExpiredAt?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface AgentAuthFile {
|
|
50
|
+
claudeOauthToken?: string;
|
|
51
|
+
/**
|
|
52
|
+
* When `claudeOauthToken` was stored – the token's age, and ONLY that. Every
|
|
53
|
+
* other write leaves it alone, or saving an account would make a
|
|
54
|
+
* months-old token look freshly captured.
|
|
55
|
+
*/
|
|
56
|
+
updatedAt?: string;
|
|
57
|
+
claudeAccounts?: ClaudeAccountRecord[];
|
|
58
|
+
/** Absent means the machine login – the pre-#422 file reads exactly as before. */
|
|
59
|
+
claudeActiveAccount?: string;
|
|
60
|
+
claudeMachine?: ClaudeMachineRecord;
|
|
61
|
+
/**
|
|
62
|
+
* The saved Codex logins of this machine (#422 S4). Their files live in the
|
|
63
|
+
* runner's CODEX_HOME (`accounts/<id>/auth.json`); WHICH one is active is not
|
|
64
|
+
* here but in the home itself – the `auth.json` link and its mark – because
|
|
65
|
+
* that is what the CLI reads.
|
|
66
|
+
*/
|
|
67
|
+
codexAccounts?: CodexAccountRecord[];
|
|
68
|
+
}
|
|
69
|
+
/** Everything this runner keeps in `agent-auth.json`, read forgivingly. */
|
|
70
|
+
export declare function readAgentAuth(): AgentAuthFile;
|
|
71
|
+
/**
|
|
72
|
+
* Change some fields of the file and keep every other one.
|
|
73
|
+
*
|
|
74
|
+
* Read → change → write, never «write what I know». The two writers this file
|
|
75
|
+
* had before #422 wrote the WHOLE file (`storeClaudeToken`) or deleted it
|
|
76
|
+
* (`clearStoredClaudeToken`), and with a list of accounts beside the token either
|
|
77
|
+
* would have erased every saved login's record on its way past (S1 item 3).
|
|
78
|
+
*
|
|
79
|
+
* A file that exists but cannot be parsed is moved aside, not overwritten: it may
|
|
80
|
+
* be the only record of which home belongs to which account, and «unreadable»
|
|
81
|
+
* must not quietly become «empty». A file that cannot be READ (EACCES) throws –
|
|
82
|
+
* writing over something we could not look at is the same mistake.
|
|
83
|
+
*
|
|
84
|
+
* Synchronous from read to rename, so two changes inside this process cannot
|
|
85
|
+
* interleave. Same write-then-rename as the config, 0600.
|
|
86
|
+
*/
|
|
87
|
+
export declare function updateAgentAuth(change: (file: AgentAuthFile) => void): AgentAuthFile;
|
|
2
88
|
/** The token we hold for Claude, or null once it is too old to trust. */
|
|
3
89
|
export declare function storedClaudeToken(): string | null;
|
|
90
|
+
/** When the stored token was captured, in ms – null without one. */
|
|
91
|
+
export declare function storedClaudeTokenAtMs(): number | null;
|
|
4
92
|
export declare function storeClaudeToken(token: string): void;
|
|
93
|
+
/** Drop the stored token – and nothing else: the accounts beside it stay (#422). */
|
|
5
94
|
export declare function clearStoredClaudeToken(): void;
|
|
6
95
|
/**
|
|
7
96
|
* Put a stored token into this process's environment, unless the operator
|
|
@@ -15,6 +104,17 @@ export declare function clearStoredClaudeToken(): void;
|
|
|
15
104
|
* Returns true when it applied one.
|
|
16
105
|
*/
|
|
17
106
|
export declare function applyStoredClaudeToken(): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Was a session refused with the token this runner captured – after it was
|
|
109
|
+
* captured? A token stored after the refusal is newer evidence and outranks it.
|
|
110
|
+
* A token with no readable capture date is judged by the mark alone.
|
|
111
|
+
*/
|
|
112
|
+
export declare function storedClaudeTokenRefused(): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* The daemon's environment carries exactly the token this runner captured – so
|
|
115
|
+
* a machine-login session refused now was refused WITH it.
|
|
116
|
+
*/
|
|
117
|
+
export declare function environmentCarriesStoredToken(): boolean;
|
|
18
118
|
/**
|
|
19
119
|
* The OAuth token `claude setup-token` printed, reassembled out of pty output.
|
|
20
120
|
*
|