agent-dealer 1.2.2 → 1.2.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.
Files changed (32) hide show
  1. package/bundle/server/dist/capacity/claude-events.js +27 -10
  2. package/bundle/server/dist/capacity/claude-local-cache.js +788 -0
  3. package/bundle/server/dist/capacity/claude-local-cache.test.js +520 -0
  4. package/bundle/server/dist/capacity/cursor-individual-credentials.js +203 -16
  5. package/bundle/server/dist/capacity/cursor-individual-credentials.test.js +186 -2
  6. package/bundle/server/dist/capacity/cursor-individual.js +69 -2
  7. package/bundle/server/dist/capacity/cursor-individual.test.js +268 -2
  8. package/bundle/server/dist/capacity/muse-host.js +748 -0
  9. package/bundle/server/dist/capacity/muse-host.test.js +480 -0
  10. package/bundle/server/dist/capacity/muse-lifecycle.test.js +97 -0
  11. package/bundle/server/dist/capacity/muse.js +307 -60
  12. package/bundle/server/dist/capacity/muse.test.js +8 -51
  13. package/bundle/server/dist/coordinator/muse-spawn.js +143 -1
  14. package/bundle/server/dist/index.js +15 -2
  15. package/bundle/server/dist/routes/index.js +26 -9
  16. package/bundle/server/dist/routes/runtime-capacity.test.js +12 -6
  17. package/bundle/server/dist/runners/muse-code-jsonl.js +7 -1
  18. package/bundle/server/dist/runners/muse-serve-session.js +313 -0
  19. package/bundle/server/dist/runners/muse-serve-session.test.js +325 -0
  20. package/bundle/server/package.json +2 -2
  21. package/bundle/server/static-ui/assets/codex-YZSpL-SB.png +0 -0
  22. package/bundle/server/static-ui/assets/{index-DUYzboWh.css → index-CYqRh_-S.css} +1 -1
  23. package/bundle/server/static-ui/assets/index-UO4lHZw4.js +60 -0
  24. package/bundle/server/static-ui/assets/muse-CtdcC6ve.svg +33 -0
  25. package/bundle/server/static-ui/index.html +2 -2
  26. package/bundle/shared/package.json +1 -1
  27. package/dist/doctor.d.ts +57 -0
  28. package/dist/doctor.js +206 -1
  29. package/dist/doctor.test.d.ts +1 -0
  30. package/dist/doctor.test.js +246 -0
  31. package/package.json +1 -1
  32. package/bundle/server/static-ui/assets/index-0GDYfbaM.js +0 -60
@@ -13,8 +13,9 @@ import fs from "node:fs";
13
13
  import os from "node:os";
14
14
  import path from "node:path";
15
15
  process.env.AGENT_DEALER_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "dealer-cursor-indiv-"));
16
- import { CURSOR_INDIVIDUAL_ALLOWED_ORIGINS, CURSOR_INDIVIDUAL_DEFAULT_ORIGIN, CURSOR_INDIVIDUAL_MAX_BODY_BYTES, CURSOR_INDIVIDUAL_OPT_IN_ENV, CURSOR_INDIVIDUAL_OPT_IN_VALUE, CURSOR_INDIVIDUAL_USAGE_PATHS, cursorIndividualCapacityAdapter, cursorIndividualFailureReason, cursorIndividualPayloadToReadings, getCursorIndividualBillingSnapshot, ingestCursorIndividualObservation, isCursorIndividualExperimentalEnabled, readBoundedText, readCursorIndividualBilling, refreshCursorIndividualBilling, refreshCursorIndividualBillingIfStale, refreshCursorIndividualCapacityIfStale, resetCursorIndividualPollStateForTests, } from "./cursor-individual.js";
17
- import { CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV, cursorIndividualAuthHeader, } from "./cursor-individual-credentials.js";
16
+ import { CURSOR_INDIVIDUAL_ALLOWED_ORIGINS, CURSOR_INDIVIDUAL_DEFAULT_ORIGIN, CURSOR_INDIVIDUAL_MAX_BODY_BYTES, CURSOR_INDIVIDUAL_OPT_IN_ENV, CURSOR_INDIVIDUAL_OPT_IN_VALUE, CURSOR_INDIVIDUAL_RUNTIME, CURSOR_INDIVIDUAL_USAGE_PATHS, cursorIndividualCapacityAdapter, cursorIndividualFailureReason, cursorIndividualPayloadToReadings, getCursorIndividualBillingSnapshot, ingestCursorIndividualObservation, isCursorIndividualExperimentalEnabled, readBoundedText, readCursorIndividualBilling, refreshCursorIndividualBilling, refreshCursorIndividualBillingIfStale, refreshCursorIndividualCapacityIfStale, resetCursorIndividualPollStateForTests, } from "./cursor-individual.js";
17
+ import Database from "better-sqlite3";
18
+ import { CURSOR_DESKTOP_ACCESS_TOKEN_KEY, CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV, CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV, CURSOR_INDIVIDUAL_HOME_ENV, cursorIndividualAuthHeader, } from "./cursor-individual-credentials.js";
18
19
  const { migrate } = await import("../db/index.js");
