@stage5/lumine 0.2.79 → 0.2.80

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
@@ -3619,13 +3619,17 @@ function printAdminEnergyBudget(energyBudget) {
3619
3619
  );
3620
3620
  }
3621
3621
  console.log(
3622
- "day | charged | overflow | users | rechg | runs | calls/run avg/p90 | $/run avg/p90 | stops chg/unchg | busy | telemetry rows",
3622
+ "day | charged | overflow | users | rechg | runs | calls/run avg/p90 | $/run avg/p90 | stops chg/unchg | busy | queue rst/stop/resume | telemetry rows",
3623
3623
  );
3624
3624
  for (const day of energyBudget.byDay || []) {
3625
3625
  const m = day.telemetry.metrics;
3626
3626
  const runs = day.runs.total;
3627
+ // Queue metrics are absent from APIs older than the queued-request telemetry.
3628
+ const queue = ["queued_restored", "queued_stopped", "busy_resume_requested"]
3629
+ .map((metric) => String(m[metric]?.count ?? "-"))
3630
+ .join("/");
3627
3631
  console.log(
3628
- `${day.dayKey}${day.inProgress ? "*" : " "}| ${formatAdminUsd(day.chargedUsd).padStart(7)} | ${formatAdminUsd(day.overflowUsd).padStart(8)} | ${String(day.users).padStart(5)} | ${String(day.recharges).padStart(5)} | ${String(runs.runs).padStart(4)} | ${String(runs.callsPerRun.avg).padStart(7)}/${String(runs.callsPerRun.p90).padEnd(9)} | ${formatAdminUsd(runs.usdPerRun.avg).padStart(6)}/${formatAdminUsd(runs.usdPerRun.p90).padEnd(6)} | ${String(m.budget_stop_changed.count).padStart(7)}/${String(m.budget_stop_unchanged.count).padEnd(7)} | ${String(m.busy_refusal.count).padStart(4)} | ${day.telemetry.rowCount}`,
3632
+ `${day.dayKey}${day.inProgress ? "*" : " "}| ${formatAdminUsd(day.chargedUsd).padStart(7)} | ${formatAdminUsd(day.overflowUsd).padStart(8)} | ${String(day.users).padStart(5)} | ${String(day.recharges).padStart(5)} | ${String(runs.runs).padStart(4)} | ${String(runs.callsPerRun.avg).padStart(7)}/${String(runs.callsPerRun.p90).padEnd(9)} | ${formatAdminUsd(runs.usdPerRun.avg).padStart(6)}/${formatAdminUsd(runs.usdPerRun.p90).padEnd(6)} | ${String(m.budget_stop_changed.count).padStart(7)}/${String(m.budget_stop_unchanged.count).padEnd(7)} | ${String(m.busy_refusal.count).padStart(4)} | ${queue.padEnd(21)} | ${day.telemetry.rowCount}`,
3629
3633
  );
3630
3634
  }
3631
3635
  console.log("* = in-progress UTC day; never headline it.");
@@ -3957,6 +3961,32 @@ function printRewardReviewResult({ operation, data }) {
3957
3961
  console.log("Use --json for the full record.");
3958
3962
  }
3959
3963
 
3964
+ function formatAdminCounts(counts) {
3965
+ return Object.entries(counts || {})
3966
+ .sort(([a], [b]) => a.localeCompare(b))
3967
+ .map(([key, count]) => `${key} ${count}`)
3968
+ .join(" · ");
3969
+ }
3970
+
3971
+ function printRewardReviewLifecycle(lifecycle) {
3972
+ // Absent from APIs older than the review-lifecycle telemetry.
3973
+ if (!lifecycle) return;
3974
+ const totals = lifecycle.totals || {};
3975
+ if (!Object.keys(totals.actions || {}).length) {
3976
+ console.log("Review lifecycle: no review events in the window.");
3977
+ return;
3978
+ }
3979
+ console.log(`Review lifecycle: ${formatAdminCounts(totals.actions)}.`);
3980
+ if (Object.keys(totals.thumbnails || {}).length)
3981
+ console.log(
3982
+ ` automatic thumbnail after review publishes: ${formatAdminCounts(totals.thumbnails)}`,
3983
+ );
3984
+ if (Object.keys(totals.refusals || {}).length)
3985
+ console.log(
3986
+ ` refused decisions (decision:code): ${formatAdminCounts(totals.refusals)}`,
3987
+ );
3988
+ }
3989
+
3960
3990
  function printRewardActivity(data) {
3961
3991
  const apps = Array.isArray(data.apps) ? data.apps : [];
3962
3992
  const suspects = Array.isArray(data.suspects) ? data.suspects : [];
@@ -3968,6 +3998,7 @@ function printRewardActivity(data) {
3968
3998
  ` app ${app.buildId} ${app.title}: ${app.claims} claim(s) · ${app.earners} earner(s) · ${app.xp} XP · ${app.coins} Coins · ${app.flagged} flagged`,
3969
3999
  );
3970
4000
  }
