@velum-labs/routekit-daemon 0.16.1 → 0.16.3

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
@@ -8,19 +8,18 @@
8
8
  */
9
9
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync } from "node:fs";
10
10
  import { basename, dirname, join } from "node:path";
11
- import { AccountActivityCoordinator, CLIPROXY_API_KEY_ENV, CLIPROXY_BASE_URL_ENV, accountStoreEntries, cliproxyAuthDirectory, cliproxyAccountEntries, cliproxyAccountMatchesKind, cliproxyApiKey, cliproxyBaseUrl, cliproxyCredentialValid, defaultSubscriptionAccountDirectory, RateLimitTracker, removeCliproxyAccount, removeSubscriptionAccount, renameSubscriptionAccount, sanitizeSubscriptionLabel, subscriptionAccountIdentity } from "@velum-labs/routekit-accounts";
11
+ import { AccountActivityCoordinator, accountStoreEntries, CLIPROXY_API_KEY_ENV, CLIPROXY_BASE_URL_ENV, cliproxyAccountEntries, cliproxyAccountMatchesKind, cliproxyApiKey, cliproxyAuthDirectory, cliproxyBaseUrl, cliproxyCredentialValid, defaultSubscriptionAccountDirectory, RateLimitTracker, removeCliproxyAccount, removeSubscriptionAccount, renameSubscriptionAccount, sanitizeSubscriptionLabel, subscriptionAccountIdentity } from "@velum-labs/routekit-accounts";
12
12
  import { configuredProviderIds, globalRouterConfigPath, parseRouterConfigDocument, routekitHome, writeRouterConfig } from "@velum-labs/routekit-config";
13
13
  import { createRouteKitControlHandler, ROUTEKIT_CONTROL_CAPABILITY } from "@velum-labs/routekit-control";
14
- import { startSwitchingGatewayProxy } from "@velum-labs/routekit-gateway";
15
- import { resolveLeaderboardConfig } from "@velum-labs/routekit-gateway";
16
- import { PROVIDERS, accountKindForCliproxyAuthType, resolveAccountConnector } from "@velum-labs/routekit-registry";
14
+ import { resolveLeaderboardConfig, startSwitchingGatewayProxy } from "@velum-labs/routekit-gateway";
15
+ import { accountKindForCliproxyAuthType, PROVIDERS, resolveAccountConnector } from "@velum-labs/routekit-registry";
17
16
  import { startRouter } from "@velum-labs/routekit-router";
18
17
  import { acquireLifecycleLock, CONTROL_PROTOCOL_VERSION, ControlClient, ControlError, createPortlessSession, createServiceRecordStore, createTokenStore, encodeJoinCredential, extendCleanupGrace, generateControlToken, nextServiceGeneration, processIdentity, registerCleanup, SERVICE_HOME_MODE, startControlServer, supervisorFromEnv, writeFileAtomic } from "@velum-labs/routekit-runtime";
19
18
  import { createConsentManager } from "@velum-labs/routekit-telemetry-core";
20
19
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
21
- import { createCliproxySidecar } from "./cliproxy-sidecar.js";
22
20
  import { cleanupAccountTransaction, markAccountTransactionCommitted, prepareAccountTransaction, recoverAccountTransactions, rollbackAccountTransaction } from "./account-transaction.js";
23
21
  import { CallAttributionStore, callInspection } from "./call-attribution-store.js";
22
+ import { createCliproxySidecar } from "./cliproxy-sidecar.js";
24
23
  import { aggregateInspections, buildLeaderboardResult, defaultLeaderboardWindow, LeaderboardRollupStore } from "./leaderboard.js";
25
24
  export const ROUTEKIT_DAEMON_KIND = "daemon";
26
25
  export const ROUTEKIT_PRODUCT = "routekit";
@@ -111,9 +110,7 @@ function readRevisions(home) {
111
110
  accounts: typeof parsed.accounts === "number" && Number.isSafeInteger(parsed.accounts)
112
111
  ? parsed.accounts
113
112
  : 0,
114
- daemon: typeof parsed.daemon === "number" && Number.isSafeInteger(parsed.daemon)
115
- ? parsed.daemon
116
- : 0
113
+ daemon: typeof parsed.daemon === "number" && Number.isSafeInteger(parsed.daemon) ? parsed.daemon : 0
117
114
  };