19
20
  const { clearCursorIndividualBilling, readCursorIndividualBillingRow, } = await import("../repository/cursor-individual-billing.js");
20
21
  const { clearAllCapacitySnapshots } = await import("../repository/runtime-capacity.js");
@@ -28,6 +29,8 @@ let credDir;
28
29
  let savedOptIn;
29
30
  let savedCredFile;
30
31
  let savedRefresh;
32
+ let savedDesktopFile;
33
+ let savedHome;
31
34
  before(() => {
32
35
  migrate();
33
36
  });
@@ -36,9 +39,13 @@ beforeEach(() => {
36
39
  savedOptIn = process.env[CURSOR_INDIVIDUAL_OPT_IN_ENV];
37
40
  savedCredFile = process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV];
38
41
  savedRefresh = process.env.AGENT_DEALER_CURSOR_INDIVIDUAL_REFRESH;
42
+ savedDesktopFile = process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV];
43
+ savedHome = process.env[CURSOR_INDIVIDUAL_HOME_ENV];
39
44
  delete process.env[CURSOR_INDIVIDUAL_OPT_IN_ENV];
40
45
  delete process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV];
41
46
  delete process.env.AGENT_DEALER_CURSOR_INDIVIDUAL_REFRESH;
47
+ delete process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV];
48
+ delete process.env[CURSOR_INDIVIDUAL_HOME_ENV];
42
49
  clearCursorIndividualBilling();
43
50
  clearAllCapacitySnapshots();
44
51
  clearAllRuntimeAvailability();
@@ -57,6 +64,14 @@ afterEach(() => {
57
64
  delete process.env.AGENT_DEALER_CURSOR_INDIVIDUAL_REFRESH;
58
65
  else
59
66
  process.env.AGENT_DEALER_CURSOR_INDIVIDUAL_REFRESH = savedRefresh;
67
+ if (savedDesktopFile === undefined)
68
+ delete process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV];
69
+ else
70
+ process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = savedDesktopFile;
71
+ if (savedHome === undefined)
72
+ delete process.env[CURSOR_INDIVIDUAL_HOME_ENV];
73
+ else
74
+ process.env[CURSOR_INDIVIDUAL_HOME_ENV] = savedHome;
60
75
  fs.rmSync(credDir, { recursive: true, force: true });
61
76
  });
