@stage5/lumine 0.2.43 → 0.2.44
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/README.md +17 -6
- package/lib/admin.js +175 -3
- package/lib/commands.js +7 -3
- package/lib/constants.js +23 -2
- package/lib/doctor.js +4 -3
- package/package.json +1 -1
- package/sdk/LUMINE_ADMIN.md +90 -4
package/README.md
CHANGED
|
@@ -217,6 +217,12 @@ lumine admin identity list --json
|
|
|
217
217
|
lumine admin identity inspect Jay1216 \
|
|
218
218
|
--reason "Confirm account family before a quota-bucket change" --json
|
|
219
219
|
lumine admin daily-run start --identity auto --comment-mode off --json
|
|
220
|
+
lumine admin todo list --json
|
|
221
|
+
lumine admin todo add --kind experiment --status in_progress \
|
|
222
|
+
--title "Validate Zero/Ciel cost optimization" \
|
|
223
|
+
--note "Complete only after old-vs-new response-quality parity." --json
|
|
224
|
+
lumine admin todo update 12 --status blocked \
|
|
225
|
+
--note "Waiting for a complete cost bucket and parity replay." --json
|
|
220
226
|
lumine admin recommendations list --all --checkpoint recommendations.json --json
|
|
221
227
|
lumine admin recommendations list --after 2026-08-14T00:00:00Z --all --json
|
|
222
228
|
lumine admin recommendations list --include-legacy --all --json
|
|
@@ -295,14 +301,19 @@ cursors are bound to the original date and effort filters.
|
|
|
295
301
|
`news claim` can write both the canonical leased digest and an editable
|
|
296
302
|
editorial scaffold. `news validate` is local and checks every citation and
|
|
297
303
|
quote before submission; `news submit --claim` reads the lease identity from
|
|
298
|
-
the claim file. `daily-run
|
|
299
|
-
|
|
300
|
-
|
|
304
|
+
the claim file. Every `daily-run start` response includes writer-confirmed
|
|
305
|
+
unfinished private todos, with once-per-run surfacing telemetry, so an agent
|
|
306
|
+
can resume earlier work without relying on conversation memory. Record progress
|
|
307
|
+
with `todo update`; completing a run does not complete its todos. Experiments
|
|
308
|
+
must meet their stated acceptance criteria—lower AI cost with weaker user
|
|
309
|
+
responses is not a successful optimization. `daily-run report` summarizes
|
|
310
|
+
confirmed mutations, completed queue coverage, explicitly recorded escalations,
|
|
311
|
+
unfinished todos, and the run brief before the run is completed.
|
|
301
312
|
|
|
302
313
|
Identity inspection, escalation dispositions, AI-bucket maintenance, and
|
|
303
|
-
approved Notable User additions are private operator bookkeeping and
|
|
304
|
-
require a delegated daily run. Identity inspection always requires an
|
|
305
|
-
`--reason`; raw email/DOB evidence additionally requires
|
|
314
|
+
approved Notable User additions and todos are private operator bookkeeping and
|
|
315
|
+
do not require a delegated daily run. Identity inspection always requires an
|
|
316
|
+
audited `--reason`; raw email/DOB evidence additionally requires
|
|
306
317
|
`--include-private-evidence`. Routine briefs omit raw email identities.
|
|
307
318
|
|
|
308
319
|
The complete run lifecycle, command contracts, nullable fields, Karma approval
|
package/lib/admin.js
CHANGED
|
@@ -23,6 +23,8 @@ const MAX_COMPOSED_TEXT_LENGTH = 10_000;
|
|
|
23
23
|
const MAX_NOTABLE_NOTE_LENGTH = 2_000;
|
|
24
24
|
const MAX_IDENTITY_INSPECTION_REASON_LENGTH = 500;
|
|
25
25
|
const MAX_ESCALATION_DECISION_NOTE_LENGTH = 2_000;
|
|
26
|
+
const MAX_TODO_TITLE_LENGTH = 200;
|
|
27
|
+
const MAX_TODO_NOTE_LENGTH = 4_000;
|
|
26
28
|
|
|
27
29
|
// Operator-composed persona text (plain UTF-8, not JSON). The agent writes
|
|
28
30
|
// the content in the bot's persona itself; the server never invokes
|
|
@@ -246,6 +248,9 @@ export async function adminCommand(options) {
|
|
|
246
248
|
expectedContent: operation.body.content,
|
|
247
249
|
});
|
|
248
250
|
}
|
|
251
|
+
if (operation.name === "daily-run.start") {
|
|
252
|
+
assertAdminTodoHandoffResult(result);
|
|
253
|
+
}
|
|
249
254
|
if (operation.name === "news.claim") {
|
|
250
255
|
const artifacts = writeNewsClaimArtifacts({
|
|
251
256
|
result,
|
|
@@ -506,7 +511,9 @@ function adminOperationRequiresRun(operation) {
|
|
|
506
511
|
"escalation.list",
|
|
507
512
|
"escalation.set",
|
|
508
513
|
"notable.add",
|
|
509
|
-
].includes(operation.name) &&
|
|
514
|
+
].includes(operation.name) &&
|
|
515
|
+
!operation.name.startsWith("ai-bucket.") &&
|
|
516
|
+
!operation.name.startsWith("todo.")
|
|
510
517
|
);
|
|
511
518
|
}
|
|
512
519
|
|
|
@@ -621,6 +628,69 @@ export function parseAdminOperation(options) {
|
|
|
621
628
|
}
|
|
622
629
|
}
|
|
623
630
|
|
|
631
|
+
if (namespace === "todo" || namespace === "todos") {
|
|
632
|
+
if (!action || action === "list") {
|
|
633
|
+
return readOperation(
|
|
634
|
+
"todo.list",
|
|
635
|
+
withQuery("/cli/admin/todos", {
|
|
636
|
+
status: parseTodoListStatus(options.adminStatus || "pending"),
|
|
637
|
+
limit: options.limit,
|
|
638
|
+
}),
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
if (action === "add" || action === "create") {
|
|
642
|
+
const title = String(options.title || "").trim();
|
|
643
|
+
const details = String(options.note || "").trim();
|
|
644
|
+
if (!title || !details) {
|
|
645
|
+
throw cliValidationError(
|
|
646
|
+
"Usage: lumine admin todo add --title <title> --note <handoff and acceptance criteria> [--kind task|experiment] [--status open|in_progress|blocked].",
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
if (title.length > MAX_TODO_TITLE_LENGTH) {
|
|
650
|
+
throw cliValidationError(
|
|
651
|
+
`A todo title must be at most ${MAX_TODO_TITLE_LENGTH} characters.`,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
if (details.length > MAX_TODO_NOTE_LENGTH) {
|
|
655
|
+
throw cliValidationError(
|
|
656
|
+
`Todo details must be at most ${MAX_TODO_NOTE_LENGTH} characters.`,
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
return writeOperation("todo.add", "POST", "/cli/admin/todos", {
|
|
660
|
+
kind: parseTodoKind(options.adminKind || "task"),
|
|
661
|
+
title,
|
|
662
|
+
details,
|
|
663
|
+
status: parseTodoInitialStatus(options.adminStatus || "open"),
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (action === "update") {
|
|
667
|
+
const todoId = parseRequiredInteger(target, "Todo ID", 1);
|
|
668
|
+
const note = String(options.note || "").trim();
|
|
669
|
+
if (!note) {
|
|
670
|
+
throw cliValidationError(
|
|
671
|
+
"Record concrete progress, evidence, or the reason for the state change with --note <text>.",
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
if (note.length > MAX_TODO_NOTE_LENGTH) {
|
|
675
|
+
throw cliValidationError(
|
|
676
|
+
`A todo progress note must be at most ${MAX_TODO_NOTE_LENGTH} characters.`,
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
return writeOperation(
|
|
680
|
+
"todo.update",
|
|
681
|
+
"PUT",
|
|
682
|
+
`/cli/admin/todos/${todoId}`,
|
|
683
|
+
{
|
|
684
|
+
status: parseTodoStatus(options.adminStatus),
|
|
685
|
+
note,
|
|
686
|
+
},
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
throw cliValidationError(
|
|
690
|
+
"Usage: lumine admin todo list [--status pending|open|in_progress|blocked|completed|cancelled|all] | todo add --title <title> --note <details> | todo update <id> --status <status> --note <progress>.",
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
624
694
|
if (namespace === "daily-run") {
|
|
625
695
|
if (action === "start") {
|
|
626
696
|
return writeOperation(
|
|
@@ -1240,7 +1310,7 @@ export function parseAdminOperation(options) {
|
|
|
1240
1310
|
}
|
|
1241
1311
|
|
|
1242
1312
|
throw cliValidationError(
|
|
1243
|
-
"Usage: lumine admin identity|daily-run|escalation|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|bot-output|notable ...",
|
|
1313
|
+
"Usage: lumine admin identity|daily-run|escalation|todo|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|bot-output|notable ...",
|
|
1244
1314
|
);
|
|
1245
1315
|
}
|
|
1246
1316
|
|
|
@@ -1573,6 +1643,28 @@ export function formatAdminJsonError(error) {
|
|
|
1573
1643
|
};
|
|
1574
1644
|
}
|
|
1575
1645
|
|
|
1646
|
+
export function assertAdminTodoHandoffResult(result) {
|
|
1647
|
+
const runId = Number(result?.data?.run?.id || 0);
|
|
1648
|
+
const handoff = result?.data?.carryoverTodos;
|
|
1649
|
+
if (
|
|
1650
|
+
!runId ||
|
|
1651
|
+
!handoff ||
|
|
1652
|
+
!Array.isArray(handoff.items) ||
|
|
1653
|
+
Number(handoff.count) !== handoff.items.length ||
|
|
1654
|
+
Number(handoff.surfacedForRunId) !== runId ||
|
|
1655
|
+
!Number.isSafeInteger(Number(handoff.newlySurfacedCount)) ||
|
|
1656
|
+
Number(handoff.newlySurfacedCount) < 0 ||
|
|
1657
|
+
Number(handoff.newlySurfacedCount) > handoff.items.length
|
|
1658
|
+
) {
|
|
1659
|
+
const error = cliValidationError(
|
|
1660
|
+
"The API did not confirm the canonical carry-over todo handoff. Deploy the todo migration/API before using this Lumine CLI for community management.",
|
|
1661
|
+
);
|
|
1662
|
+
error.code = "LUMINE_ADMIN_TODO_HANDOFF_UNSUPPORTED";
|
|
1663
|
+
throw error;
|
|
1664
|
+
}
|
|
1665
|
+
return handoff;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1576
1668
|
function readOperation(name, path, extra = {}) {
|
|
1577
1669
|
return {
|
|
1578
1670
|
name,
|
|
@@ -1643,6 +1735,56 @@ function parseEscalationListStatus(value) {
|
|
|
1643
1735
|
return status;
|
|
1644
1736
|
}
|
|
1645
1737
|
|
|
1738
|
+
function parseTodoKind(value) {
|
|
1739
|
+
const kind = String(value || "task")
|
|
1740
|
+
.trim()
|
|
1741
|
+
.toLowerCase();
|
|
1742
|
+
if (!["task", "experiment"].includes(kind)) {
|
|
1743
|
+
throw cliValidationError("--kind must be task or experiment.");
|
|
1744
|
+
}
|
|
1745
|
+
return kind;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
function parseTodoInitialStatus(value) {
|
|
1749
|
+
const status = String(value || "open")
|
|
1750
|
+
.trim()
|
|
1751
|
+
.toLowerCase();
|
|
1752
|
+
if (!["open", "in_progress", "blocked"].includes(status)) {
|
|
1753
|
+
throw cliValidationError(
|
|
1754
|
+
"A new todo --status must be open, in_progress, or blocked.",
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
return status;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
function parseTodoStatus(value) {
|
|
1761
|
+
const status = String(value || "")
|
|
1762
|
+
.trim()
|
|
1763
|
+
.toLowerCase();
|
|
1764
|
+
if (
|
|
1765
|
+
![
|
|
1766
|
+
"open",
|
|
1767
|
+
"in_progress",
|
|
1768
|
+
"blocked",
|
|
1769
|
+
"completed",
|
|
1770
|
+
"cancelled",
|
|
1771
|
+
].includes(status)
|
|
1772
|
+
) {
|
|
1773
|
+
throw cliValidationError(
|
|
1774
|
+
"--status must be open, in_progress, blocked, completed, or cancelled.",
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
return status;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function parseTodoListStatus(value) {
|
|
1781
|
+
const status = String(value || "pending")
|
|
1782
|
+
.trim()
|
|
1783
|
+
.toLowerCase();
|
|
1784
|
+
if (status === "pending" || status === "all") return status;
|
|
1785
|
+
return parseTodoStatus(status);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1646
1788
|
function parseOrderedIds(value) {
|
|
1647
1789
|
const ids = String(value || "")
|
|
1648
1790
|
.split(",")
|
|
@@ -1776,7 +1918,7 @@ function printAdminResult({ operation, result }) {
|
|
|
1776
1918
|
if (data.report) {
|
|
1777
1919
|
const report = data.report;
|
|
1778
1920
|
console.log(
|
|
1779
|
-
`Run #${report.run.id}: ${report.mutations.successfulMutationCount} successful mutation(s), ${report.queueCoverage.length} queue coverage record(s), ${report.escalations.length} escalation(s).`,
|
|
1921
|
+
`Run #${report.run.id}: ${report.mutations.successfulMutationCount} successful mutation(s), ${report.queueCoverage.length} queue coverage record(s), ${report.escalations.length} escalation(s), ${report.carryoverTodos?.count || 0} unfinished todo(s).`,
|
|
1780
1922
|
);
|
|
1781
1923
|
for (const coverage of report.queueCoverage) {
|
|
1782
1924
|
console.log(
|
|
@@ -1791,6 +1933,7 @@ function printAdminResult({ operation, result }) {
|
|
|
1791
1933
|
` ${String(escalation.severity || "attention").toUpperCase()} ${target} — ${escalation.summary}`,
|
|
1792
1934
|
);
|
|
1793
1935
|
}
|
|
1936
|
+
printTodoItems(report.carryoverTodos?.items || [], "Unfinished work");
|
|
1794
1937
|
const surfaces = report.brief?.engagementPulse?.surfaces;
|
|
1795
1938
|
if (surfaces && typeof surfaces === "object") {
|
|
1796
1939
|
const deltas = Object.entries(surfaces)
|
|
@@ -1861,6 +2004,19 @@ function printAdminResult({ operation, result }) {
|
|
|
1861
2004
|
);
|
|
1862
2005
|
return;
|
|
1863
2006
|
}
|
|
2007
|
+
if (Array.isArray(data.todos)) {
|
|
2008
|
+
printTodoItems(data.todos, "Private carry-over work");
|
|
2009
|
+
if (data.truncated) {
|
|
2010
|
+
console.log(
|
|
2011
|
+
"More matching todos exist than the requested limit; raise --limit or narrow --status.",
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
if (data.todo) {
|
|
2017
|
+
printTodoItems([data.todo], "Canonical todo");
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
1864
2020
|
if (data.bucket && Array.isArray(data.memberUserIds)) {
|
|
1865
2021
|
const added = Array.isArray(data.accounts)
|
|
1866
2022
|
? `; added ${data.accounts.length} explicit account(s)`
|
|
@@ -1883,6 +2039,9 @@ function printAdminResult({ operation, result }) {
|
|
|
1883
2039
|
console.log(
|
|
1884
2040
|
`Run #${data.run.id}: ${data.run.status}; identity ${data.run.identity.key}; comments ${data.run.commentMode}.`,
|
|
1885
2041
|
);
|
|
2042
|
+
if (data.carryoverTodos) {
|
|
2043
|
+
printTodoItems(data.carryoverTodos.items || [], "Carry-over work");
|
|
2044
|
+
}
|
|
1886
2045
|
return;
|
|
1887
2046
|
}
|
|
1888
2047
|
if (Array.isArray(data.identities)) {
|
|
@@ -2070,6 +2229,19 @@ function printAdminResult({ operation, result }) {
|
|
|
2070
2229
|
);
|
|
2071
2230
|
}
|
|
2072
2231
|
|
|
2232
|
+
function printTodoItems(items, heading) {
|
|
2233
|
+
console.log(`${heading}: ${items.length} item(s).`);
|
|
2234
|
+
for (const todo of items) {
|
|
2235
|
+
console.log(
|
|
2236
|
+
` #${todo.id} ${String(todo.status || "open").toUpperCase()} ${todo.kind || "task"} — ${todo.title || "(untitled)"}`,
|
|
2237
|
+
);
|
|
2238
|
+
if (todo.details) console.log(` ${todo.details}`);
|
|
2239
|
+
if (todo.lastProgressNote) {
|
|
2240
|
+
console.log(` Latest progress: ${todo.lastProgressNote}`);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2073
2245
|
function printPagination(pagination) {
|
|
2074
2246
|
if (!pagination) return;
|
|
2075
2247
|
console.log(
|
package/lib/commands.js
CHANGED
|
@@ -2720,6 +2720,9 @@ export function printHelp() {
|
|
|
2720
2720
|
lumine admin daily-run escalation add --target <target> --note <summary> [--severity attention|urgent] [--json]
|
|
2721
2721
|
lumine admin escalation list [--status open|acknowledged|resolved|all] [--limit <number>] [--json]
|
|
2722
2722
|
lumine admin escalation set <audit-id> --status open|acknowledged|resolved --note <decision> [--json]
|
|
2723
|
+
lumine admin todo list [--status pending|open|in_progress|blocked|completed|cancelled|all] [--limit <number>] [--json]
|
|
2724
|
+
lumine admin todo add --title <title> --note <handoff-and-acceptance-criteria> [--kind task|experiment] [--status open|in_progress|blocked] [--json]
|
|
2725
|
+
lumine admin todo update <todo-id> --status open|in_progress|blocked|completed|cancelled --note <progress-or-evidence> [--json]
|
|
2723
2726
|
lumine admin recommendations list [--since-run|--after <date>|--include-legacy] [--all --checkpoint <file> [--resume]] [--content-types comment,dailyReflection] [--unviewed|--viewed] [--cursor <cursor>] [--json]
|
|
2724
2727
|
lumine admin builds candidates [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--limit <number>] [--json]
|
|
2725
2728
|
lumine admin builds review <build-url-or-id> [--output-dir <dir>] [--wait-ms <ms>] [--browser-path <path>] [--json]
|
|
@@ -2816,11 +2819,11 @@ Options:
|
|
|
2816
2819
|
--target <build> Explicit Build URL or ID for rename/describe/upgrade
|
|
2817
2820
|
--main With pull/versions/restore: target the team project's main
|
|
2818
2821
|
--version <n> With pull: read-only checkout of previous save v<n>
|
|
2819
|
-
--title <text> Build title for new/rename
|
|
2822
|
+
--title <text> Build title for new/rename, or private todo title
|
|
2820
2823
|
--description <text> Build description for new/describe
|
|
2821
2824
|
--no-description Skip New description or clear with describe
|
|
2822
2825
|
--summary <text> Save summary
|
|
2823
|
-
--note <text> Suggestion, notable-user,
|
|
2826
|
+
--note <text> Suggestion, notable-user, AI-bucket, or todo context
|
|
2824
2827
|
--cursor <id> Continue suggestions, Forum activity, or admin listing
|
|
2825
2828
|
--poll-ms <ms> Forum listener interval (1000-60000; default 3000)
|
|
2826
2829
|
--after <date> Admin listing: inclusive Unix/ISO creation boundary
|
|
@@ -2836,6 +2839,7 @@ Options:
|
|
|
2836
2839
|
--target-file <file> JSON array or newline list for audited batch skips
|
|
2837
2840
|
--review-receipt <f> Confirmed managed Build runtime review receipt
|
|
2838
2841
|
--severity <level> Run escalation severity: attention or urgent
|
|
2842
|
+
--status <state> Private escalation or todo lifecycle filter/state
|
|
2839
2843
|
--wait-ms <ms> Managed Build runtime observation time (1000-45000)
|
|
2840
2844
|
--browser-path <path> Chrome/Chromium executable for managed Build review
|
|
2841
2845
|
--effort unassigned Admin subjects: show only unassigned effort
|
|
@@ -2851,7 +2855,7 @@ Options:
|
|
|
2851
2855
|
--label <name> Name for a new unbanned AI identity bucket
|
|
2852
2856
|
--user-ids <ids> Explicit user IDs for an AI bucket batch (up to 500)
|
|
2853
2857
|
--type <type> Admin target: subject, comment, build, aiStory, or dailyReflection
|
|
2854
|
-
--kind
|
|
2858
|
+
--kind <kind> Admin recommendation kind or todo task/experiment kind
|
|
2855
2859
|
--anyone-can-reward Enable canonical reward eligibility
|
|
2856
2860
|
--reward-twinkles 3 Pair a recommendation with exactly 3 Twinkles
|
|
2857
2861
|
--twinkles 3 Give exactly 3 Twinkles through the normal economy
|
package/lib/constants.js
CHANGED
|
@@ -31,6 +31,18 @@ export const THUMBNAIL_CONTENT_TYPE_BY_EXTENSION = {
|
|
|
31
31
|
export const THUMBNAIL_MAX_FILE_SIZE_BYTES = 8 * 1024 * 1024;
|
|
32
32
|
export const UPDATE_CHECK_TIMEOUT_MS = 1500;
|
|
33
33
|
export const DEFAULT_PROJECT_LIMIT = 50;
|
|
34
|
+
export const BUILD_VENDOR_THREE_VERSION = "0.184.0";
|
|
35
|
+
export const BUILD_VENDOR_THREE_LEGACY_VERSION = "0.160.0";
|
|
36
|
+
export const BUILD_VENDOR_THREE_PREFIX =
|
|
37
|
+
`/build/vendor/three/${BUILD_VENDOR_THREE_VERSION}/`;
|
|
38
|
+
export const BUILD_VENDOR_THREE_MODULE_IMPORT =
|
|
39
|
+
`${BUILD_VENDOR_THREE_PREFIX}three.module.min.js`;
|
|
40
|
+
export const BUILD_VENDOR_THREE_WEBGPU_MODULE_IMPORT =
|
|
41
|
+
`${BUILD_VENDOR_THREE_PREFIX}three.webgpu.min.js`;
|
|
42
|
+
export const BUILD_VENDOR_THREE_TSL_MODULE_IMPORT =
|
|
43
|
+
`${BUILD_VENDOR_THREE_PREFIX}three.tsl.min.js`;
|
|
44
|
+
export const BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX =
|
|
45
|
+
`${BUILD_VENDOR_THREE_PREFIX}addons/`;
|
|
34
46
|
export const PROJECT_METADATA_DIR = ".twinkle";
|
|
35
47
|
export const PROJECT_METADATA_FILE = "lumine-project.json";
|
|
36
48
|
export const ASSETS_METADATA_FILE = "assets.json";
|
|
@@ -120,6 +132,14 @@ Use these current source-of-truth rules:
|
|
|
120
132
|
- Use Twinkle.preview for canvas, WebGL, Three.js, fullscreen, and game layout.
|
|
121
133
|
- Prefer existing documented Twinkle.* methods over guessing names from old code.
|
|
122
134
|
`;
|
|
135
|
+
export const LUMINE_THREE_VENDOR_GUIDANCE = `- For Three.js, use the first-party core module: import * as THREE from '${BUILD_VENDOR_THREE_MODULE_IMPORT}';
|
|
136
|
+
- Twinkle serves the supported official Three.js ${BUILD_VENDOR_THREE_VERSION} addon tree under ${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}, including controls, loaders, CSS renderers, shaders, physics and WebXR helpers, and WebGLRenderer post-processing modules such as EffectComposer, RenderPass, SSAOPass/GTAOPass, UnrealBloomPass, and OutputPass. Example: import { EffectComposer } from '${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}postprocessing/EffectComposer.js';
|
|
137
|
+
- The workspace file tree does not enumerate vendor modules, so absence there is not evidence that an official addon is unavailable. Use its documented addon subpath and run lumine check; validation checks the exact file and its transitive imports.
|
|
138
|
+
- WebGPU and TSL entry modules are also vendored at ${BUILD_VENDOR_THREE_WEBGPU_MODULE_IMPORT} and ${BUILD_VENDOR_THREE_TSL_MODULE_IMPORT}, but WebGL EffectComposer passes do not work with WebGPURenderer and runtime GPU support varies.
|
|
139
|
+
- Treat addons as available tools, not defaults. Use them when they materially serve the requested experience; for continuously animated mobile builds, include a performance profile that targets about 30 FPS, caps render pixel ratio around 1-1.25, and lowers expensive post-processing resolution or quality only when that preserves the requested visual behavior. Do not remove or disable a requested visual effect as a performance tradeoff without the user's explicit approval.
|
|
140
|
+
- Keep one Three.js version throughout a project. If a project using ${BUILD_VENDOR_THREE_LEGACY_VERSION} needs current addons, migrate every Three.js import to ${BUILD_VENDOR_THREE_VERSION} in one coherent change; never mix the legacy core with current addons.
|
|
141
|
+
- This vendor surface covers supported official Three.js modules, not arbitrary third-party Three.js packages. Do not paste library source into project files or use npm/CDN copies. Loader runtime assets are served too: point decoder/transcoder paths at the addon prefix (example: dracoLoader.setDecoderPath('${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}libs/draco/');).
|
|
142
|
+
- Size both WebGLRenderer and EffectComposer from Twinkle.preview layout dimensions, coalesce resize work, and render with composer.render() when a composer owns the pass chain.`;
|
|
123
143
|
export const LUMINE_AGENT_INSTRUCTIONS = `${LUMINE_AGENT_INSTRUCTIONS_MARKER}
|
|
124
144
|
# Lumine Project Agent Guide
|
|
125
145
|
|
|
@@ -260,14 +280,15 @@ lumine save --summary "Describe the change"
|
|
|
260
280
|
- Interface text must not be selectable on touch devices: long-pressing UI on mobile must not highlight it. Apply user-select: none plus -webkit-user-select: none and -webkit-touch-callout: none to interface text (HUD, buttons, labels, menus, scores, game controls). Keep text inputs and genuinely user-copyable content (story text, chat messages, user-written text) selectable. lumine check flags projects whose reachable files have clickable UI but no user-select: none rule.
|
|
261
281
|
- CAUTION: the preview runtime AUTO-DETECTS "game apps" — any <canvas> in the body (even a decorative background canvas) or game-y words in visible text switch the app to viewport-app mode: html/body get overflow:hidden !important and body becomes a centering flexbox, so tall document-flow pages clip and stop scrolling. Document-style apps that use a canvas must call Twinkle.preview.subscribe (or getLayout/reserveInsets) early at boot — any of those opts out of auto game mode — then pad by layout.safeInsets and scroll within layout.viewport.height.
|
|
262
282
|
- For canvas, WebGL, Three.js, fullscreen, or game builds, use Twinkle.preview for layout. Do not size roots from 100vh, 100vw, 100dvh, 100dvw, window.innerWidth, window.innerHeight, visualViewport, or document viewport dimensions.
|
|
263
|
-
|
|
283
|
+
${LUMINE_THREE_VENDOR_GUIDANCE}
|
|
264
284
|
- Do not invent or guess Twinkle.* SDK method names. Use ${SDK_REFERENCE_FILE} as the local SDK reference and prefer Twinkle.capabilities checks for gated features.
|
|
265
285
|
- Match storage to update frequency. Twinkle.privateDb and Twinkle.sharedDb are for LOW-frequency durable state only — things that change on a user action (settings, inventory checkpoints, completed quests, saved progress; comments, votes, room settings, submitted records). NEVER write high-frequency or per-frame/per-tick state to them (camera or cursor position, animation state, live movement, presence, autosave every frame/tick). Keep live state in client memory, broadcast realtime/presence via Twinkle.world, and for durable per-user state flush an occasional snapshot on an interval or on exit (never per frame) — e.g. the viewer/user DB or a single latest-snapshot key. The server rate-limits these writes per key and returns 429 on excess; never retry-loop a 429.
|
|
266
286
|
|
|
267
287
|
## Local Testing (Playwright / browser probes)
|
|
268
288
|
|
|
269
289
|
- Serve the workspace with a tiny local HTTP server and drive it with Playwright. NEVER copy probe/vendor files into the workspace dir — lumine save uploads everything here (and binary files fail validation). Build a sibling probe dir that symlinks the workspace files instead.
|
|
270
|
-
- Vendored imports like
|
|
290
|
+
- Vendored imports like ${BUILD_VENDOR_THREE_PREFIX}... are absolute paths: mirror that directory under your probe dir's root and fetch the files from the LIVE SITE (e.g. ${DEFAULT_SITE_URL}${BUILD_VENDOR_THREE_WEBGPU_MODULE_IMPORT}). Three.js ${BUILD_VENDOR_THREE_VERSION} splits into three.module.min.js + three.core.min.js — mirror BOTH plus every addon's relative import closure or imports fail. Do NOT use npm/CDN copies — the platform's vendored builds have rewritten import specifiers (npm three.tsl.min.js still imports bare "three/webgpu" and breaks the module graph).
|
|
291
|
+
- A local 404 caused by an incomplete vendor mirror is a probe setup failure, not evidence that the addon is unavailable on Twinkle. Check the live same-origin vendor URL or the saved Twinkle preview before reporting a platform limitation.
|
|
271
292
|
- The three WebGPU renderer falls back to WebGL2 in headless Chromium automatically. Headless software rendering runs at ~2-5fps, so anything time-based (walking a character, timers) takes ~10-20x longer than real time — loop with generous waits instead of fixed short sleeps, and bump navigation timeouts.
|
|
272
293
|
- To inspect module-scope game state, append debug getters when SERVING main.js (e.g. body += "window.__dbg = () => ({...})") rather than editing workspace files.
|
|
273
294
|
- SDK calls are absent when serving locally; well-written builds optional-chain window.Twinkle and fall back to localStorage. Seed localStorage in the probe to fake saves.
|
package/lib/doctor.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createRequire } from "module";
|
|
|
3
3
|
import { buildApiJson, mintBuildApiToken } from "./api.js";
|
|
4
4
|
import { ensureAuth, assertAuthScope } from "./auth.js";
|
|
5
5
|
import { uploadRuntimeAsset } from "./assets.js";
|
|
6
|
+
import { BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX } from "./constants.js";
|
|
6
7
|
import { requestJson } from "./http.js";
|
|
7
8
|
import { resolveSdkBuildId } from "./sdk.js";
|
|
8
9
|
import { formatBytes, trimTrailingSlash } from "./util.js";
|
|
@@ -389,7 +390,7 @@ async function createRuntimeAssetsPreviewSession({
|
|
|
389
390
|
};
|
|
390
391
|
}
|
|
391
392
|
|
|
392
|
-
function buildRuntimeAssetsProbeHtml({ hdrUrl, glbUrl }) {
|
|
393
|
+
export function buildRuntimeAssetsProbeHtml({ hdrUrl, glbUrl }) {
|
|
393
394
|
return `<!doctype html>
|
|
394
395
|
<html>
|
|
395
396
|
<head>
|
|
@@ -429,7 +430,7 @@ function buildRuntimeAssetsProbeHtml({ hdrUrl, glbUrl }) {
|
|
|
429
430
|
}
|
|
430
431
|
|
|
431
432
|
async function loadHdr(url) {
|
|
432
|
-
const { RGBELoader } = await import('
|
|
433
|
+
const { RGBELoader } = await import('${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}loaders/RGBELoader.js');
|
|
433
434
|
const texture = await new Promise((resolve, reject) => {
|
|
434
435
|
new RGBELoader().load(url, resolve, undefined, reject);
|
|
435
436
|
});
|
|
@@ -442,7 +443,7 @@ function buildRuntimeAssetsProbeHtml({ hdrUrl, glbUrl }) {
|
|
|
442
443
|
}
|
|
443
444
|
|
|
444
445
|
async function loadGlb(url) {
|
|
445
|
-
const { GLTFLoader } = await import('
|
|
446
|
+
const { GLTFLoader } = await import('${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}loaders/GLTFLoader.js');
|
|
446
447
|
const gltf = await new Promise((resolve, reject) => {
|
|
447
448
|
new GLTFLoader().load(url, resolve, undefined, reject);
|
|
448
449
|
});
|
package/package.json
CHANGED
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -579,7 +579,10 @@ lumine admin escalation set 123 --status resolved \
|
|
|
579
579
|
Schemas:
|
|
580
580
|
|
|
581
581
|
```ts
|
|
582
|
-
type DailyRunStart = Success<{
|
|
582
|
+
type DailyRunStart = Success<{
|
|
583
|
+
run: DailyRun;
|
|
584
|
+
carryoverTodos: CarryoverTodos;
|
|
585
|
+
}>;
|
|
583
586
|
type DailyRunStatus = Success<{
|
|
584
587
|
run: DailyRun | null;
|
|
585
588
|
lastRun: DailyRun | null;
|
|
@@ -632,6 +635,86 @@ mutation when a caller needs the same retry identity across processes. The CLI
|
|
|
632
635
|
generates a fresh key for every mutation invocation; if a mutation fails, its
|
|
633
636
|
JSON error includes `details.retryIdempotencyKey` for a safe exact retry.
|
|
634
637
|
|
|
638
|
+
## Private carry-over todos
|
|
639
|
+
|
|
640
|
+
```bash
|
|
641
|
+
lumine admin todo list --json
|
|
642
|
+
lumine admin todo list --status all --json
|
|
643
|
+
lumine admin todo add --kind experiment --status in_progress \
|
|
644
|
+
--title "Validate Zero/Ciel cost optimization" \
|
|
645
|
+
--note "Replay baseline and optimized conversations. Complete only after response-quality parity; lower cost with a weaker reply fails." --json
|
|
646
|
+
lumine admin todo update 12 --status blocked \
|
|
647
|
+
--note "Implementation is ready; waiting for a complete cost bucket and old-vs-new quality replay." --json
|
|
648
|
+
lumine admin todo update 12 --status completed \
|
|
649
|
+
--note "Blind parity comparison passed every required dimension; measured cost and latency evidence attached in this note." --json
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Todos are private operator work, not Zero/Ciel public actions. They persist
|
|
653
|
+
independently of daily runs and are therefore available before a run starts and
|
|
654
|
+
after it closes. Creating or updating one uses the run-independent transactional
|
|
655
|
+
audit path: canonical todo state and its private `todo.create` / `todo.update`
|
|
656
|
+
audit response commit together, and no public bot, public mutation count, or
|
|
657
|
+
rotation signal is involved.
|
|
658
|
+
|
|
659
|
+
Every successful `daily-run start` response automatically includes all
|
|
660
|
+
unfinished items under `data.carryoverTodos`. The same run ID increments an
|
|
661
|
+
item's surfacing telemetry at most once, even when start is retried. This is the
|
|
662
|
+
canonical handoff: read it before discretionary new work, resume what can safely
|
|
663
|
+
progress after the run's mandatory newspaper/brief/conduct duties, and record a
|
|
664
|
+
concrete progress note before the run closes. The daily-run report includes the
|
|
665
|
+
still-unfinished set again. Completing a daily run never silently completes its
|
|
666
|
+
todos. A CLI carrying this contract rejects a start response that does not echo
|
|
667
|
+
the canonical handoff, so a newer CLI against an API deployed before the todo
|
|
668
|
+
migration cannot quietly treat unsupported telemetry as an empty list.
|
|
669
|
+
|
|
670
|
+
`kind` is `task` or `experiment`. New items may start `open`, `in_progress`, or
|
|
671
|
+
`blocked`; updates may also use `completed` or `cancelled`. A progress note is
|
|
672
|
+
required for every update. For experiments, put the acceptance criteria in the
|
|
673
|
+
initial details and use evidence—not implementation status—as the completion
|
|
674
|
+
boundary. In particular, an AI-cost experiment is not complete until old-vs-new
|
|
675
|
+
response-quality parity is demonstrated; a cheaper but weaker user response is
|
|
676
|
+
a failed experiment. Up to 50 unfinished items may be carried so the automatic
|
|
677
|
+
start payload remains complete and bounded.
|
|
678
|
+
|
|
679
|
+
```ts
|
|
680
|
+
type AdminTodo = {
|
|
681
|
+
id: number;
|
|
682
|
+
kind: "task" | "experiment";
|
|
683
|
+
title: string;
|
|
684
|
+
details: string;
|
|
685
|
+
status: "open" | "in_progress" | "blocked" | "completed" | "cancelled";
|
|
686
|
+
revision: number;
|
|
687
|
+
createdRunId: number | null;
|
|
688
|
+
lastWorkedRunId: number | null;
|
|
689
|
+
lastSurfacedRunId: number | null;
|
|
690
|
+
surfaceCount: number;
|
|
691
|
+
lastProgressNote: string | null;
|
|
692
|
+
createdAt: number;
|
|
693
|
+
updatedAt: number;
|
|
694
|
+
lastSurfacedAt: number | null;
|
|
695
|
+
completedAt: number | null;
|
|
696
|
+
cancelledAt: number | null;
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
type AdminTodoList = Success<{
|
|
700
|
+
todos: AdminTodo[];
|
|
701
|
+
statusFilter:
|
|
702
|
+
| "pending"
|
|
703
|
+
| "all"
|
|
704
|
+
| AdminTodo["status"];
|
|
705
|
+
truncated: boolean;
|
|
706
|
+
}>;
|
|
707
|
+
|
|
708
|
+
type AdminTodoMutation = Success<{ todo: AdminTodo }>;
|
|
709
|
+
|
|
710
|
+
type CarryoverTodos = {
|
|
711
|
+
items: AdminTodo[];
|
|
712
|
+
count: number;
|
|
713
|
+
surfacedForRunId: number;
|
|
714
|
+
newlySurfacedCount: number;
|
|
715
|
+
};
|
|
716
|
+
```
|
|
717
|
+
|
|
635
718
|
## Canonical lists and inspection
|
|
636
719
|
|
|
637
720
|
```bash
|
|
@@ -2176,9 +2259,12 @@ Public content actions use ordinary Twinkle fan-out:
|
|
|
2176
2259
|
- effort/creator changes emit `edit_content`;
|
|
2177
2260
|
- Featured changes emit a canonical `home_outdated` refresh.
|
|
2178
2261
|
|
|
2179
|
-
Apply `twinkle-api/scripts/migrations/add-lumine-admin-delegation.sql
|
|
2180
|
-
|
|
2181
|
-
|
|
2262
|
+
Apply `twinkle-api/scripts/migrations/add-lumine-admin-delegation.sql`, then
|
|
2263
|
+
`add-lumine-admin-comment-targets.sql`, and apply
|
|
2264
|
+
`add-lumine-admin-todos.sql` before deploying an API that exposes carry-over
|
|
2265
|
+
todos. They add
|
|
2266
|
+
only focused daily-run, rotation, draft, audit, and private-todo tables/columns
|
|
2267
|
+
and indexes;
|
|
2182
2268
|
there are no runtime schema checks. The comment-targets migration backfills
|
|
2183
2269
|
existing subject drafts into the generalized target columns. The local CLI changes
|
|
2184
2270
|
are not available to users until a separately authorized npm publication.
|