@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/index.js CHANGED
@@ -7,14 +7,17 @@ import path from 'node:path';
7
7
  import { promisify } from 'node:util';
8
8
  import { ClaudeAdapter } from './adapters/claude.js';
9
9
  import { CodexAdapter } from './adapters/codex.js';
10
- import { ensureCodexHome } from './adapters/codex-home.js';
10
+ import { configureCodexAuth, discardAbandonedCodexStagingHomes, ensureCodexHome, } from './adapters/codex-home.js';
11
11
  import { sessionClaudePath } from './agent-binary.js';
12
12
  import { claimCageAuthority, runSystemctl } from './cage-authority.js';
13
13
  import { acquireDaemonLock, isHeldByAnother } from './daemon-lock.js';
14
- import { loadConfig, mergeIntoPairedConfig, requireConfig, saveConfig, } from './config.js';
14
+ import { loadConfig, mergeIntoPairedConfig, requireConfig, saveConfig } from './config.js';
15
15
  import { log } from './log.js';
16
16
  import { installIsWritable, installPrefixFor, isSupervisedProcess, restartCapability, manualUpdateCommand, resolveInstalledPackageDir, } from './self-update.js';
17
17
  import { applyStoredClaudeToken } from './agent-auth.js';
18
+ import { discardAbandonedStagingHomes, recoverInterruptedSwaps, refreshAccountIdentity, } from './claude-homes.js';
19
+ import { ACCOUNT_AGENTS, ACCOUNT_COMMANDS } from './account-commands.js';
20
+ import { MACHINE_ACCOUNT_ID } from './login-marks.js';
18
21
  import { Supervisor } from './supervisor.js';
19
22
  import { readStatusFile, isPidAlive, writeStatusFile, STATUS_FRESH_MS } from './status-file.js';
20
23
  import { RunnerWsClient } from './ws-client.js';
@@ -515,6 +518,13 @@ function runnerCapabilities(apiUrlOverride) {
515
518
  * `kind: 'oom'` is inferred from the cgroup counter — see `crash-note.ts`.
516
519
  */
517
520
  ...(LAST_EXIT ? { lastExit: LAST_EXIT } : {}),
521
+ /**
522
+ * #422 (R11): the agents this runner keeps several logins for. Static per
523
+ * version – the LIST of accounts is always asked with `agent_accounts`,
524
+ * never read from here (a snapshot taken at start, #395). The dashboard
525
+ * shows the account window only to an agent named here.
526
+ */
527
+ accounts: [...ACCOUNT_AGENTS],
518
528
  /** Commands beyond the stage-A/B set. */
519
529
  commands: [
520
530
  'purge_session',
@@ -578,6 +588,10 @@ function runnerCapabilities(apiUrlOverride) {
578
588
  // ANSWERS the command, with a sentence saying why — which is a better
579
589
  // outcome than a frame nobody replies to.
580
590
  'runner_restart',
591
+ // #422: list, sign in to, switch and forget agent logins (plan §8).
592
+ // Unconditional: they read and write only the runner's own state, and an
593
+ // agent they do not serve yet is answered with a sentence.
594
+ ...ACCOUNT_COMMANDS,
581
595
  ],
582
596
  };
583
597
  }
