agent-dealer 1.2.1 → 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 (57) hide show
  1. package/bundle/server/dist/adapters/linear-inbox.js +9 -5
  2. package/bundle/server/dist/capacity/adapter.js +2 -0
  3. package/bundle/server/dist/capacity/claude-events.js +45 -10
  4. package/bundle/server/dist/capacity/claude-events.test.js +7 -0
  5. package/bundle/server/dist/capacity/claude-local-cache.js +788 -0
  6. package/bundle/server/dist/capacity/claude-local-cache.test.js +520 -0
  7. package/bundle/server/dist/capacity/codex-app-server.js +100 -10
  8. package/bundle/server/dist/capacity/codex-app-server.test.js +195 -2
  9. package/bundle/server/dist/capacity/cursor-individual-credentials.js +425 -0
  10. package/bundle/server/dist/capacity/cursor-individual-credentials.test.js +369 -0
  11. package/bundle/server/dist/capacity/cursor-individual.js +1037 -0
  12. package/bundle/server/dist/capacity/cursor-individual.test.js +1081 -0
  13. package/bundle/server/dist/capacity/muse-host.js +748 -0
  14. package/bundle/server/dist/capacity/muse-host.test.js +480 -0
  15. package/bundle/server/dist/capacity/muse-lifecycle.test.js +97 -0
  16. package/bundle/server/dist/capacity/muse.js +389 -91
  17. package/bundle/server/dist/capacity/muse.test.js +100 -61
  18. package/bundle/server/dist/capacity/service.js +1 -0
  19. package/bundle/server/dist/coordinator/muse-spawn.js +143 -1
  20. package/bundle/server/dist/db/index.js +30 -0
  21. package/bundle/server/dist/db/schema.sql +27 -0
  22. package/bundle/server/dist/index.js +17 -2
  23. package/bundle/server/dist/repository/cursor-individual-billing.js +84 -0
  24. package/bundle/server/dist/repository/repository-mappings.js +46 -0
  25. package/bundle/server/dist/repository/repository-mappings.test.js +83 -0
  26. package/bundle/server/dist/repository/runtime-capacity.js +8 -3
  27. package/bundle/server/dist/repository/runtime-capacity.test.js +4 -0
  28. package/bundle/server/dist/routes/cursor-individual-billing.test.js +80 -0
  29. package/bundle/server/dist/routes/index.js +58 -9
  30. package/bundle/server/dist/routes/intake-linear.test.js +55 -1
  31. package/bundle/server/dist/routes/repository-mappings.js +22 -0
  32. package/bundle/server/dist/routes/repository-mappings.test.js +110 -0
  33. package/bundle/server/dist/routes/runtime-capacity.test.js +84 -8
  34. package/bundle/server/dist/runners/muse-code-jsonl.js +7 -1
  35. package/bundle/server/dist/runners/muse-serve-session.js +313 -0
  36. package/bundle/server/dist/runners/muse-serve-session.test.js +325 -0
  37. package/bundle/server/package.json +2 -2
  38. package/bundle/server/static-ui/assets/codex-YZSpL-SB.png +0 -0
  39. package/bundle/server/static-ui/assets/index-CYqRh_-S.css +1 -0
  40. package/bundle/server/static-ui/assets/index-UO4lHZw4.js +60 -0
  41. package/bundle/server/static-ui/assets/muse-CtdcC6ve.svg +33 -0
  42. package/bundle/server/static-ui/index.html +2 -2
  43. package/bundle/shared/dist/index.d.ts +6 -6
  44. package/bundle/shared/dist/linear-intake.d.ts +71 -0
  45. package/bundle/shared/dist/linear-intake.js +90 -0
  46. package/bundle/shared/dist/linear-intake.test.js +80 -1
  47. package/bundle/shared/dist/runtime-capacity.d.ts +89 -0
  48. package/bundle/shared/dist/runtime-capacity.js +60 -0
  49. package/bundle/shared/dist/runtime-capacity.test.js +1 -0
  50. package/bundle/shared/package.json +1 -1
  51. package/dist/doctor.d.ts +57 -0
  52. package/dist/doctor.js +211 -0
  53. package/dist/doctor.test.d.ts +1 -0
  54. package/dist/doctor.test.js +246 -0
  55. package/package.json +1 -1
  56. package/bundle/server/static-ui/assets/index-ChnI0F02.css +0 -1
  57. package/bundle/server/static-ui/assets/index-CyB1573Q.js +0 -60
@@ -1,5 +1,6 @@
1
- import { resolveLinearRepoLabels } from "@agent-dealer/shared";
1
+ import { resolveLinearRepoWithMappings } from "@agent-dealer/shared";
2
2
  import { DEFAULT_LINEAR_STATE_FILTER, getLinearIntakeConfig } from "../repository/intake-settings.js";
