@bridge4dev/runner 0.65.1 → 0.66.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 +44 -0
- package/dist/account-commands.js +99 -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/types.d.ts +22 -0
- package/dist/agent-auth.d.ts +87 -0
- package/dist/agent-auth.js +249 -15
- package/dist/auth-relay.d.ts +110 -26
- package/dist/auth-relay.js +339 -232
- package/dist/claude-homes.d.ts +436 -0
- package/dist/claude-homes.js +1567 -0
- package/dist/commit-message.js +4 -1
- package/dist/config.d.ts +14 -14
- package/dist/index.js +53 -1
- package/dist/login-marks.d.ts +31 -0
- package/dist/login-marks.js +47 -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/claude.js
CHANGED
|
@@ -13,6 +13,7 @@ import { percentFromUtilization, RATE_WINDOW_MINUTES, rateWindowKey } from './ra
|
|
|
13
13
|
import { AgentTaskTray } from './agent-tasks.js';
|
|
14
14
|
import { applyUsagePercentages, lastUsageRows, readUsageRows } from './claude-usage.js';
|
|
15
15
|
import { assertClaudeInstalled, claudeExecutableOption, sessionClaudePath, } from '../agent-binary.js';
|
|
16
|
+
import { noteSessionAccount, refreshAccountIdentity, releaseSessionAccount, resolveSessionAccount, currentHomeOrgId, usageSignature, withAccountHome, } from '../claude-homes.js';
|
|
16
17
|
/** Same 2KB the SDK keeps: enough for the CLI's last words, not a log sink. */
|
|
17
18
|
const STDERR_TAIL_LIMIT = 2048;
|
|
18
19
|
import { answerSummary, answerValue, discussMessage, invalidationMessage, mirrorOptions, newAskId, MAX_OPTIONS, MAX_QUESTIONS, OPTION_TEXT_LIMIT, QUESTION_TEXT_LIMIT, } from './questions.js';
|
|
@@ -101,8 +102,18 @@ export function scrubbedEnv() {
|
|
|
101
102
|
* and there is nothing left for `IS_SANDBOX` to weaken. In every other mode the
|
|
102
103
|
* scrub stands, which is what the comment on `ENV_ALLOWLIST` has always meant.
|
|
103
104
|
*/
|
|
104
|
-
function agentEnv(mode, sessionId) {
|
|
105
|
-
|
|
105
|
+
export function agentEnv(mode, sessionId, account = { kind: 'machine', home: null }) {
|
|
106
|
+
/**
|
|
107
|
+
* The account's home (#422 S1 item 4) – HERE, in the session's environment, and
|
|
108
|
+
* never in the daemon's (К1): the daemon's environment is also what the verdict
|
|
109
|
+
* and `doctor` would read, and a variable there turns every verdict green.
|
|
110
|
+
*
|
|
111
|
+
* For a saved account the operator's `CLAUDE_CODE_OAUTH_TOKEN` comes OUT: the
|
|
112
|
+
* CLI ranks it above every login file, so with it left in, switching accounts
|
|
113
|
+
* would change nothing under a green verdict (К14). For the machine login the
|
|
114
|
+
* environment is exactly what it was before #422.
|
|
115
|
+
*/
|
|
116
|
+
const env = withAccountHome(scrubbedEnv(), account);
|
|
106
117
|
if (mode === 'full' && process.getuid?.() === 0)
|
|
107
118
|
env['IS_SANDBOX'] = '1';
|
|
108
119
|
/**
|
|
@@ -480,8 +491,30 @@ class ClaudeSession {
|
|
|
480
491
|
this.beginTurnFacts();
|
|
481
492
|
}
|
|
482
493
|
events = this.output;
|
|
494
|
+
/**
|
|
495
|
+
* The account this process runs under, captured at its start and kept to its
|
|
496
|
+
* end (#422 R14, R16): the limits it measures, the refusal it may meet and the
|
|
497
|
+
* account its card names all belong to THIS account, whatever the machine is
|
|
498
|
+
* switched to meanwhile.
|
|
499
|
+
*/
|
|
500
|
+
account;
|
|
501
|
+
accountId;
|
|
502
|
+
/** Started on the operator's token: its limits go out unsigned (see the constructor). */
|
|
503
|
+
onOperatorToken;
|
|
483
504
|
constructor(spec, queryFn) {
|
|
484
505
|
this.spec = spec;
|
|
506
|
+
const account = resolveSessionAccount();
|
|
507
|
+
// A machine-login session that runs on a TOKEN is not the account of the
|
|
508
|
+
// credentials file: `claude auth status` is asked without the token, so the
|
|
509
|
+
// identity on file names the file's login, and this session's card and its
|
|
510
|
+
// limits would carry somebody else's name (found by the independent check of
|
|
511
|
+
// S1). Such a session keeps only the row id – unsigned, as before #422.
|
|
512
|
+
this.onOperatorToken =
|
|
513
|
+
account.kind === 'machine' && Boolean(process.env['CLAUDE_CODE_OAUTH_TOKEN']);
|
|
514
|
+
this.account = this.onOperatorToken
|
|
515
|
+
? { id: account.id, kind: account.kind, home: null }
|
|
516
|
+
: account;
|
|
517
|
+
this.accountId = this.account.id;
|
|
485
518
|
// Refused rather than honoured, and BEFORE anything is built from it: on a
|
|
486
519
|
// STRICT workspace `full` would launch the CLI in `bypassPermissions`, and
|
|
487
520
|
// there layer 1 is never consulted — the manager's shield would be spent by
|
|
@@ -522,7 +555,7 @@ class ClaudeSession {
|
|
|
522
555
|
this.mcpConfigFile = mcpConfigFile;
|
|
523
556
|
const options = {
|
|
524
557
|
cwd: spec.cwd,
|
|
525
|
-
env: agentEnv(this.mode, spec.sessionId),
|
|
558
|
+
env: agentEnv(this.mode, spec.sessionId, this.account),
|
|
526
559
|
// Empty while `USE_BUNDLED_CLAUDE` — the SDK keeps resolving its own
|
|
527
560
|
// bundled binary, exactly as before. After C3 this pins the system
|
|
528
561
|
// `claude`, which is the file the card measures and the button installs.
|
|
@@ -615,7 +648,17 @@ class ClaudeSession {
|
|
|
615
648
|
? { mcpServers }
|
|
616
649
|
: {}),
|
|
617
650
|
};
|
|
618
|
-
|
|
651
|
+
// Registered before the process exists, released when it is gone
|
|
652
|
+
// (`consume`'s finally): `forget` refuses an account a live process writes
|
|
653
|
+
// into, and a replaced login's old home waits for exactly these to end.
|
|
654
|
+
noteSessionAccount(spec.sessionId, this.account.id);
|
|
655
|
+
try {
|
|
656
|
+
this.q = queryFn({ prompt: this.input, options });
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
releaseSessionAccount(spec.sessionId);
|
|
660
|
+
throw error;
|
|
661
|
+
}
|
|
619
662
|
void this.consume();
|
|
620
663
|
// Ticket #119 / QA-114 MAJOR-3: if the protected file could not be written,
|
|
621
664
|
// say so where a person will see it, not only in journald.
|
|
@@ -897,12 +940,25 @@ class ClaudeSession {
|
|
|
897
940
|
refreshUsage() {
|
|
898
941
|
if (this.stopped)
|
|
899
942
|
return;
|
|
900
|
-
|
|
943
|
+
// THIS session's account (#422 R16): the reading is keyed by its
|
|
944
|
+
// subscription, measured under its home, and signed by what that home says
|
|
945
|
+
// right after – never whatever the machine is switched to now.
|
|
946
|
+
const account = {
|
|
947
|
+
id: this.account.id,
|
|
948
|
+
home: this.account.home,
|
|
949
|
+
...(this.account.orgId ? { orgId: this.account.orgId } : {}),
|
|
950
|
+
};
|
|
951
|
+
if (this.absorbUsageReading(lastUsageRows(account)))
|
|
901
952
|
this.emitRateLimits();
|
|
902
953
|
const binary = sessionClaudePath();
|
|
903
954
|
if (!binary)
|
|
904
955
|
return;
|
|
905
|
-
void readUsageRows({
|
|
956
|
+
void readUsageRows({
|
|
957
|
+
binary,
|
|
958
|
+
account,
|
|
959
|
+
env: withAccountHome({ ...process.env }, this.account),
|
|
960
|
+
sign: () => usageSignature(this.account),
|
|
961
|
+
})
|
|
906
962
|
.then((reading) => {
|
|
907
963
|
if (this.stopped)
|
|
908
964
|
return;
|
|
@@ -913,6 +969,48 @@ class ClaudeSession {
|
|
|
913
969
|
}
|
|
914
970
|
/** The measurement already folded in, so an older one cannot undo a newer event. */
|
|
915
971
|
usageAppliedAtMs = 0;
|
|
972
|
+
/**
|
|
973
|
+
* The CLI's own signature on the last reading folded in (#422 §8). Only ever
|
|
974
|
+
* this session's account: a reading signed by another subscription never
|
|
975
|
+
* reaches `absorbUsageReading`'s fold.
|
|
976
|
+
*/
|
|
977
|
+
usageAccountUuid;
|
|
978
|
+
/**
|
|
979
|
+
* The subscription of a session that could not name one at start: the first
|
|
980
|
+
* signed reading measured under its home, and never overwritten. Taken from
|
|
981
|
+
* every reading instead, it moved with the home – a terminal login into
|
|
982
|
+
* another subscription re-based a running session onto it, and the latch below
|
|
983
|
+
* never saw a change (found by the second independent check of S2).
|
|
984
|
+
*/
|
|
985
|
+
firstReadingOrgId;
|
|
986
|
+
/** Latched: this home was signed in to another subscription while the session ran. */
|
|
987
|
+
signedInElsewhere = false;
|
|
988
|
+
/**
|
|
989
|
+
* Has somebody signed this session's home in to a different subscription?
|
|
990
|
+
*
|
|
991
|
+
* «Log in again» on the machine row, or `claude auth login` in a terminal,
|
|
992
|
+
* rewrites the home the session reads by PATH, and the CLI process moves onto
|
|
993
|
+
* the new login at its next token refresh (gotcha 531). From then on its
|
|
994
|
+
* figures are the new subscription's, and signing them with the one captured at
|
|
995
|
+
* start would put B's percentages under A's name – exactly what the signature
|
|
996
|
+
* exists to prevent (found by the independent check of S2). The frame then goes
|
|
997
|
+
* unsigned, as before #422, until the process ends.
|
|
998
|
+
*/
|
|
999
|
+
homeSignedInElsewhere() {
|
|
1000
|
+
if (this.signedInElsewhere)
|
|
1001
|
+
return true;
|
|
1002
|
+
const captured = this.signatureOrgId();
|
|
1003
|
+
if (!captured)
|
|
1004
|
+
return false;
|
|
1005
|
+
const now = currentHomeOrgId(this.account.home);
|
|
1006
|
+
if (now !== undefined && now !== captured) {
|
|
1007
|
+
this.signedInElsewhere = true;
|
|
1008
|
+
log.info('claude: the session home was signed in to another subscription – limits unsigned', {
|
|
1009
|
+
sessionId: this.spec.sessionId,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
return this.signedInElsewhere;
|
|
1013
|
+
}
|
|
916
1014
|
/**
|
|
917
1015
|
* Fold a `/usage` reading into this session's window map.
|
|
918
1016
|
*
|
|
@@ -936,6 +1034,23 @@ class ClaudeSession {
|
|
|
936
1034
|
absorbUsageReading(reading) {
|
|
937
1035
|
if (!reading || reading.rows.length === 0)
|
|
938
1036
|
return false;
|
|
1037
|
+
// Somebody else's figures (#422 S1 item 5): a reading signed by a different
|
|
1038
|
+
// subscription than this session's never lands in its windows. Unsigned on
|
|
1039
|
+
// either side is taken as before – that is every pre-#422 reading.
|
|
1040
|
+
const own = this.signatureOrgId();
|
|
1041
|
+
if (reading.orgId && own && reading.orgId !== own)
|
|
1042
|
+
return false;
|
|
1043
|
+
// A session that could not name its subscription takes the first signed
|
|
1044
|
+
// reading as its own – but only one of the subscription its home names NOW.
|
|
1045
|
+
// The machine's cache can still hold a reading from the home's previous
|
|
1046
|
+
// login (a terminal sign-in is not seen by `forgetClaudeUsage`), and fixing
|
|
1047
|
+
// that as the signature refused every fresh reading for the rest of the
|
|
1048
|
+
// session (found by the check of the second fix round of S2).
|
|
1049
|
+
if (reading.orgId && !own) {
|
|
1050
|
+
const homeNow = currentHomeOrgId(this.account.home);
|
|
1051
|
+
if (homeNow !== undefined && homeNow !== reading.orgId)
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
939
1054
|
const fresh = reading.measuredAtMs > this.usageAppliedAtMs;
|
|
940
1055
|
const before = this.rateLimitsFingerprint();
|
|
941
1056
|
// A percentage proves a plan as surely as the event does.
|
|
@@ -949,8 +1064,16 @@ class ClaudeSession {
|
|
|
949
1064
|
}
|
|
950
1065
|
if (fresh)
|
|
951
1066
|
this.usageAppliedAtMs = reading.measuredAtMs;
|
|
1067
|
+
if (reading.accountUuid)
|
|
1068
|
+
this.usageAccountUuid = reading.accountUuid;
|
|
1069
|
+
if (reading.orgId && !own)
|
|
1070
|
+
this.firstReadingOrgId = reading.orgId;
|
|
952
1071
|
return this.rateLimitsFingerprint() !== before;
|
|
953
1072
|
}
|
|
1073
|
+
/** The subscription this session signs with: captured at start, else its first signed reading. */
|
|
1074
|
+
signatureOrgId() {
|
|
1075
|
+
return this.account.orgId ?? this.firstReadingOrgId;
|
|
1076
|
+
}
|
|
954
1077
|
rateLimitsFingerprint() {
|
|
955
1078
|
return JSON.stringify([this.rateLimitsAvailable, [...this.rateLimitWindows.values()]]);
|
|
956
1079
|
}
|
|
@@ -961,9 +1084,20 @@ class ClaudeSession {
|
|
|
961
1084
|
return blocked;
|
|
962
1085
|
}
|
|
963
1086
|
emitRateLimits(blocked = null) {
|
|
1087
|
+
// Signed by THIS session's account (#422 §8, S2 item 4): the windows come
|
|
1088
|
+
// from its own stream and from readings measured under its own home. The
|
|
1089
|
+
// subscription is the one captured at start; a session that could not name
|
|
1090
|
+
// it borrows the reading's, which was measured in the very same home. A
|
|
1091
|
+
// session on the operator's token signs nothing: the home's file names the
|
|
1092
|
+
// file's login, not the token's (the constructor's note).
|
|
1093
|
+
const unsigned = this.onOperatorToken || this.homeSignedInElsewhere();
|
|
1094
|
+
const orgId = unsigned ? undefined : this.signatureOrgId();
|
|
1095
|
+
const accountUuid = unsigned ? undefined : this.usageAccountUuid;
|
|
964
1096
|
this.emit({
|
|
965
1097
|
type: 'rate_limits',
|
|
966
1098
|
limits: {
|
|
1099
|
+
...(accountUuid ? { accountUuid } : {}),
|
|
1100
|
+
...(orgId ? { orgId } : {}),
|
|
967
1101
|
blocked,
|
|
968
1102
|
available: this.rateLimitsAvailable,
|
|
969
1103
|
planType: this.ratePlanType,
|
|
@@ -1204,12 +1338,24 @@ class ClaudeSession {
|
|
|
1204
1338
|
currentMode: this.mode,
|
|
1205
1339
|
...(this.model ? { currentModel: this.model } : {}),
|
|
1206
1340
|
...(this.effort ? { currentEffort: this.effort } : {}),
|
|
1207
|
-
|
|
1341
|
+
// What the live CLI says first, what the runner last read about the home
|
|
1342
|
+
// second; plus WHICH of the machine's accounts this is (#422 §8), which
|
|
1343
|
+
// only the runner knows. No block at all when neither side knows anything
|
|
1344
|
+
// – exactly as before.
|
|
1345
|
+
...(account || this.account.email
|
|
1208
1346
|
? {
|
|
1209
1347
|
account: {
|
|
1210
|
-
|
|
1211
|
-
...(account
|
|
1212
|
-
|
|
1348
|
+
id: this.account.id,
|
|
1349
|
+
...((account?.email ?? this.account.email)
|
|
1350
|
+
? { email: account?.email ?? this.account.email }
|
|
1351
|
+
: {}),
|
|
1352
|
+
...((account?.organization ?? this.account.orgName)
|
|
1353
|
+
? { organization: account?.organization ?? this.account.orgName }
|
|
1354
|
+
: {}),
|
|
1355
|
+
...((account?.subscriptionType ?? this.account.plan)
|
|
1356
|
+
? { plan: account?.subscriptionType ?? this.account.plan }
|
|
1357
|
+
: {}),
|
|
1358
|
+
...(this.account.orgId ? { orgId: this.account.orgId } : {}),
|
|
1213
1359
|
},
|
|
1214
1360
|
}
|
|
1215
1361
|
: {}),
|
|
@@ -2502,6 +2648,7 @@ class ClaudeSession {
|
|
|
2502
2648
|
// the path that catches a session which died before the CLI ever
|
|
2503
2649
|
// answered.
|
|
2504
2650
|
this.removeMcpConfig();
|
|
2651
|
+
releaseSessionAccount(this.spec.sessionId);
|
|
2505
2652
|
this.stopped = true;
|
|
2506
2653
|
this.input.end();
|
|
2507
2654
|
this.output.end();
|
|
@@ -2735,7 +2882,20 @@ export class ClaudeAdapter {
|
|
|
2735
2882
|
*/
|
|
2736
2883
|
if (this.queryFn === query)
|
|
2737
2884
|
assertClaudeInstalled();
|
|
2738
|
-
|
|
2885
|
+
const session = new ClaudeSession(spec, this.queryFn);
|
|
2886
|
+
/**
|
|
2887
|
+
* Who the home is, asked once when nobody knows or the home says it changed
|
|
2888
|
+
* (#422 R14 c) – in the background, after the session is on its way. The
|
|
2889
|
+
* next session and the account list get the answer; this one keeps what it
|
|
2890
|
+
* started with.
|
|
2891
|
+
*
|
|
2892
|
+
* Only for the real SDK, for the reason above: an injected `query` is a test,
|
|
2893
|
+
* and a test must not run `claude auth status` against this machine's login.
|
|
2894
|
+
*/
|
|
2895
|
+
if (this.queryFn === query) {
|
|
2896
|
+
void refreshAccountIdentity(session.accountId).catch(() => undefined);
|
|
2897
|
+
}
|
|
2898
|
+
return session;
|
|
2739
2899
|
}
|
|
2740
2900
|
}
|
|
2741
2901
|
//# sourceMappingURL=claude.js.map
|
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,83 @@
|
|
|
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
|
+
/** What the runner remembers about the machine row itself – it has no record of its own. */
|
|
33
|
+
export interface ClaudeMachineRecord {
|
|
34
|
+
lastSeenIdentity?: ClaudeIdentity;
|
|
35
|
+
/**
|
|
36
|
+
* A refusal held against the token this runner captured (`claudeOauthToken`)
|
|
37
|
+
* – set only when the refused session actually ran with that token. It
|
|
38
|
+
* replaces the old «discard the token after a refusal», which erased the whole
|
|
39
|
+
* file and with it every saved account (#422 S1 item 3).
|
|
40
|
+
*/
|
|
41
|
+
loginExpiredAt?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentAuthFile {
|
|
44
|
+
claudeOauthToken?: string;
|
|
45
|
+
/**
|
|
46
|
+
* When `claudeOauthToken` was stored – the token's age, and ONLY that. Every
|
|
47
|
+
* other write leaves it alone, or saving an account would make a
|
|
48
|
+
* months-old token look freshly captured.
|
|
49
|
+
*/
|
|
50
|
+
updatedAt?: string;
|
|
51
|
+
claudeAccounts?: ClaudeAccountRecord[];
|
|
52
|
+
/** Absent means the machine login – the pre-#422 file reads exactly as before. */
|
|
53
|
+
claudeActiveAccount?: string;
|
|
54
|
+
claudeMachine?: ClaudeMachineRecord;
|
|
55
|
+
}
|
|
56
|
+
/** Everything this runner keeps in `agent-auth.json`, read forgivingly. */
|
|
57
|
+
export declare function readAgentAuth(): AgentAuthFile;
|
|
58
|
+
/**
|
|
59
|
+
* Change some fields of the file and keep every other one.
|
|
60
|
+
*
|
|
61
|
+
* Read → change → write, never «write what I know». The two writers this file
|
|
62
|
+
* had before #422 wrote the WHOLE file (`storeClaudeToken`) or deleted it
|
|
63
|
+
* (`clearStoredClaudeToken`), and with a list of accounts beside the token either
|
|
64
|
+
* would have erased every saved login's record on its way past (S1 item 3).
|
|
65
|
+
*
|
|
66
|
+
* A file that exists but cannot be parsed is moved aside, not overwritten: it may
|
|
67
|
+
* be the only record of which home belongs to which account, and «unreadable»
|
|
68
|
+
* must not quietly become «empty». A file that cannot be READ (EACCES) throws –
|
|
69
|
+
* writing over something we could not look at is the same mistake.
|
|
70
|
+
*
|
|
71
|
+
* Synchronous from read to rename, so two changes inside this process cannot
|
|
72
|
+
* interleave. Same write-then-rename as the config, 0600.
|
|
73
|
+
*/
|
|
74
|
+
export declare function updateAgentAuth(change: (file: AgentAuthFile) => void): AgentAuthFile;
|
|
2
75
|
/** The token we hold for Claude, or null once it is too old to trust. */
|
|
3
76
|
export declare function storedClaudeToken(): string | null;
|
|
77
|
+
/** When the stored token was captured, in ms – null without one. */
|
|
78
|
+
export declare function storedClaudeTokenAtMs(): number | null;
|
|
4
79
|
export declare function storeClaudeToken(token: string): void;
|
|
80
|
+
/** Drop the stored token – and nothing else: the accounts beside it stay (#422). */
|
|
5
81
|
export declare function clearStoredClaudeToken(): void;
|
|
6
82
|
/**
|
|
7
83
|
* Put a stored token into this process's environment, unless the operator
|
|
@@ -15,6 +91,17 @@ export declare function clearStoredClaudeToken(): void;
|
|
|
15
91
|
* Returns true when it applied one.
|
|
16
92
|
*/
|
|
17
93
|
export declare function applyStoredClaudeToken(): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Was a session refused with the token this runner captured – after it was
|
|
96
|
+
* captured? A token stored after the refusal is newer evidence and outranks it.
|
|
97
|
+
* A token with no readable capture date is judged by the mark alone.
|
|
98
|
+
*/
|
|
99
|
+
export declare function storedClaudeTokenRefused(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* The daemon's environment carries exactly the token this runner captured – so
|
|
102
|
+
* a machine-login session refused now was refused WITH it.
|
|
103
|
+
*/
|
|
104
|
+
export declare function environmentCarriesStoredToken(): boolean;
|
|
18
105
|
/**
|
|
19
106
|
* The OAuth token `claude setup-token` printed, reassembled out of pty output.
|
|
20
107
|
*
|