@@ -633,15 +647,19 @@ async function cmdPair(args) {
633
647
  * per-session: a fresh home clones ~90 MB of plugin marketplace on first use.
634
648
  * A failure here disables Codex rather than the whole daemon.
635
649
  */
636
- function bootstrapCodex(config) {
650
+ function bootstrapCodex() {
637
651
  try {
638
- const home = ensureCodexHome({ auth: config.codex?.auth ?? 'link' });
639
- log.info('codex: isolated home ready', { path: home.path, auth: home.auth });
652
+ const home = ensureCodexHome();
653
+ log.info('codex: isolated home ready', {
654
+ path: home.path,
655
+ auth: home.auth,
656
+ ...(home.accountId ? { account: home.accountId } : {}),
657
+ });
640
658
  // Deliberately NOT passing `codexHome`: the adapter re-asserts the home on
641
659
  // every session. Freezing this snapshot is how a credential that went
642
660
  // missing mid-day kept being reported as present while sessions failed.
643
- // The mode does travel, so those repairs honour `[codex] auth = "own"`.
644
- return new CodexAdapter({ authMode: config.codex?.auth ?? 'link' });
661
+ // Nor the mode: those repairs read it from `configureCodexAuth`.
662
+ return new CodexAdapter();
645
663
  }
646
664
  catch (error) {
647
665
  log.error('codex: could not prepare an isolated home — Codex disabled', {
@@ -955,9 +973,43 @@ async function cmdDaemon() {
955
973
  if (applyStoredClaudeToken()) {
956
974
  log.info('daemon: using the Claude token stored on this server');
957
975
  }
976
+ /**
977
+ * #422: a swap of Claude homes the previous daemon died inside is finished or
978
+ * undone before anything can run under an account; and who the machine login
979
+ * is gets asked once, in the background (R14 c) – the verdict and the list read
980
+ * the answer, the minute poll never asks.
981
+ */
982
+ try {
983
+ recoverInterruptedSwaps();
984
+ }
985
+ catch (error) {
986
+ log.warn('daemon: could not finish an interrupted swap of Claude homes', {
987
+ error: String(error),
988
+ });
989
+ }
990
+ try {
991
+ // A sign-in the previous daemon accepted but never adopted (S2): nothing can
992
+ // be adopting before the supervisor exists. Codex sign-ins too (S4) – and
993
+ // the fixed-path staging home runners before S4 never removed.
994
+ discardAbandonedStagingHomes();
995
+ discardAbandonedCodexStagingHomes();
996
+ }
997
+ catch (error) {
998
+ log.warn('daemon: could not remove sign-in homes a previous daemon left', {
999
+ error: String(error),
1000
+ });
1001
+ }
1002
+ void refreshAccountIdentity(MACHINE_ACCOUNT_ID).catch(() => undefined);
1003
+ // Only a mode the owner WROTE is ever forced, and never over a saved login
1004
+ // (#422 S4 item 2): the defaults that used to be passed around re-linked the
1005
+ // host login at every start, session and probe. Recorded whatever is on PATH:
1006
+ // Codex may be installed from the dashboard later, and the account commands and
1007
+ // the minute probe read this from the first moment (found by the independent
1008
+ // check of S4).
1009
+ configureCodexAuth(config.codex?.auth);
958
1010
  const agents = installedAgents();
959
1011
  const ws = new RunnerWsClient(config.api.ws_url, config.server.token, runnerCapabilities());
960
- const codex = agents.includes('codex') ? bootstrapCodex(config) : null;
1012
+ const codex = agents.includes('codex') ? bootstrapCodex() : null;
961
1013
  const supervisor = new Supervisor(ws, {
962
1014
  adapters: {
963
1015
  CLAUDE: new ClaudeAdapter(),
@@ -976,7 +1028,7 @@ async function cmdDaemon() {
976
1028
  * `hasExecutable` is asked HERE, at the moment of the question, which is
977
1029
  * the whole point.
978
1030
  */
979
- makeAdapter: (agent) => agent === 'CODEX' && hasExecutable('codex') ? bootstrapCodex(config) : null,
1031
+ makeAdapter: (agent) => (agent === 'CODEX' && hasExecutable('codex') ? bootstrapCodex() : null),
980
1032
  ...(config.mcp ? { mcp: { url: config.mcp.url, token: config.mcp.token } } : {}),
981
1033
  ...(config.limits?.max_sessions ? { maxSessionsLimit: config.limits.max_sessions } : {}),
982
1034
  /**
@@ -1379,6 +1431,15 @@ async function runnerChecks() {
1379
1431
  });
1380
1432
  return checks;
1381
1433
  }
1434
+ /** « · account <email> (saved)» – nothing for the machine login nobody named. */
1435
+ function accountLine(account) {
1436
+ if (!account)
1437
+ return '';
1438
+ if (account.id === MACHINE_ACCOUNT_ID) {
1439
+ return account.email ? ` · machine login ${account.email}` : '';
1440
+ }
1441
+ return ` · saved account ${account.email ?? account.id}`;
1442
+ }
1382
1443
  async function agentChecks() {
1383
1444
  const me = runnerIdentity();
1384
1445
  // The probes log a verdict line each, which belongs in the journal, not in
@@ -1440,8 +1501,12 @@ async function agentChecks() {
1440
1501
  checks.push({
1441
1502
  ok: signedIn,
1442
1503
  name: `${agent} login`,
1504
+ // Which account the verdict is about (#422): the same rule as the panel,
1505
+ // read from the same file, so doctor and the dashboard cannot answer about
1506
+ // two different logins on one machine.
1443
1507
  detail: (info.detail ?? info.status) +
1444
- (info.expiresAt ? ` · until ${formatLocalDay(info.expiresAt)}` : ''),
1508
+ (info.expiresAt ? ` · until ${formatLocalDay(info.expiresAt)}` : '') +
1509
+ accountLine(info.activeAccount),
1445
1510
  ...(signedIn
1446
1511
  ? {}
1447
1512
  : {
@@ -1621,6 +1686,9 @@ async function cmdDoctor(args) {
1621
1686
  // reloads systemd, which is the `install` grade and nothing above it (#403).
1622
1687
  claimCageAuthority(fix ? 'install' : 'probe');
1623
1688
  const config = loadConfig();
1689
+ // The same Codex mode the daemon honours, or doctor's verdict would repair the
1690
+ // home by a rule the daemon does not follow (#422 S4 item 2).
1691
+ configureCodexAuth(config?.codex?.auth);
1624
1692
  print(`devbridge-runner ${RUNNER_VERSION}`);
1625
1693
  print(config ? `Paired with: ${config.server.name} (${config.api.url})` : 'Not paired');
1626
1694
  // Paths given on the command line win; otherwise check what this runner has
@@ -0,0 +1,42 @@
1
+ import type { RelayAgentName } from './agent-registry.js';
2
+ /**
3
+ * What the agent actually experienced with a sign-in — per agent AND per account.
4
+ *
5
+ * The credentials file cannot tell us the provider revoked a login: the refresh
6
+ * token still sits there, dated. The one authority on that is a real refusal
7
+ * from a real run — so a session that failed to authenticate demotes the file's
8
+ * verdict for a while, and any successful turn clears it again.
9
+ *
10
+ * Moved out of `auth-relay.ts` for #422 and keyed by account as well as agent.
11
+ * With several Claude logins on one machine «Claude was refused» stopped being
12
+ * a statement about anything: the session that was refused ran under ONE
13
+ * account, and the machine may have been switched to another since. The mark
14
+ * belongs to the account the session started under (R14) – which is why both
15
+ * the account list (`claude-homes.ts`) and the panel verdict (`auth-relay.ts`)
16
+ * read it from here rather than each keeping a copy.
17
+ *
18
+ * Process memory on purpose: no protocol, no disk. The consequence is worth
19
+ * naming — restarting the daemon forgets the refusal and the panel goes back
20
+ * to trusting the file until the next session tries. (A saved account also
21
+ * carries a mark on disk, D2 – see `markLoginExpired` in `claude-homes.ts`.)
22
+ */
23
+ /** The machine's own login – the one row every agent has. */
24
+ export declare const MACHINE_ACCOUNT_ID = "machine";
25
+ /**
26
+ * A saved account's id – twelve lowercase letters and digits, never `machine`.
27
+ * One format for both agents (#422 S4): the wire, the refusal marks and the
28
+ * dashboard read the same alphabet whichever agent a row belongs to.
29
+ */
30
+ export declare function isAccountId(id: unknown): id is string;
31
+ /** A refusal meant for a person: the wire (S2) sends its message as it is, and it names no path. */
32
+ export declare class AccountError extends Error {
33
+ constructor(message: string);
34
+ }
35
+ export declare function newAccountId(): string;
36
+ /** A session under this agent and account was just refused. */
37
+ export declare function noteRefusal(agent: RelayAgentName, accountId?: string): void;
38
+ /** The sign-in worked again. Answers whether a refusal was being held. */
39
+ export declare function clearRefusal(agent: RelayAgentName, accountId?: string): boolean;
40
+ /** Is a refusal still being held against this agent and account? */
41
+ export declare function refusalActive(agent: RelayAgentName, accountId?: string): boolean;
42
+ //# sourceMappingURL=login-marks.d.ts.map
@@ -0,0 +1,70 @@
1
+ import crypto from 'node:crypto';
2
+ /**
3
+ * What the agent actually experienced with a sign-in — per agent AND per account.
4
+ *
5
+ * The credentials file cannot tell us the provider revoked a login: the refresh
6
+ * token still sits there, dated. The one authority on that is a real refusal
7
+ * from a real run — so a session that failed to authenticate demotes the file's
8
+ * verdict for a while, and any successful turn clears it again.
9
+ *
10
+ * Moved out of `auth-relay.ts` for #422 and keyed by account as well as agent.
11
+ * With several Claude logins on one machine «Claude was refused» stopped being
12
+ * a statement about anything: the session that was refused ran under ONE
13
+ * account, and the machine may have been switched to another since. The mark
14
+ * belongs to the account the session started under (R14) – which is why both
15
+ * the account list (`claude-homes.ts`) and the panel verdict (`auth-relay.ts`)
16
+ * read it from here rather than each keeping a copy.
17
+ *
18
+ * Process memory on purpose: no protocol, no disk. The consequence is worth
19
+ * naming — restarting the daemon forgets the refusal and the panel goes back
20
+ * to trusting the file until the next session tries. (A saved account also
21
+ * carries a mark on disk, D2 – see `markLoginExpired` in `claude-homes.ts`.)
22
+ */
23
+ /** The machine's own login – the one row every agent has. */
24
+ export const MACHINE_ACCOUNT_ID = 'machine';
25
+ const ACCOUNT_ID = /^[a-z0-9]{12}$/;
26
+ /**
27
+ * A saved account's id – twelve lowercase letters and digits, never `machine`.
28
+ * One format for both agents (#422 S4): the wire, the refusal marks and the
29
+ * dashboard read the same alphabet whichever agent a row belongs to.
30
+ */
31
+ export function isAccountId(id) {
32
+ return typeof id === 'string' && ACCOUNT_ID.test(id);
33
+ }
34
+ /** A refusal meant for a person: the wire (S2) sends its message as it is, and it names no path. */
35
+ export class AccountError extends Error {
36
+ constructor(message) {
37
+ super(message);
38
+ this.name = 'AccountError';
39
+ }
40
+ }
41
+ export function newAccountId() {
42
+ // base32-ish from random bytes: 12 characters of [a-z0-9].
43
+ const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
44
+ const bytes = crypto.randomBytes(12);
45
+ return Array.from(bytes, (byte) => alphabet[byte % alphabet.length]).join('');
46
+ }
47
+ const AUTH_FAILURE_TTL_MS = 15 * 60_000;
48
+ const refusals = new Map();
49
+ const keyOf = (agent, accountId) => `${agent}:${accountId}`;
50
+ /** A session under this agent and account was just refused. */
51
+ export function noteRefusal(agent, accountId = MACHINE_ACCOUNT_ID) {
52
+ refusals.set(keyOf(agent, accountId), Date.now());
53
+ }
54
+ /** The sign-in worked again. Answers whether a refusal was being held. */
55
+ export function clearRefusal(agent, accountId = MACHINE_ACCOUNT_ID) {
56
+ return refusals.delete(keyOf(agent, accountId));
57
+ }
58
+ /** Is a refusal still being held against this agent and account? */
59
+ export function refusalActive(agent, accountId = MACHINE_ACCOUNT_ID) {
60
+ const key = keyOf(agent, accountId);
61
+ const at = refusals.get(key);
62
+ if (at === undefined)
63
+ return false;
64
+ if (Date.now() - at > AUTH_FAILURE_TTL_MS) {
65
+ refusals.delete(key);
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+ //# sourceMappingURL=login-marks.js.map
@@ -235,11 +235,11 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
235
235
  token: string;
236
236
  }>>;
237
237
  }, "strip", z.ZodTypeAny, {
238
- mode: "ask" | "plan" | "auto" | "full";
239
- agent: "CLAUDE" | "CODEX";
240
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
238
+ mode: "plan" | "ask" | "auto" | "full";
241
239
  id: string;
240
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
242
241
  kind: "TICKET" | "CHAT";
242
+ agent: "CLAUDE" | "CODEX";
243
243
  model: string | null;
244
244
  effort: string | null;
245
245
  prompt: string;
@@ -286,10 +286,10 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
286
286
  baseSha?: string | undefined;
287
287
  } | undefined;
288
288
  }, {
289
- agent: "CLAUDE" | "CODEX";
290
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
291
289
  id: string;
290
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
292
291
  kind: "TICKET" | "CHAT";
292
+ agent: "CLAUDE" | "CODEX";
293
293
  prompt: string;
294
294
  providerSessionId: string | null;
295
295
  workspace: {
@@ -316,7 +316,7 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
316
316
  url: string;
317
317
  token: string;
318
318
  } | undefined;
319
- mode?: "ask" | "plan" | "auto" | "full" | undefined;
319
+ mode?: "plan" | "ask" | "auto" | "full" | undefined;
320
320
  workMode?: unknown;
321
321
  model?: string | null | undefined;
322
322
  effort?: string | null | undefined;
@@ -385,8 +385,8 @@ export declare const QuestionAnswerArgsSchema: z.ZodObject<{
385
385
  }>, "many">>;
386
386
  readonly text: z.ZodOptional<z.ZodString>;
387
387
  }, "strip", z.ZodTypeAny, {
388
- askId: string;
389
388
  action: "answer" | "discuss";
389
+ askId: string;
390
390
  text?: string | undefined;
391
391
  answers?: {
392
392
  values: string[];
@@ -395,8 +395,8 @@ export declare const QuestionAnswerArgsSchema: z.ZodObject<{
395
395
  notes?: string | undefined;
396
396
  }[] | undefined;
397
397
  }, {
398
- askId: string;
399
398
  action: "answer" | "discuss";
399
+ askId: string;
400
400
  text?: string | undefined;
401
401
  answers?: {
402
402
  values: string[];
@@ -685,11 +685,11 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
685
685
  token: string;
686
686
  }>>;
687
687
  }, "strip", z.ZodTypeAny, {
688
- mode: "ask" | "plan" | "auto" | "full";
689
- agent: "CLAUDE" | "CODEX";
690
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
688
+ mode: "plan" | "ask" | "auto" | "full";
691
689
  id: string;
690
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
692
691
  kind: "TICKET" | "CHAT";
692
+ agent: "CLAUDE" | "CODEX";
693
693
  model: string | null;
694
694
  effort: string | null;
695
695
  prompt: string;
@@ -736,10 +736,10 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
736
736
  baseSha?: string | undefined;
737
737
  } | undefined;
738
738
  }, {
739
- agent: "CLAUDE" | "CODEX";
740
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
741
739
  id: string;
740
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
742
741
  kind: "TICKET" | "CHAT";
742
+ agent: "CLAUDE" | "CODEX";
743
743
  prompt: string;
744
744
  providerSessionId: string | null;
745
745
  workspace: {
@@ -766,7 +766,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
766
766
  url: string;
767
767
  token: string;
768
768
  } | undefined;
769
- mode?: "ask" | "plan" | "auto" | "full" | undefined;
769
+ mode?: "plan" | "ask" | "auto" | "full" | undefined;
770
770
  workMode?: unknown;
771
771
  model?: string | null | undefined;
772
772
  effort?: string | null | undefined;
@@ -785,11 +785,11 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
785
785
  }, "strip", z.ZodTypeAny, {
786
786
  type: "hello_ack";
787
787
  sessions: {
788
- mode: "ask" | "plan" | "auto" | "full";
789
- agent: "CLAUDE" | "CODEX";
790
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
788
+ mode: "plan" | "ask" | "auto" | "full";
791
789
  id: string;
790
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
792
791
  kind: "TICKET" | "CHAT";
792
+ agent: "CLAUDE" | "CODEX";
793
793
  model: string | null;
794
794
  effort: string | null;
795
795
  prompt: string;
@@ -842,10 +842,10 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
842
842
  }, {
843
843
  type: "hello_ack";
844
844
  sessions: {
845
- agent: "CLAUDE" | "CODEX";
846
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
847
845
  id: string;
846
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
848
847
  kind: "TICKET" | "CHAT";
848
+ agent: "CLAUDE" | "CODEX";
849
849
  prompt: string;
850
850
  providerSessionId: string | null;
851
851
  workspace: {
@@ -872,7 +872,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
872
872
  url: string;
873
873
  token: string;
874
874
  } | undefined;
875
- mode?: "ask" | "plan" | "auto" | "full" | undefined;
875
+ mode?: "plan" | "ask" | "auto" | "full" | undefined;
876
876
  workMode?: unknown;
877
877
  model?: string | null | undefined;
878
878
  effort?: string | null | undefined;
@@ -1154,11 +1154,11 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1154
1154
  token: string;
1155
1155
  }>>;
1156
1156
  }, "strip", z.ZodTypeAny, {
1157
- mode: "ask" | "plan" | "auto" | "full";
1158
- agent: "CLAUDE" | "CODEX";
1159
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1157
+ mode: "plan" | "ask" | "auto" | "full";
1160
1158
  id: string;
1159
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1161
1160
  kind: "TICKET" | "CHAT";
1161
+ agent: "CLAUDE" | "CODEX";
1162
1162
  model: string | null;
1163
1163
  effort: string | null;
1164
1164
  prompt: string;
@@ -1205,10 +1205,10 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1205
1205
  baseSha?: string | undefined;
1206
1206
  } | undefined;
1207
1207
  }, {
1208
- agent: "CLAUDE" | "CODEX";
1209
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1210
1208
  id: string;
1209
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1211
1210
  kind: "TICKET" | "CHAT";
1211
+ agent: "CLAUDE" | "CODEX";
1212
1212
  prompt: string;
1213
1213
  providerSessionId: string | null;
1214
1214
  workspace: {
@@ -1235,7 +1235,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1235
1235
  url: string;
1236
1236
  token: string;
1237
1237
  } | undefined;
1238
- mode?: "ask" | "plan" | "auto" | "full" | undefined;
1238
+ mode?: "plan" | "ask" | "auto" | "full" | undefined;
1239
1239
  workMode?: unknown;
1240
1240
  model?: string | null | undefined;
1241
1241
  effort?: string | null | undefined;
@@ -1253,11 +1253,11 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1253
1253
  }>;
1254
1254
  }, "strip", z.ZodTypeAny, {
1255
1255
  session: {
1256
- mode: "ask" | "plan" | "auto" | "full";
1257
- agent: "CLAUDE" | "CODEX";
1258
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1256
+ mode: "plan" | "ask" | "auto" | "full";
1259
1257
  id: string;
1258
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1260
1259
  kind: "TICKET" | "CHAT";
1260
+ agent: "CLAUDE" | "CODEX";
1261
1261
  model: string | null;
1262
1262
  effort: string | null;
1263
1263
  prompt: string;
@@ -1307,10 +1307,10 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1307
1307
  type: "session_start";
1308
1308
  }, {
1309
1309
  session: {
1310
- agent: "CLAUDE" | "CODEX";
1311
- status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1312
1310
  id: string;
1311
+ status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1313
1312
  kind: "TICKET" | "CHAT";
1313
+ agent: "CLAUDE" | "CODEX";
1314
1314
  prompt: string;
1315
1315
  providerSessionId: string | null;
1316
1316
  workspace: {
@@ -1337,7 +1337,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1337
1337
  url: string;
1338
1338
  token: string;
1339
1339
  } | undefined;
1340
- mode?: "ask" | "plan" | "auto" | "full" | undefined;
1340
+ mode?: "plan" | "ask" | "auto" | "full" | undefined;
1341
1341
  workMode?: unknown;
1342
1342
  model?: string | null | undefined;
1343
1343
  effort?: string | null | undefined;
@@ -1440,8 +1440,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1440
1440
  }, "strip", z.ZodTypeAny, {
1441
1441
  sessionId: string;
1442
1442
  type: "question_answer";
1443
- askId: string;
1444
1443
  action: "answer" | "discuss";
1444
+ askId: string;
1445
1445
  text?: string | undefined;
1446
1446
  answers?: {
1447
1447
  values: string[];
@@ -1452,8 +1452,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1452
1452
  }, {
1453
1453
  sessionId: string;
1454
1454
  type: "question_answer";
1455
- askId: string;
1456
1455
  action: "answer" | "discuss";
1456
+ askId: string;
1457
1457
  text?: string | undefined;
1458
1458
  answers?: {
1459
1459
  values: string[];
@@ -1513,8 +1513,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1513
1513
  }, "strip", z.ZodTypeAny, {
1514
1514
  type: "workspace_settings";
1515
1515
  workspaceId: string;
1516
- agentAutoCommit?: boolean | undefined;
1517
1516
  trustMode?: "STRICT" | "NORMAL" | "AUTO" | undefined;
1517
+ agentAutoCommit?: boolean | undefined;
1518
1518
  agentPushBan?: boolean | undefined;
1519
1519
  agentProtectedBranches?: string[] | undefined;
1520
1520
  agentAllowForcePush?: boolean | undefined;
@@ -1523,8 +1523,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1523
1523
  }, {
1524
1524
  type: "workspace_settings";
1525
1525
  workspaceId: string;
1526
- agentAutoCommit?: boolean | undefined;
1527
1526
  trustMode?: "STRICT" | "NORMAL" | "AUTO" | undefined;
1527
+ agentAutoCommit?: boolean | undefined;
1528
1528
  agentPushBan?: boolean | undefined;
1529
1529
  agentProtectedBranches?: string[] | undefined;
1530
1530
  agentAllowForcePush?: boolean | undefined;
@@ -1539,13 +1539,13 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1539
1539
  }, "strip", z.ZodTypeAny, {
1540
1540
  sessionId: string;
1541
1541
  type: "session_settings";
1542
- mode?: "ask" | "plan" | "auto" | "full" | undefined;
1542
+ mode?: "plan" | "ask" | "auto" | "full" | undefined;
1543
1543
  model?: string | undefined;
1544
1544
  effort?: string | null | undefined;
1545
1545
  }, {
1546
1546
  sessionId: string;
1547
1547
  type: "session_settings";
1548
- mode?: "ask" | "plan" | "auto" | "full" | undefined;
1548
+ mode?: "plan" | "ask" | "auto" | "full" | undefined;
1549
1549
  model?: string | undefined;
1550
1550
  effort?: string | null | undefined;
1551
1551
  }>, z.ZodObject<{
package/dist/protocol.js CHANGED
@@ -430,7 +430,9 @@ export const GatewayFrameSchema = z.discriminatedUnion('type', [
430
430
  // git_branches, update_from_base, git_push, git_refs, workspace_state,
431
431
  // recipe_state, verify_start, verify_status, verify_cancel,
432
432
  // preview_checkout, preview_stop, propose_commit_message, git_stage,
433
- // git_unstage, git_discard, git_pull, git_merge_abort, fs_publish.
433
+ // git_unstage, git_discard, git_pull, git_merge_abort, fs_publish,
434
+ // agent_accounts, agent_account_login_start, agent_account_login_code,
435
+ // agent_account_activate, agent_account_forget (#422).
434
436
  name: z.string().min(1).max(64),
435
437
  workspaceId: z.string().optional(),
436
438
  sessionId: z.string().optional(),
@@ -19,6 +19,7 @@ import { applySession, gitBranches, gitCommit, gitDiff, gitLog, gitPush, gitRefs
19
19
  import { fsView } from './fsview.js';
20
20
  import { publishFile } from './file-publish.js';
21
21
  import { agentAuthStatuses, AuthRelay, clearAgentAuthFailure, noteAgentAuthFailure, } from './auth-relay.js';
22
+ import { isAccountCommand, runAccountCommand } from './account-commands.js';
22
23
  import { isSupervisedProcess, selfUpdate } from './self-update.js';
23
24
  import { installAgent } from './agent-install.js';
24
25
  import { autoUpdateRefusal, claimAgentAutoUpdate } from './agent-auto-update.js';
@@ -2702,7 +2703,7 @@ export class Supervisor {
2702
2703
  const { descriptor } = running;
2703
2704
  try {
2704
2705
  for await (const event of session.events) {
2705
- this.forwardEvent(running, event);
2706
+ this.forwardEvent(running, event, session);
2706
2707
  }
2707
2708
  }
2708
2709
  catch (error) {
@@ -3618,7 +3619,14 @@ export class Supervisor {
3618
3619
  this.syncBudgetClock(running);
3619
3620
  this.reportStatus(running.descriptor.id, 'RUNNING', {});
3620
3621
  }
3621
- forwardEvent(running, event) {
3622
+ forwardEvent(running, event,
3623
+ /**
3624
+ * The process this event came from (#422). A refusal and a working turn are
3625
+ * attributed to the account THAT process started under – read off the
3626
+ * session that produced the event, not off whatever holds the slot by the
3627
+ * time the event is handled.
3628
+ */
3629
+ source = running.session) {
3622
3630
  const { descriptor } = running;
3623
3631
  // Ticket #225: ANY event is the process proving it came up — its own
3624
3632
  // capabilities probe answers within about two seconds of a healthy launch,
@@ -3812,7 +3820,7 @@ export class Supervisor {
3812
3820
  if (event.code === 'auth_expired') {
3813
3821
  const refused = relayAgent(String(descriptor.agent).toLowerCase());
3814
3822
  if (refused)
3815
- noteAgentAuthFailure(refused);
3823
+ noteAgentAuthFailure(refused, source?.accountId);
3816
3824
  }
3817
3825
  // Only `auth_missing` is worth a relaunch, and the distinction is the
3818
3826
  // adapter's own (`probeAuth`), not a guess from prose.
@@ -3917,7 +3925,7 @@ export class Supervisor {
3917
3925
  if (event.role === 'assistant') {
3918
3926
  const working = relayAgent(String(descriptor.agent).toLowerCase());
3919
3927
  if (working)
3920
- clearAgentAuthFailure(working);
3928
+ clearAgentAuthFailure(working, source?.accountId);
3921
3929
  }
3922
3930
  this.sendEvent(running, 'message', { role: event.role, text: event.text });
3923
3931
  return;
@@ -7483,6 +7491,11 @@ export class Supervisor {
7483
7491
  });
7484
7492
  }
7485
7493
  default:
7494
+ // #422: the five account commands – their own module, one door each
7495
+ // over `claude-homes.ts` (plan §8).
7496
+ if (isAccountCommand(frame.name)) {
7497
+ return void reply(await runAccountCommand(frame.name, frame.args ?? {}, this.authRelay));
7498
+ }
7486
7499
  return void reply({ ok: false, error: `Unknown command` });
7487
7500
  }
7488
7501
  }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const RUNNER_VERSION = "0.65.1";
1
+ export declare const RUNNER_VERSION = "0.67.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in sync with package.json by the release script (manual for now).
2
- export const RUNNER_VERSION = '0.65.1';
2
+ export const RUNNER_VERSION = '0.67.0';
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge4dev/runner",
3
- "version": "0.65.1",
3
+ "version": "0.67.0",
4
4
  "description": "DevBridge dev runner — connects a dev server to DevBridge and runs agent sessions (Claude Code / Codex)",
5
5
  "homepage": "https://bridge4.dev",
6
6
  "license": "MIT",