3
+ import { listRepositoryMappings } from "../repository/repository-mappings.js";
3
4
  import { linearGraphqlRequest } from "./linear-graphql.js";
4
5
  export { DEFAULT_LINEAR_STATE_FILTER };
5
6
  const PAGE_SIZE = 50;
@@ -20,7 +21,7 @@ async function linearQuery(operation, query, variables, opts) {
20
21
  * deterministic (one valid label resolves, zero is unresolved, several
21
22
  * conflict, a bad value is invalid) and never guesses from anything else.
22
23
  */
23
- export function nodeToCandidate(n) {
24
+ export function nodeToCandidate(n, mappings) {
24
25
  const labels = n.labels?.nodes.map((l) => l.name) ?? [];
25
26
  return {
26
27
  id: n.id,
@@ -31,7 +32,7 @@ export function nodeToCandidate(n) {
31
32
  state: n.state?.name,
32
33
  teamId: n.team?.id,
33
34
  labels,
34
- repoResolution: resolveLinearRepoLabels(labels),
35
+ repoResolution: resolveLinearRepoWithMappings(labels, mappings ?? []),
35
36
  };
36
37
  }
37
38
  const ISSUE_FIELDS = `
@@ -106,7 +107,9 @@ export async function listLinearCandidates() {
106
107
  break;
107
108
  after = data.issues.pageInfo.endCursor;
108
109
  }
109
- return nodes.map(nodeToCandidate);
110
+ // NOT-260: mappings load once per list operation, not once per candidate.
111
+ const mappings = listRepositoryMappings();
112
+ return nodes.map((n) => nodeToCandidate(n, mappings));
110
113
  }
111
114
  export async function getLinearIssue(issueId) {
112
115
  const data = (await linearQuery("getLinearIssue", `query Issue($id: String!) {
@@ -114,7 +117,8 @@ export async function getLinearIssue(issueId) {
114
117
  }`, { id: issueId }));
115
118
  if (!data.issue)
116
119
  return null;
117
- return nodeToCandidate(data.issue);
120
+ // NOT-260: mappings load once per lookup operation.
121
+ return nodeToCandidate(data.issue, listRepositoryMappings());
118
122
  }
119
123
  const RELATION_PAGE_FIELDS = `
120
124
  nodes { type issue { id identifier state { name type } } }
@@ -50,6 +50,7 @@ export function normalizeAdapterWindow(runtime, reading, nowMs = Date.now()) {
50
50
  source: reading.source,
51
51
  unavailableReason,
52
52
  evidenceRef: reading.evidenceRef ?? null,
53
+ criticalRole: reading.criticalRole ?? null,
53
54
  };
54
55
  }
55
56
  /** Normalize one provider failure into the persistable snapshot shape. */
@@ -70,6 +71,7 @@ export function normalizeUnavailableWindow(runtime, reading, nowMs = Date.now())
70
71
  source: "unavailable",
71
72
  unavailableReason: reading.reason,
72
73
  evidenceRef: null,
74
+ criticalRole: reading.criticalRole ?? null,
73
75
  };
74
76
  }
75
77
  // ---------------------------------------------------------------------------
@@ -170,6 +170,22 @@ export function inferClaudeDurationMinutes(bucket) {
170
170
  return 10080;
171
171
  return null;
172
172
  }
173
+ /**
174
+ * The account-wide critical window's identity, decided here — once,
175
+ * authoritatively — and never re-derived downstream. Deliberately an exact
176
+ * match, unlike `inferClaudeDurationMinutes`'s loose regex: a model-specific
177
+ * extra like `seven_day_sonnet`/`seven_day_opus` shares the 10,080-minute
178
+ * duration with the real account-wide `seven_day`/`weekly` bucket but is
179
+ * not the critical window, so identity can't reuse that regex.
180
+ */
181
+ export function claudeCriticalRole(bucket) {
182
+ const b = bucket.trim().toLowerCase();
183
+ if (b === "five_hour")
184
+ return "five_hour";
185
+ if (b === "seven_day" || b === "weekly")
186
+ return "weekly";
187
+ return null;
188
+ }
173
189
  function windowReadingFromEntry(bucket, entry, observedAt) {
174
190
  const cleanBucket = bucket.trim();
175
191
  if (!cleanBucket)
@@ -218,6 +234,7 @@ function windowReadingFromEntry(bucket, entry, observedAt) {
218
234
  observedAt,
219
235
  source: "observed_event",
220
236
  evidenceRef: CLAUDE_UNIFIED_WINDOWS_EVIDENCE_REF,
237
+ criticalRole: claudeCriticalRole(cleanBucket),
221
238
  };
222
239
  }
223
240
  /**
@@ -303,20 +320,21 @@ export function extractClaudeCapacityFromEvents(events, nowMs = Date.now(), fall
303
320
  return { runtime: CLAUDE_RUNTIME, windows: [...byKey.values()], unavailable: [] };
304
321
  }
305
322
  /**
306
- * Persist naturally observed unified windows as normalized capacity
307
- * snapshots (per-window upserts — siblings not in this observation are left
308
- * untouched, and a stored row newer than the incoming reading is never
309
- * overwritten). Returns the number of windows persisted, or null when the
310
- * runtime is not Claude or no usable windows were observed. Never touches
311
- * `runtime_availability`.
323
+ * Persist Claude window readings as normalized capacity snapshots
324
+ * (per-window upserts — siblings not in this observation are left untouched,
325
+ * and a stored row newer than the incoming reading is never overwritten).
326
+ * Shared by session-event ingestion and the local-cache/probe path (NOT-268):
327
+ * both sources write the same `claude_unified_*` window keys, so the freshest
328
+ * observation wins per window regardless of which source produced it.
329
+ * Returns the number of windows persisted, or null when no usable window was
330
+ * observed. Never touches `runtime_availability`.
312
331
  */
313
- export function recordClaudeCapacityFromEvents(events, runtime, nowMs = Date.now(), fallbackObservedAtMs = nowMs) {
332
+ export function recordClaudeWindowReadings(readings, runtime, nowMs = Date.now()) {
314
333
  if (runtime !== CLAUDE_RUNTIME)
315
334
  return null;
316
- const result = extractClaudeCapacityFromEvents(events, nowMs, fallbackObservedAtMs);
317
- if (!result)
335
+ if (readings.length === 0)
318
336
  return null;
319
- const normalized = result.windows.map((w) => normalizeAdapterWindow(runtime, w, nowMs));
337
+ const normalized = readings.map((w) => normalizeAdapterWindow(runtime, w, nowMs));
320
338
  const persistable = normalized.filter((n) => n.unavailableReason === null && n.remainingPercent !== null);
321
339
  if (persistable.length === 0)
322
340
  return null;
@@ -350,9 +368,26 @@ export function recordClaudeCapacityFromEvents(events, runtime, nowMs = Date.now
350
368
  source: w.source,
351
369
  unavailableReason: w.unavailableReason,
352
370
  evidenceRef: w.evidenceRef,
371
+ criticalRole: w.criticalRole,
353
372
  })));
