@stage5/lumine 0.2.84 → 0.2.86
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/api.js +33 -0
- package/lib/commands.js +77 -5
- package/lib/constants.js +7 -4
- package/lib/sdk.js +5 -1
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +19 -19
- package/sdk/LUMINE_ADMIN.md +69 -8
package/lib/api.js
CHANGED
|
@@ -439,6 +439,39 @@ export async function adoptBuildThumbnailSuggestion({
|
|
|
439
439
|
});
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
export async function suggestBuildTitleToOwner({
|
|
443
|
+
options,
|
|
444
|
+
auth,
|
|
445
|
+
rootBuildId,
|
|
446
|
+
contributionBuildId,
|
|
447
|
+
title,
|
|
448
|
+
note,
|
|
449
|
+
}) {
|
|
450
|
+
return await requestJson({
|
|
451
|
+
method: "POST",
|
|
452
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}/suggest-title`,
|
|
453
|
+
authToken: auth.token,
|
|
454
|
+
body: { title, note },
|
|
455
|
+
timeoutMs: options.timeoutMs,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export async function adoptBuildTitleSuggestion({
|
|
460
|
+
options,
|
|
461
|
+
auth,
|
|
462
|
+
rootBuildId,
|
|
463
|
+
contributionBuildId,
|
|
464
|
+
suggestionMessageId,
|
|
465
|
+
}) {
|
|
466
|
+
return await requestJson({
|
|
467
|
+
method: "POST",
|
|
468
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}/adopt-title`,
|
|
469
|
+
authToken: auth.token,
|
|
470
|
+
body: { suggestionMessageId },
|
|
471
|
+
timeoutMs: options.timeoutMs,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
442
475
|
export async function mergeContributionIntoMain({
|
|
443
476
|
options,
|
|
444
477
|
auth,
|
package/lib/commands.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "./constants.js";
|
|
23
23
|
import {
|
|
24
24
|
adoptBuildThumbnailSuggestion,
|
|
25
|
+
adoptBuildTitleSuggestion,
|
|
25
26
|
createBuild,
|
|
26
27
|
fetchAllRuntimeAssets,
|
|
27
28
|
forkBuild,
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
resolveBranchBuild,
|
|
46
47
|
saveProjectFiles,
|
|
47
48
|
suggestBuildThumbnailToOwner,
|
|
49
|
+
suggestBuildTitleToOwner,
|
|
48
50
|
updateBuildMetadata,
|
|
49
51
|
upgradeBuildProjectLimits,
|
|
50
52
|
} from "./api.js";
|
|
@@ -523,11 +525,22 @@ export async function forum(options) {
|
|
|
523
525
|
|
|
524
526
|
export async function sendSuggestion(options) {
|
|
525
527
|
const suggestionType = String(options.suggestionAction || "").trim();
|
|
526
|
-
if (
|
|
528
|
+
if (!["branch", "thumbnail", "title"].includes(suggestionType)) {
|
|
527
529
|
throw new Error(
|
|
528
|
-
|
|
530
|
+
'Usage: lumine suggest branch [--note <message>] | lumine suggest thumbnail | lumine suggest title "New Name" [--note <why>]',
|
|
529
531
|
);
|
|
530
532
|
}
|
|
533
|
+
const suggestedTitle =
|
|
534
|
+
suggestionType === "title"
|
|
535
|
+
? String(
|
|
536
|
+
options.title || (options.positional || []).slice(1).join(" "),
|
|
537
|
+
)
|
|
538
|
+
.replace(/\s+/g, " ")
|
|
539
|
+
.trim()
|
|
540
|
+
: "";
|
|
541
|
+
if (suggestionType === "title" && !suggestedTitle) {
|
|
542
|
+
throw new Error('Pass the name: lumine suggest title "New Name" [--note <why>]');
|
|
543
|
+
}
|
|
531
544
|
const auth = await resolveAuth(options);
|
|
532
545
|
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
533
546
|
const build = await loadTargetBuildMetadata({ options, auth });
|
|
@@ -548,6 +561,21 @@ export async function sendSuggestion(options) {
|
|
|
548
561
|
return;
|
|
549
562
|
}
|
|
550
563
|
|
|
564
|
+
if (suggestionType === "title") {
|
|
565
|
+
await suggestBuildTitleToOwner({
|
|
566
|
+
options,
|
|
567
|
+
auth,
|
|
568
|
+
rootBuildId,
|
|
569
|
+
contributionBuildId,
|
|
570
|
+
title: suggestedTitle,
|
|
571
|
+
note: options.note,
|
|
572
|
+
});
|
|
573
|
+
console.log(
|
|
574
|
+
`Suggested the name "${suggestedTitle}" to the owner of Build #${rootBuildId}. They can use it from their chat or with \`lumine suggestions adopt-title\`.`,
|
|
575
|
+
);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
551
579
|
await suggestBuildThumbnailToOwner({
|
|
552
580
|
options,
|
|
553
581
|
auth,
|
|
@@ -696,8 +724,38 @@ export async function suggestions(options) {
|
|
|
696
724
|
return;
|
|
697
725
|
}
|
|
698
726
|
|
|
727
|
+
if (action === "adopt-title") {
|
|
728
|
+
if (suggestion.type !== "title") {
|
|
729
|
+
throw new Error(`Suggestion #${suggestionId} is not a name suggestion.`);
|
|
730
|
+
}
|
|
731
|
+
if (!options.assumeYes) {
|
|
732
|
+
const confirmed = await confirmPrompt(
|
|
733
|
+
`Rename Build #${rootBuildId} from "${suggestion.currentTitle}" to "${suggestion.suggestedTitle}"? [y/N] `,
|
|
734
|
+
);
|
|
735
|
+
if (confirmed === null) {
|
|
736
|
+
console.log("Not a TTY — re-run with --yes to rename the Build.");
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (!confirmed) {
|
|
740
|
+
console.log("Aborted. Name unchanged.");
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
const adoptResult = await adoptBuildTitleSuggestion({
|
|
745
|
+
options,
|
|
746
|
+
auth,
|
|
747
|
+
rootBuildId,
|
|
748
|
+
contributionBuildId,
|
|
749
|
+
suggestionMessageId: suggestionId,
|
|
750
|
+
});
|
|
751
|
+
console.log(
|
|
752
|
+
`Renamed Build #${rootBuildId} to "${String(adoptResult?.build?.title || suggestion.suggestedTitle)}" (suggestion #${suggestionId}).`,
|
|
753
|
+
);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
699
757
|
throw new Error(
|
|
700
|
-
"Usage: lumine suggestions [build] | lumine suggestions merge|replace-main|adopt-thumbnail <suggestion-id> [--build <id>]",
|
|
758
|
+
"Usage: lumine suggestions [build] | lumine suggestions merge|replace-main|adopt-thumbnail|adopt-title <suggestion-id> [--build <id>]",
|
|
701
759
|
);
|
|
702
760
|
}
|
|
703
761
|
|
|
@@ -723,8 +781,8 @@ export function printBuildSuggestions({
|
|
|
723
781
|
if (!suggestions.length) {
|
|
724
782
|
console.log(
|
|
725
783
|
hasMore
|
|
726
|
-
? "No open branch or
|
|
727
|
-
: "No open branch or
|
|
784
|
+
? "No open branch, thumbnail or name suggestions on this page."
|
|
785
|
+
: "No open branch, thumbnail or name suggestions.",
|
|
728
786
|
);
|
|
729
787
|
if (hasMore && nextCursor > 0) {
|
|
730
788
|
console.log(
|
|
@@ -761,6 +819,17 @@ export function printBuildSuggestions({
|
|
|
761
819
|
);
|
|
762
820
|
continue;
|
|
763
821
|
}
|
|
822
|
+
if (suggestion.type === "title") {
|
|
823
|
+
console.log(
|
|
824
|
+
`[#${suggestionId}] ${contributor} suggested a new name from ${branchLabel} (${createdAt})`,
|
|
825
|
+
);
|
|
826
|
+
console.log(` "${suggestion.currentTitle}" → "${suggestion.suggestedTitle}"`);
|
|
827
|
+
if (suggestion.note) console.log(` ${suggestion.note}`);
|
|
828
|
+
console.log(
|
|
829
|
+
` apply: lumine suggestions adopt-title ${suggestionId} --build ${rootBuildId}`,
|
|
830
|
+
);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
764
833
|
console.log(
|
|
765
834
|
`[#${suggestionId}] ${contributor} suggested a thumbnail from ${branchLabel} (${createdAt})`,
|
|
766
835
|
);
|
|
@@ -2342,6 +2411,7 @@ export function parseArgs(args) {
|
|
|
2342
2411
|
"merge",
|
|
2343
2412
|
"replace-main",
|
|
2344
2413
|
"adopt-thumbnail",
|
|
2414
|
+
"adopt-title",
|
|
2345
2415
|
]);
|
|
2346
2416
|
const suggestionAction =
|
|
2347
2417
|
command === "suggest"
|
|
@@ -2840,10 +2910,12 @@ export function printHelp() {
|
|
|
2840
2910
|
lumine forum listen [twinkle-build-url-or-id] [--cursor <sequence>] [--poll-ms <ms>] [--json]
|
|
2841
2911
|
lumine suggest branch [message] [--target <twinkle-branch-url>]
|
|
2842
2912
|
lumine suggest thumbnail [--target <twinkle-branch-url>]
|
|
2913
|
+
lumine suggest title "New Name" [--note <why>] [--target <twinkle-branch-url>]
|
|
2843
2914
|
lumine suggestions [twinkle-build-url-or-id]
|
|
2844
2915
|
lumine suggestions merge <suggestion-id> [--build <id>]
|
|
2845
2916
|
lumine suggestions replace-main <suggestion-id> [--build <id>]
|
|
2846
2917
|
lumine suggestions adopt-thumbnail <suggestion-id> [--build <id>] [--yes]
|
|
2918
|
+
lumine suggestions adopt-title <suggestion-id> [--build <id>] [--yes]
|
|
2847
2919
|
lumine explore [search terms]
|
|
2848
2920
|
lumine select [twinkle-build-url]
|
|
2849
2921
|
lumine pull [twinkle-build-url]
|
package/lib/constants.js
CHANGED
|
@@ -225,11 +225,14 @@ lumine save --summary "Describe the change"
|
|
|
225
225
|
|
|
226
226
|
- After saving contribution-branch work, use \`lumine suggest branch "Ready for review"\`
|
|
227
227
|
when the user wants to notify the project owner. Use \`lumine suggest thumbnail\`
|
|
228
|
-
when the branch's current thumbnail should be offered to the owner.
|
|
228
|
+
when the branch's current thumbnail should be offered to the owner. Only the
|
|
229
|
+
owner can rename the project: when the user wants a different app name, use
|
|
230
|
+
\`lumine suggest title "New Name" --note "why"\` instead of renaming.
|
|
229
231
|
- On an owned canonical team project, \`lumine suggestions\` lists the owner's
|
|
230
|
-
currently open branch and
|
|
231
|
-
id it prints with \`lumine suggestions merge\`, \`replace-main\`,
|
|
232
|
-
\`adopt-thumbnail\`; do not infer an action from stale
|
|
232
|
+
currently open branch, thumbnail and name suggestions. Act on the exact
|
|
233
|
+
suggestion id it prints with \`lumine suggestions merge\`, \`replace-main\`,
|
|
234
|
+
\`adopt-thumbnail\` or \`adopt-title\`; do not infer an action from stale
|
|
235
|
+
local branch state.
|
|
233
236
|
|
|
234
237
|
## Assets (Runtime Media)
|
|
235
238
|
|
package/lib/sdk.js
CHANGED
|
@@ -204,7 +204,11 @@ export const SDK_CLI_METHODS = {
|
|
|
204
204
|
operation: "claim",
|
|
205
205
|
scopes: ["rewards:claim"],
|
|
206
206
|
write: true,
|
|
207
|
-
mapArgs: (args) => ({
|
|
207
|
+
mapArgs: (args) => ({
|
|
208
|
+
challengeId: args.challengeId,
|
|
209
|
+
answers: args.answers,
|
|
210
|
+
...(args.completionToken !== undefined ? { completionToken: args.completionToken } : {}),
|
|
211
|
+
}),
|
|
208
212
|
},
|
|
209
213
|
// Leaderboards use the public leaderboard routes (regular login auth, no
|
|
210
214
|
// build API token). args.boardKey selects the board; remaining args are
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Version: 1.47.0
|
|
4
4
|
Updated: 2026-09-21
|
|
5
|
-
Generated: 2026-09-
|
|
5
|
+
Generated: 2026-09-23T02:44:06.901Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -36,15 +36,15 @@ Generated: 2026-09-21T08:06:52.743Z
|
|
|
36
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. Registered proof profiles also include breadface-v1 (server-simulated Breadface inputs)
|
|
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. Registered proof profiles also include breadface-v1, breadface-v2 and breadface-v3 (server-simulated Breadface inputs; each is one reviewed release's exact physics, chosen by the reviewer) study-record-v1 (a private study record reviewed by JEV, billed to the learner’s AI Energy), and Groove Lab's groove-lab-song-v1 (a song the learner published today, checked on the server for length, notes and originality) and groove-lab-heard-v1 (three established accounts finished the learner's songs today). Profiles are platform-owned; an app cannot invent a verifier or authorize its own reward.
|
|
40
40
|
|
|
41
41
|
## AI decision design
|
|
42
42
|
- When planning a new app or an improvement, consider whether model-based judgments would materially improve the requested experience. Twinkle.ai.decide runs JEV, a model for narrow decisions over supplied text or structured state. Potential uses include interpreting a player's request to an NPC, choosing among legal game actions, classifying user content, ranking supplied candidates, and adapting an activity to evidence about the learner. These are examples to reason from, not a keyword checklist or a requirement to add AI to every app.
|
|
43
43
|
- Choose the mechanism by the work: use ordinary code for known rules, arithmetic, physics, legal moves, storage, and permissions; use ai.decide for contextual choices, yes/no probabilities, and scores; use ai.chat or ai.generateObject for generated dialogue, explanations, open-ended content, and multi-step reasoning. JEV does not generate text, browse the web, inspect images, or supply missing app data. Combine these tools when it benefits the actual request, without silently turning every decision into a more expensive text-model call.
|
|
44
|
-
- Before adopting JEV, identify the concrete decision, the context available at that moment, the allowed outcomes, and why
|
|
44
|
+
- Before adopting JEV, identify the concrete decision, the context available at that moment, the allowed outcomes, and why the added latency is worthwhile. Use your model judgment to make that choice. Do not substitute regex or keyword matching for understanding human-language intent. Explain the user-visible benefit simply; the creator does not need to select a provider, enter an API key, or design question schemas.
|
|
45
45
|
- Send related independent questions about the same state together in one ai.decide request (up to 32). Write complete instructions and distinct option or level descriptions. Include an other/uncertain option when the choices might not cover the input. A second call is useful only when it needs newly obtained context or choices that depend on the first result; answers in one batch do not see each other.
|
|
46
46
|
- Keep code in control. Choice returns an option and its probability distribution; score returns a position on the declared levels; noul is the probability of yes, with 0.5 meaning uncertainty rather than medium strength. Confidence describes the distribution and is not proof of correctness. Test the app's criteria and thresholds on clear, ambiguous, and out-of-scope examples, and provide a suitable uncertain outcome. JEV decisions never authorize XP, Coins, purchases, access, or other server-owned state; use the appropriate existing SDK and server approval paths.
|
|
47
|
-
- Call at meaningful user actions or discrete app decision points, not every render, animation frame, keystroke, or background poll. Batch work, debounce changing input, keep one request active per decision flow, and discard results whose app state changed while waiting. Keep rendering, movement, and controls local and responsive.
|
|
47
|
+
- Call at meaningful user actions or discrete app decision points, not every render, animation frame, keystroke, or background poll. Batch work, debounce changing input, keep one request active per decision flow, and discard results whose app state changed while waiting. Keep rendering, movement, and controls local and responsive. JEV decisions are covered by Twinkle and never use the viewer's AI Energy; the runtime AI rate limits still apply. Handle unavailable, timeout, and rate-limit errors with a clear retry or an explicitly local fallback; never fabricate an AI answer or run an automatic retry loop. Consider the guest and offline experience before making AI essential.
|
|
48
48
|
|
|
49
49
|
## Token Scopes
|
|
50
50
|
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
|
|
@@ -463,7 +463,7 @@ renderBattery(policy?.energyPercent, policy?.energySegmentsRemaining);
|
|
|
463
463
|
- Returns: { text, response, model, webSearch, aiUsagePolicy }
|
|
464
464
|
- Generate text with the default Lumine text model, optionally using live web search and streaming text updates through onText.
|
|
465
465
|
- Signed-in viewers only.
|
|
466
|
-
- Uses
|
|
466
|
+
- Uses GPT-6 Luna by default.
|
|
467
467
|
- Each successful text generation consumes AI Energy from the signed-in viewer.
|
|
468
468
|
- history must be an array of { role: 'user' | 'assistant', content: string }. Twinkle.ai.chat does not read a text field.
|
|
469
469
|
- The server keeps the latest 12 valid history entries.
|
|
@@ -484,8 +484,8 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
|
|
|
484
484
|
- Score: { type: 'score', instructions, criteria: [lowestLevelDescription, ..., highestLevelDescription] }. Supply 2–10 concrete ordered descriptions. Returns { type: 'score', score, probabilities, confidence, legend }. Levels start at 0; score may be fractional and is a probability-weighted position between 0 and criteria.length - 1.
|
|
485
485
|
- Yes/no probability: { type: 'noul', instructions, criteria?: { true: description, false: description } }. Returns { type: 'noul', noul }, where noul is the probability of yes between 0 and 1. There is no separate confidence field. If criteria is supplied, describe both true and false.
|
|
486
486
|
- A request accepts 1–32 questions, at most 64 KB of JSON and 20 levels of nesting. Question IDs and option names must be non-empty, at most 128 characters, and cannot be __proto__, constructor, or prototype. Descriptions may use structured JSON; state must contain only data the app is allowed to access.
|
|
487
|
-
- Requires a signed-in viewer and uses the normal runtime AI rate limits. Twinkle selects and calls JEV on its server; never put provider credentials or an external endpoint in app code.
|
|
488
|
-
- Errors include invalid_ai_decision (bad input), ai_decision_unavailable, ai_decision_timeout, ai_decision_rate_limited, ai_decision_invalid_response, and ai_usage_unavailable, plus the existing auth, access, Energy, and rate-limit errors. Invalid answers are rejected.
|
|
487
|
+
- Requires a signed-in viewer and uses the normal runtime AI rate limits. Twinkle selects and calls JEV on its server; never put provider credentials or an external endpoint in app code. Decisions are covered by Twinkle: they never use the viewer's AI Energy, and a viewer with empty Energy still gets answers. The response includes the viewer's current canonical aiUsagePolicy, unchanged by the call.
|
|
488
|
+
- Errors include invalid_ai_decision (bad input), ai_decision_unavailable, ai_decision_timeout, ai_decision_rate_limited, ai_decision_invalid_response, and ai_usage_unavailable, plus the existing auth, access, Energy, and rate-limit errors. Invalid answers are rejected. An answer that fails validation is retried once on the server within the same deadline; there is no other provider retry, fabricated answer, or paid LLM fallback. Do not automatically retry a rejected request.
|
|
489
489
|
- Call on meaningful app events with bounded frequency, batch questions, and keep rendering and deterministic game rules in local code. Ignore a result if the app state or turn changed while it was pending. Test uncertain inputs and choose a fallback appropriate to the experience; confidence is not a correctness guarantee.
|
|
490
490
|
- Example: const { answers } = await Twinkle.ai.decide({ state: { playerRequest, availableActions }, questions: { action: { type: 'choice', instructions: 'Which available companion action best fits playerRequest? Use wait when unclear.', criteria: { follow: 'Follow the player', guard: 'Stay and keep watch', wait: 'Do nothing until clarified' } }, needsClarification: { type: 'noul', instructions: 'Is playerRequest too ambiguous to act on?' } } }); const action = answers.needsClarification.noul > 0.5 ? 'wait' : answers.action.choice;
|
|
491
491
|
- async generateObject({ prompt, expectedStructure, thinkingMode, mode, model, instructions, systemPrompt, webSearch, requestId, onText, onStatus, onReasoning } = {}) | scopes: none
|
|
@@ -495,11 +495,11 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
|
|
|
495
495
|
- Use this instead of asking Twinkle.ai.chat to return JSON.
|
|
496
496
|
- expectedStructure must be a JSON object that describes the exact returned object shape.
|
|
497
497
|
- mode is accepted as an alias for thinkingMode, and mid is accepted as an alias for medium.
|
|
498
|
-
- Omit model to use the normal Lite/Medium/High routing. model accepts gpt-6-
|
|
499
|
-
- thinkingMode low uses GPT-
|
|
500
|
-
- thinkingMode medium uses
|
|
501
|
-
- thinkingMode high without model uses GPT-
|
|
502
|
-
- claude-opus-5 uses Anthropic adaptive High thinking
|
|
498
|
+
- Omit model to use the normal Lite/Medium/High routing. model accepts gpt-6-sol or claude-opus-5-5, and every explicit model must be paired with thinkingMode: 'high'; unknown model IDs reject instead of silently falling back. Retired IDs still work and run on their replacement: gpt-5.6-sol runs gpt-6-sol; gpt-6-astra, claude-opus-5 and claude-fable-5-1 run claude-opus-5-5.
|
|
499
|
+
- thinkingMode low uses GPT-6 Luna and consumes the viewer's AI Energy from confirmed provider usage; its smaller model is usually cheaper than Medium or High.
|
|
500
|
+
- thinkingMode medium uses GPT-6 Luna with medium reasoning and consumes normal AI Energy.
|
|
501
|
+
- thinkingMode high without model uses GPT-6 Sol with high reasoning and consumes high AI Energy. Explicit model: 'gpt-6-sol' selects Sol with xhigh reasoning at the same High AI Energy tier.
|
|
502
|
+
- claude-opus-5-5 uses Anthropic adaptive High thinking and debits confirmed provider usage at the High tier.
|
|
503
503
|
- Pass onStatus, onReasoning, and/or onText to stream progress from the same structured generation. onStatus receives high-level phases such as thinking, searching_web, responding, validating, and completed.
|
|
504
504
|
- onReasoning receives accumulated provider-supplied, app-visible reasoning summaries plus { done, delta, requestId, status }. A provider retry may replace the accumulated summary; treat each callback's first argument as the current source of truth. This callback never exposes hidden/private model chain-of-thought.
|
|
505
505
|
- onText receives accumulated structured-output text plus { done, delta, requestId, status }. Partial output is intentionally incomplete and may include provider formatting; parse only when done is true, when the callback receives the canonical object serialized as JSON, and use the resolved object as the source of truth.
|
|
@@ -507,7 +507,7 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
|
|
|
507
507
|
- When AI Energy is empty, every automatic or named model choice rejects before new provider work; there is no free fallback mode.
|
|
508
508
|
- Live web search is enabled by default in Medium and High modes. Pass webSearch: false to disable it for the app. Low/Lite Mode remains tool-free; explicitly forcing webSearch: true in Low Mode returns an error.
|
|
509
509
|
- The server validates the final shape; automatic OpenAI/xAI routes can retry malformed output, while explicit Anthropic routes use native JSON Schema output and retry one malformed or shape-invalid result. App code should still validate business-specific enum values.
|
|
510
|
-
- Example: const { object } = await Twinkle.ai.generateObject({ thinkingMode: 'high', model: 'claude-opus-5', prompt: 'Plan the next section from: ' + currentState, expectedStructure: { producerNotes: 'string', action: 'string', confidence: 0 }, onStatus: (phase) => showPhase(phase), onReasoning: (summary, meta) => showReasoningProgress(summary, meta), onText: (partialJson, meta) => showStructuredProgress(partialJson, meta) });
|
|
510
|
+
- Example: const { object } = await Twinkle.ai.generateObject({ thinkingMode: 'high', model: 'claude-opus-5-5', prompt: 'Plan the next section from: ' + currentState, expectedStructure: { producerNotes: 'string', action: 'string', confidence: 0 }, onStatus: (phase) => showPhase(phase), onReasoning: (summary, meta) => showReasoningProgress(summary, meta), onText: (partialJson, meta) => showStructuredProgress(partialJson, meta) });
|
|
511
511
|
- onChatStatus(listener) | scopes: none
|
|
512
512
|
- Returns: unsubscribe function
|
|
513
513
|
- Listen to shared runtime AI chat stream events.
|
|
@@ -550,9 +550,9 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
|
|
|
550
550
|
- The character route also accepts text or message fields for compatibility, but generated apps should use content.
|
|
551
551
|
- The server keeps the latest 16 valid character history entries.
|
|
552
552
|
- Pass onText/onStatus for streaming dialogue. Omit callbacks for non-streaming dialogue where the promise resolves with the final response.
|
|
553
|
-
- Inside Build character chat, thinkingMode low uses Lite Mode: Zero and Ciel both use GPT-
|
|
554
|
-
- Inside Build character chat, thinkingMode medium uses the same normal chat model routing: Zero and Ciel both use GPT-
|
|
555
|
-
- Inside Build character chat, thinkingMode high uses Think Hard chat routing and high AI Energy: Zero uses Grok 4.
|
|
553
|
+
- Inside Build character chat, thinkingMode low uses Lite Mode: Zero and Ciel both use GPT-6 Luna with reasoning disabled; confirmed provider usage consumes the viewer's AI Energy and is usually cheaper than High.
|
|
554
|
+
- Inside Build character chat, thinkingMode medium uses the same normal chat model routing: Zero and Ciel both use GPT-6 Luna with reasoning disabled and normal AI Energy.
|
|
555
|
+
- Inside Build character chat, thinkingMode high uses Think Hard chat routing and high AI Energy: Zero uses Grok 4.7 with high reasoning and Ciel uses Claude Opus 5.5 with high thinking.
|
|
556
556
|
- When AI Energy is empty, Low, Medium, and High all reject before new provider work; there is no free fallback mode.
|
|
557
557
|
- Pass roomContext as a short shared scene transcript so Zero and Ciel can know what happened in the same room.
|
|
558
558
|
- includeWebsiteContext defaults to true. Set includeWebsiteContext: false for in-world NPC dialogue that should only use Zero/Ciel's basic character identity plus your scene/instructions.
|
|
@@ -1050,14 +1050,14 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1050
1050
|
- 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.
|
|
1051
1051
|
- 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).
|
|
1052
1052
|
- 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.
|
|
1053
|
-
- For breadface-v1, pass the zero-based canonical levelIndex. Record [dt, inputBits] from the first physics frame; start returns its server token and maxFrames. For study-record-v1, start returns completion { profile, usesAiEnergy: true }; there is no client-authored proof token.
|
|
1053
|
+
- For breadface-v1, breadface-v2 and breadface-v3, pass the zero-based canonical levelIndex. Record [dt, inputBits] from the first physics frame; start returns its server token and maxFrames. For study-record-v1, start returns completion { profile, usesAiEnergy: true }; there is no client-authored proof token. For groove-lab-song-v1 and groove-lab-heard-v1, start returns completion { profile }; there is no progress step.
|
|
1054
1054
|
- await Twinkle.rewards.progress({ challengeId, completionToken?, frames?, record?, requestId? }) | scopes: rewards:claim
|
|
1055
1055
|
- Returns: { mode: "live" | "preview", completion: { profile, token?, completed, failed?, decision?, message?, maxFrames? }, aiUsagePolicy? }
|
|
1056
1056
|
- Verify a bounded batch of inputs for an approved server-simulated climb.
|
|
1057
1057
|
- 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.
|
|
1058
1058
|
- 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.
|
|
1059
1059
|
- 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.
|
|
1060
|
-
- For breadface-v1, send 1–1,000 chronological [dt, inputBits] frames (dt > 0 and <= 0.033). Bits are left=1, right=2, jumpHeld=4, jumpQueued=8, fireHeld=16. The server replays the registered frozen game; only a legitimate goal and any rule-specific bonus qualify. Honor maxFrames and retain unacknowledged inputs for a bounded retry.
|
|
1060
|
+
- For breadface-v1, breadface-v2 and breadface-v3, send 1–1,000 chronological [dt, inputBits] frames (dt > 0 and <= 0.033). Bits are left=1, right=2, jumpHeld=4, jumpQueued=8, fireHeld=16. The server replays the registered frozen game; only a legitimate goal and any rule-specific bonus qualify. Honor maxFrames and retain unacknowledged inputs for a bounded retry.
|
|
1061
1061
|
- For study-record-v1, send record { work, learning, nextStep? } and one stable requestId (16–64 ASCII letters, numbers, hyphens or underscores, e.g. crypto.randomUUID()). The combined record is at most 4,000 UTF-8 bytes. Show that each check uses AI Energy before sending; accept, revise and uncertain decisions all incur measured check cost. Reuse the requestId for a lost response; do not poll in a loop. An accepted, billed review returns completion.completed and a bound token; use that token with claim. A preview charges for the check but never awards real XP/Coins. Keep the record and show completion.message on a refusal or uncertain result.
|
|
1062
1062
|
- await Twinkle.rewards.claim({ challengeId, answers?: [number], completionToken?: string }) | scopes: rewards:claim
|
|
1063
1063
|
- 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 } }
|
|
@@ -1066,7 +1066,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1066
1066
|
- 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.
|
|
1067
1067
|
- 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.
|
|
1068
1068
|
- 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.
|
|
1069
|
-
- For study-record-v1, the server verifies the settled private review row and its viewer, app, exact release, challenge and day binding. It never trusts the client’s decision, requested award amount or AI answer. Each successful daily study record is claimable once; display only canonical receipt/balances.
|
|
1069
|
+
- For study-record-v1, the server verifies the settled private review row and its viewer, app, exact release, challenge and day binding. It never trusts the client’s decision, requested award amount or AI answer. Each successful daily study record is claimable once; display only canonical receipt/balances. For groove-lab-song-v1, pass completionToken as the published song's sharedDb entry id; for groove-lab-heard-v1, pass none. The server reads the app's own groove-lab-songs, groove-lab-song-parts and groove-lab-listens rows and refuses with build_reward_completion_proof_required and a plain reason when the song or listeners do not qualify.
|
|
1070
1070
|
- await Twinkle.rewards.getLeaderboard({ metric?: "xp" | "coins", period?: "day" | "week" | "all", limit? }) | scopes: rewards:claim
|
|
1071
1071
|
- 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 }
|
|
1072
1072
|
- 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
|
@@ -2997,7 +2997,18 @@ Mikey"** section carrying only
|
|
|
2997
2997
|
the deltas and anomalies worth his time, next to the escalation list. Never
|
|
2998
2998
|
dump raw sections at him.
|
|
2999
2999
|
|
|
3000
|
-
### Jev serving and audits (standing duty, every full daily review; updated 2026-09-
|
|
3000
|
+
### Jev serving and audits (standing duty, every full daily review; updated 2026-09-23)
|
|
3001
|
+
|
|
3002
|
+
**September 23, 2026: LLM audits retired.** Mikey judged the Jev experiment a
|
|
3003
|
+
success and asked to drop the LLM comparison on served routes. Comments, chat
|
|
3004
|
+
routing and Lumine Auto no longer run any background LLM audit; the LLM runs
|
|
3005
|
+
only when Jev cannot decide. From that day, audit coverage, paired/served
|
|
3006
|
+
disagreements and the `jev_reply_gate_audit`, `jev_chat_routing_audit` and
|
|
3007
|
+
`jev_lumine_model_audit` operations read zero by design; do not report that as
|
|
3008
|
+
missing evidence. Report fallback rates and reasons instead. Auto rows served
|
|
3009
|
+
without a comparison carry `baselineStatus: not_run`; its fallback LLM spend is
|
|
3010
|
+
`lumine_model_fallback`. The eight comparison-only routing families below are
|
|
3011
|
+
unchanged. The rest of this section describes the pre-September-23 pilot.
|
|
3001
3012
|
|
|
3002
3013
|
Read `data.jevPilot` from `lumine admin brief --json` and carry it into
|
|
3003
3014
|
the full report for Mikey. The active `daily-run report --json` also includes
|
|
@@ -3259,6 +3270,55 @@ carry-over todo with the exact figures and day. **Never auto-enforce** —
|
|
|
3259
3270
|
escalate to Mikey; this duty observes, it does not change budgets, caps, or
|
|
3260
3271
|
user state.
|
|
3261
3272
|
|
|
3273
|
+
#### Energy pacing calibration (JEV; since 2026-09-21)
|
|
3274
|
+
|
|
3275
|
+
Lumine asks JEV two questions inside every budgeted run, and acts on confident
|
|
3276
|
+
answers (`JEV_LUMINE_PACING_MODE`: `serve` by default, `shadow` records without
|
|
3277
|
+
acting, `off` disables):
|
|
3278
|
+
|
|
3279
|
+
- **fit**, once before a fresh request's first paid round: `fits` / `too_big`.
|
|
3280
|
+
`too_big` at or above `pacingThresholds.tooBigConfidence`, with a lighter
|
|
3281
|
+
model that fits, keeps the run from starting and shows the switch-model card.
|
|
3282
|
+
- **pace**, before each tool round: `continue` / `apply_next` / `hand_off`.
|
|
3283
|
+
`apply_next` at or above `applyNextConfidence` asks the agent to edit in its
|
|
3284
|
+
next round; `hand_off` at or above `handOffConfidence` ends the run before
|
|
3285
|
+
its pending reads, keeping the unspent Energy.
|
|
3286
|
+
|
|
3287
|
+
Separately from JEV, a run that reaches its round cap with nothing saved while
|
|
3288
|
+
Energy remains gets one extra apply-only round (`applyOnlyRound`).
|
|
3289
|
+
|
|
3290
|
+
Every UTC day in `lumine admin energy-budget --json` now carries `pacing`.
|
|
3291
|
+
Report in **"Insights for Mikey"** in every full run, for the last completed
|
|
3292
|
+
day and the in-progress day while the feature is new:
|
|
3293
|
+
|
|
3294
|
+
- `paceCalibration` and `fitCalibration`: verdict → confidence bucket (`low`
|
|
3295
|
+
below the apply-next bar, `mid` up to the hand-off bar, `high` above it) →
|
|
3296
|
+
passes that ended `saved` / `unsaved`. The verdict is the most severe one of
|
|
3297
|
+
the pass. Counts are agent passes, not requests (a repair pass is judged
|
|
3298
|
+
again). Served stops are excluded because nothing shows what would have
|
|
3299
|
+
happened.
|
|
3300
|
+
- `served`: early hand-offs with the Energy they kept (`energyKeptUsd`),
|
|
3301
|
+
apply-next nudges with how those passes ended, and `tooBigNotStarted`.
|
|
3302
|
+
- `applyOnlyRound`: extra rounds granted and whether they saved anything.
|
|
3303
|
+
- `passesWithUnavailableRounds`: passes where JEV gave no verdict for at least
|
|
3304
|
+
one round (timeout, outage); those rounds ran on the arithmetic alone.
|
|
3305
|
+
|
|
3306
|
+
How to read it, with the exact counts: `hand_off` in `low`/`mid` that mostly
|
|
3307
|
+
ended `unsaved` means the hand-off bar is too high (runs JEV doubted went on to
|
|
3308
|
+
waste Energy); `hand_off` that mostly ended `saved` means JEV is too
|
|
3309
|
+
pessimistic and the bar must not drop. `continue` in `high` ending `unsaved`
|
|
3310
|
+
are misses: inspect those requests. `fits` ending `unsaved` on heavy models
|
|
3311
|
+
are fit-check misses; `too_big` below the bar ending `saved` means the bar is
|
|
3312
|
+
right to be high. `applyOnlyRound.saved` shows the extra round rescuing runs.
|
|
3313
|
+
Compare `budget_stop_unchanged` and its ratio with the days before 2026-09-21.
|
|
3314
|
+
The host script's stop cases add per-request `fitVerdict`, `pacingVerdicts` and
|
|
3315
|
+
`stopReason` (`pacing_hand_off`, `pacing_too_big`, `tool_round_limit`,
|
|
3316
|
+
`unaffordable_work_turn`). JEV spend for these calls appears under surface
|
|
3317
|
+
`jev_routing`, operations `jev_lumine_pacing_*` and `jev_lumine_fit_*`.
|
|
3318
|
+
Recommend threshold changes to Mikey with the figures; **never change them or
|
|
3319
|
+
the mode yourself**. Fewer than about 20 judged passes is too little to
|
|
3320
|
+
calibrate on: report the counts and say so.
|
|
3321
|
+
|
|
3262
3322
|
### Lumine media feature cost and cleanup watch (standing duty, every full daily review)
|
|
3263
3323
|
|
|
3264
3324
|
Run `lumine admin media-costs monthly --json` during every full daily management
|
|
@@ -4381,8 +4441,8 @@ shown above are the canonical interface.
|
|
|
4381
4441
|
|
|
4382
4442
|
September 21 routing expansion: read `jevRoutingShadow` alongside `jevPilot` in
|
|
4383
4443
|
both the admin brief and full daily report. The reviewed `chat-routing-v3` path
|
|
4384
|
-
is primary; report actual selection latency, chat baseline calls avoided
|
|
4385
|
-
|
|
4444
|
+
is primary; report actual selection latency, chat baseline calls avoided and
|
|
4445
|
+
fallback reasons (no audits since September 23). New routing
|
|
4386
4446
|
families remain comparison-only until Mikey reviews each one and explicitly
|
|
4387
4447
|
promotes it. Report every registered family, including zero-sample and skipped
|
|
4388
4448
|
families; no traffic is not a pass. Show paired counts, exact differing fields,
|
|
@@ -4395,15 +4455,16 @@ proof an action was performed. Do not call agreement accuracy or infer speedups
|
|
|
4395
4455
|
from shadow timings. Full daily costs must include these operations once only.
|
|
4396
4456
|
|
|
4397
4457
|
September 21 Auto exception: Mikey approved JEV as Lumine Auto's primary model
|
|
4398
|
-
selector immediately
|
|
4458
|
+
selector immediately. Its per-choice LLM comparison was retired September 23;
|
|
4459
|
+
the LLM runs only as the fallback when JEV cannot decide.
|
|
4399
4460
|
Auto is the new default; stored manual preferences remain manual. Review
|
|
4400
4461
|
`jevRoutingShadow` / `byRoute.lumine_model` separately from the eight
|
|
4401
4462
|
comparison-only families: selected model/effort, both decisions, exact selection
|
|
4402
4463
|
context, confidence, fallback, actual selection latency, missing evidence and
|
|
4403
|
-
observed task outcome. Report `jev_lumine_model_serve` and the
|
|
4404
|
-
`
|
|
4405
|
-
double-counting. JEV choice confidence
|
|
4406
|
-
|
|
4464
|
+
observed task outcome. Report `jev_lumine_model_serve` and the fallback
|
|
4465
|
+
`lumine_model_fallback` spend separately, using canonical AI-cost totals without
|
|
4466
|
+
double-counting. JEV choice confidence is not a correctness score. The other
|
|
4467
|
+
eight families still require review before promotion.
|
|
4407
4468
|
|
|
4408
4469
|
|
|
4409
4470
|
### September 21 verified reward follow-up
|