@stage5/lumine 0.3.41 → 0.3.43
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 +8 -14
- package/lib/admin-featured.js +3 -2
- package/lib/admin.js +103 -214
- package/lib/build-review.js +41 -2
- package/lib/commands.js +8 -2
- package/package.json +1 -1
- package/sdk/LUMINE_ADMIN.md +45 -12
package/README.md
CHANGED
|
@@ -61,20 +61,14 @@ Mikey reviews with `lumine admin storage list`, `show <user>`,
|
|
|
61
61
|
or grants directly with `lumine admin storage grant <user> --size <size>`.
|
|
62
62
|
Approvals are capped at 2 GB and never lower a creator below the default.
|
|
63
63
|
|
|
64
|
-
Bridge Builder
|
|
65
|
-
|
|
66
|
-
`show <crewId
|
|
67
|
-
|
|
68
|
-
`
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
achievement for every member ticked once the server confirms the requirements.
|
|
73
|
-
`request-changes` holds the current crew or grown-up approval step, shows the
|
|
74
|
-
staff note to every member on the crew page and chat tracker, and announces it
|
|
75
|
-
in the crew chat. Members can request review again after making the changes;
|
|
76
|
-
staff still need to approve the step. Use `info` only for private questions to
|
|
77
|
-
one member about their identity.
|
|
64
|
+
Bridge Builder crews (the website's /achievements/bridge-builder page) are
|
|
65
|
+
managed with `lumine admin meetup list [--status review|active|completed|all]`,
|
|
66
|
+
`show <crewId>`, `info <crewId> <userId> --decision ...` (a member's private
|
|
67
|
+
who-are-you check), `set-branch`, `branches`, `emails`, `funnel` and the
|
|
68
|
+
`announce-checkins [--send] [--as zero|ciel]` (the check-in announcement posts
|
|
69
|
+
itself once; this previews it or retries a failed crew). Since 2026-10-17
|
|
70
|
+
Bridge Builder unlocks from QR check-ins (7 people, or one meetup a grown-up
|
|
71
|
+
witnessed); crews no longer wait for staff approvals.
|
|
78
72
|
|
|
79
73
|
For team projects, Lumine mirrors the website workspace flow: choosing or
|
|
80
74
|
pulling the owner's main project creates or reuses your contribution branch and
|
package/lib/admin-featured.js
CHANGED
|
@@ -253,6 +253,7 @@ export async function runFeaturedWorkflow({
|
|
|
253
253
|
operation,
|
|
254
254
|
authToken,
|
|
255
255
|
runId,
|
|
256
|
+
sourceRunId = 0,
|
|
256
257
|
request = requestJson,
|
|
257
258
|
}) {
|
|
258
259
|
const apiUrl = String(options.apiUrl).replace(/\/$/, "");
|
|
@@ -273,9 +274,9 @@ export async function runFeaturedWorkflow({
|
|
|
273
274
|
options.adminFile,
|
|
274
275
|
options.adminApprove,
|
|
275
276
|
);
|
|
276
|
-
if (plan.runId !== runId)
|
|
277
|
+
if (plan.runId !== runId && !(sourceRunId > 0 && plan.runId === sourceRunId))
|
|
277
278
|
throw invalid(
|
|
278
|
-
"This plan belongs to a different run; prepare and approve a fresh plan.",
|
|
279
|
+
"This plan belongs to a different run; prepare and approve a fresh plan, or apply it from an approvals run started on its full run (--scope approvals --source-run).",
|
|
279
280
|
);
|
|
280
281
|
const result = await call(
|
|
281
282
|
"/plan/apply",
|
package/lib/admin.js
CHANGED
|
@@ -323,7 +323,7 @@ function buildNotableScoringOperation({ action, target, options }) {
|
|
|
323
323
|
|
|
324
324
|
// Management approvals (dob/mentor/meetup) and the teacher audit.
|
|
325
325
|
const APPROVAL_USAGE =
|
|
326
|
-
"Use: lumine admin approvals list [--status pending|approved|rejected|all] [--type dob|mentor|meetup] | show <id> | approve <id> [--reason <text>] | reject <id> [--reason <text>] | supermod <id> [--reason <text>]";
|
|
326
|
+
"Use: lumine admin approvals list [--status pending|approved|rejected|settled|all] [--type dob|mentor|meetup] | show <id> | approve <id> [--reason <text>] | reject <id> [--reason <text>] | supermod <id> [--reason <text>]";
|
|
327
327
|
const TEACHER_USAGE =
|
|
328
328
|
"Use: lumine admin teachers audit [--limit <1-100>] [--cursor <c>] [--all] | branches suggest|networks|confirm|unconfirm|set|clear|apply|list (`lumine admin teachers branches usage` prints each one) | revoke <userId> --reason <text> | review <userId> --decision legit|revoke --note <text> [--fingerprint <fp>] (revoke needs it) | review --all-flagged --expect <id[:fp],...> --note <text>";
|
|
329
329
|
|
|
@@ -343,6 +343,8 @@ function buildApprovalOperation({ action, target, options }) {
|
|
|
343
343
|
"pending",
|
|
344
344
|
"approved",
|
|
345
345
|
"rejected",
|
|
346
|
+
// a Bridge Builder hold that resolved on its own
|
|
347
|
+
"settled",
|
|
346
348
|
"all",
|
|
347
349
|
]),
|
|
348
350
|
type: options.adminType
|
|
@@ -1310,6 +1312,8 @@ export async function adminCommand(options) {
|
|
|
1310
1312
|
});
|
|
1311
1313
|
let runId = 0;
|
|
1312
1314
|
let runScope = null;
|
|
1315
|
+
// The full run an approvals run applies (0 for every other run).
|
|
1316
|
+
let sourceRunId = 0;
|
|
1313
1317
|
let correctionSessionId = 0;
|
|
1314
1318
|
let correctionSession = null;
|
|
1315
1319
|
let commentSessionId = 0;
|
|
@@ -1404,6 +1408,8 @@ export async function adminCommand(options) {
|
|
|
1404
1408
|
if (selectedRun) {
|
|
1405
1409
|
runScope = canonicalAdminRunScope(selectedRun);
|
|
1406
1410
|
assertAdminOperationAllowedForRunScope({ operation, runScope });
|
|
1411
|
+
sourceRunId =
|
|
1412
|
+
runScope === "approvals" ? Number(selectedRun.sourceRunId || 0) : 0;
|
|
1407
1413
|
}
|
|
1408
1414
|
if (options.adminIdentity && commentSession) {
|
|
1409
1415
|
const requestedIdentity = parseIdentity(options.adminIdentity);
|
|
@@ -1517,6 +1523,8 @@ export async function adminCommand(options) {
|
|
|
1517
1523
|
operation,
|
|
1518
1524
|
authToken: auth.token,
|
|
1519
1525
|
runId,
|
|
1526
|
+
// An approvals run applies its source full run's Featured plan.
|
|
1527
|
+
sourceRunId,
|
|
1520
1528
|
});
|
|
1521
1529
|
} else if (operation.name === "build.review") {
|
|
1522
1530
|
result = await runManagedBuildReview({
|
|
@@ -2300,7 +2308,8 @@ export function canonicalAdminRunScope(run) {
|
|
|
2300
2308
|
const raw = run?.runScope;
|
|
2301
2309
|
if (raw === undefined || raw === null || raw === "") return "full";
|
|
2302
2310
|
const scope = String(raw);
|
|
2303
|
-
if (["full", "featured", "newspaper"].includes(scope))
|
|
2311
|
+
if (["full", "featured", "newspaper", "approvals"].includes(scope))
|
|
2312
|
+
return scope;
|
|
2304
2313
|
throw cliValidationError(
|
|
2305
2314
|
"The API returned an invalid administrator run scope.",
|
|
2306
2315
|
);
|
|
@@ -2332,6 +2341,17 @@ const FEATURED_RUN_OPERATIONS = new Set([
|
|
|
2332
2341
|
"daily-run.fail",
|
|
2333
2342
|
]);
|
|
2334
2343
|
|
|
2344
|
+
// An approvals run acts on Mikey's decisions whenever he gets to them: a full
|
|
2345
|
+
// run's write work without the newspaper, sponsor scan or bank seeding (the
|
|
2346
|
+
// API holds the same line through the run's scopes).
|
|
2347
|
+
const APPROVALS_RUN_EXCLUDED_OPERATIONS = new Set([
|
|
2348
|
+
"news.claim",
|
|
2349
|
+
"news.print",
|
|
2350
|
+
"news.submit",
|
|
2351
|
+
"sponsor.integrity.scan",
|
|
2352
|
+
"reward-bank.seed",
|
|
2353
|
+
]);
|
|
2354
|
+
|
|
2335
2355
|
export function assertAdminOperationAllowedForRunScope({
|
|
2336
2356
|
operation,
|
|
2337
2357
|
runScope,
|
|
@@ -2352,6 +2372,12 @@ export function assertAdminOperationAllowedForRunScope({
|
|
|
2352
2372
|
`A newspaper-only run does not authorize ${operation.name}.`,
|
|
2353
2373
|
);
|
|
2354
2374
|
}
|
|
2375
|
+
if (runScope === "approvals") {
|
|
2376
|
+
if (!APPROVALS_RUN_EXCLUDED_OPERATIONS.has(operation.name)) return;
|
|
2377
|
+
throw cliValidationError(
|
|
2378
|
+
`An approvals run does not do ${operation.name}; that belongs to a full daily run.`,
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
2355
2381
|
if (runScope !== "featured") return;
|
|
2356
2382
|
const subjectCommentAlias =
|
|
2357
2383
|
operation.name === "post.comments" &&
|
|
@@ -3013,28 +3039,15 @@ export function parseAdminOperation(options) {
|
|
|
3013
3039
|
}
|
|
3014
3040
|
|
|
3015
3041
|
if (MEETUP_NAMESPACES.has(namespace)) {
|
|
3016
|
-
// Bridge Builder
|
|
3017
|
-
//
|
|
3018
|
-
// decision, any time, never in a daily run.
|
|
3042
|
+
// Bridge Builder crews (achievement type 'meetup'): the same admin tools as
|
|
3043
|
+
// the website. Crews no longer wait for staff approvals (check-ins are the
|
|
3044
|
+
// proof, 2026-10-17). Mikey's own decision, any time, never in a daily run.
|
|
3019
3045
|
const usage =
|
|
3020
|
-
"Usage: lumine admin meetup list [--status review|active|completed|all] |
|
|
3046
|
+
"Usage: lumine admin meetup list [--status review|active|completed|all] | show <crewId> | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | set-branch <crewId> <userId> --branch <name> [--note <text>] | branches [list|show|approve|reject|merge] | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | funnel [--days <1-90>] | story ... | announce-checkins [--send] [--as zero|ciel].";
|
|
3021
3047
|
const note = String(options.note || "").trim();
|
|
3022
3048
|
if (note.length > 1000) {
|
|
3023
3049
|
throw cliValidationError("--note must be at most 1000 characters.");
|
|
3024
3050
|
}
|
|
3025
|
-
if (action === "parent-reply") {
|
|
3026
|
-
// Answer a parent's question: shown on their page, and they are emailed.
|
|
3027
|
-
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
3028
|
-
const memberId = parseRequiredInteger(extra, "Member (child) user ID", 1);
|
|
3029
|
-
if (!note) throw cliValidationError("parent-reply needs --note <your answer>.");
|
|
3030
|
-
return writeOperation(
|
|
3031
|
-
"meetup.parent-reply",
|
|
3032
|
-
"POST",
|
|
3033
|
-
`/cli/admin/meetup-quest/crews/${crewId}/parents/${memberId}/reply`,
|
|
3034
|
-
{ reply: note },
|
|
3035
|
-
{ requiresRun: false },
|
|
3036
|
-
);
|
|
3037
|
-
}
|
|
3038
3051
|
if (action === "branches" || action === "branch") {
|
|
3039
3052
|
// Every branch name a member types is registered; the first sighting alerts
|
|
3040
3053
|
// Mikey (a Zero DM card). Approve = a real branch, reject = not real (the
|
|
@@ -3229,96 +3242,25 @@ export function parseAdminOperation(options) {
|
|
|
3229
3242
|
{ requiresRun: false },
|
|
3230
3243
|
);
|
|
3231
3244
|
}
|
|
3232
|
-
if (action === "
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
"
|
|
3238
|
-
`/cli/admin/meetup-quest/crews/${crewId}/request-changes`,
|
|
3239
|
-
{ note },
|
|
3240
|
-
{ requiresRun: false },
|
|
3241
|
-
);
|
|
3242
|
-
}
|
|
3243
|
-
if (action === "approve-crew" || action === "approve-grownup") {
|
|
3244
|
-
// Every step passes through the owner: the crew (members + branches) and
|
|
3245
|
-
// the grown-up step (parents' yes + the named adult).
|
|
3246
|
-
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
3247
|
-
return writeOperation(
|
|
3248
|
-
action === "approve-crew" ? "meetup.approve-crew" : "meetup.approve-grownup",
|
|
3249
|
-
"POST",
|
|
3250
|
-
`/cli/admin/meetup-quest/crews/${crewId}/${action}`,
|
|
3251
|
-
{},
|
|
3252
|
-
{ requiresRun: false },
|
|
3253
|
-
);
|
|
3254
|
-
}
|
|
3255
|
-
if (action === "approve-plan") {
|
|
3256
|
-
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
3257
|
-
return writeOperation(
|
|
3258
|
-
"meetup.approve-plan",
|
|
3259
|
-
"POST",
|
|
3260
|
-
`/cli/admin/meetup-quest/crews/${crewId}/approve-plan`,
|
|
3261
|
-
{ note },
|
|
3262
|
-
{ requiresRun: false },
|
|
3263
|
-
);
|
|
3264
|
-
}
|
|
3265
|
-
if (action === "send-back") {
|
|
3266
|
-
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
3267
|
-
if (!note) {
|
|
3268
|
-
throw cliValidationError(
|
|
3269
|
-
"lumine admin meetup send-back <crewId> needs --note <what to change> (the crew sees it).",
|
|
3270
|
-
);
|
|
3271
|
-
}
|
|
3272
|
-
return writeOperation(
|
|
3273
|
-
"meetup.send-back",
|
|
3274
|
-
"POST",
|
|
3275
|
-
`/cli/admin/meetup-quest/crews/${crewId}/send-back`,
|
|
3276
|
-
{ note },
|
|
3277
|
-
{ requiresRun: false },
|
|
3278
|
-
);
|
|
3279
|
-
}
|
|
3280
|
-
if (action === "approve") {
|
|
3281
|
-
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
3282
|
-
const attendedUserIds = String(options.adminAttended || "")
|
|
3283
|
-
.split(",")
|
|
3284
|
-
.map((part) => part.trim())
|
|
3285
|
-
.filter(Boolean)
|
|
3286
|
-
.map((part) => parseRequiredInteger(part, "--attended user ID", 1));
|
|
3287
|
-
if (!attendedUserIds.length) {
|
|
3288
|
-
throw cliValidationError(
|
|
3289
|
-
"lumine admin meetup approve <crewId> needs --attended <userId,userId,...> (the members who showed up in the video).",
|
|
3290
|
-
);
|
|
3245
|
+
if (action === "announce-checkins") {
|
|
3246
|
+
// The check-in rework's one announcement in every active crew chat
|
|
3247
|
+
// (pre-written, no AI Energy). Without --send it only shows the messages.
|
|
3248
|
+
const agent = String(options.adminAs || "zero").toLowerCase();
|
|
3249
|
+
if (!["zero", "ciel"].includes(agent)) {
|
|
3250
|
+
throw cliValidationError("lumine admin meetup announce-checkins [--send] [--as zero|ciel]");
|
|
3291
3251
|
}
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
{ requiresRun: false },
|
|
3298
|
-
);
|
|
3299
|
-
}
|
|
3300
|
-
if (action === "slot") {
|
|
3301
|
-
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
3302
|
-
if (options.adminConfirm === "") {
|
|
3303
|
-
throw cliValidationError(
|
|
3304
|
-
"lumine admin meetup slot <crewId> needs --confirm <slotIndex> (0 is the first offered slot; see `show`).",
|
|
3252
|
+
if (!options.adminSend) {
|
|
3253
|
+
return readOperation(
|
|
3254
|
+
"meetup.announce-checkins",
|
|
3255
|
+
withQuery("/cli/admin/meetup-quest/announce-checkins", { agent }),
|
|
3256
|
+
{ requiresRun: false },
|
|
3305
3257
|
);
|
|
3306
3258
|
}
|
|
3307
3259
|
return writeOperation(
|
|
3308
|
-
"meetup.
|
|
3309
|
-
"POST",
|
|
3310
|
-
`/cli/admin/meetup-quest/crews/${crewId}/slot`,
|
|
3311
|
-
{ slotIndex: parseRequiredInteger(options.adminConfirm, "--confirm", 0, 9) },
|
|
3312
|
-
{ requiresRun: false },
|
|
3313
|
-
);
|
|
3314
|
-
}
|
|
3315
|
-
if (action === "resend-email") {
|
|
3316
|
-
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
3317
|
-
return writeOperation(
|
|
3318
|
-
"meetup.resend-email",
|
|
3260
|
+
"meetup.announce-checkins",
|
|
3319
3261
|
"POST",
|
|
3320
|
-
|
|
3321
|
-
{},
|
|
3262
|
+
"/cli/admin/meetup-quest/announce-checkins",
|
|
3263
|
+
{ send: true, agent },
|
|
3322
3264
|
{ requiresRun: false },
|
|
3323
3265
|
);
|
|
3324
3266
|
}
|
|
@@ -3870,8 +3812,23 @@ export function parseAdminOperation(options) {
|
|
|
3870
3812
|
if (namespace === "daily-run") {
|
|
3871
3813
|
if (action === "start") {
|
|
3872
3814
|
const runScope = parseDailyRunScope(options.adminScope || "full");
|
|
3873
|
-
|
|
3874
|
-
|
|
3815
|
+
// An approvals run acts on decisions, so it posts unless told otherwise.
|
|
3816
|
+
const commentMode = parseCommentMode(
|
|
3817
|
+
options.commentMode || (runScope === "approvals" ? "post" : "off"),
|
|
3818
|
+
);
|
|
3819
|
+
// Optional: lets an approvals run reuse that run's plans and drafts.
|
|
3820
|
+
const sourceRunId =
|
|
3821
|
+
runScope === "approvals" && options.adminSourceRun
|
|
3822
|
+
? parseRequiredInteger(options.adminSourceRun, "--source-run", 1)
|
|
3823
|
+
: undefined;
|
|
3824
|
+
if (runScope !== "approvals" && options.adminSourceRun) {
|
|
3825
|
+
throw cliValidationError("--source-run is only for --scope approvals.");
|
|
3826
|
+
}
|
|
3827
|
+
if (
|
|
3828
|
+
runScope !== "full" &&
|
|
3829
|
+
runScope !== "approvals" &&
|
|
3830
|
+
commentMode !== "off"
|
|
3831
|
+
) {
|
|
3875
3832
|
throw cliValidationError("A scoped run requires --comment-mode off.");
|
|
3876
3833
|
}
|
|
3877
3834
|
return writeOperation(
|
|
@@ -3887,6 +3844,7 @@ export function parseAdminOperation(options) {
|
|
|
3887
3844
|
commentMode,
|
|
3888
3845
|
scope: runScope,
|
|
3889
3846
|
runKey: options.runKey || defaultDailyRunKey(runScope),
|
|
3847
|
+
...(sourceRunId ? { sourceRunId } : {}),
|
|
3890
3848
|
},
|
|
3891
3849
|
);
|
|
3892
3850
|
}
|
|
@@ -5537,8 +5495,10 @@ function parseDailyRunScope(value) {
|
|
|
5537
5495
|
const scope = String(value || "full")
|
|
5538
5496
|
.trim()
|
|
5539
5497
|
.toLowerCase();
|
|
5540
|
-
if (!["full", "featured", "newspaper"].includes(scope)) {
|
|
5541
|
-
throw cliValidationError(
|
|
5498
|
+
if (!["full", "featured", "newspaper", "approvals"].includes(scope)) {
|
|
5499
|
+
throw cliValidationError(
|
|
5500
|
+
"--scope must be full, featured, newspaper, or approvals.",
|
|
5501
|
+
);
|
|
5542
5502
|
}
|
|
5543
5503
|
return scope;
|
|
5544
5504
|
}
|
|
@@ -5870,17 +5830,24 @@ function printAdminEnergyBudget(energyBudget) {
|
|
|
5870
5830
|
);
|
|
5871
5831
|
}
|
|
5872
5832
|
console.log(
|
|
5873
|
-
"day | charged | overflow | users | rechg | runs | calls/run avg/p90 | $/run avg/p90 | stops chg/unchg | busy | queue rst/stop/resume | telemetry rows",
|
|
5833
|
+
"day | charged | overflow | users | rechg | runs | calls/run avg/p90 | $/run avg/p90 | stops chg/unchg | busy | queue rst/stop/resume/wait-cancel | telemetry rows",
|
|
5874
5834
|
);
|
|
5875
5835
|
for (const day of energyBudget.byDay || []) {
|
|
5876
5836
|
const m = day.telemetry.metrics;
|
|
5877
5837
|
const runs = day.runs.total;
|
|
5878
5838
|
// Queue metrics are absent from APIs older than the queued-request telemetry.
|
|
5879
|
-
|
|
5839
|
+
// worker_wait_cancelled: a request cancelled because the Build worker
|
|
5840
|
+
// stayed unreachable through the API handoff wait.
|
|
5841
|
+
const queue = [
|
|
5842
|
+
"queued_restored",
|
|
5843
|
+
"queued_stopped",
|
|
5844
|
+
"busy_resume_requested",
|
|
5845
|
+
"worker_wait_cancelled",
|
|
5846
|
+
]
|
|
5880
5847
|
.map((metric) => String(m[metric]?.count ?? "-"))
|
|
5881
5848
|
.join("/");
|
|
5882
5849
|
console.log(
|
|
5883
|
-
`${day.dayKey}${day.inProgress ? "*" : " "}| ${formatAdminUsd(day.chargedUsd).padStart(7)} | ${formatAdminUsd(day.overflowUsd).padStart(8)} | ${String(day.users).padStart(5)} | ${String(day.recharges).padStart(5)} | ${String(runs.runs).padStart(4)} | ${String(runs.callsPerRun.avg).padStart(7)}/${String(runs.callsPerRun.p90).padEnd(9)} | ${formatAdminUsd(runs.usdPerRun.avg).padStart(6)}/${formatAdminUsd(runs.usdPerRun.p90).padEnd(6)} | ${String(m.budget_stop_changed.count).padStart(7)}/${String(m.budget_stop_unchanged.count).padEnd(7)} | ${String(m.busy_refusal.count).padStart(4)} | ${queue.padEnd(
|
|
5850
|
+
`${day.dayKey}${day.inProgress ? "*" : " "}| ${formatAdminUsd(day.chargedUsd).padStart(7)} | ${formatAdminUsd(day.overflowUsd).padStart(8)} | ${String(day.users).padStart(5)} | ${String(day.recharges).padStart(5)} | ${String(runs.runs).padStart(4)} | ${String(runs.callsPerRun.avg).padStart(7)}/${String(runs.callsPerRun.p90).padEnd(9)} | ${formatAdminUsd(runs.usdPerRun.avg).padStart(6)}/${formatAdminUsd(runs.usdPerRun.p90).padEnd(6)} | ${String(m.budget_stop_changed.count).padStart(7)}/${String(m.budget_stop_unchanged.count).padEnd(7)} | ${String(m.busy_refusal.count).padStart(4)} | ${queue.padEnd(33)} | ${day.telemetry.rowCount}`,
|
|
5884
5851
|
);
|
|
5885
5852
|
}
|
|
5886
5853
|
console.log("* = in-progress UTC day; never headline it.");
|
|
@@ -6419,37 +6386,6 @@ function shortDate(seconds) {
|
|
|
6419
6386
|
return value > 0 ? new Date(value * 1000).toISOString().slice(0, 10) : "";
|
|
6420
6387
|
}
|
|
6421
6388
|
|
|
6422
|
-
export function meetupAdultLine(adult) {
|
|
6423
|
-
const request = adult?.request || null;
|
|
6424
|
-
if (adult?.display?.kind === "classroom") {
|
|
6425
|
-
return adult.display.status === "confirmed"
|
|
6426
|
-
? ` Adult coming: classroom meetup, supervised at ${adult.display.name} (Twinkle arranges the grown-up)`
|
|
6427
|
-
: " Adult coming: classroom meetup, Twinkle arranges the grown-up (slot not confirmed yet)";
|
|
6428
|
-
}
|
|
6429
|
-
if (!adult?.kind) {
|
|
6430
|
-
return request?.status === "declined"
|
|
6431
|
-
? ` Adult coming: not named yet (Twinkle teacher ${request.teacherUsername} (user ${request.teacherUserId}) declined${request.afterYes ? " after saying yes" : ""} ${shortDate(request.answeredAt)})`
|
|
6432
|
-
: "";
|
|
6433
|
-
}
|
|
6434
|
-
if (adult.kind !== "teacher") return ` Adult coming: parent: ${adult.name}`;
|
|
6435
|
-
if (adult.teacher?.userId) {
|
|
6436
|
-
const who = `Twinkle teacher ${adult.teacher.username} (user ${adult.teacher.userId}, approved teacher account)`;
|
|
6437
|
-
if (Number(adult.confirmedAt || 0) > 0) {
|
|
6438
|
-
return ` Adult coming: ${who}, confirmed ${shortDate(adult.confirmedAt)}`;
|
|
6439
|
-
}
|
|
6440
|
-
if (adult.display?.status === "waiting" || request?.status === "open") {
|
|
6441
|
-
return ` Adult coming: ${who}, waiting for confirmation${request?.sentAt ? ` (asked ${shortDate(request.sentAt)})` : ""}`;
|
|
6442
|
-
}
|
|
6443
|
-
if (adult.display?.status === "confirmed" || (!adult.display && !adult.teacherProblem)) {
|
|
6444
|
-
return ` Adult coming: ${who}`;
|
|
6445
|
-
}
|
|
6446
|
-
}
|
|
6447
|
-
// the API blanks `name` for a teacher that does not count; staff get what
|
|
6448
|
-
// is on file as `unconfirmedName`
|
|
6449
|
-
const typed = adult.unconfirmedName || adult.name;
|
|
6450
|
-
return ` Adult coming: Twinkle teacher NOT CONFIRMED${typed ? ` (on file: "${typed}")` : ""}: ${adult.teacherProblem || "no approved teacher account picked"}`;
|
|
6451
|
-
}
|
|
6452
|
-
|
|
6453
6389
|
function printMeetupCrew(crew) {
|
|
6454
6390
|
const progress = crew.progress || {};
|
|
6455
6391
|
console.log(
|
|
@@ -6460,9 +6396,10 @@ function printMeetupCrew(crew) {
|
|
|
6460
6396
|
console.log(` ${step.number}. ${step.label} [${step.state}] ${step.detail}`);
|
|
6461
6397
|
}
|
|
6462
6398
|
console.log(` Members: ${formatMeetupMembers(crew.members)}`);
|
|
6463
|
-
|
|
6464
|
-
console.log(
|
|
6465
|
-
|
|
6399
|
+
if (crew.completedAt) {
|
|
6400
|
+
console.log(
|
|
6401
|
+
` Met up ${shortDate(crew.completedAt)}${crew.tier ? ` (${crew.tier})` : ""}: checked in ${(crew.members || []).filter((m) => m.attended).map((m) => m.username).join(", ") || "nobody"}`,
|
|
6402
|
+
);
|
|
6466
6403
|
}
|
|
6467
6404
|
// staff's "who are you" checks on these members (any crew), newest first
|
|
6468
6405
|
for (const check of crew.memberChecks || []) {
|
|
@@ -6481,43 +6418,11 @@ function printMeetupCrew(crew) {
|
|
|
6481
6418
|
` Branch set by ${change.actorUsername || "staff"} ${shortDate(change.createdAt)} · ${change.username} (${change.userId}): "${change.oldBranch || "none"}" -> "${change.newBranch}"${change.note ? ` · private note: ${change.note}` : ""}`,
|
|
6482
6419
|
);
|
|
6483
6420
|
}
|
|
6484
|
-
for (const parent of crew.parentContacts || []) {
|
|
6485
|
-
console.log(
|
|
6486
|
-
` Parent of ${parent.childUsername}: ${parent.status}${parent.verifiedGuardian ? " (guardian on file)" : parent.staffVerified ? " (staff verified)" : " (child-typed address: NOT verified)"} · ${parent.email}${parent.question ? ` · asks: "${parent.question}"` : ""}`,
|
|
6487
|
-
);
|
|
6488
|
-
}
|
|
6489
6421
|
if (Array.isArray(crew.invites) && crew.invites.length) {
|
|
6490
6422
|
console.log(
|
|
6491
6423
|
` Pending invites: ${crew.invites.map((invite) => `${invite.username} (${invite.userId}, invite ${invite.inviteId})`).join("; ")}`,
|
|
6492
6424
|
);
|
|
6493
6425
|
}
|
|
6494
|
-
const adultLine = meetupAdultLine(crew.adult);
|
|
6495
|
-
if (adultLine) console.log(adultLine);
|
|
6496
|
-
const plan = crew.plan || {};
|
|
6497
|
-
if (plan.status && plan.status !== "none") {
|
|
6498
|
-
console.log(
|
|
6499
|
-
` Plan (${plan.status}): ${plan.date}${plan.time ? ` at ${plan.time}` : ""} in ${plan.area}: ${plan.activity}${plan.note ? ` · note: ${plan.note}` : ""}`,
|
|
6500
|
-
);
|
|
6501
|
-
}
|
|
6502
|
-
if (crew.planApprovedBy?.username) {
|
|
6503
|
-
console.log(` Plan approved by ${crew.planApprovedBy.username}`);
|
|
6504
|
-
}
|
|
6505
|
-
const venue = crew.venue || {};
|
|
6506
|
-
if (venue.status && venue.status !== "none") {
|
|
6507
|
-
const slots = (venue.slots || []).map(
|
|
6508
|
-
(slot, index) => `[${index}] ${slot.date} ${slot.start}-${slot.end}`,
|
|
6509
|
-
);
|
|
6510
|
-
console.log(
|
|
6511
|
-
` Venue (${venue.status}): ${venue.room ? `${venue.branch} · ${venue.room}` : "no classroom (meets elsewhere with the grown-up)"}${slots.length ? ` · slots ${slots.join(", ")}` : ""}${venue.confirmedSlot ? ` · SET: ${venue.confirmedSlot.date} ${venue.confirmedSlot.start}-${venue.confirmedSlot.end}` : ""}`,
|
|
6512
|
-
);
|
|
6513
|
-
}
|
|
6514
|
-
const video = crew.video || {};
|
|
6515
|
-
if (video.status && video.status !== "none") {
|
|
6516
|
-
console.log(
|
|
6517
|
-
` Video (${video.status}): ${video.fileName}${video.note ? ` · note: ${video.note}` : ""}`,
|
|
6518
|
-
);
|
|
6519
|
-
if (video.url) console.log(` Watch (link expires in 1 hour): ${video.url}`);
|
|
6520
|
-
}
|
|
6521
6426
|
}
|
|
6522
6427
|
|
|
6523
6428
|
function printMeetupResult({ operation, data, result }) {
|
|
@@ -6589,7 +6494,7 @@ function printMeetupResult({ operation, data, result }) {
|
|
|
6589
6494
|
const open = (data.drafts || []).filter((d) => d.status === "held" || d.status === "failed");
|
|
6590
6495
|
if (!open.length) console.log("Nothing is waiting.");
|
|
6591
6496
|
for (const d of open) {
|
|
6592
|
-
console.log(`\n#${d.id} [${d.status}] crew #${d.crewId} ${d.kind === "brief" ? "meetup request" : "reply"} -> ${d.to}`);
|
|
6497
|
+
console.log(`\n#${d.id} [${d.status}] ${d.kind === "witness" ? `member #${d.childUserId} asks a parent to be the grown-up` : `crew #${d.crewId} ${d.kind === "brief" ? "meetup request" : "reply"}`} -> ${d.to}`);
|
|
6593
6498
|
console.log(` Subject: ${d.subject}`);
|
|
6594
6499
|
console.log(` ${String(d.body).split("\n").join("\n ")}`);
|
|
6595
6500
|
}
|
|
@@ -6607,8 +6512,18 @@ function printMeetupResult({ operation, data, result }) {
|
|
|
6607
6512
|
console.log(` quest page viewers: ${data.questPage?.viewers ?? 0}, of whom ever in a crew: ${data.questPage?.viewersEverInCrew ?? 0}`);
|
|
6608
6513
|
console.log(` crews by status: ${JSON.stringify(data.crews?.byStatus || {})}; active by step: ${JSON.stringify(data.crews?.activeByStep || {})}; active by size: ${JSON.stringify(data.crews?.activeBySize || {})}`);
|
|
6609
6514
|
if (data.branches) console.log(` branch names: ${JSON.stringify(data.branches.byStatus)}${data.branches.oldestPendingHours ? `; oldest pending ${data.branches.oldestPendingHours}h` : ""}`);
|
|
6515
|
+
if (data.checkins) {
|
|
6516
|
+
const c = data.checkins;
|
|
6517
|
+
console.log(` check-ins: ${c.ok} pairs (${c.together} together), ${c.refused} refused ${JSON.stringify(c.refusedBy || {})}, crews meeting: ${c.crews}`);
|
|
6518
|
+
// the proximity ladder: which rung showed "together" (a pair can pass several)
|
|
6519
|
+
if (c.byNetwork !== undefined) {
|
|
6520
|
+
console.log(` together by: network ${c.byNetwork}, academy branch ${c.byAcademy}, sound ${c.bySound} (${c.soundOnly} by sound alone); later proved ${c.upgraded}`);
|
|
6521
|
+
console.log(` sound check: ${JSON.stringify(c.soundStatus || {})}; grown-up scans ${c.witnessScans?.total || 0} (${c.witnessScans?.inPlace || 0} in place, ${c.witnessScans?.bySound || 0} by sound)`);
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
if (data.completedByTier) console.log(` crews that met, by tier: ${JSON.stringify(data.completedByTier)}`);
|
|
6610
6525
|
for (const crew of data.activeCrews || []) {
|
|
6611
|
-
console.log(` #${crew.crewId} step ${crew.step} · ${crew.members} member(s) · idle ${crew.daysIdle}d · ${crew.unverifiedBranches ? `${crew.unverifiedBranches} branch(es) to verify · ` : ""}${crew.
|
|
6526
|
+
console.log(` #${crew.crewId} step ${crew.step} · ${crew.members} member(s) · idle ${crew.daysIdle}d · ${crew.unverifiedBranches ? `${crew.unverifiedBranches} branch(es) to verify · ` : ""}${crew.metAt ? "met up · " : ""}chat7d ${crew.chat7d ? `${crew.chat7d.messages} msg/${crew.chat7d.speakers} people` : "n/a"} · ${crew.blocking}`);
|
|
6612
6527
|
}
|
|
6613
6528
|
return;
|
|
6614
6529
|
}
|
|
@@ -6622,7 +6537,7 @@ function printMeetupResult({ operation, data, result }) {
|
|
|
6622
6537
|
}
|
|
6623
6538
|
if (crews.some((crew) => crew.waitingFor)) {
|
|
6624
6539
|
console.log(
|
|
6625
|
-
"Review: lumine admin meetup show <crewId>, then
|
|
6540
|
+
"Review: lumine admin meetup show <crewId>, then info <crewId> <userId> --decision accept|ask-again (a member's who-are-you answer).",
|
|
6626
6541
|
);
|
|
6627
6542
|
}
|
|
6628
6543
|
return;
|
|
@@ -6647,46 +6562,20 @@ function printMeetupResult({ operation, data, result }) {
|
|
|
6647
6562
|
if (data.crew) printMeetupCrew(data.crew);
|
|
6648
6563
|
return;
|
|
6649
6564
|
}
|
|
6650
|
-
if (operation.name === "meetup.
|
|
6651
|
-
printMeetupCrew(data.crew || {});
|
|
6652
|
-
return;
|
|
6653
|
-
}
|
|
6654
|
-
if (operation.name === "meetup.slot") {
|
|
6565
|
+
if (operation.name === "meetup.announce-checkins") {
|
|
6655
6566
|
console.log(
|
|
6656
|
-
|
|
6567
|
+
`${data.send ? "Announced" : "Preview (nothing sent; the announcement posts itself after the update floor reaches 2.3.112, and --send retries a crew not announced yet)"} as ${data.agent === "ciel" ? "Ciel" : "Zero"}:`,
|
|
6657
6568
|
);
|
|
6658
|
-
|
|
6569
|
+
for (const crew of data.crews || []) {
|
|
6570
|
+
console.log(`\n#${crew.crewId} "${crew.name}" ${crew.sent ? "SENT" : crew.already ? "(already announced)" : crew.failed ? "FAILED (not posted; --send retries it)" : "(not announced yet)"}`);
|
|
6571
|
+
console.log(` ${String(crew.text).split("\n").join("\n ")}`);
|
|
6572
|
+
}
|
|
6659
6573
|
return;
|
|
6660
6574
|
}
|
|
6661
|
-
if (operation.name === "meetup.
|
|
6662
|
-
|
|
6663
|
-
console.log(
|
|
6664
|
-
email.sent
|
|
6665
|
-
? `Coordinator ${email.kind} email ${email.mode === "sent" ? "sent" : `built (${email.mode})`} to ${email.to}.`
|
|
6666
|
-
: `No email sent: ${email.reason || "unknown"}.`,
|
|
6667
|
-
);
|
|
6575
|
+
if (operation.name === "meetup.show") {
|
|
6576
|
+
printMeetupCrew(data.crew || {});
|
|
6668
6577
|
return;
|
|
6669
6578
|
}
|
|
6670
|
-
const stage = data.result?.stage === "video" ? "video" : "plan";
|
|
6671
|
-
if (operation.name === "meetup.approve-crew" || operation.name === "meetup.approve-grownup") {
|
|
6672
|
-
console.log(
|
|
6673
|
-
`${result?.changed === false ? "Already approved" : "Approved"} crew #${data.result?.crewId}'s ${operation.name === "meetup.approve-crew" ? "crew step" : "grown-up step"}.`,
|
|
6674
|
-
);
|
|
6675
|
-
} else if (operation.name === "meetup.approve-plan") {
|
|
6676
|
-
console.log(
|
|
6677
|
-
result?.changed === false
|
|
6678
|
-
? `Crew #${data.result?.crewId}'s plan was already approved.`
|
|
6679
|
-
: `Approved crew #${data.result?.crewId}'s plan. Next the crew gets its parents' yes and names a grown-up.`,
|
|
6680
|
-
);
|
|
6681
|
-
} else if (operation.name === "meetup.send-back") {
|
|
6682
|
-
console.log(`Sent crew #${data.result?.crewId}'s ${stage} back with your note.`);
|
|
6683
|
-
} else if (operation.name === "meetup.approve") {
|
|
6684
|
-
const unlocked = data.unlocks?.unlockedUserIds || [];
|
|
6685
|
-
const failed = data.unlocks?.failed || [];
|
|
6686
|
-
console.log(
|
|
6687
|
-
`${result?.changed === false ? "Already approved" : "Approved"} crew #${data.result?.crewId}${data.result?.tier ? ` at ${data.result.tier} tier` : ""}: unlocked for user(s) ${unlocked.join(", ") || "none"}.${failed.length ? ` Unlock FAILED for ${failed.join(", ")}; run the same command again.` : ""}`,
|
|
6688
|
-
);
|
|
6689
|
-
}
|
|
6690
6579
|
if (data.crew) printMeetupCrew(data.crew);
|
|
6691
6580
|
}
|
|
6692
6581
|
|
package/lib/build-review.js
CHANGED
|
@@ -909,15 +909,19 @@ async function runChromeReview({
|
|
|
909
909
|
}
|
|
910
910
|
}
|
|
911
911
|
await cdp.send("Browser.close").catch(() => {});
|
|
912
|
+
let forcedTermination = false;
|
|
912
913
|
const closeResult = await Promise.race([
|
|
913
914
|
closed,
|
|
914
915
|
delay(5_000).then(() => {
|
|
916
|
+
forcedTermination = true;
|
|
915
917
|
child.kill("SIGTERM");
|
|
916
918
|
return closed;
|
|
917
919
|
}),
|
|
918
920
|
]);
|
|
919
921
|
return {
|
|
920
922
|
...closeResult,
|
|
923
|
+
closeRequested: true,
|
|
924
|
+
forcedTermination,
|
|
921
925
|
stdout,
|
|
922
926
|
stderr,
|
|
923
927
|
consoleLines: consoleLines.slice(-MAX_CAPTURED_LOG_LINES),
|
|
@@ -934,6 +938,31 @@ async function runChromeReview({
|
|
|
934
938
|
}
|
|
935
939
|
}
|
|
936
940
|
|
|
941
|
+
const CHROME_KILLED_AFTER_CLOSE_CODE = 2;
|
|
942
|
+
|
|
943
|
+
// Chrome sometimes exits 2 (RESULT_CODE_KILLED) while tearing down a
|
|
944
|
+
// GPU-heavy page after our own Browser.close. Every piece of evidence was
|
|
945
|
+
// captured before that close, so that one shutdown result is cleanup, not a
|
|
946
|
+
// failed review. Any other code, a signal, or our forced kill still fails.
|
|
947
|
+
export function reviewBrowserExitedCleanly(browser) {
|
|
948
|
+
return (
|
|
949
|
+
browser?.code === 0 ||
|
|
950
|
+
(browser?.code === CHROME_KILLED_AFTER_CLOSE_CODE &&
|
|
951
|
+
!browser.signal &&
|
|
952
|
+
browser.closeRequested === true &&
|
|
953
|
+
browser.forcedTermination === false)
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// The last few Chrome stderr lines, for diagnosing a nonzero exit.
|
|
958
|
+
function chromeStderrTail(stderr) {
|
|
959
|
+
return String(stderr || "")
|
|
960
|
+
.split(/\r?\n/)
|
|
961
|
+
.filter(Boolean)
|
|
962
|
+
.slice(-20)
|
|
963
|
+
.map((line) => line.slice(0, 400));
|
|
964
|
+
}
|
|
965
|
+
|
|
937
966
|
function captureConsoleEvidence(stderr, cdpLines = []) {
|
|
938
967
|
const chromeLines = String(stderr || "")
|
|
939
968
|
.split(/\r?\n/)
|
|
@@ -1003,8 +1032,9 @@ export async function runManagedBuildReview({ options, authToken, buildId }) {
|
|
|
1003
1032
|
const interactionOutcome = browser.interaction || null;
|
|
1004
1033
|
const interactionCompleted =
|
|
1005
1034
|
!interaction || interactionOutcome?.status === "completed";
|
|
1035
|
+
const browserExitedCleanly = reviewBrowserExitedCleanly(browser);
|
|
1006
1036
|
const confirmed =
|
|
1007
|
-
|
|
1037
|
+
browserExitedCleanly &&
|
|
1008
1038
|
screenshotBytes > 0 &&
|
|
1009
1039
|
browser.runtimeReadiness?.ready === true &&
|
|
1010
1040
|
beforeVersion === afterVersion &&
|
|
@@ -1025,6 +1055,10 @@ export async function runManagedBuildReview({ options, authToken, buildId }) {
|
|
|
1025
1055
|
executable,
|
|
1026
1056
|
exitCode: browser.code,
|
|
1027
1057
|
signal: browser.signal,
|
|
1058
|
+
closeRequested: browser.closeRequested === true,
|
|
1059
|
+
forcedTermination: browser.forcedTermination === true,
|
|
1060
|
+
exitedCleanly: browserExitedCleanly,
|
|
1061
|
+
stderrTail: browser.code === 0 ? null : chromeStderrTail(browser.stderr),
|
|
1028
1062
|
runtimeReadiness: browser.runtimeReadiness,
|
|
1029
1063
|
},
|
|
1030
1064
|
screenshot: {
|
|
@@ -1059,7 +1093,12 @@ export async function runManagedBuildReview({ options, authToken, buildId }) {
|
|
|
1059
1093
|
? "The Build was republished during review; review the new artifact before commenting."
|
|
1060
1094
|
: browser.runtimeReadiness?.runtimeErrorVisible
|
|
1061
1095
|
? "The isolated Build runtime exposed a runtime error instead of a reviewable app."
|
|
1062
|
-
:
|
|
1096
|
+
: !browserExitedCleanly &&
|
|
1097
|
+
screenshotBytes > 0 &&
|
|
1098
|
+
browser.runtimeReadiness?.ready === true &&
|
|
1099
|
+
interactionCompleted
|
|
1100
|
+
? `Chrome exited with ${browser.signal ? `signal ${browser.signal}` : `code ${browser.code}`}${browser.forcedTermination ? " after the CLI had to stop it" : ""} once the review evidence was captured. The app loaded and the screenshots are in the receipt, but the receipt is not confirmed; see browser.stderrTail and review again.`
|
|
1101
|
+
: interactionFailure
|
|
1063
1102
|
? `Interaction step ${interactionOutcome?.failedStep?.index ?? "?"} (${interactionOutcome?.failedStep?.kind ?? "?"}) failed: ${interactionOutcome?.failedStep?.error ?? "unknown"} The receipt lists the ${interactionOutcome?.stepsCompleted ?? 0} completed step(s) and their screenshots; fix the script and review again.`
|
|
1064
1103
|
: "The isolated Build runtime did not become visibly reviewable before the observation window ended.",
|
|
1065
1104
|
interactionFailure
|
package/lib/commands.js
CHANGED
|
@@ -2483,9 +2483,10 @@ export function parseArgs(args) {
|
|
|
2483
2483
|
"confirmEmailRule",
|
|
2484
2484
|
"includePrefix",
|
|
2485
2485
|
"allowSharedNetwork",
|
|
2486
|
+
"send",
|
|
2486
2487
|
]);
|
|
2487
2488
|
// A flag that may stand alone (true) or carry a value: `--confirm` confirms
|
|
2488
|
-
// an AI bucket change
|
|
2489
|
+
// an AI bucket change.
|
|
2489
2490
|
const optionalValueFlags = new Set(["confirm"]);
|
|
2490
2491
|
|
|
2491
2492
|
for (let i = 0; i < rest.length; i += 1) {
|
|
@@ -2792,8 +2793,12 @@ export function parseArgs(args) {
|
|
|
2792
2793
|
adminConfirm: raw.confirm === undefined ? "" : String(raw.confirm),
|
|
2793
2794
|
adminInto: raw.into === undefined ? "" : String(raw.into),
|
|
2794
2795
|
adminNoAnnounce: Boolean(raw.noAnnounce),
|
|
2796
|
+
// meetup announce-checkins: post for real, and who speaks (zero | ciel)
|
|
2797
|
+
adminSend: Boolean(raw.send),
|
|
2798
|
+
adminAs: raw.as ? String(raw.as) : "",
|
|
2795
2799
|
adminContextLimit: raw.limit === undefined ? 20 : Number(raw.limit),
|
|
2796
2800
|
adminRun: raw.run ? String(raw.run) : "",
|
|
2801
|
+
adminSourceRun: raw.sourceRun ? String(raw.sourceRun) : "",
|
|
2797
2802
|
adminTarget: raw.target ? String(raw.target) : "",
|
|
2798
2803
|
adminActions: raw.actions ? String(raw.actions) : "",
|
|
2799
2804
|
adminDate: raw.date ? String(raw.date) : "",
|
|
@@ -3231,6 +3236,7 @@ export function printHelp() {
|
|
|
3231
3236
|
lumine admin ai-email-policy get --email <address> [--json]
|
|
3232
3237
|
lumine admin ai-email-policy set --email <address> --mode <automatic|separate_accounts> --note <text> [--json]
|
|
3233
3238
|
lumine admin daily-run start [--scope full|featured|newspaper] [--identity zero|ciel|auto] [--comment-mode off|draft|post] [--run-key <key>] [--json]
|
|
3239
|
+
lumine admin daily-run start --scope approvals [--source-run <run-id>] [--identity zero|ciel] [--comment-mode off|draft|post] [--json] # act on Mikey's decisions; --source-run reuses that run's plans and drafts
|
|
3234
3240
|
lumine admin daily-run status|report|complete|fail [--run <completed-run-id>] [--reason <text>] [--json]
|
|
3235
3241
|
lumine admin daily-run escalation add --target <target> --note <summary> [--severity attention|urgent] [--json]
|
|
3236
3242
|
lumine admin escalation list [--status open|acknowledged|resolved|all] [--limit <number>] [--json]
|
|
@@ -3255,7 +3261,7 @@ export function printHelp() {
|
|
|
3255
3261
|
lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
|
|
3256
3262
|
lumine admin craft seed --build <id> --file <pairs.json> [--confirm] | stats --build <id> [--limit <1-100>] | revise --build <id> --item <id|name> [--why <text>] [--emoji <emoji>] [--category <field>] [--confirm] | revise --build <id> --pair <pairId|a+b> --why <text> [--confirm] | retire --build <id> --item <id|name> --reason <text> [--confirm] | unfizzle --build <id> [--since <iso>] [--pair <a+b>] [--confirm] (Twinkle.craft curation: dry run without --confirm)
|
|
3257
3263
|
lumine admin storage list [--status pending|approved|rejected|all] [--cursor <id>] | show <user-id-or-username> | approve <request-id> [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <request-id> [--reason <text>] | grant <user-id-or-username> --size <500MB|1GB|2GB|n MB> [--reason <text>]
|
|
3258
|
-
lumine admin meetup list [--status review|active|completed|all] |
|
|
3264
|
+
lumine admin meetup list [--status review|active|completed|all] | show <crewId> | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | set-branch <crewId> <userId> --branch "<name>" [--note <text>] | branches [list [--status pending|official|rejected|all] | show <id> | approve <id> | reject <id> [--note <text>] | merge <id> --into <officialId>] | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | funnel [--days <1-90>] | announce-checkins [--send] [--as zero|ciel] (Bridge Builder crews and check-ins)
|
|
3259
3265
|
lumine admin meetup story list | show <crewId> | approve <crewId> [--note <text>] [--no-announce] | send-back <crewId> --note <text> | unpublish <crewId> --note <text> (Bridge Builder story pages: publishes only what every tagged member's parent allowed)
|
|
3260
3266
|
lumine admin reward-activity [--date YYYY-MM-DD] [--days <1..31>] [--build <id>] [--json]
|
|
3261
3267
|
lumine admin chat-reports list [--status pending|open|reviewing|resolved|dismissed|all] [--cursor <id>] [--json]
|
package/package.json
CHANGED
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -28,6 +28,30 @@ canonical structured data.
|
|
|
28
28
|
`off`. It cannot inspect queues, conduct, logs, Builds, or Featured.
|
|
29
29
|
Completion does not advance full-review windows or carry-over telemetry.
|
|
30
30
|
Its distinct `/daily-runs/start/newspaper` endpoint fails closed on older APIs.
|
|
31
|
+
- `--scope approvals [--source-run <id>]` acts on Mikey's decisions whenever
|
|
32
|
+
he gets to them (Mikey, 10-17: don't be trapped by the morning run). It has
|
|
33
|
+
a full run's write work: Featured plans and changes, Featured-comment and
|
|
34
|
+
generic recommendations, rewards, comments (draft, post, edit) and chat. It
|
|
35
|
+
has none of the newspaper, sponsor scan, bank seeding or reading duties, and
|
|
36
|
+
never counts as a full review, so review windows don't move.
|
|
37
|
+
- `--source-run` is optional. It lets the run use that run's prepared
|
|
38
|
+
Featured plan (same hash) and review receipts for up to 7 days, abandoned
|
|
39
|
+
runs included. A stale board is still refused at apply, so prepare a new
|
|
40
|
+
plan then.
|
|
41
|
+
- Source drafts expire after 30 minutes: re-draft an approved comment with
|
|
42
|
+
its approved text (with a fresh `builds review` receipt for an app). The
|
|
43
|
+
API refuses a second published comment by the same identity in the same
|
|
44
|
+
place for the same source run.
|
|
45
|
+
- Identity defaults to the source run's (else today's scheduled one) and
|
|
46
|
+
`--identity` overrides it. Either way it is named explicitly, so Bangkok
|
|
47
|
+
midnight doesn't end the run. `--comment-mode` defaults to `post`.
|
|
48
|
+
Its distinct `/daily-runs/start/approvals` endpoint fails closed on older
|
|
49
|
+
APIs.
|
|
50
|
+
- **Complete a full run when its report is delivered** (`daily-run complete`),
|
|
51
|
+
even with Mikey's decisions still pending. Do not hold it open for them: an
|
|
52
|
+
expired run can never be completed, so the review would not count, its
|
|
53
|
+
report would be unavailable, and the next run would re-read the whole
|
|
54
|
+
window. When Mikey answers, start an approvals run on it.
|
|
31
55
|
- The public content actor is Zero or Ciel. Mikey's operator ID is retained in
|
|
32
56
|
private audit rows and is not embedded in public comment metadata.
|
|
33
57
|
- Delegated HTTP work never authenticates as the bot, opens a bot socket, changes
|
|
@@ -1017,6 +1041,8 @@ identity, or advances rotation.
|
|
|
1017
1041
|
lumine admin daily-run start --identity auto --comment-mode off --json
|
|
1018
1042
|
lumine admin daily-run start --scope featured --identity auto --json
|
|
1019
1043
|
lumine admin daily-run start --scope newspaper --identity auto --json
|
|
1044
|
+
lumine admin daily-run start --scope approvals --source-run 125 --json
|
|
1045
|
+
lumine admin daily-run start --scope approvals --identity zero --json
|
|
1020
1046
|
lumine admin daily-run start --identity ciel --comment-mode draft \
|
|
1021
1047
|
--run-key daily:2026-08-06:review --json
|
|
1022
1048
|
lumine admin daily-run status --json
|
|
@@ -2769,7 +2795,9 @@ inferred. The CLI then reads the live board and verifies exact membership/order.
|
|
|
2769
2795
|
Retry the identical apply command after uncertain transport failure; it reuses
|
|
2770
2796
|
the same idempotency key. If the final read fails or finds an intervening change,
|
|
2771
2797
|
the error retains the canonical apply receipt and never restores old state.
|
|
2772
|
-
|
|
2798
|
+
A different run requires a fresh plan and renewed approval, except an
|
|
2799
|
+
approvals run started with `--source-run <the plan's run>`, which can apply
|
|
2800
|
+
that exact plan with the same hash (or prepare a new one).
|
|
2773
2801
|
|
|
2774
2802
|
Use the following workflow for full daily comment review, or only when Mikey's
|
|
2775
2803
|
Featured slice includes comment encouragement:
|
|
@@ -5346,17 +5374,22 @@ Study's registered `study-record-v1` checks use learner AI Energy for accepted,
|
|
|
5346
5374
|
|
|
5347
5375
|
For total JEV cost, include `dailyAiCosts.byProviderModel` for TypeSafe/JEV across both AI Energy and system-covered ledgers. Report serving/shadow/audit operations separately as above; the Build SDK's system-covered/failed calls use `jev_decision`, while ordinary viewer SDK checks are already counted by the AI Energy ledger. Do not add the Study report's known-cost sum on top of the canonical overall AI-cost total: it is a diagnostic breakdown of those same calls.
|
|
5348
5376
|
|
|
5349
|
-
## Bridge Builder
|
|
5350
|
-
|
|
5351
|
-
On the website, Zero and Ciel acting for an admin can make
|
|
5352
|
-
|
|
5353
|
-
- `lumine admin meetup list` shows
|
|
5354
|
-
- `lumine admin meetup show <crewId>` shows canonical progress,
|
|
5355
|
-
- `lumine admin meetup
|
|
5356
|
-
- `lumine admin meetup
|
|
5357
|
-
- `
|
|
5358
|
-
-
|
|
5359
|
-
-
|
|
5377
|
+
## Bridge Builder crews and check-ins (no daily run required)
|
|
5378
|
+
|
|
5379
|
+
Since 2026-10-17 Bridge Builder unlocks from QR check-ins: 7 different people, or one meetup a grown-up witnessed. Crews no longer wait for staff approvals. On the website, Zero and Ciel acting for an admin can make the staff decisions below (who-are-you checks, branch corrections and branch reviews). They use the same routes as the admin's own taps, and only after the admin's yes on the approval card (Mikey, 2026-10-10). A member's Zero or Ciel still cannot.
|
|
5380
|
+
|
|
5381
|
+
- `lumine admin meetup list` shows crews whose member answered a who-are-you check. `list --status active` shows every live crew, and `list --status completed` the crews that have met up.
|
|
5382
|
+
- `lumine admin meetup show <crewId>` shows canonical progress, who checked in, the tier and private staff evidence.
|
|
5383
|
+
- `lumine admin meetup info <crewId> <userId> --decision ask|accept|ask-again|withdraw` is a **private identity check**. Its question and answer are visible only to the named member and staff. Until staff accept, that member doesn't count toward the crew's tier.
|
|
5384
|
+
- `lumine admin meetup set-branch <crewId> <userId> --branch <name> [--note <text>]` corrects a member's branch instead of a database write.
|
|
5385
|
+
- `--branch` must be one of: an official branch (an alias such as `Busan` resolves to `Haeundae`), a name still pending in the branch registry, or `"Not a Twinkle student"`. Former Twinkle students get the branch they went to.
|
|
5386
|
+
- An official branch is verified at once and the crew's tier follows; a pending name waits for its branch decision.
|
|
5387
|
+
- The private note and the change history are admin-only; nobody is messaged.
|
|
5388
|
+
- It refuses members who left and crews that are not active.
|
|
5389
|
+
- `lumine admin meetup funnel [--days <1-90>]` shows views, crews by stage, and check-ins: pairs, how many were on one network or at an academy, and refusals by reason.
|
|
5390
|
+
- `lumine admin meetup emails` lists parent emails held for review. Kind `witness` is a child asking a parent to be the grown-up.
|
|
5391
|
+
- `lumine admin meetup announce-checkins [--send] [--as zero|ciel]` is about the one-time announcement of the check-in rework in each crew chat. It posts itself when the website with check-ins is out. The messages are pre-written; without `--send` this only shows them. `--send` retries a crew whose post failed. Each crew gets it once.
|
|
5392
|
+
- **Held unlocks:** a member who reached 7 people when no counted check-in was on one network or at an academy gets a `meetup` approval item instead of an instant unlock (`lumine admin approvals list --type meetup`). Approving it unlocks Bridge Builder.
|
|
5360
5393
|
|
|
5361
5394
|
Every decision requires Mikey's authorization for that crew and action. Mutations are audited and idempotent; `--output <path>` is an explicit request to retain the canonical JSON. General write receipts omit private crew details.
|
|
5362
5395
|
|