@stage5/lumine 0.2.80 → 0.2.82
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 +13 -5
- package/lib/rewards.js +1 -1
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +14 -4
- package/sdk/LUMINE_ADMIN.md +52 -6
package/lib/admin.js
CHANGED
|
@@ -174,7 +174,10 @@ export function readRewardProposalDirectory(directory) {
|
|
|
174
174
|
let bytes = 0;
|
|
175
175
|
const walk = (dir) => {
|
|
176
176
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
177
|
-
if (
|
|
177
|
+
if (
|
|
178
|
+
entry.name.startsWith(".") ||
|
|
179
|
+
REWARD_PROPOSAL_SKIPPED_DIRS.has(entry.name)
|
|
180
|
+
)
|
|
178
181
|
continue;
|
|
179
182
|
const fullPath = path.join(dir, entry.name);
|
|
180
183
|
if (entry.isSymbolicLink()) {
|
|
@@ -194,7 +197,10 @@ export function readRewardProposalDirectory(directory) {
|
|
|
194
197
|
);
|
|
195
198
|
}
|
|
196
199
|
bytes += buffer.length;
|
|
197
|
-
const relative = path
|
|
200
|
+
const relative = path
|
|
201
|
+
.relative(realRoot, fullPath)
|
|
202
|
+
.split(path.sep)
|
|
203
|
+
.join("/");
|
|
198
204
|
files.push({ path: `/${relative}`, content: buffer.toString("utf8") });
|
|
199
205
|
}
|
|
200
206
|
};
|
|
@@ -221,7 +227,7 @@ export function readRewardConfigFile(filePath) {
|
|
|
221
227
|
const normalizedPath = String(filePath || "").trim();
|
|
222
228
|
if (!normalizedPath) {
|
|
223
229
|
throw cliValidationError(
|
|
224
|
-
"Pass the earning rules with --config <rules.json> (
|
|
230
|
+
"Pass the earning rules with --config <rules.json> (userDailyXP, userDailyCoins, optional userDailyClaims, rules[]).",
|
|
225
231
|
);
|
|
226
232
|
}
|
|
227
233
|
let contents;
|
|
@@ -3918,7 +3924,9 @@ function printRewardReviewResult({ operation, data }) {
|
|
|
3918
3924
|
console.log(` ${file.status}: ${file.path}`);
|
|
3919
3925
|
}
|
|
3920
3926
|
if (review.status === "rejected" && review.declinedByCreator) {
|
|
3921
|
-
console.log(
|
|
3927
|
+
console.log(
|
|
3928
|
+
"The creator declined the proposed changes; the request is closed.",
|
|
3929
|
+
);
|
|
3922
3930
|
}
|
|
3923
3931
|
if (review.status === "approved" && review.publishedArtifactVersionId) {
|
|
3924
3932
|
console.log(
|
|
@@ -3944,7 +3952,7 @@ function printRewardReviewResult({ operation, data }) {
|
|
|
3944
3952
|
const config = review.config || {};
|
|
3945
3953
|
if (Array.isArray(config.rules)) {
|
|
3946
3954
|
console.log(
|
|
3947
|
-
`Budgets:
|
|
3955
|
+
`Budgets: per user/day ${config.userDailyXP} XP/${config.userDailyCoins} Coins${config.userDailyClaims ? ` · ${config.userDailyClaims} claim(s)` : ""}`,
|
|
3948
3956
|
);
|
|
3949
3957
|
for (const rule of config.rules)
|
|
3950
3958
|
console.log(` ${formatRewardRuleLine(rule)}`);
|
package/lib/rewards.js
CHANGED
|
@@ -214,7 +214,7 @@ function printRewardsHelp() {
|
|
|
214
214
|
lumine rewards sheet --show Summarize the sheet on file (never prints answer keys)
|
|
215
215
|
|
|
216
216
|
rewards.json (project root) declares the economy the reviewer approves:
|
|
217
|
-
{ "
|
|
217
|
+
{ "userDailyXP", "userDailyCoins", "userDailyClaims"?,
|
|
218
218
|
"rules": [{ "id", "title", "xp", "coins", "verifier": "numeric-quiz" | "completion",
|
|
219
219
|
"maxAttempts"?, "retry"?: { "xpPercent", "coinsPercent", "paidAttempts"? }, "minSeconds"? (completion), "progression"?: "dated" | "until-earned" (quiz) }] }
|
|
220
220
|
Questions and answer keys never go in project files; they belong in the sheet.`);
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Build SDK Index
|
|
2
2
|
|
|
3
|
-
Version: 1.45.
|
|
3
|
+
Version: 1.45.2
|
|
4
4
|
Updated: 2026-09-14
|
|
5
|
-
Generated: 2026-09-
|
|
5
|
+
Generated: 2026-09-19T10:34:03.685Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -33,9 +33,10 @@ Generated: 2026-09-15T02:06:06.654Z
|
|
|
33
33
|
- Use Twinkle.live for one-way app livestreams and Twinkle.chat for the accompanying thread. Free livestreams require a verified host, end after at most 15 minutes, and issue at most 10 private viewer grants. Twinkle keeps platform-owned live-status/end controls above active hosts, so app code cannot hide or replace the broadcaster's Stop path.
|
|
34
34
|
- Media Energy is separate from AI Energy. Replace Media Energy UI only from canonical mediaEnergy/getUsage responses; never decrement, reserve, or synthesize it in app code.
|
|
35
35
|
- Twinkle.rewards awards real XP and Coins only in the current approved published release. Drafts, local previews, private apps and superseded releases cannot earn. The server supplies a published-runtime grant; app code cannot choose a recipient or award amount.
|
|
36
|
-
- The creator's agent designs the rewards. Declare the economy in a project file `rewards.json` at the root: budgets (
|
|
36
|
+
- The creator's agent designs the rewards. Declare the economy in a project file `rewards.json` at the root: budgets (userDailyXP, userDailyCoins, optional userDailyClaims; there is no app-wide daily or lifetime budget, only what one learner can earn per day) and rules [{ id, title, xp, coins, verifier: 'numeric-quiz' | 'completion', maxAttempts?, retry?: { xpPercent, coinsPercent, paidAttempts? }, minSeconds? (completion), progression?: 'dated' | 'until-earned' (quiz) }]. Wire the matching Twinkle.rewards calls with those literal rule ids. Questions and answer keys NEVER go in project files (published source is readable by every player): quiz rules get them from the private question sheet uploaded with `lumine rewards sheet <file.json>` ({ rules: { <ruleId>: { questions?, sets? } } }); `lumine rewards check` validates both together. A review request freezes the code and proposes rewards.json merged with the sheet; the administrator reads the code, checks the amounts and whether the app is exploitable, may change any amount, and approves. Creators are kids and teens: show approval status and one Send for review action; do not ask them to fill in technical forms. Every code update that retains rewards needs a new approval before publishing. Removing the SDK automatically clears its gate. Apps read amounts, tries and sets from getStatus, never from their own file.
|
|
37
37
|
- Verifiers: 'numeric-quiz' pays for server-checked numeric answers (retry share, attempt limits, dated sets or until-earned sets that stay up until somebody earns them, after-answer guides). 'completion' pays when the app reports an activity finished — a cleared stage, a finished round — at least minSeconds after start({ ruleId }); the server checks only the elapsed time, once per learner per site day (UTC midnight), and the budgets. Call start when the activity begins and claim({ challengeId }) with no answers when it ends; keep completion amounts and userDailyXP small enough that a player scripting the calls would not matter, because nothing else is verified.
|
|
38
38
|
- Numeric quiz answers are verified on the server; client scores, privateDb state, timers and completion booleans are not verified reward evidence. Limits reset at UTC midnight. Rules are earned once per viewer per UTC day; attempt limits and retry payouts come from the approved rule. Challenges expire at the UTC day boundary. Budgets apply across release changes.
|
|
39
|
+
- Optional reward rule controls: maxLifetimeClaims caps one learner’s receipts for that rule across every day and release; completionProof: classic-tower-v1 requires a server-simulated Classic Tower finish in addition to minSeconds. These are server-enforced controls. Existing completion rules without completionProof still verify elapsed time only.
|
|
39
40
|
|
|
40
41
|
## Token Scopes
|
|
41
42
|
files:read, media:read, media:write, live:read, live:write, user:read, users:read, dailyReflections:read, content:read, content:write, sharedDb:read, sharedDb:write, privateDb:read, privateDb:write, files:write, chat:read, chat:write, notifications:read, notifications:write, notifications:emit, reminders:read, reminders:write, rewards:claim
|
|
@@ -1017,6 +1018,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1017
1018
|
- rules[].available is false on a site day (UTC) the reviewer scheduled no questions for; show the rule as not available instead of starting it. xp/coins are the first-try amounts; retryReward is what a correct answer pays after a wrong one (equal to xp/coins unless the reviewer set a retry share). maxAttempts null means unlimited wrong answers until the site's daily reset (UTC midnight, 9:00 AM in Korea). retry.paidAttempts, when set, is the last attempt number a correct answer is still paid on: a later correct answer is recorded as solved (receipt xp 0, coins 0) and pays nothing — tell the learner before they pass it.
|
|
1018
1019
|
- challenges lists challenges this viewer already started today with their questions, so an app can resume after a reload without calling start. A question's guide (reviewer-approved JSON teaching content: explanation, interactive-model configuration) is present only once the viewer has answered at least once, right or wrong; render it as the after-attempt lesson. claimsToday against userDailyClaims (null = uncapped) tells whether another bounty can still pay today.
|
|
1019
1020
|
- Under progression 'until-earned' the same set stays up day after day until somebody earns it; setKey names the set currently up. Completion rules are always available and have questionCount 0.
|
|
1021
|
+
- Rules may set maxLifetimeClaims, a per-learner limit for that rule across days and releases. rules[].lifetime contains the server-confirmed limit and remaining claims. App storage never enforces this limit.
|
|
1020
1022
|
- await Twinkle.rewards.getReceipt({ challengeId }) | scopes: rewards:claim
|
|
1021
1023
|
- Returns: { mode: "live", status: "awarded" | "pending" | "expired" | "not_found", receipt: { id, challengeId, ruleId, reviewId, artifactVersionId, dayKey, xp, coins, attempt, createdAt } | null, balances: { xp, coins } } | { mode: "preview", status: "not_found", receipt: null, message }
|
|
1022
1024
|
- Read an existing receipt for this app and signed-in viewer by server-issued challengeId, including previous UTC days and previous approved versions. Requires the current approved published release and runtime grant; a stale frame must reload first. Never awards, retries a claim, returns answer keys, or restores removed rewards permission.
|
|
@@ -1026,12 +1028,20 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1026
1028
|
- Creates or resumes a server-issued challenge for the signed-in viewer. Render its questions (prompt and optional hint) and collect numeric answers in the same order. One daily challenge per rule/review; repeat starts cannot reset attempts. A challenge stays open until the site's daily reset (UTC midnight, 9:00 AM in Korea) (expiresAt). Resuming after a wrong answer includes each question's guide.
|
|
1027
1029
|
- Errors: build_reward_not_scheduled when the rule has no questions for today; build_reward_daily_claims_reached when the viewer already earned today’s cap. attemptsRemaining is null for unlimited rules.
|
|
1028
1030
|
- For a completion rule call start when the activity begins (the moment the stage starts); the challenge's age is what the claim is measured against. In preview mode start also works for the owner (a stateless simulation).
|
|
1029
|
-
-
|
|
1031
|
+
- For completionProof: classic-tower-v1, start also returns completion { profile, token, maxFrames, completed, failed }. A new start resets only the simulated climb to its canonical spawn; it cannot reset daily or lifetime rewards. Record inputs from the first physics frame. The completion token is bound to the viewer, challenge, rule and published release.
|
|
1032
|
+
- await Twinkle.rewards.progress({ challengeId, completionToken, frames }) | scopes: rewards:claim
|
|
1033
|
+
- Returns: { mode: "live" | "preview", completion: { profile: "classic-tower-v1", token, maxFrames, completed, failed } }
|
|
1034
|
+
- Verify a bounded batch of inputs for an approved server-simulated climb.
|
|
1035
|
+
- Only for completionProof: classic-tower-v1. Send 1 to 600 chronological physics frames, each [dt, moveX, moveY, cameraForwardX, cameraForwardZ, jumpPressed, jumpHeld, speedMultiplier]. dt is in seconds, at most 0.05; movement axes are -1 through 1; the camera values are the horizontal components before normalization; jump flags are 0 or 1; speedMultiplier is an existing Classic Tower trail speed (1, 1.1, 1.2, 1.3, 1.35 or 1.4). Geometry and player state are owned by the registered server simulation.
|
|
1036
|
+
- Send occasional batches with at most one request in flight. Keep the previous token and the exact batch until a response confirms it; an identical retry is safe. Use the returned token for the next batch. Simulation time cannot outrun wall time. A completed token is evidence of a legal simulated run, not proof that a human played or that inputs were not automated.
|
|
1037
|
+
- On respawn or a return from another world, begin a fresh run at the canonical spawn via start. Preserve other worlds and gameplay. Do not submit positions, scores, secret keys, or a client completion flag. Preview tokens can never be redeemed in the published app.
|
|
1038
|
+
- await Twinkle.rewards.claim({ challengeId, answers?: [number], completionToken?: string }) | scopes: rewards:claim
|
|
1030
1039
|
- Returns: { awarded: false, attempts, attemptsRemaining, questions: [{ prompt, hint?, guide? }] } | { awarded: true, duplicate, receipt: { ruleId, xp, coins, attempt, firstTry }, questions: [{ prompt, hint?, guide? }], balances: { xp, coins } }
|
|
1031
1040
|
- Twinkle verifies every answer, approval, current published artifact and budget before atomically recording XP and Coins. The receipt’s xp/coins are what was actually paid: the full amounts on a first try, the retry share after a wrong answer (attempt > 1). Retry the same challengeId after a lost response; a confirmed claim returns its original receipt without another award. Never update balance UI optimistically. Under retry.paidAttempts a correct answer past that attempt returns awarded: true with a zero receipt: solved, not paid.
|
|
1032
1041
|
- Every claim response, wrong or right, returns the questions with their guides unlocked: show the teaching content right after the first answer. Answer keys are never returned.
|
|
1033
1042
|
- A wrong answer within two seconds of the previous one is refused with build_reward_throttled (HTTP 429) and does not count; wait for the person to try again rather than retry-looping.
|
|
1034
1043
|
- Completion rules take no answers: claim({ challengeId }) when the activity is finished. build_reward_too_fast (HTTP 409) means fewer than minSeconds passed since start; show nothing and let play continue. In preview mode the receipt carries preview: true and nothing is paid.
|
|
1044
|
+
- A completionProof rule also requires the signed completionToken from a successful rewards.progress response. The server simulates the registered game physics and must reach the goal. A timer, forged position, client win flag, altered inventory or token from another viewer, challenge or release cannot authorize payment. maxLifetimeClaims is enforced from receipts in the same award transaction. build_reward_lifetime_claims_reached means all rewards for this rule have been collected; do not retry it.
|
|
1035
1045
|
- await Twinkle.rewards.getLeaderboard({ metric?: "xp" | "coins", period?: "day" | "week" | "all", limit? }) | scopes: rewards:claim
|
|
1036
1046
|
- Returns: { mode: "live", metric, period, limit, dayKey, from, available: { xp, coins }, entries: [{ rank, userId, username, profilePicUrl, xp, coins, claims, lastAt }], me: { rank, xp, coins, claims } | null } | { mode: "preview", metric, period, available, entries: [], me: null, message }
|
|
1037
1047
|
- 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.
|
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -1143,17 +1143,18 @@ Review questions to settle with Mikey before approving:
|
|
|
1143
1143
|
it, then the next one comes up the following site day (UTC midnight, 9:00 AM Korea)) keep them honest.
|
|
1144
1144
|
- Do the rule IDs in `rewards.json` match what the code starts? Unknown IDs
|
|
1145
1145
|
simply never pay.
|
|
1146
|
-
- Are the amounts and the per-
|
|
1147
|
-
|
|
1146
|
+
- Are the amounts and the per-learner daily budgets right for what the app
|
|
1147
|
+
actually asks of people? There is no app-wide budget, per day or lifetime:
|
|
1148
|
+
a good app keeps paying everyone who plays it. Older configs may still
|
|
1149
|
+
carry `dailyXP`/`dailyCoins`/`lifetimeXP`/`lifetimeCoins`; the API accepts
|
|
1150
|
+
and ignores them, so never add or tune them.
|
|
1148
1151
|
|
|
1149
1152
|
`rules.json` (what `--config` takes, and what the app's `rewards.json` plus
|
|
1150
1153
|
sheet compose into):
|
|
1151
1154
|
|
|
1152
1155
|
```json
|
|
1153
1156
|
{
|
|
1154
|
-
"dailyXP": 2000000, "dailyCoins": 0,
|
|
1155
1157
|
"userDailyXP": 10000, "userDailyCoins": 0,
|
|
1156
|
-
"lifetimeXP": 200000000, "lifetimeCoins": 0,
|
|
1157
1158
|
"rules": [
|
|
1158
1159
|
{ "id": "stage-1", "title": "Clear Stage 1", "xp": 300, "coins": 0,
|
|
1159
1160
|
"verifier": "completion", "minSeconds": 20 },
|
|
@@ -1191,8 +1192,8 @@ day, elementary 50,000 XP + 1,000 Coins, middle 70,000 + 5,000, high
|
|
|
1191
1192
|
`userDailyClaims` 1; until-earned sets authored from the Korean curriculum.
|
|
1192
1193
|
Arcade Typing (Mikey, 2026-09-12): XP for clearing campaign stages, up to
|
|
1193
1194
|
10,000 XP per learner per day, no Coins. Platform ceilings: 100,000 XP /
|
|
1194
|
-
10,000 Coins per rule and per learner per day
|
|
1195
|
-
per
|
|
1195
|
+
10,000 Coins per rule and per learner per day. No app-wide ceiling exists,
|
|
1196
|
+
per day or lifetime.
|
|
1196
1197
|
|
|
1197
1198
|
Approval freezes these rules with the reviewed snapshot and publishes that
|
|
1198
1199
|
snapshot immediately (the result carries `published.version`); an approval
|
|
@@ -2670,6 +2671,24 @@ Read every line, and put these in the daily report verbatim:
|
|
|
2670
2671
|
line;
|
|
2671
2672
|
- the `day-over-day … worker_heap=…` delta.
|
|
2672
2673
|
|
|
2674
|
+
**The memory report looks back only 24 hours; the run's window is often longer.**
|
|
2675
|
+
On 2026-09-19 a four-day window hid two heap-OOM aborts (09-16 and 09-17) that
|
|
2676
|
+
`memory-daily` no longer showed. In every full review, also search the reviewed
|
|
2677
|
+
error log for `[cluster] worker exited` lines that are not planned or operator
|
|
2678
|
+
recycles, across the whole window since the last completed full run, and treat
|
|
2679
|
+
each one exactly like a non-zero `oom_aborts` / `unexpected_worker_exits`.
|
|
2680
|
+
|
|
2681
|
+
Each unexpected exit is followed by a `[cluster] worker last work slot=… pid=…
|
|
2682
|
+
in_flight=[…] recent=[…]` line (added 2026-09-19). The worker writes that trail
|
|
2683
|
+
synchronously as each piece of work begins and ends, so it survives an abort
|
|
2684
|
+
inside a single synchronous burst. `in_flight` names the requests or socket
|
|
2685
|
+
events that were running when the process died; `recent` lists the last
|
|
2686
|
+
sixteen begin/label/end records with their age before exit. Quote both lists
|
|
2687
|
+
verbatim in the report and in the todo: they are the evidence that pinpoints
|
|
2688
|
+
the code path. `unavailable (no trail file)` means the worker died before its
|
|
2689
|
+
trail existed or an older generation is still running; say so rather than
|
|
2690
|
+
guessing.
|
|
2691
|
+
|
|
2673
2692
|
Escalate in the report (a todo, and a note for Mikey) when any of these hold:
|
|
2674
2693
|
`oom_aborts` > 0, `service_restarts` > 0, `unexpected_worker_exits` > 0, any
|
|
2675
2694
|
worker `heap_high_water_pct` ≥ 95, or `heap_recycles` ≥ 4 in a day (the
|
|
@@ -2976,6 +2995,33 @@ Mikey"** section carrying only
|
|
|
2976
2995
|
the deltas and anomalies worth his time, next to the escalation list. Never
|
|
2977
2996
|
dump raw sections at him.
|
|
2978
2997
|
|
|
2998
|
+
### Jev shadow pilot (standing duty, every full daily review; added 2026-09-19)
|
|
2999
|
+
|
|
3000
|
+
Read `data.jevPilot` from `lumine admin brief --json` and carry it into
|
|
3001
|
+
the full report for Mikey. The active `daily-run report --json` also includes
|
|
3002
|
+
`data.report.brief.jevPilot`. This duty does not authorize a separate full run.
|
|
3003
|
+
If the deployed API lacks the field, say the telemetry is not deployed; do not
|
|
3004
|
+
treat a missing section as zero traffic or a healthy pilot.
|
|
3005
|
+
|
|
3006
|
+
State the configuration and operating status even when off/blocked/awaiting
|
|
3007
|
+
samples. Headline the named last completed UTC day, compare with the trailing
|
|
3008
|
+
seven completed days, and keep the in-progress day separate. Include paired
|
|
3009
|
+
decision counts, disagreements (especially Jev react / baseline respond),
|
|
3010
|
+
p50/p95 latency for each model, provider errors/timeouts, pending observations,
|
|
3011
|
+
known incremental cost, unknown-cost requests, ledger gaps and cap status.
|
|
3012
|
+
The pilot's known recorded spend is already in application AI costs: never add
|
|
3013
|
+
it again. Shadow mode changes no user-visible decisions and has not established
|
|
3014
|
+
savings. Agreement is not accuracy; confidence is not a measured success rate.
|
|
3015
|
+
|
|
3016
|
+
Privately inspect the bounded `reviewCandidates` when needed, name what was
|
|
3017
|
+
actually reviewed, and account for edited comments. Ordinary model disagreements
|
|
3018
|
+
are evaluation findings; outages, stuck telemetry or missing ledger entries are
|
|
3019
|
+
operational findings. Report a recommendation to continue, adjust or stop, without
|
|
3020
|
+
automatically enabling traffic, raising caps or promoting Jev to serving. Data
|
|
3021
|
+
handling for under-18 users and TypeSafe account access must be resolved before
|
|
3022
|
+
real conversations are sent. See `twinkle-api/JEV_PILOT.md` for configuration,
|
|
3023
|
+
the synthetic evaluation step and release checks.
|
|
3024
|
+
|
|
2979
3025
|
### Application AI calendar-month cost (standing duty, every full daily review)
|
|
2980
3026
|
|
|
2981
3027
|
Run `lumine admin ai-costs monthly --json` during every full daily management
|