4001
+ printRewardReviewLifecycle(data.reviewLifecycle);
3971
4002
  if (!suspects.length) {
3972
4003
  console.log(
3973
4004
  "Nothing unusual: no claim on the minimum time, no bursts, no sweeps, no repeated cap days, no guessing.",
package/lib/sdk.js CHANGED
@@ -180,6 +180,16 @@ export const SDK_CLI_METHODS = {
180
180
  readOnly: true,
181
181
  mapArgs: (args) => ({ challengeId: args.challengeId }),
182
182
  },
183
+ "rewards.getTimeline": {
184
+ path: "api/rewards/timeline", special: "rewards", operation: "timeline",
185
+ scopes: ["rewards:claim"], readOnly: true,
186
+ mapArgs: (args) => ({ ruleId: args.ruleId, cursor: args.cursor, limit: args.limit }),
187
+ },
188
+ "rewards.getArchivedProblem": {
189
+ path: "api/rewards/archived-problem", special: "rewards", operation: "archived-problem",
190
+ scopes: ["rewards:claim"], readOnly: true,
191
+ mapArgs: (args) => ({ receiptId: args.receiptId }),
192
+ },
183
193
  "rewards.start": {
184
194
  path: "api/rewards/start",
185
195
  special: "rewards",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.79",
3
+ "version": "0.2.80",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  Version: 1.45.0
4
4
  Updated: 2026-09-14
5
- Generated: 2026-09-14T06:42:36.041Z
5
+ Generated: 2026-09-15T02:06:06.654Z
6
6
 
7
7
  ## Notes
8
8
  - This SDK is injected into Build iframes via the Build preview/runtime.
@@ -1037,6 +1037,17 @@ world.updatePresence({ x, y, z, facing });
1037
1037
  - Standings of who earned the most XP or Coins in THIS app, computed by Twinkle from its own receipts (never from anything the app submits). period 'day' is today (site day, UTC), 'week' the last 7 site days, 'all' (default) every day since approval. limit defaults to 20, max 100.
1038
1038
  - available says which boards this app's approved rules can pay: show a Coins board only when available.coins is true (an app whose rules pay XP only has no Coins standings). me is the signed-in viewer's own standing even when they fall outside the page, or null when they earned nothing in the period.
1039
1039
  - Drafts and previews return mode 'preview' with no entries. Use Twinkle.leaderboards for app-defined scores; use this for real XP and Coins earned.
1040
+ - await Twinkle.rewards.getTimeline({ ruleId?, cursor?, limit? } = {}) | scopes: rewards:claim
1041
+ - Returns: { mode: "live", dayKey, entries: [{ receiptId, ruleId, ruleTitle, setKey, title, promptPreview, dayKey, closedAt, solvedAt, solver: { userId, username }, firstSolver, xp, coins, attempt }], nextCursor } | { mode: "preview", entries: [], nextCursor: null, message }
1042
+ - Browse confirmed solves of retired until-earned quiz bounties, newest first.
1043
+ - A solve appears only after the next site reset (UTC midnight, 9 AM Korea). Today’s solves, unsolved sets, standing quizzes and dated quizzes are excluded. The server decides retirement; client dates cannot unlock content. Zero-reward correct solves are included.
1044
+ - One entry per solve receipt, with firstSolver identifying the first receipt for that rule and set across approved versions. limit defaults to 20, max 50; pass nextCursor unchanged for older solves and omit it when changing ruleId. Pages may have fewer entries when old question sheets cannot be recovered; continue while nextCursor is present.
1045
+ - No daily-claim limit applies to reading. Requires the current approved published runtime grant; previews return an empty timeline. Never awards or changes balances. Open a receipt with getArchivedProblem to load the original question and guide.
1046
+ - await Twinkle.rewards.getArchivedProblem({ receiptId }) | scopes: rewards:claim
1047
+ - Returns: { mode: "live", entry: <same solve entry as getTimeline>, questions: [{ prompt, hint?, guide? }] } | { mode: "preview", entry: null, questions: [], message }
1048
+ - Read a retired bounty’s original questions and guides from the approval attached to its solve receipt.
1049
+ - Use a receiptId returned by getTimeline. Both reads independently check retirement and app ownership; guessing an active or other app’s receipt cannot reveal its questions or guides. Returns build_reward_archive_unavailable (404) if unavailable.
1050
+ - Uses that receipt’s frozen approved sheet, never today’s edited question or the mutable draft. Answer keys and tolerances are never returned. Render the question first and offer Reveal guide for learning, without a reward-claim button.
1040
1051
 
1041
1052
  ## Examples
1042
1053
 
@@ -1018,6 +1018,34 @@ Mikey gives his go-ahead; a pending proposal is not a completed refresh. After
1018
1018
  approval, execute the entire approved plan and verify it without asking for
1019
1019
  each swap again. Never omit this section from a full-run report.
1020
1020
 
1021
+ ### Full management report in Chrome
1022
+
1023
+ Mikey's standing delivery preference (2026-09-15): after a full daily run, open
1024
+ the **complete management report as a browsable localhost page in Chrome**.
1025
+ Do this as part of finishing the authorized run; a Markdown path in chat alone
1026
+ is insufficient, and no additional confirmation is needed to open the report.
1027
+
1028
+ 1. Save the complete report as
1029
+ `/private/tmp/twinkle-daily-YYYY-MM-DD/daily-management-report.md`, using the
1030
+ run's Bangkok date. Include every required reporting section, coverage gap,
1031
+ pending decision and carryover. Reflect later owner decisions accurately.
1032
+ 2. Render the entire Markdown into a readable HTML page with section links,
1033
+ usable tables and access to the original Markdown. Preserve complete
1034
+ appendices and flagged rows; navigation or collapsible details must not
1035
+ discard them. Use local assets so the report does not depend on a CDN.
1036
+ 3. Serve the view on `127.0.0.1` using an available port. Expose only the HTML,
1037
+ its required assets and the report Markdown through a dedicated directory
1038
+ or explicit routes; do not serve the surrounding private evidence folder.
1039
+ Keep the server available after the response so Mikey can browse it.
1040
+ 4. Open the localhost URL in Mikey's Chrome. Verify that the page renders,
1041
+ section navigation works, and the full report is accessible. Keep the
1042
+ report tab open. Include both the localhost URL and Markdown file link in
1043
+ the final response.
1044
+
1045
+ For a follow-up that only opens or updates an existing report, reuse that
1046
+ report and its existing tab/server where available. This does not authorize
1047
+ starting another management run or repeating unrelated daily duties.
1048
+
1021
1049
  Creating an escalation belongs to the active run; acknowledging, annotating,
1022
1050
  resolving, or reopening it does not. Use the run-independent `escalation`
1023
1051
  commands after Mikey responds instead of starting a follow-up delegated run.
@@ -1239,6 +1267,24 @@ open the player with `admin identity inspect` before proposing anything, and
1239
1267
  propose to Mikey (revoke the app's rule, or a bucket ban) rather than acting.
1240
1268
  Never revoke an approval from a daily run.
1241
1269
 
1270
+ The same report carries `reviewLifecycle` (added 2026-09-15): the reviews'
1271
+ audit trail counted per UTC day (`byDay`) and for the window (`totals`).
1272
+ `actions` counts every event: `request`, `approve`/`reject`/`revoke`,
1273
+ `publish` (the approved version went live), `propose`, `proposal_viewed` (the
1274
+ creator opened an offer's comparison; once per revision), `accept`/`decline`,
1275
+ `thumbnail` and `<decision>_refused`. `thumbnails` is the automatic thumbnail
1276
+ after a review publish: `captured`, `existing` (the app already had one),
1277
+ `superseded` (the creator's own thumbnail or a newer release won),
1278
+ `not_needed`, `owner_missing` or `failed`. `refusals` counts conflicts that
1279
+ rolled back, keyed `decision:code` (for example
1280
+ `approve:build_reward_review_stale`, `accept:build_reward_proposal_stale`).
1281
+ Report the totals. Each `failed` thumbnail and each `publish` without a
1282
+ `thumbnail` event on a completed day is a carry-over todo with the review
1283
+ ids (`reward-review show <id>` lists its events). An `accept` without a
1284
+ `proposal_viewed` for that revision means the creator accepted without opening
1285
+ the comparison; mention it, it is not a fault. Refusals are the concurrency
1286
+ guards working; report them, and escalate only a repeated pattern on one app.
1287
+
1242
1288
  ## Private carry-over todos
1243
1289
 
1244
1290
  ```bash
@@ -2273,6 +2319,21 @@ type PostSkip = Success<{
2273
2319
 
2274
2320
  ## Twinkle Newspaper
2275
2321
 
2322
+ **Audience (Mikey, 2026-09-15): children and young Twinkle users aged 10–15.**
2323
+ Choose and write stories for what these readers would voluntarily spend time
2324
+ reading. Before selecting a story, identify its appeal to them: curiosity,
2325
+ humor, a relatable experience, a creative idea, something useful, or a chance
2326
+ to join in. Games, art, puzzles, friendships, shared reflections, and community
2327
+ discussions can all supply good stories; read the actual source to find the
2328
+ substance.
2329
+
2330
+ Use clear, lively language that respects readers' intelligence. Give enough
2331
+ context for someone who missed the original post, and make the interesting
2332
+ part clear in the headline and opening. Avoid talking down to readers, forced
2333
+ slang, preachy lessons, administrative summaries, and blurbs that merely say
2334
+ someone uploaded or posted something. Keep the appeal grounded in the source;
2335
+ never invent excitement, reactions, popularity, or drama.
2336
+
2276
2337
  ```bash
2277
2338
  lumine admin news --json
2278
2339
  lumine admin news claim --output claim.json --scaffold editorial.json --json
@@ -2311,6 +2372,7 @@ re-checked transactionally at commit.
2311
2372
 
2312
2373
  ```ts
2313
2374
  type GeneratedEditorial = {
2375
+ excludedSubjectEventKeys?: string[]; // experiment-video Subjects omitted entirely
2314
2376
  mastheadHeadline: string;
2315
2377
  mastheadDeck: string;
2316
2378
  lead: {
@@ -2336,6 +2398,20 @@ nothing disappears silently: digest events the editorial does not account for
2336
2398
  are added back. Two mechanisms make real curation possible within that
2337
2399
  guarantee:
2338
2400
 
2401
+ **Exception — experiment videos (Mikey, 2026-09-15):** exclude these entirely,
2402
+ including school science-contest entries. The editor/model identifies them from
2403
+ the supplied context and lists their exact keys in `excludedSubjectEventKeys`.
2404
+ Do not cite, summarize, or group them into newspaper coverage. The API must have
2405
+ this exclusion support deployed before submitting such an editorial; older APIs
2406
+ ignore the field and restore the posts. The server accepts only canonical Subject
2407
+ keys and never lets this field remove official announcements. After excluding
2408
+ these posts, look for worthwhile replacement stories among the edition's eligible
2409
+ Subjects and shared Daily Reflections. A bounded digest dominated by experiment
2410
+ videos does not establish that the day has no other stories. Check the available
2411
+ canonical sources within the coverage window, and keep replacement stories within
2412
+ the claim and citation contract. Do not stop at deletion when suitable material
2413
+ is available, or invent filler to reach an article count.
2414
+
2339
2415
  - **`coveredEventKeys`** — an arc story may list the other events it narrates
2340
2416
  (an app's release + its update stream + its open-sourcing; one member's
2341
2417
  related posts). Covered events are omitted from the layout — the arc IS
@@ -2368,17 +2444,17 @@ within the returned digest. On a typical day every front subject arrives with
2368
2444
  the same priority, so treat a tied score (or recency) as no signal at all and
2369
2445
  make the call by reading:
2370
2446
 
2371
- - **Choose the lead by argument, not by score or recency.** The best lead is
2372
- the front event where something is actually _at stake_: a claim with
2373
- reasoning, a question with a position behind it — ideally while another
2374
- member is already responding. A claim plus a reply is a conversation in
2375
- motion; a drawing, a greeting, or a link is a share, and shares belong
2376
- further down the page, not in the lead.
2447
+ - **Choose the lead for readers aged 10–15.** Lead with the eligible front
2448
+ event whose substance is most likely to catch their interest and reward
2449
+ reading. A thoughtful conversation, a funny or relatable reflection, a
2450
+ striking creation, or an inviting community challenge can all qualify.
2451
+ Read the source and any supplied replies to understand the appeal; priority,
2452
+ recency, and the mere presence of an argument do not decide the lead.
2377
2453
  - **Thread a theme through the paper.** Pick the strongest idea of the day
2378
2454
  and let the masthead, the lead, and the editor's note all carry it, with
2379
- the editor's note reprising a community value from one of the day's posts
2380
- rather than summarizing the edition. The paper should end on something a
2381
- child can take with them.
2455
+ the editor's note leaving readers with an observation, question, or invitation
2456
+ grounded in one of the day's posts. Let a theme emerge from the material;
2457
+ keep each story's meaning intact and avoid forcing a moral lesson.
2382
2458
  - **Cross-reference events into arcs.** The same thing often appears in the
2383
2459
  digest several times (an app's release, its open-sourcing, and its maker's
2384
2460
  Daily Reflection about it). Write those as one story arc — the origin
@@ -2653,14 +2729,14 @@ The additive host-owner migration and compatible API must be live before this
2653
2729
  CLI capability is published.
2654
2730
 
2655
2731
  Review every participating host, including primary private-helper logs. A
2656
- primary review does not cover the target. Finish the exclusive review before
2657
- that host is held; a held/unavailable owner returns an explicit retryable failure,
2658
- not another host's snapshots or an independent log service. Do not abandon its
2659
- lease merely to bypass a deployment guard. After a planned hold, final shutdown
2660
- deltas are reviewed via management SSH outside any active lease, recorded, and
2661
- API stderr is cleared only with the existing guarded `npm run logs:clear-errors`
2662
- plus post-clear re-read. This is the deployment runbook's final boundary, not
2663
- permission to bypass an active Lumine lease. A stopped target whose final logs
2732
+ primary review does not cover the target. An open review does not block a
2733
+ deployment or host hold. Its files, lease and database boundaries persist;
2734
+ active requests use the normal drain. A held or unavailable owner returns a
2735
+ retryable failure, so keep the session and retry when that host is available
2736
+ again. Release operators can review final shutdown deltas via management SSH
2737
+ and record their own evidence. An active review keeps ownership of clearing;
2738
+ otherwise API stderr is cleared with the existing guarded
2739
+ `npm run logs:clear-errors` plus post-clear re-read. A stopped target whose final logs
2664
2740
  were reviewed does not need to be started for daily management; starting EC2
2665
2741
  requires separate authority. See `twinkle-api/DEPLOY_TIME_HANDOFF.md`.
2666
2742
 
@@ -3035,9 +3111,16 @@ energy ledger (`chargedUsd`, `overflowUsd`, `users`, `recharges`; 1,000,000
3035
3111
  units = $1), every telemetry counter (`busy_refusal`, `autofix_yielded`,
3036
3112
  `autofix_superseded`, `reservation_admitted` with `avgRunBudgetUsd`,
3037
3113
  `budget_stop_changed` / `budget_stop_unchanged` / `run_completed` with a
3038
- per-model breakdown, `stop_settled`, `tool_limit_settled`) and per-model
3039
- per-run usage stats (`runs`, `callsPerRun`, `usdPerRun`, each avg and
3040
- nearest-rank p90). The current UTC day is returned with `inProgress: true`.
3114
+ per-model breakdown, `stop_settled`, `tool_limit_settled`, and since
3115
+ 2026-09-15 the queued-request counters `queued_restored` (a workspace reload
3116
+ restored its owner's still-queued request, shown with Stop), `queued_stopped`
3117
+ (Stop cancelled a request while it was still queued) and
3118
+ `busy_resume_requested` (after a busy refusal the website resumed the
3119
+ existing request)) and per-model per-run usage stats (`runs`, `callsPerRun`,
3120
+ `usdPerRun`, each avg and nearest-rank p90). `busy_refusal` with no
3121
+ `busy_resume_requested` on a day with website traffic means clients are not
3122
+ resuming the refused request; `queued_restored` shows how often creators
3123
+ reload while waiting in the queue. The current UTC day is returned with `inProgress: true`.
3041
3124
  **Headline `lastCompletedDay` (its exact `dayKey`) — never the in-progress
3042
3125
  day**, exactly as the closed-day AI-cost duty does.
3043
3126