@stage5/lumine 0.2.53 → 0.2.55

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/lib/admin.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
+ import { once } from "node:events";
2
3
  import { readFileSync } from "node:fs";
3
4
  import { assertAuthScope, resolveAuth } from "./auth.js";
4
5
  import { requestJson } from "./http.js";
@@ -8,7 +9,13 @@ import {
8
9
  validateNewsEditorial,
9
10
  writeNewsClaimArtifacts,
10
11
  } from "./admin-news.js";
11
- import { runAutomaticPagination, runBatchSkips } from "./admin-workflows.js";
12
+ import {
13
+ forEachPaginatedResultItem,
14
+ getPaginatedResultStorage,
15
+ runAutomaticPagination,
16
+ runBatchSkips,
17
+ writePaginatedResultJson,
18
+ } from "./admin-workflows.js";
12
19
  import {
13
20
  parseBuildReviewReceipt,
14
21
  runManagedBuildReview,
@@ -380,9 +387,25 @@ export function assertRecommendationWindowResult({ operation, result }) {
380
387
  }
381
388
  }
382
389
 
383
- function finishAdminOutput({ options, operation, result }) {
390
+ async function finishAdminOutput({ options, operation, result }) {
391
+ const paginationStorage = getPaginatedResultStorage(result);
384
392
  if (options.json) {
385
- console.log(JSON.stringify(result));
393
+ if (paginationStorage) {
394
+ await writePaginatedResultJson({
395
+ result,
396
+ write: async (chunk) => {
397
+ if (!process.stdout.write(chunk)) {
398
+ await once(process.stdout, "drain");
399
+ }
400
+ },
401
+ });
402
+ } else {
403
+ console.log(JSON.stringify(result));
404
+ }
405
+ return result;
406
+ }
407
+ if (paginationStorage) {
408
+ await printSpooledAdminResult({ result, storage: paginationStorage });
386
409
  return result;
387
410
  }
388
411
  printAdminResult({ operation, result });
@@ -814,6 +837,108 @@ export function parseAdminOperation(options) {
814
837
  );
815
838
  }
816
839
 
840
+ if (namespace === "sponsor" || namespace === "sponsors") {
841
+ if (action === "applications") {
842
+ if (!target || target === "list") {
843
+ return readOperation(
844
+ "sponsor.applications.list",
845
+ withQuery("/cli/admin/sponsors/applications", {
846
+ status: options.adminStatus || "pending",
847
+ limit: options.limit,
848
+ }),
849
+ );
850
+ }
851
+ if (target === "review") {
852
+ const applicationId = parseRequiredInteger(
853
+ extra,
854
+ "Sponsor application ID",
855
+ 1,
856
+ );
857
+ const decision = parseChoice(
858
+ options.adminDecision || options.adminStatus,
859
+ "--decision",
860
+ ["approve", "reject"],
861
+ );
862
+ return writeOperation(
863
+ "sponsor.application.review",
864
+ "POST",
865
+ `/cli/admin/sponsors/applications/${applicationId}/review`,
866
+ { decision, note: options.note || undefined },
867
+ );
868
+ }
869
+ }
870
+ if (action === "status" && target === "set") {
871
+ const sponsorUserId = parseRequiredInteger(extra, "Sponsor user ID", 1);
872
+ const status = parseChoice(options.adminStatus, "--status", [
873
+ "probationary",
874
+ "trusted",
875
+ "suspended",
876
+ "revoked",
877
+ ]);
878
+ return writeOperation(
879
+ "sponsor.status.set",
880
+ "PUT",
881
+ `/cli/admin/sponsors/${sponsorUserId}/status`,
882
+ { status, note: options.note || undefined },
883
+ );
884
+ }
885
+ if (action === "integrity") {
886
+ if (target === "status") {
887
+ return readOperation(
888
+ "sponsor.integrity.status",
889
+ "/cli/admin/daily-runs/sponsor-integrity/status",
890
+ );
891
+ }
892
+ if (target === "scan") {
893
+ return writeOperation(
894
+ "sponsor.integrity.scan",
895
+ "POST",
896
+ "/cli/admin/daily-runs/sponsor-integrity/scan",
897
+ {},
898
+ );
899
+ }
900
+ if (target === "cases") {
901
+ return readOperation(
902
+ "sponsor.integrity.cases",
903
+ withQuery("/cli/admin/daily-runs/sponsor-integrity/cases", {
904
+ status: options.adminStatus || "open",
905
+ limit: options.limit,
906
+ }),
907
+ );
908
+ }
909
+ if (target === "get") {
910
+ const caseId = parseRequiredInteger(extra, "Integrity case ID", 1);
911
+ return readOperation(
912
+ "sponsor.integrity.get",
913
+ `/cli/admin/daily-runs/sponsor-integrity/cases/${caseId}`,
914
+ );
915
+ }
916
+ if (target === "review") {
917
+ const caseId = parseRequiredInteger(extra, "Integrity case ID", 1);
918
+ const decision = parseChoice(options.adminDecision, "--decision", [
919
+ "clear",
920
+ "hold",
921
+ "flag",
922
+ "disqualify",
923
+ ]);
924
+ if (decision !== "clear" && !String(options.note || "").trim()) {
925
+ throw cliValidationError(
926
+ "Sponsor-integrity hold, flag, and disqualify decisions require --note <evidence>.",
927
+ );
928
+ }
929
+ return writeOperation(
930
+ "sponsor.integrity.review",
931
+ "POST",
932
+ `/cli/admin/daily-runs/sponsor-integrity/cases/${caseId}/review`,
933
+ { decision, note: options.note || undefined },
934
+ );
935
+ }
936
+ }
937
+ throw cliValidationError(
938
+ "Usage: lumine admin sponsor applications list|review | sponsor status set | sponsor integrity status|scan|cases|get|review.",
939
+ );
940
+ }
941
+
817
942
  if (namespace === "daily-run") {
818
943
  if (action === "start") {
819
944
  return writeOperation(
@@ -1490,7 +1615,7 @@ export function parseAdminOperation(options) {
1490
1615
  }
1491
1616
 
1492
1617
  throw cliValidationError(
1493
- "Usage: lumine admin identity|economy|rescue|daily-run|escalation|todo|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|ai-costs|media-costs|bot-output|notable ...",
1618
+ "Usage: lumine admin identity|economy|rescue|sponsor|daily-run|escalation|todo|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|ai-costs|media-costs|bot-output|notable ...",
1494
1619
  );
1495
1620
  }
1496
1621
 
@@ -2049,6 +2174,16 @@ function parseRequiredInteger(
2049
2174
  return number;
2050
2175
  }
2051
2176
 
2177
+ function parseChoice(value, label, choices) {
2178
+ const normalized = String(value || "")
2179
+ .trim()
2180
+ .toLowerCase();
2181
+ if (!choices.includes(normalized)) {
2182
+ throw cliValidationError(`${label} must be ${choices.join(", ")}.`);
2183
+ }
2184
+ return normalized;
2185
+ }
2186
+
2052
2187
  function defaultDailyRunKey() {
2053
2188
  const parts = new Intl.DateTimeFormat("en-CA", {
2054
2189
  timeZone: "Asia/Bangkok",
@@ -2263,8 +2398,132 @@ function printAdminMonthlyMediaCosts(monthlyMediaCosts) {
2263
2398
  );
2264
2399
  }
2265
2400
 
2401
+ async function printSpooledAdminResult({ result, storage }) {
2402
+ const data = result?.data || {};
2403
+ const count = Number(storage.candidateCount || 0);
2404
+ if (storage.collectionKey === "subjects") {
2405
+ console.log(`${count} subject(s):`);
2406
+ await forEachPaginatedResultItem(result, async (subject) => {
2407
+ console.log(
2408
+ `#${subject.id} ${subject.title || "(untitled)"} — ${subject.author?.username || "unknown"} — effort ${subject.effortLevel ?? "unknown"}`,
2409
+ );
2410
+ console.log(` ${subject.url}`);
2411
+ });
2412
+ } else if (storage.collectionKey === "events") {
2413
+ console.log(`${count} audit event(s):`);
2414
+ await forEachPaginatedResultItem(result, async (event) => {
2415
+ const target =
2416
+ event.targetType && event.targetId
2417
+ ? ` ${event.targetType}:${event.targetId}`
2418
+ : "";
2419
+ console.log(
2420
+ `#${event.id} run ${event.runId ?? "-"} ${event.action}${target} — ${event.result}${event.changed === true ? " (changed)" : event.changed === false ? " (no change)" : ""}`,
2421
+ );
2422
+ });
2423
+ } else if (storage.collectionKey === "comments") {
2424
+ console.log(`${count} comment(s) for ${data.subject?.url || "this target"}:`);
2425
+ await forEachPaginatedResultItem(result, async (comment) => {
2426
+ console.log(
2427
+ `#${comment.id} ${comment.author?.username || "unknown"}: ${comment.content || "(empty)"}`,
2428
+ );
2429
+ });
2430
+ } else if (storage.collectionKey === "items") {
2431
+ console.log(`${count} recommendation candidate(s):`);
2432
+ await forEachPaginatedResultItem(result, async (item) => {
2433
+ console.log(`#${item.contentId} ${item.contentType}`);
2434
+ if (item.url || item.subjectUrl) {
2435
+ console.log(` ${item.url || item.subjectUrl}`);
2436
+ }
2437
+ });
2438
+ } else if (storage.collectionKey === "builds") {
2439
+ console.log(`${count} Build candidate(s):`);
2440
+ await forEachPaginatedResultItem(result, async (build) => {
2441
+ console.log(`#${build.id} ${build.title || "(untitled)"}`);
2442
+ if (build.url) console.log(` ${build.url}`);
2443
+ });
2444
+ } else {
2445
+ console.log(
2446
+ `${count} ${storage.collectionKey} candidate(s) saved at ${storage.spoolPath}.`,
2447
+ );
2448
+ }
2449
+ printPagination(data.pagination);
2450
+ }
2451
+
2266
2452
  function printAdminResult({ operation, result }) {
2267
2453
  const data = result?.data || {};
2454
+ if (Array.isArray(data.applications)) {
2455
+ console.log(`${data.applications.length} sponsor application(s):`);
2456
+ for (const application of data.applications) {
2457
+ console.log(
2458
+ ` #${application.id} ${String(application.status).toUpperCase()} · ${application.username || `user ${application.userId}`} · agreement ${application.agreementVersion || "unknown"}`,
2459
+ );
2460
+ }
2461
+ return;
2462
+ }
2463
+ if (data.application) {
2464
+ console.log(
2465
+ `Sponsor application #${data.application.id}: ${String(data.application.status).toUpperCase()} · ${data.application.username || `user ${data.application.userId}`}`,
2466
+ );
2467
+ return;
2468
+ }
2469
+ if (data.sponsor) {
2470
+ console.log(
2471
+ `Sponsor ${data.sponsor.username || `user ${data.sponsor.userId}`}: ${String(data.sponsor.status).toUpperCase()}.`,
2472
+ );
2473
+ return;
2474
+ }
2475
+ if (data.scan) {
2476
+ const scan = data.scan;
2477
+ console.log(
2478
+ `Sponsor-integrity scan #${scan.id || "not started"}: ${scan.status || "not started"} · ${scan.scannedCount || 0} scanned · ${scan.selectedReviewCount || 0} selected · ${data.openCaseCount || 0} open.`,
2479
+ );
2480
+ if (Array.isArray(data.casesCreated) && data.casesCreated.length > 0) {
2481
+ console.log(
2482
+ `Created ${data.casesCreated.length} review case(s) on this page.`,
2483
+ );
2484
+ }
2485
+ return;
2486
+ }
2487
+ if (Array.isArray(data.cases)) {
2488
+ console.log(`${data.cases.length} sponsor-integrity case(s):`);
2489
+ for (const item of data.cases) {
2490
+ const flags =
2491
+ Array.isArray(item.hardFlags) && item.hardFlags.length
2492
+ ? ` · flags=${item.hardFlags.join(",")}`
2493
+ : "";
2494
+ console.log(
2495
+ ` #${item.id} ${String(item.status).toUpperCase()} · job #${item.jobId} · ${item.sponsorUsername || `sponsor ${item.sponsorUserId}`}${flags}`,
2496
+ );
2497
+ }
2498
+ return;
2499
+ }
2500
+ if (data.case) {
2501
+ const item = data.case;
2502
+ console.log(
2503
+ `Sponsor-integrity case #${item.id}: ${String(item.status).toUpperCase()} · job #${item.job?.id} · ${item.sponsorUsername || `sponsor ${item.sponsorUserId}`}.`,
2504
+ );
2505
+ const relays = Array.isArray(item.relays) && item.relays.length > 0
2506
+ ? item.relays
2507
+ : item.relay
2508
+ ? [item.relay]
2509
+ : [];
2510
+ console.log(
2511
+ `Relays: ${relays.map((relay) => `${relay.kind || "initial_request"}: ${relay.summary || "(missing)"}`).join(" | ") || "(missing)"}`,
2512
+ );
2513
+ console.log(
2514
+ `Provenance: ${(item.agents || []).map((agent) => `${agent.role}:${agent.provider}:${agent.resolvedModel || "unresolved"}:${agent.resolvedEffort || "unresolved"}`).join(" · ") || "missing"}`,
2515
+ );
2516
+ console.log(
2517
+ `Artifact files: ${(item.artifactFiles || []).map((file) => file.path).join(", ") || "none"}`,
2518
+ );
2519
+ return;
2520
+ }
2521
+ if (data.caseId) {
2522
+ console.log(
2523
+ `Sponsor-integrity case #${data.caseId}: ${data.decision}${data.scanCompleted ? "; scan complete" : ""}.`,
2524
+ );
2525
+ return;
2526
+ }
2268
2527
  if (data.review) {
2269
2528
  console.log(
2270
2529
  `Confirmed Build #${data.review.buildId} runtime at published artifact #${data.review.publishedArtifactVersionId}.`,
@@ -2313,6 +2572,11 @@ function printAdminResult({ operation, result }) {
2313
2572
  );
2314
2573
  }
2315
2574
  printTodoItems(report.carryoverTodos?.items || [], "Unfinished work");
2575
+ if (report.sponsorIntegrity) {
2576
+ console.log(
2577
+ `Sponsor integrity: ${report.sponsorIntegrity.scan?.status || "not started"}; ${report.sponsorIntegrity.cases?.open || 0} open case(s); ${report.sponsorIntegrity.pendingApplications || 0} pending application(s).`,
2578
+ );
2579
+ }
2316
2580
  const surfaces = report.brief?.engagementPulse?.surfaces;
2317
2581
  if (surfaces && typeof surfaces === "object") {
2318
2582
  const deltas = Object.entries(surfaces)
@@ -79,8 +79,12 @@ export async function runClaudeCodeAgentPass({
79
79
  args,
80
80
  cwd: isolationDir,
81
81
  streamJson: true,
82
+ signal: options.agentAbortSignal,
82
83
  });
83
- return { finalText: result.finalText };
84
+ return {
85
+ finalText: result.finalText,
86
+ provenance: result.provenance,
87
+ };
84
88
  }
85
89
 
86
90
  export async function reviewClaudeCodeAgentLoop({
@@ -96,6 +100,7 @@ export async function reviewClaudeCodeAgentLoop({
96
100
  binary,
97
101
  cwd: isolationDir,
98
102
  streamJson: false,
103
+ signal: options.agentAbortSignal,
99
104
  args: [
100
105
  "-p",
101
106
  "--output-format",
@@ -129,16 +134,22 @@ export async function reviewClaudeCodeAgentLoop({
129
134
  }
130
135
  }
131
136
 
132
- async function runClaudeProcess({ binary, args, cwd, streamJson }) {
137
+ async function runClaudeProcess({ binary, args, cwd, streamJson, signal }) {
133
138
  const child = spawn(binary, args, {
134
139
  cwd,
135
140
  stdio: ["ignore", "pipe", "pipe"],
136
141
  env: createSubscriptionAgentEnvironment(),
142
+ ...(signal ? { signal } : {}),
137
143
  });
138
144
  child.stderr.on("data", (chunk) => process.stderr.write(chunk));
139
145
  let finalText = "";
140
146
  let structuredOutput = null;
141
147
  let rawOutput = "";
148
+ let providerModel = null;
149
+ let providerEffort = null;
150
+ let providerUsage = {};
151
+ let providerServiceTier = null;
152
+ let providerRuntimeVersion = null;
142
153
 
143
154
  if (streamJson) {
144
155
  const lines = readline.createInterface({
@@ -155,13 +166,53 @@ async function runClaudeProcess({ binary, args, cwd, streamJson }) {
155
166
  return;
156
167
  }
157
168
  if (event?.type === "assistant") {
169
+ providerModel =
170
+ firstNonEmptyString(event.message?.model, providerModel) || null;
171
+ providerEffort =
172
+ firstNonEmptyString(
173
+ event.message?.effort,
174
+ event.message?.reasoning_effort,
175
+ providerEffort,
176
+ ) || null;
177
+ providerUsage = mergeUsageEvidence(providerUsage, event.message?.usage);
158
178
  const texts = (event.message?.content || [])
159
179
  .filter((block) => block?.type === "text")
160
180
  .map((block) => String(block.text || "").trim())
161
181
  .filter(Boolean);
162
182
  if (texts.length > 0) finalText = texts.join("\n\n");
163
183
  }
184
+ if (event?.type === "system") {
185
+ providerModel = firstNonEmptyString(event.model, providerModel) || null;
186
+ providerRuntimeVersion =
187
+ firstNonEmptyString(
188
+ event.claude_code_version,
189
+ event.claudeCodeVersion,
190
+ event.version,
191
+ providerRuntimeVersion,
192
+ ) || null;
193
+ }
164
194
  if (event?.type === "result") {
195
+ providerModel = firstNonEmptyString(event.model, providerModel) || null;
196
+ providerEffort =
197
+ firstNonEmptyString(
198
+ event.effort,
199
+ event.reasoning_effort,
200
+ providerEffort,
201
+ ) || null;
202
+ providerServiceTier =
203
+ firstNonEmptyString(
204
+ event.service_tier,
205
+ event.serviceTier,
206
+ providerServiceTier,
207
+ ) || null;
208
+ providerUsage = mergeUsageEvidence(providerUsage, event.usage);
209
+ providerRuntimeVersion =
210
+ firstNonEmptyString(
211
+ event.claude_code_version,
212
+ event.claudeCodeVersion,
213
+ event.version,
214
+ providerRuntimeVersion,
215
+ ) || null;
165
216
  if (typeof event.result === "string" && event.result.trim()) {
166
217
  finalText = event.result.trim();
167
218
  }
@@ -200,8 +251,58 @@ async function runClaudeProcess({ binary, args, cwd, streamJson }) {
200
251
  }
201
252
  finalText = String(parsed?.result || "").trim();
202
253
  structuredOutput = parsed?.structured_output || null;
254
+ providerModel = firstNonEmptyString(parsed?.model) || null;
255
+ providerEffort =
256
+ firstNonEmptyString(parsed?.effort, parsed?.reasoning_effort) || null;
257
+ providerServiceTier =
258
+ firstNonEmptyString(parsed?.service_tier, parsed?.serviceTier) || null;
259
+ providerRuntimeVersion =
260
+ firstNonEmptyString(
261
+ parsed?.claude_code_version,
262
+ parsed?.claudeCodeVersion,
263
+ parsed?.version,
264
+ ) || null;
265
+ providerUsage = mergeUsageEvidence(providerUsage, parsed?.usage);
203
266
  }
204
- return { finalText, structuredOutput };
267
+ return {
268
+ finalText,
269
+ structuredOutput,
270
+ provenance: {
271
+ resolvedModel: providerModel,
272
+ resolvedEffort: providerEffort,
273
+ resolvedServiceTier: providerServiceTier,
274
+ runtimeVersion: providerRuntimeVersion,
275
+ evidenceTier:
276
+ providerModel || providerEffort || providerServiceTier
277
+ ? "provider_reported"
278
+ : "requested_only",
279
+ usage: providerUsage,
280
+ },
281
+ };
282
+ }
283
+
284
+ function firstNonEmptyString(...values) {
285
+ for (const value of values) {
286
+ const normalized = String(value || "").trim();
287
+ if (normalized) return normalized.slice(0, 160);
288
+ }
289
+ return null;
290
+ }
291
+
292
+ function mergeUsageEvidence(current, value) {
293
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
294
+ return current;
295
+ }
296
+ return {
297
+ ...current,
298
+ ...Object.fromEntries(
299
+ Object.entries(value)
300
+ .filter(([, amount]) =>
301
+ ["number", "string", "boolean"].includes(typeof amount),
302
+ )
303
+ .slice(0, 30),
304
+ ),
305
+ };
205
306
  }
206
307
 
207
308
  function normalizeProviderLaunchError(error, binary) {