62
77
  function enable() {
@@ -280,6 +295,49 @@ test("endpoint drift falls through to the next candidate path", async () => {
280
295
  assert.equal(obs.billing.remainingPercent, 62.5);
281
296
  assert.equal(calls.length, 2);
282
297
  });
298
+ test("a 200 HTML SPA shell on the first candidate (not a 404) still falls through", async () => {
299
+ enable();
300
+ fixtureCredential();
301
+ const now = Date.now();
302
+ const calls = [];
303
+ const obs = await readCursorIndividualBilling({
304
+ baseUrl: MOCK_BASE,
305
+ fetchImpl: mockFetch({
306
+ [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: {
307
+ status: 200,
308
+ headers: { "content-type": "text/html; charset=utf-8" },
309
+ body: "<!doctype html><html><body>app shell</body></html>",
310
+ },
311
+ [CURSOR_INDIVIDUAL_USAGE_PATHS[1]]: { status: 200, body: usagePayload(now) },
312
+ }, calls),
313
+ nowMs: now,
314
+ });
315
+ assert.equal(obs.failure, null);
316
+ assert.equal(obs.billing.cycleLabel, "September 2026");
317
+ assert.equal(obs.billing.remainingPercent, 62.5);
318
+ assert.equal(calls.length, 2);
319
+ });
320
+ test("a 200 HTML SPA shell on every candidate reads unparsable, not missing", async () => {
321
+ enable();
322
+ fixtureCredential();
323
+ const obs = await readCursorIndividualBilling({
324
+ baseUrl: MOCK_BASE,
325
+ fetchImpl: mockFetch({
326
+ [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: {
327
+ status: 200,
328
+ headers: { "content-type": "text/html; charset=utf-8" },
329
+ body: "<!doctype html><html><body>app shell</body></html>",
330
+ },
331
+ [CURSOR_INDIVIDUAL_USAGE_PATHS[1]]: {
332
+ status: 200,
333
+ headers: { "content-type": "text/html; charset=utf-8" },
334
+ body: "<!doctype html><html><body>app shell</body></html>",
335
+ },
336
+ }),
337
+ });
338
+ assert.equal(obs.failure?.kind, "malformed");
339
+ assert.equal(obs.billing.unavailableReason, "unparsable");
340
+ });
283
341
  test("failure modes return explicit N/A without affecting runtime health", async () => {
284
342
  enable();
285
343
  fixtureCredential();
@@ -813,3 +871,211 @@ test("readBoundedText aborts once the body exceeds the size cap, without bufferi
813
871
  });
814
872
  await assert.rejects(() => readBoundedText(new Response(anotherStream), abortedController.signal));
815
873
  });
874
+ // ---------------------------------------------------------------------------
875
+ // NOT-267: desktop-login acquisition and one-window normalization of the
876
+ // live `/api/usage-summary` shape.
877
+ // ---------------------------------------------------------------------------
878
+ /** header.payload.signature with { sub } — no signature verification needed. */
879
+ function desktopJwt(sub) {
880
+ const b64url = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
881
+ return `${b64url({ alg: "none" })}.${b64url({ sub })}.sig`;
882
+ }
883
+ /** Point the desktop resolution at a fixture state.vscdb (real SQLite). */
884
+ function fixtureDesktopDb(token) {
885
+ const file = path.join(credDir, "state.vscdb");
886
+ const db = new Database(file);
887
+ try {
888
+ db.exec("CREATE TABLE ItemTable (key TEXT PRIMARY KEY, value TEXT)");
889
+ db.prepare("INSERT INTO ItemTable (key, value) VALUES (?, ?)").run(CURSOR_DESKTOP_ACCESS_TOKEN_KEY, token);
890
+ }
891
+ finally {
892
+ db.close();
893
+ }
894
+ process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = file;
895
+ return file;
896
+ }
897
+ /** Live-shape payload: cycle dates top-level, account total nested in the plan. */
898
+ function liveUsagePayload(nowMs, over = {}) {
899
+ return {
900
+ billingCycleStart: "2026-09-01T00:00:00.000Z",
901
+ billingCycleEnd: new Date(nowMs + 8 * 24 * 3600_000).toISOString(),
902
+ individualUsage: {
903
+ plan: { totalPercentUsed: 37.5 },
904
+ apiUsage: { apiPercentUsed: 91.2 },
905
+ },
906
+ teamUsage: { totalPercentUsed: 99.9 },
907
+ ...over,
908
+ };
909
+ }
910
+ test("a desktop-login read sends the desktop session cookie and normalizes one window", async () => {
911
+ enable();
912
+ // Default resolution (no pinned credential file): the desktop database
913
+ // answers, the empty Agent home does not.
914
+ process.env[CURSOR_INDIVIDUAL_HOME_ENV] = credDir;
915
+ const jwt = desktopJwt(USER_ID);
916
+ fixtureDesktopDb(jwt);
917
+ const now = Date.now();
918
+ const calls = [];
919
+ const obs = await readCursorIndividualBilling({
920
+ baseUrl: MOCK_BASE,
921
+ fetchImpl: mockFetch({ [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: { status: 200, body: liveUsagePayload(now) } }, calls),
922
+ nowMs: now,
923
+ });
924
+ assert.equal(obs.failure, null);
925
+ assert.equal(obs.billing.remainingPercent, 62.5);
926
+ assert.equal(obs.billing.cycleStart, "2026-09-01T00:00:00.000Z");
927
+ assert.equal(obs.billing.cycleEnd, new Date(now + 8 * 24 * 3600_000).toISOString());
928
+ assert.equal(calls.length, 1);
929
+ assert.equal(calls[0].headers.Cookie, cursorIndividualAuthHeader(USER_ID, jwt));
930
+ assert.ok(!JSON.stringify(obs).includes(jwt), "raw desktop token stays in the header only");
931
+ });
932
+ test("default resolution with neither login performs no HTTP and reads absent-credential", async () => {
933
+ enable();
934
+ process.env[CURSOR_INDIVIDUAL_HOME_ENV] = credDir;
935
+ process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = path.join(credDir, "missing.vscdb");
936
+ const calls = [];
937
+ const obs = await readCursorIndividualBilling({
938
+ baseUrl: MOCK_BASE,
939
+ fetchImpl: mockFetch({}, calls),
940
+ });
941
+ assert.equal(obs.failure?.kind, "absent-credential");
942
+ assert.equal(obs.configured, false);
943
+ assert.equal(calls.length, 0);
944
+ assert.equal(cursorIndividualFailureReason("absent-credential"), "missing");
945
+ });
946
+ test("live usage-summary shape writes exactly one billing_cycle snapshot and one billing row", async () => {
947
+ enable();
948
+ fixtureCredential();
949
+ const now = Date.now();
950
+ const cycleEnd = new Date(now + 8 * 24 * 3600_000).toISOString();
951
+ await refreshCursorIndividualCapacityIfStale(now, {
952
+ baseUrl: MOCK_BASE,
953
+ fetchImpl: mockFetch({ [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: { status: 200, body: liveUsagePayload(now) } }),
954
+ nowMs: now,
955
+ });
956
+ const { listCapacitySnapshots } = await import("../repository/runtime-capacity.js");
957
+ const rows = listCapacitySnapshots(CURSOR_INDIVIDUAL_RUNTIME);
958
+ assert.equal(rows.length, 1);
959
+ assert.equal(rows[0].windowKey, "billing_cycle");
960
+ assert.equal(rows[0].remainingPercent, 62.5);
961
+ assert.equal(rows[0].resetAt, cycleEnd);
962
+ const billingRow = readCursorIndividualBillingRow();
963
+ assert.ok(billingRow, "one billing row is written");
964
+ assert.equal(billingRow.cycleStart, "2026-09-01T00:00:00.000Z");
965
+ assert.equal(billingRow.cycleEnd, cycleEnd);
966
+ assert.equal(billingRow.remainingPercent, 62.5);
967
+ const snap = await getCursorIndividualBillingSnapshot(now);
968
+ assert.equal(snap.unavailableReason, null);
969
+ assert.equal(snap.remainingPercent, 62.5);
970
+ });
971
+ test("with multiple Cursor pools the primary value is 100 - totalPercentUsed; no second window", async () => {
972
+ // totalPercentUsed 37.5 → 62.5 remaining — NOT 100 - 91.2 (API pool) and
973
+ // NOT 100 - 99.9 (team scope). The adapter emits exactly one window.
974
+ const readings = cursorIndividualPayloadToReadings(liveUsagePayload(Date.now()));
975
+ assert.ok(readings);
976
+ assert.equal(readings.remainingPercent, 62.5);
977
+ enable();
978
+ fixtureCredential();
979
+ const now = Date.now();
980
+ const result = await cursorIndividualCapacityAdapter({
981
+ baseUrl: MOCK_BASE,
982
+ fetchImpl: mockFetch({ [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: { status: 200, body: liveUsagePayload(now) } }),
983
+ nowMs: now,
984
+ }).read(now);
985
+ assert.equal(result.windows.length, 1);
986
+ assert.equal(result.unavailable.length, 0);
987
+ assert.equal(result.windows[0].windowKey, "billing_cycle");
988
+ });
989
+ test("a pool-only scale (apiPercentUsed) is never substituted for the account total", async () => {
990
+ const poolOnly = cursorIndividualPayloadToReadings({
991
+ billingCycleStart: "2026-09-01T00:00:00.000Z",
992
+ billingCycleEnd: new Date(Date.now() + 8 * 24 * 3600_000).toISOString(),
993
+ individualUsage: { plan: { apiPercentUsed: 91.2 } },
994
+ });
995
+ // Cycle dates parse, but the remaining scale is absent — never 100 - 91.2.
996
+ assert.ok(poolOnly);
997
+ assert.equal(poolOnly.remainingPercent, null);
998
+ assert.equal(cursorIndividualPayloadToReadings({ individualUsage: { plan: { apiPercentUsed: 91.2 } } }), null);
999
+ // End to end: no usable scale reads unparsable with no window emitted.
1000
+ enable();
1001
+ fixtureCredential();
1002
+ const obs = await readCursorIndividualBilling({
1003
+ baseUrl: MOCK_BASE,
1004
+ fetchImpl: mockFetch({
1005
+ [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: {
1006
+ status: 200,
1007
+ body: { individualUsage: { plan: { apiPercentUsed: 91.2 } } },
1008
+ },
1009
+ }),
1010
+ });
1011
+ assert.equal(obs.failure?.kind, "malformed");
1012
+ });
1013
+ test("the plan's explicit used/limit pair is a last resort when total percent is absent", async () => {
1014
+ const now = Date.now();
1015
+ const cycle = {
1016
+ billingCycleStart: "2026-09-01T00:00:00.000Z",
1017
+ billingCycleEnd: new Date(now + 8 * 24 * 3600_000).toISOString(),
1018
+ };
1019
+ // 30 used of a 100 limit → 70 remaining.
1020
+ assert.equal(cursorIndividualPayloadToReadings({ ...cycle, individualUsage: { plan: { used: 30, limit: 100 } } })
1021
+ ?.remainingPercent, 70);
1022
+ // A present total percent always wins over the pair.
1023
+ assert.equal(cursorIndividualPayloadToReadings({
1024
+ ...cycle,
1025
+ individualUsage: { plan: { totalPercentUsed: 37.5, used: 30, limit: 100 } },
1026
+ })?.remainingPercent, 62.5);
1027
+ // Degenerate pairs are not scales: zero limit, negative used.
1028
+ assert.equal(cursorIndividualPayloadToReadings({ ...cycle, individualUsage: { plan: { used: 30, limit: 0 } } })
1029
+ ?.remainingPercent, null);
1030
+ assert.equal(cursorIndividualPayloadToReadings({ ...cycle, individualUsage: { plan: { used: -5, limit: 100 } } })
1031
+ ?.remainingPercent, null);
1032
+ // Top-level numerics never form the plan pair: without individualUsage.plan
1033
+ // there is no usable scale, even when the names line up.
1034
+ assert.equal(cursorIndividualPayloadToReadings({ ...cycle, used: 30, limit: 100 })?.remainingPercent, null);
1035
+ });
1036
+ test("a completed live billing cycle reads expired with its start/end intact", async () => {
1037
+ enable();
1038
+ fixtureCredential();
1039
+ const now = Date.now();
1040
+ await refreshCursorIndividualCapacityIfStale(now, {
1041
+ baseUrl: MOCK_BASE,
1042
+ fetchImpl: mockFetch({ [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: { status: 200, body: liveUsagePayload(now) } }),
1043
+ nowMs: now,
1044
+ });
1045
+ // Past the cycle end, the stored row still carries its dates — the read
1046
+ // model reports them as expired, never as current capacity.
1047
+ const pastEnd = Date.parse(new Date(now + 8 * 24 * 3600_000).toISOString()) + 1000;
1048
+ const snap = await getCursorIndividualBillingSnapshot(pastEnd);
1049
+ assert.equal(snap.unavailableReason, "expired");
1050
+ assert.equal(snap.remainingPercent, null);
1051
+ const row = readCursorIndividualBillingRow();
1052
+ assert.equal(row?.cycleStart, "2026-09-01T00:00:00.000Z");
1053
+ assert.equal(row?.remainingPercent, 62.5);
1054
+ });
1055
+ test("401/429/network failures keep last-good data and never leak the credential", async () => {
1056
+ enable();
1057
+ fixtureCredential();
1058
+ const now = Date.now();
1059
+ const good = {
1060
+ baseUrl: MOCK_BASE,
1061
+ fetchImpl: mockFetch({ [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: { status: 200, body: liveUsagePayload(now) } }),
1062
+ nowMs: now,
1063
+ };
1064
+ await refreshCursorIndividualBilling(good);
1065
+ assert.equal(readCursorIndividualBillingRow()?.remainingPercent, 62.5);
1066
+ const later = now + 60_000;
1067
+ for (const routes of [
1068
+ { [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: { status: 401, body: {} } },
1069
+ { [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: { status: 429, body: {} } },
1070
+ { [CURSOR_INDIVIDUAL_USAGE_PATHS[0]]: new Error("socket hang up") },
1071
+ ]) {
1072
+ const obs = await readCursorIndividualBilling({ baseUrl: MOCK_BASE, fetchImpl: mockFetch(routes), nowMs: later });
1073
+ assert.ok(obs.failure, "failure is explicit");
1074
+ assert.equal(obs.billing.unavailableReason, "missing");
1075
+ assert.ok(!JSON.stringify(obs).includes(SECRET), "no credential material in the observation");
1076
+ await refreshCursorIndividualBilling({ baseUrl: MOCK_BASE, fetchImpl: mockFetch(routes), nowMs: later });
1077
+ const snap = await getCursorIndividualBillingSnapshot(later);
1078
+ assert.equal(snap.remainingPercent, 62.5);
1079
+ assert.equal(snap.unavailableReason, null);
1080
+ }
1081
+ });