118
115
  }
119
116
  catch {
@@ -170,7 +167,7 @@ function providerCredentialAvailable(provider, accounts, env) {
170
167
  return accounts.some((entry) => entry.subscriptionKind === provider);
171
168
  }
172
169
  if (provider === "cliproxy") {
173
- return ((env[CLIPROXY_API_KEY_ENV] ?? "").length > 0 || cliproxyApiKey(env) !== undefined);
170
+ return (env[CLIPROXY_API_KEY_ENV] ?? "").length > 0 || cliproxyApiKey(env) !== undefined;
174
171
  }
175
172
  const info = PROVIDERS[provider];
176
173
  if (info?.keyEnv === undefined)
@@ -203,7 +200,7 @@ function safeCliproxyCredentialBlob(kind, value) {
203
200
  const type = typeof record.type === "string" ? record.type : undefined;
204
201
  const classified = type === undefined
205
202
  ? undefined
206
- : accountKindForCliproxyAuthType(type) ?? resolveAccountConnector(type)?.kind;
203
+ : (accountKindForCliproxyAuthType(type) ?? resolveAccountConnector(type)?.kind);
207
204
  if (classified !== kind || !cliproxyCredentialValid(record, type)) {
208
205
  throw new ControlError({
209
206
  code: "bad_request",
@@ -396,10 +393,12 @@ export async function startRouteKitDaemon(options) {
396
393
  };
397
394
  }
398
395
  });
399
- portless = await createPortlessSession(options.portless ?? env.ROUTEKIT_PORTLESS !== "0", { project: "routekit", ownerLabel: "routekit-daemon", bareNames: [] });
400
- const dataUrl = portless.enabled
401
- ? portless.register("gateway", proxy.port())
402
- : proxy.url();
396
+ portless = await createPortlessSession(options.portless ?? env.ROUTEKIT_PORTLESS !== "0", {
397
+ project: "routekit",
398
+ ownerLabel: "routekit-daemon",
399
+ bareNames: []
400
+ });
401
+ const dataUrl = portless.enabled ? portless.register("gateway", proxy.port()) : proxy.url();
403
402
  const replaceRouter = async (nextConfig, nextDocument, input) => {
404
403
  // Sidecar reconcile runs before the generation commits; any failure
405
404
  // below must put the sidecar back to the still-live currentConfig.
@@ -708,6 +707,9 @@ export async function startRouteKitDaemon(options) {
708
707
  inFlight: 0,
709
708
  lastSelected: false,
710
709
  active: false,
710
+ ...(entry.credentialValid
711
+ ? {}
712
+ : { readinessReasons: [{ code: "credential_invalid" }] }),
711
713
  models: []
712
714
  };
713
715
  }
@@ -730,6 +732,11 @@ export async function startRouteKitDaemon(options) {
730
732
  : {}),
731
733
  lastSelected: member?.lastSelected ?? false,
732
734
  active: member?.lastSelected ?? false,
735
+ ...(member?.readinessReasons !== undefined
736
+ ? { readinessReasons: member.readinessReasons }
737
+ : member === undefined
738
+ ? { readinessReasons: [{ code: "credential_invalid" }] }
739
+ : {}),
733
740
  models: member?.models ?? [],
734
741
  ...(member?.limits !== undefined ? { limits: member.limits } : {})
735
742
  };
@@ -797,8 +804,7 @@ export async function startRouteKitDaemon(options) {
797
804
  const label = connector === "native"
798
805
  ? sanitizeSubscriptionLabel(account.label)
799
806
  : safeCliproxyLabel(account.label);
800
- if (label !== account.label ||
801
- (connector === "native" && label.startsWith("."))) {
807
+ if (label !== account.label || (connector === "native" && label.startsWith("."))) {
802
808
  throw new ControlError({
803
809
  code: "bad_request",
804
810
  message: "account label must already be normalized"
@@ -887,8 +893,7 @@ export async function startRouteKitDaemon(options) {
887
893
  });
888
894
  }
889
895
  }
890
- const unchanged = prepared.every((entry) => existsSync(entry.path) &&
891
- readFileSync(entry.path, "utf8") === entry.content);
896
+ const unchanged = prepared.every((entry) => existsSync(entry.path) && readFileSync(entry.path, "utf8") === entry.content);
892
897
  if (unchanged &&
893
898
  currentConfig.providers[provider] !== undefined) {
894
899
  return;
@@ -1023,8 +1028,7 @@ export async function startRouteKitDaemon(options) {
1023
1028
  !Array.isArray(raw.providers)
1024
1029
  ? { ...raw.providers }
1025
1030
  : {};
1026
- const disableProvider = !hasRemainingAccount &&
1027
- currentConfig.providers[nativeKind] !== undefined;
1031
+ const disableProvider = !hasRemainingAccount && currentConfig.providers[nativeKind] !== undefined;
1028
1032
  if (disableProvider) {
1029
1033
  for (const providerKey of nativeKind === "claude-code"
1030
1034
  ? ["claude-code", "claudeCode", "claude"]
@@ -1037,12 +1041,8 @@ export async function startRouteKitDaemon(options) {
1037
1041
  delete raw.defaultModel;
1038
1042
  }
1039
1043
  }
1040
- const nextDocument = disableProvider
1041
- ? stringifyYaml(raw)
1042
- : currentDocument;
1043
- const nextConfig = disableProvider
1044
- ? parseConfigDocument(nextDocument)
1045
- : currentConfig;
1044
+ const nextDocument = disableProvider ? stringifyYaml(raw) : currentDocument;
1045
+ const nextConfig = disableProvider ? parseConfigDocument(nextDocument) : currentConfig;
1046
1046
  const activityPath = join(home, "usage", "account-activity.v1.json");
1047
1047
  const transaction = prepareAccountTransaction({
1048
1048
  home,
@@ -1054,7 +1054,9 @@ export async function startRouteKitDaemon(options) {
1054
1054
  labels: [params.label]
1055
1055
  });
1056
1056
  try {
1057
- const result = removeSubscriptionAccount(nativeKind, params.label, { accountsDirectory: activeNativeDirectory });
1057
+ const result = removeSubscriptionAccount(nativeKind, params.label, {
1058
+ accountsDirectory: activeNativeDirectory
1059
+ });
1058
1060
  removed = result.removed;
1059
1061
  if (!result.removed) {
1060
1062
  cleanupAccountTransaction(transaction);
@@ -1235,6 +1237,22 @@ export async function startRouteKitDaemon(options) {
1235
1237
  "accounts.usage": async (_params, context) => {
1236
1238
  return await activeRouter.usage(context.signal);
1237
1239
  },
1240
+ "accounts.resetCredits": async (params, context) => {
1241
+ try {
1242
+ return {
1243
+ kind: params.kind,
1244
+ label: params.label,
1245
+ resetCredits: await activeRouter.listResetCredits(params.kind, params.label, context.signal)
1246
+ };
1247
+ }
1248
+ catch (error) {
1249
+ const message = error instanceof Error ? error.message : String(error);
1250
+ if (message.includes("is not enrolled") || message.includes("no codex account pool")) {
1251
+ throw new ControlError({ code: "not_found", message });
1252
+ }
1253
+ throw error;
1254
+ }
1255
+ },
1238
1256
  "accounts.redeemReset": async (params, context) => {
1239
1257
  try {
1240
1258
  const result = await activeRouter.redeemReset({
@@ -1252,9 +1270,7 @@ export async function startRouteKitDaemon(options) {
1252
1270
  label: result.label,
1253
1271
  redeemRequestId: result.redeemRequestId,
1254
1272
  ...(result.creditId !== undefined ? { creditId: result.creditId } : {}),
1255
- ...(result.windowsReset !== undefined
1256
- ? { windowsReset: result.windowsReset }
1257
- : {}),
1273
+ ...(result.windowsReset !== undefined ? { windowsReset: result.windowsReset } : {}),
1258
1274
  usage: result.usage
1259
1275
  };
1260
1276
  }
@@ -1289,9 +1305,7 @@ export async function startRouteKitDaemon(options) {
1289
1305
  const missingProviders = [
1290
1306
  ...new Set(accounts
1291
1307
  .filter((entry) => {
1292
- const provider = entry.connector === "cliproxy"
1293
- ? "cliproxy"
1294
- : entry.subscriptionKind;
1308
+ const provider = entry.connector === "cliproxy" ? "cliproxy" : entry.subscriptionKind;
1295
1309
  return currentConfig.providers[provider] === undefined;
1296
1310
  })
1297
1311
  .map((entry) => entry.subscriptionKind))
@@ -1364,7 +1378,9 @@ export async function startRouteKitDaemon(options) {
1364
1378
  if (model === undefined || !listed.models.some((entry) => entry.id === model)) {
1365
1379
  throw new ControlError({
1366
1380
  code: "not_found",
1367
- message: params.model === undefined ? "no model is available" : `unknown model: ${params.model}`
1381
+ message: params.model === undefined
1382
+ ? "no model is available"
1383
+ : `unknown model: ${params.model}`
1368
1384
  });
1369
1385
  }
1370
1386
  return {
@@ -1381,9 +1397,7 @@ export async function startRouteKitDaemon(options) {
1381
1397
  label: params.label,
1382
1398
  plane: params.plane,
1383
1399
  role: "admin",
1384
- createdBy: params.createdBy ??
1385
- context.principal?.label ??
1386
- "control"
1400
+ createdBy: params.createdBy ?? context.principal?.label ?? "control"
1387
1401
  });
1388
1402
  if (issued.plane === "data") {
1389
1403
  dataTokenCache.set(issued.label, issued.token);
@@ -8,8 +8,8 @@ import { CLIPROXY_PINNED_VERSION } from "@velum-labs/routekit-accounts";
8
8
  import { RouteKitControlClient } from "@velum-labs/routekit-control";
9
9
  import { ControlClient, ControlError, createServiceRecordStore } from "@velum-labs/routekit-runtime";
10
10
  import { parse as parseYaml } from "yaml";
11
- import { startRouteKitDaemon } from "../index.js";
12
11
  import { prepareAccountTransaction } from "../account-transaction.js";
12
+ import { startRouteKitDaemon } from "../index.js";
13
13
  async function mockProvider() {
14
14
  const server = createServer((req, res) => {
15
15
  if (req.url === "/v1/models") {
@@ -58,7 +58,9 @@ async function withMockAnthropicDiscovery(run) {
58
58
  globalThis.fetch = async (input, init) => {
59
59
  const url = new URL(input instanceof Request ? input.url : input.toString());
60
60
  if (url.hostname === "api.anthropic.com" && url.pathname === "/v1/models") {
61
- return new Response(JSON.stringify({ data: [{ id: "claude-test-model", type: "model" }] }), { headers: { "content-type": "application/json" } });
61
+ return new Response(JSON.stringify({ data: [{ id: "claude-test-model", type: "model" }] }), {
62
+ headers: { "content-type": "application/json" }
63
+ });
62
64
  }
63
65
  return await originalFetch(input, init);
64
66
  };
@@ -69,7 +71,7 @@ async function withMockAnthropicDiscovery(run) {
69
71
  globalThis.fetch = originalFetch;
70
72
  }
71
73
  }
72
- async function withMockNativeDiscovery(kind, run) {
74
+ async function withMockNativeDiscovery(kind, run, options = {}) {
73
75
  if (kind === "claude-code")
74
76
  return await withMockAnthropicDiscovery(run);
75
77
  const originalFetch = globalThis.fetch;
@@ -80,6 +82,16 @@ async function withMockNativeDiscovery(kind, run) {
80
82
  url.pathname.endsWith("/models")) {
81
83
  return Response.json({ models: [{ slug: "gpt-test-model" }] });
82
84
  }
85
+ if (options.healthyUsage === true &&
86
+ url.hostname === "chatgpt.com" &&
87
+ url.pathname === "/backend-api/wham/usage") {
88
+ return Response.json({
89
+ plan_type: "plus",
90
+ rate_limit: {
91
+ primary_window: { used_percent: 10, reset_at: Date.now() / 1000 + 3_600 }
92
+ }
93
+ });
94
+ }
83
95
  return await originalFetch(input, init);
84
96
  };
85
97
  try {
@@ -168,6 +180,9 @@ test("singleton daemon exposes authenticated control and a stable reloadable dat
168
180
  await assert.rejects(client.call("models.info", { model: "openai/not-real" }), (error) => error instanceof ControlError &&
169
181
  error.code === "not_found" &&
170
182
  /unknown model/.test(error.message));
183
+ await assert.rejects(client.call("accounts.resetCredits", { kind: "codex", label: "work" }), (error) => error instanceof ControlError &&
184
+ error.code === "not_found" &&
185
+ /no codex account pool/.test(error.message));
171
186
  const beforeUrl = status.dataUrl;
172
187
  const snapshot = await client.call("config.get", {});
173
188
  await assert.rejects(client.call("config.update", {
@@ -473,6 +488,69 @@ test("daemon account activity persists last selection independently of leaderboa
473
488
  rmSync(root, { recursive: true, force: true });
474
489
  }
475
490
  });
491
+ test("cleared persisted cooldown remains absent and eligible after daemon reload", async () => {
492
+ const root = mkdtempSync(join(tmpdir(), "routekit-daemon-cooldown-reload-"));
493
+ const stateHome = join(root, "state");
494
+ const configPath = join(root, "router.yaml");
495
+ const accountsDirectory = join(stateHome, "subscriptions", "codex");
496
+ const statePath = join(accountsDirectory, ".state.json");
497
+ const coolingUntil = Date.now() / 1000 + 3_600;
498
+ mkdirSync(accountsDirectory, { recursive: true });
499
+ writeFileSync(join(accountsDirectory, "work.json"), `${JSON.stringify(nativeCredential("codex"))}\n`);
500
+ writeFileSync(statePath, JSON.stringify({ members: [{ id: "work", coolingUntil }] }));
501
+ writeFileSync(configPath, "providers:\n codex: {}\ndefaultModel: codex/gpt-test-model\n");
502
+ try {
503
+ await withMockNativeDiscovery("codex", async () => {
504
+ const daemon = await startRouteKitDaemon({
505
+ packageVersion: "1.2.3",
506
+ stateHome,
507
+ configPath,
508
+ port: 0,
509
+ portless: false,
510
+ env: {
511
+ HOME: root,
512
+ ROUTEKIT_HOME: stateHome,
513
+ ROUTEKIT_PORTLESS: "0"
514
+ }
515
+ });
516
+ try {
517
+ const client = new RouteKitControlClient({
518
+ url: daemon.record.url,
519
+ token: daemon.record.controlToken
520
+ });
521
+ const before = await client.call("accounts.status", {});
522
+ assert.equal(before.accounts[0]?.relayOpen, false);
523
+ assert.deepEqual(before.accounts[0]?.readinessReasons, [
524
+ { code: "cooldown_active", until: coolingUntil }
525
+ ]);
526
+ const usage = await client.call("accounts.usage", {});
527
+ const recovered = usage.accountSets[0]?.members[0];
528
+ assert.equal(recovered?.coolingUntil, undefined);
529
+ assert.equal(recovered?.poolEligible, true);
530
+ assert.equal(recovered?.relayReady, true);
531
+ assert.deepEqual(recovered?.readinessReasons, []);
532
+ const persisted = JSON.parse(readFileSync(statePath, "utf8"));
533
+ assert.equal(persisted.members[0]?.coolingUntil, undefined);
534
+ assert.equal(persisted.members[0]?.cooldownRevision, 2);
535
+ const daemonStatus = await client.call("daemon.status", {});
536
+ await client.call("daemon.reload", { expectedRevision: daemonStatus.configRevision });
537
+ const afterReload = await client.call("accounts.status", {});
538
+ assert.equal(afterReload.accounts[0]?.relayOpen, true);
539
+ assert.deepEqual(afterReload.accounts[0]?.readinessReasons, []);
540
+ const afterUsage = await client.call("accounts.usage", {});
541
+ assert.equal(afterUsage.accountSets[0]?.members[0]?.coolingUntil, undefined);
542
+ assert.equal(afterUsage.accountSets[0]?.members[0]?.poolEligible, true);
543
+ assert.equal(JSON.parse(readFileSync(statePath, "utf8")).members[0]?.coolingUntil, undefined);
544
+ }
545
+ finally {
546
+ await daemon.close();
547
+ }
548
+ }, { healthyUsage: true });
549
+ }
550
+ finally {
551
+ rmSync(root, { recursive: true, force: true });
552
+ }
553
+ });
476
554
  for (const kind of ["claude-code", "codex"]) {
477
555
  test(`native ${kind} account rename preserves routing and usage state`, async () => {
478
556
  const root = mkdtempSync(join(tmpdir(), `routekit-daemon-rename-${kind}-`));
@@ -763,12 +841,7 @@ test("native removal failure before deletion cleans its prepared transaction", a
763
841
  mode: 0o600
764
842
  });
765
843
  symlinkSync(externalPath, accountPath);
766
- writeFileSync(configPath, [
767
- "providers:",
768
- " openai: {}",
769
- "defaultModel: openai/mock-model",
770
- ""
771
- ].join("\n"));
844
+ writeFileSync(configPath, ["providers:", " openai: {}", "defaultModel: openai/mock-model", ""].join("\n"));
772
845
  const upstream = await mockProvider();
773
846
  const daemon = await startRouteKitDaemon({
774
847
  packageVersion: "1.2.3",
@@ -1019,11 +1092,10 @@ test("daemon owns the cliproxy sidecar: spawn, restart, account routing, shutdow
1019
1092
  assert.ok(await waitFor(() => readFileSync(markerPath, "utf8").trim().split("\n").length === 3, 10_000), "accounts.sync did not restart the managed sidecar");
1020
1093
  assert.equal(processAlive(respawnedPid), false);
1021
1094
  const refreshedStatus = await client.call("accounts.status", {});
1022
- assert.equal(refreshedStatus.accounts.find((entry) => entry.label === "antigravity-user@example.com")?.credentialValid, true);
1023
- assert.equal(refreshedStatus.accounts.find((entry) => entry.label === "kimi-invalid")
1024
- ?.credentialValid, false);
1025
- assert.equal(refreshedStatus.accounts.find((entry) => entry.label === "broken-account")
1026
- ?.credentialValid, false);
1095
+ assert.equal(refreshedStatus.accounts.find((entry) => entry.label === "antigravity-user@example.com")
1096
+ ?.credentialValid, true);
1097
+ assert.equal(refreshedStatus.accounts.find((entry) => entry.label === "kimi-invalid")?.credentialValid, false);
1098
+ assert.equal(refreshedStatus.accounts.find((entry) => entry.label === "broken-account")?.credentialValid, false);
1027
1099
  const syncedPid = Number(readFileSync(markerPath, "utf8").trim().split("\n")[2]);
1028
1100
  // Unclassified/corrupt auth files remain removable using the kind shown
1029
1101
  // by accounts.list rather than becoming stuck in the store.
@@ -1072,7 +1144,9 @@ test("daemon owns the cliproxy sidecar: spawn, restart, account routing, shutdow
1072
1144
  }
1073
1145
  ]
1074
1146
  };
1075
- const activated = await client.call("accounts.enrollActivate", activationParams, { idempotencyKey: "activate-grok" });
1147
+ const activated = await client.call("accounts.enrollActivate", activationParams, {
1148
+ idempotencyKey: "activate-grok"
1149
+ });
1076
1150
  assert.equal(activated.activated, true);
1077
1151
  assert.equal(activated.configRevision, beforeActivation.configRevision + 1);
1078
1152
  assert.equal(activated.accountRevision, beforeActivation.accountRevision + 1);
@@ -1081,11 +1155,14 @@ test("daemon owns the cliproxy sidecar: spawn, restart, account routing, shutdow
1081
1155
  assert.equal(existsSync(join(stateHome, "account-transactions")), false);
1082
1156
  // A fresh transport retry converges on the committed state without
1083
1157
  // incrementing either revision again.
1084
- const replayed = await client.call("accounts.enrollActivate", activationParams, { idempotencyKey: "activate-grok-retry" });
1158
+ const replayed = await client.call("accounts.enrollActivate", activationParams, {
1159
+ idempotencyKey: "activate-grok-retry"
1160
+ });
1085
1161
  assert.equal(replayed.configRevision, activated.configRevision);
1086
1162
  assert.equal(replayed.accountRevision, activated.accountRevision);
1087
1163
  const activatedStatus = await client.call("accounts.status", {});
1088
- assert.equal(activatedStatus.accounts.find((entry) => entry.label === "xai-transaction@example.com")?.configured, true);
1164
+ assert.equal(activatedStatus.accounts.find((entry) => entry.label === "xai-transaction@example.com")
1165
+ ?.configured, true);
1089
1166
  const beforeClaude = await client.call("daemon.status", {});
1090
1167
  const claudeActivation = {
1091
1168
  kind: "claude-code",
@@ -1113,14 +1190,18 @@ test("daemon owns the cliproxy sidecar: spawn, restart, account routing, shutdow
1113
1190
  assert.equal((await client.call("daemon.status", {})).configRevision, beforeClaude.configRevision);
1114
1191
  assert.equal((await client.call("daemon.status", {})).accountRevision, beforeClaude.accountRevision);
1115
1192
  await withMockAnthropicDiscovery(async () => {
1116
- const claudeActivated = await client.call("accounts.enrollActivate", claudeActivation, { idempotencyKey: "activate-claude" });
1193
+ const claudeActivated = await client.call("accounts.enrollActivate", claudeActivation, {
1194
+ idempotencyKey: "activate-claude"
1195
+ });
1117
1196
  assert.equal(claudeActivated.activated, true);
1118
1197
  assert.equal(claudeActivated.configRevision, beforeClaude.configRevision + 1);
1119
1198
  assert.equal(claudeActivated.accountRevision, beforeClaude.accountRevision + 1);
1120
1199
  assert.equal(existsSync(claudePath), true);
1121
1200
  assert.match(readFileSync(configPath, "utf8"), /claude-code:/);
1122
1201
  assert.doesNotMatch(JSON.stringify(claudeActivated), /claude-transaction-access|claude-transaction-refresh/);
1123
- const claudeReplay = await client.call("accounts.enrollActivate", claudeActivation, { idempotencyKey: "activate-claude-retry" });
1202
+ const claudeReplay = await client.call("accounts.enrollActivate", claudeActivation, {
1203
+ idempotencyKey: "activate-claude-retry"
1204
+ });
1124
1205
  assert.equal(claudeReplay.configRevision, claudeActivated.configRevision);
1125
1206
  assert.equal(claudeReplay.accountRevision, claudeActivated.accountRevision);
1126
1207
  assert.equal((await client.call("accounts.status", {})).accounts.find((entry) => entry.subscriptionKind === "claude-code" && entry.label === "claude-work")?.configured, true);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@velum-labs/routekit-daemon",
3
3
  "private": false,
4
- "version": "0.16.1",
4
+ "version": "0.16.3",
5
5
  "description": "Singleton RouteKit control daemon and stable model gateway.",
6
6
  "repository": {
7
7
  "type": "git",
@@ -34,14 +34,14 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "yaml": "2.9.0",
37
- "@velum-labs/routekit-accounts": "0.16.1",
38
- "@velum-labs/routekit-config": "0.16.1",
39
- "@velum-labs/routekit-control": "0.16.1",
40
- "@velum-labs/routekit-gateway": "0.16.1",
41
- "@velum-labs/routekit-registry": "0.16.1",
42
- "@velum-labs/routekit-router": "0.16.1",
43
- "@velum-labs/routekit-runtime": "0.16.1",
44
- "@velum-labs/routekit-telemetry-core": "0.16.1"
37
+ "@velum-labs/routekit-accounts": "0.16.3",
38
+ "@velum-labs/routekit-config": "0.16.3",
39
+ "@velum-labs/routekit-control": "0.16.3",
40
+ "@velum-labs/routekit-gateway": "0.16.3",
41
+ "@velum-labs/routekit-registry": "0.16.3",
42
+ "@velum-labs/routekit-router": "0.16.3",
43
+ "@velum-labs/routekit-telemetry-core": "0.16.3",
44
+ "@velum-labs/routekit-runtime": "0.16.3"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "tsc -b",