354
373
  return fresh.length;
355
374
  }
375
+ /**
376
+ * Persist naturally observed unified windows as normalized capacity
377
+ * snapshots. Status is irrelevant — allowed events carry the utilization
378
+ * signal; rejected ones additionally drive NOT-111 elsewhere. Thin wrapper
379
+ * over `recordClaudeWindowReadings`: per-window upserts, newer-wins, never
380
+ * touches `runtime_availability`. Returns the number of windows persisted,
381
+ * or null when the runtime is not Claude or no usable windows were observed.
382
+ */
383
+ export function recordClaudeCapacityFromEvents(events, runtime, nowMs = Date.now(), fallbackObservedAtMs = nowMs) {
384
+ if (runtime !== CLAUDE_RUNTIME)
385
+ return null;
386
+ const result = extractClaudeCapacityFromEvents(events, nowMs, fallbackObservedAtMs);
387
+ if (!result)
388
+ return null;
389
+ return recordClaudeWindowReadings(result.windows, runtime, nowMs);
390
+ }
356
391
  /**
357
392
  * File variant for session-end ingestion from a Dealer-managed Claude log.
358
393
  * `fallbackObservedAtMs` is the session's spawn start. Returns the persisted
@@ -67,6 +67,8 @@ test("five-hour and seven-day windows persist independently", () => {
67
67
  assert.equal(fiveHour.remainingPercent, 83);
68
68
  assert.equal(sevenDay.remainingPercent, 58);
69
69
  assert.notEqual(fiveHour.resetAt, sevenDay.resetAt);
70
+ assert.equal(fiveHour.criticalRole, "five_hour");
71
+ assert.equal(sevenDay.criticalRole, "weekly");
70
72
  // A later partial observation (five_hour only) upserts its row and leaves
71
73
  // the seven-day sibling untouched.
72
74
  const partial = [
@@ -94,6 +96,11 @@ test("model-specific and overage windows remain distinguishable", () => {
94
96
  assert.equal(overage.durationMinutes, 10080);
95
97
  assert.equal(sonnet.remainingPercent, 39);
96
98
  assert.equal(overage.remainingPercent, 50);
99
+ // Same 10,080-minute duration as the real account-wide weekly window, but
100
+ // neither model-specific/overage extra is ever the critical window.
101
+ assert.equal(sonnet.criticalRole, null);
102
+ assert.equal(overage.criticalRole, null);
103
+ assert.equal(plain.criticalRole, "weekly");
97
104
  });
98
105
  test("legacy rejected fixture still caps (NOT-111) and fabricates no capacity", () => {
99
106
  const events = parseNdjson(legacyRejectedFixture);