agent-dealer 1.2.2 → 1.2.4
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/bundle/server/dist/capacity/claude-events.js +27 -10
- package/bundle/server/dist/capacity/claude-local-cache.js +794 -0
- package/bundle/server/dist/capacity/claude-local-cache.test.js +524 -0
- package/bundle/server/dist/capacity/cursor-individual-credentials.js +203 -16
- package/bundle/server/dist/capacity/cursor-individual-credentials.test.js +186 -2
- package/bundle/server/dist/capacity/cursor-individual.js +69 -2
- package/bundle/server/dist/capacity/cursor-individual.test.js +268 -2
- package/bundle/server/dist/capacity/muse-host.js +778 -0
- package/bundle/server/dist/capacity/muse-host.test.js +480 -0
- package/bundle/server/dist/capacity/muse-lifecycle.test.js +96 -0
- package/bundle/server/dist/capacity/muse-probe.js +250 -0
- package/bundle/server/dist/capacity/muse-probe.test.js +183 -0
- package/bundle/server/dist/capacity/muse.js +320 -63
- package/bundle/server/dist/capacity/muse.test.js +8 -51
- package/bundle/server/dist/coordinator/muse-spawn.js +143 -1
- package/bundle/server/dist/index.js +15 -2
- package/bundle/server/dist/routes/index.js +25 -9
- package/bundle/server/dist/routes/runtime-capacity.test.js +20 -7
- package/bundle/server/dist/runners/muse-code-jsonl.js +7 -1
- package/bundle/server/dist/runners/muse-serve-session.js +314 -0
- package/bundle/server/dist/runners/muse-serve-session.test.js +325 -0
- package/bundle/server/package.json +2 -2
- package/bundle/server/static-ui/assets/codex-YZSpL-SB.png +0 -0
- package/bundle/server/static-ui/assets/{index-DUYzboWh.css → index-CYqRh_-S.css} +1 -1
- package/bundle/server/static-ui/assets/index-UO4lHZw4.js +60 -0
- package/bundle/server/static-ui/assets/muse-CtdcC6ve.svg +33 -0
- package/bundle/server/static-ui/index.html +2 -2
- package/bundle/shared/package.json +1 -1
- package/dist/doctor.d.ts +72 -0
- package/dist/doctor.js +235 -1
- package/dist/doctor.test.d.ts +1 -0
- package/dist/doctor.test.js +276 -0
- package/package.json +1 -1
- package/bundle/server/static-ui/assets/index-0GDYfbaM.js +0 -60
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// packages/cli/src/doctor.test.ts
|
|
2
|
+
//
|
|
3
|
+
// NOT-267: doctor reports which local login the experimental Cursor
|
|
4
|
+
// Individual adapter would use — desktop, Agent fallback, or none — using
|
|
5
|
+
// static lines only. Tests never touch the real local login: every case
|
|
6
|
+
// points the resolution at temporary fixtures.
|
|
7
|
+
import { test, beforeEach, afterEach } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import Database from "better-sqlite3";
|
|
14
|
+
// Local mirror of the server module's env/key contract
|
|
15
|
+
// (packages/server/src/capacity/cursor-individual-credentials.ts). A static
|
|
16
|
+
// import is impossible — CLI `rootDir` cannot include server sources — so
|
|
17
|
+
// the mirror is pinned against the live server module by the contract test
|
|
18
|
+
// below: a rename there fails that test loudly instead of letting these
|
|
19
|
+
// fixtures silently stop applying (and probe the real local login).
|
|
20
|
+
const CURSOR_DESKTOP_ACCESS_TOKEN_KEY = "cursorAuth/accessToken";
|
|
21
|
+
const CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV = "CURSOR_INDIVIDUAL_CREDENTIAL_FILE";
|
|
22
|
+
const CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV = "CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE";
|
|
23
|
+
const CURSOR_INDIVIDUAL_HOME_ENV = "CURSOR_INDIVIDUAL_HOME";
|
|
24
|
+
test("fixture env/key names match the server module's live contract", async () => {
|
|
25
|
+
const { pathToFileURL, fileURLToPath } = await import("node:url");
|
|
26
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const candidates = [
|
|
28
|
+
path.resolve(here, "..", "..", "server", "src", "capacity", "cursor-individual-credentials.ts"),
|
|
29
|
+
path.resolve(here, "..", "..", "server", "dist", "capacity", "cursor-individual-credentials.js"),
|
|
30
|
+
];
|
|
31
|
+
let mod = null;
|
|
32
|
+
for (const file of candidates) {
|
|
33
|
+
try {
|
|
34
|
+
if (!fs.existsSync(file))
|
|
35
|
+
continue;
|
|
36
|
+
mod = (await import(pathToFileURL(file).href));
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
assert.ok(mod, "the server credentials module must be loadable for the contract pin");
|
|
44
|
+
assert.equal(mod.CURSOR_DESKTOP_ACCESS_TOKEN_KEY, CURSOR_DESKTOP_ACCESS_TOKEN_KEY);
|
|
45
|
+
assert.equal(mod.CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV, CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV);
|
|
46
|
+
assert.equal(mod.CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV, CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV);
|
|
47
|
+
assert.equal(mod.CURSOR_INDIVIDUAL_HOME_ENV, CURSOR_INDIVIDUAL_HOME_ENV);
|
|
48
|
+
});
|
|
49
|
+
import { CLAUDE_CAPACITY_CACHE_FILE_ENV, CLAUDE_CAPACITY_REFRESH_ENV, CLAUDE_CAPACITY_REFRESH_PAID_VALUE, CURSOR_INDIVIDUAL_LOGIN_LINES, MUSE_CAPACITY_REFRESH_ENV, MUSE_CAPACITY_REFRESH_MS_ENV, MUSE_CAPACITY_REFRESH_PAID_VALUE, checkClaudeCapacitySource, checkCursorIndividualLogin, describeClaudeCapacitySource, describeClaudeProbeOptIn, describeCursorIndividualLogin, describeMuseProbeOptIn, } from "./doctor.js";
|
|
50
|
+
const SECRET = "doctor-fixture-secret-must-never-print";
|
|
51
|
+
const USER_ID = "user_doctor_fixture";
|
|
52
|
+
function fixtureJwt(sub) {
|
|
53
|
+
const b64url = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
54
|
+
return `${b64url({ alg: "none" })}.${b64url({ sub })}.sig`;
|
|
55
|
+
}
|
|
56
|
+
let dir;
|
|
57
|
+
const saved = {};
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), "dealer-doctor-indiv-"));
|
|
60
|
+
for (const key of [
|
|
61
|
+
CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV,
|
|
62
|
+
CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV,
|
|
63
|
+
CURSOR_INDIVIDUAL_HOME_ENV,
|
|
64
|
+
CLAUDE_CAPACITY_CACHE_FILE_ENV,
|
|
65
|
+
]) {
|
|
66
|
+
saved[key] = process.env[key];
|
|
67
|
+
delete process.env[key];
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
72
|
+
if (value === undefined)
|
|
73
|
+
delete process.env[key];
|
|
74
|
+
else
|
|
75
|
+
process.env[key] = value;
|
|
76
|
+
}
|
|
77
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
78
|
+
});
|
|
79
|
+
test("describe maps each credential state to its static line", () => {
|
|
80
|
+
assert.deepEqual(describeCursorIndividualLogin({ present: true, source: "desktop" }), {
|
|
81
|
+
kind: "desktop",
|
|
82
|
+
line: CURSOR_INDIVIDUAL_LOGIN_LINES.desktop,
|
|
83
|
+
});
|
|
84
|
+
assert.deepEqual(describeCursorIndividualLogin({ present: true, source: "agent" }), {
|
|
85
|
+
kind: "agent",
|
|
86
|
+
line: CURSOR_INDIVIDUAL_LOGIN_LINES.agent,
|
|
87
|
+
});
|
|
88
|
+
const degraded = [
|
|
89
|
+
{ present: false, source: null },
|
|
90
|
+
{ present: false, source: "desktop" },
|
|
91
|
+
{ present: true, source: null },
|
|
92
|
+
null,
|
|
93
|
+
undefined,
|
|
94
|
+
];
|
|
95
|
+
for (const state of degraded) {
|
|
96
|
+
assert.deepEqual(describeCursorIndividualLogin(state), {
|
|
97
|
+
kind: "none",
|
|
98
|
+
line: CURSOR_INDIVIDUAL_LOGIN_LINES.none,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
test("the three lines name the three states and carry no paths or secrets", () => {
|
|
103
|
+
assert.match(CURSOR_INDIVIDUAL_LOGIN_LINES.desktop, /desktop login found/);
|
|
104
|
+
assert.match(CURSOR_INDIVIDUAL_LOGIN_LINES.agent, /Agent login found/);
|
|
105
|
+
assert.match(CURSOR_INDIVIDUAL_LOGIN_LINES.none, /no usable local login/);
|
|
106
|
+
for (const line of Object.values(CURSOR_INDIVIDUAL_LOGIN_LINES)) {
|
|
107
|
+
assert.ok(!line.includes("/"), "no filesystem paths in doctor output");
|
|
108
|
+
assert.ok(!line.includes(SECRET));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
function fixtureDesktopDb(token) {
|
|
112
|
+
const file = path.join(dir, "state.vscdb");
|
|
113
|
+
const db = new Database(file);
|
|
114
|
+
try {
|
|
115
|
+
db.exec("CREATE TABLE ItemTable (key TEXT PRIMARY KEY, value TEXT)");
|
|
116
|
+
db.prepare("INSERT INTO ItemTable (key, value) VALUES (?, ?)").run(CURSOR_DESKTOP_ACCESS_TOKEN_KEY, token);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
db.close();
|
|
120
|
+
}
|
|
121
|
+
process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = file;
|
|
122
|
+
return file;
|
|
123
|
+
}
|
|
124
|
+
test("check reports desktop login found for a fixture state.vscdb", async () => {
|
|
125
|
+
process.env[CURSOR_INDIVIDUAL_HOME_ENV] = dir;
|
|
126
|
+
const jwt = fixtureJwt(USER_ID);
|
|
127
|
+
const dbFile = fixtureDesktopDb(jwt);
|
|
128
|
+
const report = await checkCursorIndividualLogin();
|
|
129
|
+
assert.equal(report.kind, "desktop");
|
|
130
|
+
assert.equal(report.line, CURSOR_INDIVIDUAL_LOGIN_LINES.desktop);
|
|
131
|
+
assert.ok(!report.line.includes(jwt), "token material must not print");
|
|
132
|
+
assert.ok(!report.line.includes(dbFile), "database paths must not print");
|
|
133
|
+
});
|
|
134
|
+
test("check reports Agent login found when only the Agent auth file answers", async () => {
|
|
135
|
+
process.env[CURSOR_INDIVIDUAL_HOME_ENV] = dir;
|
|
136
|
+
process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = path.join(dir, "missing.vscdb");
|
|
137
|
+
const agentFile = path.join(dir, ".cursor", "auth.json");
|
|
138
|
+
fs.mkdirSync(path.dirname(agentFile), { recursive: true });
|
|
139
|
+
fs.writeFileSync(agentFile, JSON.stringify({ token: SECRET, userId: USER_ID }));
|
|
140
|
+
const report = await checkCursorIndividualLogin();
|
|
141
|
+
assert.equal(report.kind, "agent");
|
|
142
|
+
assert.equal(report.line, CURSOR_INDIVIDUAL_LOGIN_LINES.agent);
|
|
143
|
+
assert.ok(!report.line.includes(SECRET), "token material must not print");
|
|
144
|
+
assert.ok(!report.line.includes(agentFile), "credential paths must not print");
|
|
145
|
+
});
|
|
146
|
+
test("check reports no usable local login when neither source answers", async () => {
|
|
147
|
+
process.env[CURSOR_INDIVIDUAL_HOME_ENV] = dir;
|
|
148
|
+
process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = path.join(dir, "missing.vscdb");
|
|
149
|
+
const report = await checkCursorIndividualLogin();
|
|
150
|
+
assert.equal(report.kind, "none");
|
|
151
|
+
assert.equal(report.line, CURSOR_INDIVIDUAL_LOGIN_LINES.none);
|
|
152
|
+
});
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// NOT-268: Claude capacity source reporting. Tests never touch the real
|
|
155
|
+
// ~/.claude.json: every case points the override at a temporary fixture.
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
test("fixture cache-file env name matches the server module's live contract", async () => {
|
|
158
|
+
const { fileURLToPath } = await import("node:url");
|
|
159
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
160
|
+
const candidates = [
|
|
161
|
+
path.resolve(here, "..", "..", "server", "src", "capacity", "claude-local-cache.ts"),
|
|
162
|
+
path.resolve(here, "..", "..", "server", "dist", "capacity", "claude-local-cache.js"),
|
|
163
|
+
];
|
|
164
|
+
const src = candidates.find((file) => fs.existsSync(file));
|
|
165
|
+
assert.ok(src, "the server cache module must exist for the contract pin");
|
|
166
|
+
const text = fs.readFileSync(src, "utf8");
|
|
167
|
+
assert.ok(text.includes(`"${CLAUDE_CAPACITY_CACHE_FILE_ENV}"`));
|
|
168
|
+
assert.ok(text.includes(`"${CLAUDE_CAPACITY_REFRESH_ENV}"`));
|
|
169
|
+
assert.ok(text.includes(`"${CLAUDE_CAPACITY_REFRESH_PAID_VALUE}"`));
|
|
170
|
+
});
|
|
171
|
+
test("describe maps each cache state to its static line", () => {
|
|
172
|
+
const fresh = describeClaudeCapacitySource({ present: true, ageMs: 5 * 60_000 });
|
|
173
|
+
assert.equal(fresh.kind, "fresh");
|
|
174
|
+
assert.match(fresh.line, /fresh/);
|
|
175
|
+
const stale = describeClaudeCapacitySource({ present: true, ageMs: 61 * 60_000 });
|
|
176
|
+
assert.equal(stale.kind, "stale");
|
|
177
|
+
assert.match(stale.line, /stale/);
|
|
178
|
+
const degraded = [
|
|
179
|
+
{ present: false, ageMs: null },
|
|
180
|
+
{ present: false, ageMs: 0 },
|
|
181
|
+
{ present: true, ageMs: null },
|
|
182
|
+
{ present: true, ageMs: -1 },
|
|
183
|
+
null,
|
|
184
|
+
undefined,
|
|
185
|
+
];
|
|
186
|
+
for (const state of degraded) {
|
|
187
|
+
const report = describeClaudeCapacitySource(state);
|
|
188
|
+
assert.equal(report.kind, "missing");
|
|
189
|
+
assert.match(report.line, /no local 5H\/1W cache/);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
test("probe warning appears for the default and explicit paid value", () => {
|
|
193
|
+
for (const setting of [undefined, "", CLAUDE_CAPACITY_REFRESH_PAID_VALUE]) {
|
|
194
|
+
const armed = describeClaudeProbeOptIn(setting);
|
|
195
|
+
assert.ok(armed);
|
|
196
|
+
assert.match(armed, /enabled/);
|
|
197
|
+
assert.match(armed, /≤\$0\.01/);
|
|
198
|
+
assert.match(armed, /=off/);
|
|
199
|
+
}
|
|
200
|
+
assert.equal(describeClaudeProbeOptIn("off"), null);
|
|
201
|
+
assert.equal(describeClaudeProbeOptIn("auto"), null);
|
|
202
|
+
});
|
|
203
|
+
test("Muse paid-fallback warning matches server defaults and legacy no-spend precedence", () => {
|
|
204
|
+
for (const setting of [undefined, "", MUSE_CAPACITY_REFRESH_PAID_VALUE]) {
|
|
205
|
+
const armed = describeMuseProbeOptIn(setting, undefined);
|
|
206
|
+
assert.ok(armed);
|
|
207
|
+
assert.match(armed, /Muse capacity paid fallback: enabled/);
|
|
208
|
+
assert.match(armed, /restricted contributor-model turn/);
|
|
209
|
+
assert.match(armed, new RegExp(`${MUSE_CAPACITY_REFRESH_ENV}=off`));
|
|
210
|
+
}
|
|
211
|
+
assert.equal(describeMuseProbeOptIn("off", undefined), null);
|
|
212
|
+
assert.equal(describeMuseProbeOptIn("auto", undefined), null);
|
|
213
|
+
assert.equal(describeMuseProbeOptIn(undefined, "off"), null);
|
|
214
|
+
assert.equal(describeMuseProbeOptIn("", "off"), null);
|
|
215
|
+
assert.ok(describeMuseProbeOptIn(MUSE_CAPACITY_REFRESH_PAID_VALUE, "off"), "the explicit new paid value overrides the legacy free-refresh switch");
|
|
216
|
+
});
|
|
217
|
+
test("Muse doctor env names and paid value match the server contract", () => {
|
|
218
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
219
|
+
const candidates = [
|
|
220
|
+
path.resolve(here, "..", "..", "server", "src", "capacity", "muse-probe.ts"),
|
|
221
|
+
path.resolve(here, "..", "..", "server", "dist", "capacity", "muse-probe.js"),
|
|
222
|
+
];
|
|
223
|
+
const src = candidates.find((file) => fs.existsSync(file));
|
|
224
|
+
assert.ok(src, "the server Muse probe module must exist for the contract pin");
|
|
225
|
+
const text = fs.readFileSync(src, "utf8");
|
|
226
|
+
assert.ok(text.includes(`"${MUSE_CAPACITY_REFRESH_ENV}"`));
|
|
227
|
+
assert.ok(text.includes(`"${MUSE_CAPACITY_REFRESH_PAID_VALUE}"`));
|
|
228
|
+
assert.ok(text.includes("AGENT_DEALER_MUSE_CAPACITY_REFRESH_MS"));
|
|
229
|
+
assert.equal(MUSE_CAPACITY_REFRESH_MS_ENV, "AGENT_DEALER_MUSE_CAPACITY_REFRESH_MS");
|
|
230
|
+
});
|
|
231
|
+
test("check reads fetchedAtMs from the config key, never mtime or the real home", async () => {
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
// Full config envelope with sensitive siblings: freshness comes from the
|
|
234
|
+
// subtree timestamp even though mtime is two days old (mtime must be
|
|
235
|
+
// ignored — unrelated config writes touch it). The line must not leak
|
|
236
|
+
// the sibling values.
|
|
237
|
+
const freshFile = path.join(dir, "cached-fresh.json");
|
|
238
|
+
fs.writeFileSync(freshFile, JSON.stringify({
|
|
239
|
+
userID: "user-secret-must-never-print",
|
|
240
|
+
email: "someone@example.com",
|
|
241
|
+
cachedUsageUtilization: { fetchedAtMs: now - 5 * 60_000, accountUuid: "acct-secret" },
|
|
242
|
+
}));
|
|
243
|
+
const ancient = new Date(now - 2 * 24 * 3600_000);
|
|
244
|
+
fs.utimesSync(freshFile, ancient, ancient);
|
|
245
|
+
process.env[CLAUDE_CAPACITY_CACHE_FILE_ENV] = freshFile;
|
|
246
|
+
const fresh = await checkClaudeCapacitySource(now);
|
|
247
|
+
assert.equal(fresh.kind, "fresh");
|
|
248
|
+
assert.ok(!fresh.line.includes("user-secret-must-never-print"));
|
|
249
|
+
assert.ok(!fresh.line.includes("acct-secret"));
|
|
250
|
+
// Bare cache object (no envelope) still works; a brand-new mtime cannot
|
|
251
|
+
// rescue a stale timestamp.
|
|
252
|
+
const staleFile = path.join(dir, "cached-stale.json");
|
|
253
|
+
fs.writeFileSync(staleFile, JSON.stringify({ fetchedAtMs: 1 }));
|
|
254
|
+
const justNow = new Date(now);
|
|
255
|
+
fs.utimesSync(staleFile, justNow, justNow);
|
|
256
|
+
process.env[CLAUDE_CAPACITY_CACHE_FILE_ENV] = staleFile;
|
|
257
|
+
const stale = await checkClaudeCapacitySource(now);
|
|
258
|
+
assert.equal(stale.kind, "stale");
|
|
259
|
+
// Missing file, garbage JSON, future timestamp, and a config without the
|
|
260
|
+
// key all report missing.
|
|
261
|
+
process.env[CLAUDE_CAPACITY_CACHE_FILE_ENV] = path.join(dir, "missing.json");
|
|
262
|
+
assert.equal((await checkClaudeCapacitySource(now)).kind, "missing");
|
|
263
|
+
const garbageFile = path.join(dir, "cached-garbage.json");
|
|
264
|
+
fs.writeFileSync(garbageFile, "{not json");
|
|
265
|
+
process.env[CLAUDE_CAPACITY_CACHE_FILE_ENV] = garbageFile;
|
|
266
|
+
assert.equal((await checkClaudeCapacitySource(now)).kind, "missing");
|
|
267
|
+
const futureFile = path.join(dir, "cached-future.json");
|
|
268
|
+
fs.writeFileSync(futureFile, JSON.stringify({ cachedUsageUtilization: { fetchedAtMs: now + 60_000 } }));
|
|
269
|
+
process.env[CLAUDE_CAPACITY_CACHE_FILE_ENV] = futureFile;
|
|
270
|
+
assert.equal((await checkClaudeCapacitySource(now)).kind, "missing");
|
|
271
|
+
const keylessFile = path.join(dir, "cached-keyless.json");
|
|
272
|
+
fs.writeFileSync(keylessFile, JSON.stringify({ userID: "x", projects: {} }));
|
|
273
|
+
process.env[CLAUDE_CAPACITY_CACHE_FILE_ENV] = keylessFile;
|
|
274
|
+
assert.equal((await checkClaudeCapacitySource(now)).kind, "missing");
|
|
275
|
+
delete process.env[CLAUDE_CAPACITY_CACHE_FILE_ENV];
|
|
276
|
+
});
|