@stage5/lumine 0.3.68 → 0.3.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/admin-arena.js +22 -4
- package/lib/admin-build-plans.js +8 -2
- package/lib/admin-daily-collection.js +88 -0
- package/lib/admin-daily.js +99 -18
- package/lib/admin-duties.js +10 -1
- package/lib/commands.js +7 -1
- package/package.json +1 -1
package/lib/admin-arena.js
CHANGED
|
@@ -14,7 +14,7 @@ import path from "node:path";
|
|
|
14
14
|
// re-importing a folder retires that paper's earlier items
|
|
15
15
|
// status --item <id> --to live|retired|checked
|
|
16
16
|
export const ARENA_USAGE =
|
|
17
|
-
"Usage: lumine admin arena queue | marking [--cursor <id>] [--limit <1-200>] | import --dir <paper folder> [--audio audio/<exam>/<paper>] [--live] [--confirm] | status --item <id> --to live|retired|checked [--confirm] | today-queue [--cursor <id>] [--limit <1-100>] | today-member --user <id> | today-import --file <reviewed-questions.json> [--confirm] [--json]";
|
|
17
|
+
"Usage: lumine admin arena queue | marking [--cursor <id>] [--limit <1-200>] | import (--dir <paper folder> | --additive --file <reviewed-items.json>) [--audio audio/<exam>/<paper>] [--live] [--confirm] | status --item <id> --to live|retired|checked [--confirm] | today-queue [--cursor <id>] [--limit <1-100>] | today-member --user <id> | today-import --file <reviewed-questions.json> [--confirm] [--json]";
|
|
18
18
|
const MAX_ITEMS = 300;
|
|
19
19
|
|
|
20
20
|
function validationError(message) {
|
|
@@ -49,6 +49,19 @@ function readPaper(dir) {
|
|
|
49
49
|
.sort((a, b) => a.number - b.number);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function readAdditiveItems(file) {
|
|
53
|
+
const resolved = path.resolve(file);
|
|
54
|
+
if (fs.statSync(resolved).size > 5_000_000) {
|
|
55
|
+
throw validationError("The additive import file must be under 5 MB.");
|
|
56
|
+
}
|
|
57
|
+
const items = JSON.parse(fs.readFileSync(resolved, "utf8"));
|
|
58
|
+
if (!Array.isArray(items) || !items.length || items.length > MAX_ITEMS ||
|
|
59
|
+
items.some((item) => !item || typeof item !== "object" || Array.isArray(item))) {
|
|
60
|
+
throw validationError(`An additive import file must contain an array of 1 to ${MAX_ITEMS} reviewed items.`);
|
|
61
|
+
}
|
|
62
|
+
return items;
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
export function buildArenaOperation({ action, options }) {
|
|
53
66
|
if (action === "today-queue") {
|
|
54
67
|
const cursor = Number(options.adminCursor || 0);
|
|
@@ -92,7 +105,11 @@ export function buildArenaOperation({ action, options }) {
|
|
|
92
105
|
};
|
|
93
106
|
}
|
|
94
107
|
if (action === "import") {
|
|
95
|
-
const
|
|
108
|
+
const additive = Boolean(options.adminArenaAdditive);
|
|
109
|
+
if ((options.adminFile && options.adminDir) || (options.adminFile && !additive)) {
|
|
110
|
+
throw validationError("Use --dir for a complete paper, or --additive with exactly one of --file or --dir for a refill.");
|
|
111
|
+
}
|
|
112
|
+
const items = options.adminFile ? readAdditiveItems(options.adminFile) : readPaper(options.adminDir);
|
|
96
113
|
const confirm = confirmed(options);
|
|
97
114
|
return {
|
|
98
115
|
name: confirm ? "arena.import" : "arena.import.preview",
|
|
@@ -103,8 +120,9 @@ export function buildArenaOperation({ action, options }) {
|
|
|
103
120
|
live: Boolean(options.adminArenaLive),
|
|
104
121
|
dryRun: !confirm,
|
|
105
122
|
...(options.adminArenaAudio ? { audioPrefix: options.adminArenaAudio } : {}),
|
|
106
|
-
//
|
|
107
|
-
paper
|
|
123
|
+
// Full papers retain their replacement semantics. Additive refills
|
|
124
|
+
// preserve the existing bank without retiring another paper's items.
|
|
125
|
+
...(!additive ? { paper: path.basename(path.resolve(options.adminDir)) } : {}),
|
|
108
126
|
},
|
|
109
127
|
mutates: confirm,
|
|
110
128
|
requiresRun: false,
|
package/lib/admin-build-plans.js
CHANGED
|
@@ -74,6 +74,12 @@ export function buildBuildPlansOperation({ action, options }) {
|
|
|
74
74
|
throw validationError(BUILD_PLANS_USAGE);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function formatStepCoverage(row) {
|
|
78
|
+
if (!Number.isFinite(row.steps)) return ""; // Older API response.
|
|
79
|
+
if (row.stepsPassed == null) return `; steps ?/${row.steps} verified (no step grading yet)`;
|
|
80
|
+
return `; steps ${row.stepsPassed}/${row.steps} verified, ${row.stepsUnverified ?? 0} unverified, ${row.stepsNeedWork ?? 0} need work`;
|
|
81
|
+
}
|
|
82
|
+
|
|
77
83
|
function formatRow(row) {
|
|
78
84
|
const ratio = row.energyRatio == null ? "?" : `${row.energyRatio}x`;
|
|
79
85
|
const milestone =
|
|
@@ -86,7 +92,7 @@ function formatRow(row) {
|
|
|
86
92
|
: row.wouldStall
|
|
87
93
|
? ` would stall (${row.wouldStall})`
|
|
88
94
|
: "";
|
|
89
|
-
return `Build ${row.buildId}: milestone ${milestone} (${row.milestonesDone} done), ${row.runs} runs, Energy ${ratio} of estimate, ${row.evaluations ?? "?"} gradings, non-pass streak ${row.consecutiveNonPass ?? "?"}, ${row.passed}/${row.criteria} passing, ${row.unverified} unverified${state};${coverage}${row.flags?.length ? ` [${row.flags.join(", ")}]` : ""}`;
|
|
95
|
+
return `Build ${row.buildId}: milestone ${milestone} (${row.milestonesDone} done), ${row.runs} runs, Energy ${ratio} of estimate, ${row.evaluations ?? "?"} gradings, non-pass streak ${row.consecutiveNonPass ?? "?"}, ${row.passed}/${row.criteria} passing, ${row.unverified} unverified${formatStepCoverage(row)}${state};${coverage}${row.flags?.length ? ` [${row.flags.join(", ")}]` : ""}`;
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
export function formatBuildPlansResult({ operation, data }) {
|
|
@@ -110,7 +116,7 @@ export function formatBuildPlansResult({ operation, data }) {
|
|
|
110
116
|
for (const result of data?.results || []) {
|
|
111
117
|
lines.push(
|
|
112
118
|
result?.ok
|
|
113
|
-
? `${result.replayed ? "(already rechecked by this request) " : ""}Build ${result.buildId}: milestone ${result.milestoneIndex + 1} ${result.verdict}, ${result.passed}/${result.criteria} passing, ${result.unverified} unverified${result.milestoneCompleted ? ", milestone completed" : ""}${result.stalled ? `, stalled (${result.stalled.reason})` : ""}${result.fileCoverage ? `; files ${result.fileCoverage.shownFiles}/${result.fileCoverage.totalFiles} + ${result.fileCoverage.toolFilesRead || 0} read in ${result.fileCoverage.toolRounds || 0} rounds` : ""}${result.usage ? `; cost ${result.usage.energyUnits} Energy units (${result.usage.inputTokens} in / ${result.usage.outputTokens} out tokens), not charged to the creator` : ""}`
|
|
119
|
+
? `${result.replayed ? "(already rechecked by this request) " : ""}Build ${result.buildId}: milestone ${result.milestoneIndex + 1} ${result.verdict}, ${result.passed}/${result.criteria} passing, ${result.unverified} unverified${formatStepCoverage(result)}${result.milestoneCompleted ? ", milestone completed" : ""}${result.stalled ? `, stalled (${result.stalled.reason})` : ""}${result.fileCoverage ? `; files ${result.fileCoverage.shownFiles}/${result.fileCoverage.totalFiles} + ${result.fileCoverage.toolFilesRead || 0} read in ${result.fileCoverage.toolRounds || 0} rounds` : ""}${result.usage ? `; cost ${result.usage.energyUnits} Energy units (${result.usage.inputTokens} in / ${result.usage.outputTokens} out tokens), not charged to the creator` : ""}`
|
|
114
120
|
: `Build ${result?.buildId}: not rechecked (${result?.reason === "run_in_progress" ? "a Lumine run is in progress; try again later" : result?.reason || "unknown"})`,
|
|
115
121
|
);
|
|
116
122
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
function invalid(message) {
|
|
6
|
+
const error = new Error(message);
|
|
7
|
+
error.code = "CLI_ADMIN_CLI_VALIDATION";
|
|
8
|
+
throw error;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const sha = (value) => createHash("sha256").update(value).digest("hex");
|
|
12
|
+
|
|
13
|
+
// Resuming reuses explicitly dated, byte-verified receipts. It does not assert
|
|
14
|
+
// that old observations are fresh, read, or an editorial duty is complete.
|
|
15
|
+
export function createDailyCollection(dir, options, { now = Date.now, clock = () => performance.now() } = {}) {
|
|
16
|
+
const started = clock();
|
|
17
|
+
const today = new Date(now()).toISOString().slice(0, 10);
|
|
18
|
+
const costDate = options.adminCostDate || options.adminDate || new Date(now() - 86400000).toISOString().slice(0, 10);
|
|
19
|
+
if (options.adminCostDate && options.adminDate && options.adminCostDate !== options.adminDate)
|
|
20
|
+
invalid("--cost-date and legacy --date must identify the same closed UTC cost day.");
|
|
21
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(costDate) || !Number.isFinite(Date.parse(`${costDate}T00:00:00Z`)) ||
|
|
22
|
+
new Date(`${costDate}T00:00:00Z`).toISOString().slice(0, 10) !== costDate || costDate >= today)
|
|
23
|
+
invalid("Use --cost-date YYYY-MM-DD for a completed UTC day, not today's run date.");
|
|
24
|
+
const phase = options.adminPhase || "all";
|
|
25
|
+
if (!["all", "priority", "evidence"].includes(phase)) invalid("--phase must be priority, evidence, or all.");
|
|
26
|
+
const maxSteps = Number(options.adminMaxSteps === "" || options.adminMaxSteps == null ? 200 : options.adminMaxSteps);
|
|
27
|
+
const maxSeconds = Number(options.adminMaxSeconds === "" || options.adminMaxSeconds == null ? 900 : options.adminMaxSeconds);
|
|
28
|
+
if (!Number.isInteger(maxSteps) || maxSteps < 1 || maxSteps > 1000 ||
|
|
29
|
+
!Number.isInteger(maxSeconds) || maxSeconds < 1 || maxSeconds > 14400)
|
|
30
|
+
invalid("Use --max-steps 1–1000 and --max-seconds 1–14400.");
|
|
31
|
+
const file = path.join(dir, "gather-checkpoint.json");
|
|
32
|
+
let checkpoint = { protocol: 1, collectionId: randomUUID(), startedAt: new Date(now()).toISOString(), costDate, entries: {} };
|
|
33
|
+
if (options.adminResume) {
|
|
34
|
+
if (!fs.existsSync(file)) invalid("No gather checkpoint exists in this directory; run without --resume first.");
|
|
35
|
+
checkpoint = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
36
|
+
if (checkpoint.protocol !== 1 || checkpoint.costDate !== costDate || !checkpoint.entries || typeof checkpoint.entries !== "object")
|
|
37
|
+
invalid("The checkpoint belongs to a different cost day or format. Use its --cost-date or start a new directory.");
|
|
38
|
+
}
|
|
39
|
+
const save = () => {
|
|
40
|
+
const temporary = `${file}.${process.pid}.next`;
|
|
41
|
+
fs.writeFileSync(temporary, JSON.stringify(checkpoint, null, 2) + "\n", { mode: 0o600 });
|
|
42
|
+
fs.renameSync(temporary, file);
|
|
43
|
+
};
|
|
44
|
+
save();
|
|
45
|
+
let executed = 0;
|
|
46
|
+
return {
|
|
47
|
+
costDate, phase, collectionId: checkpoint.collectionId, startedAt: checkpoint.startedAt,
|
|
48
|
+
warnings: options.adminDate ? ["--date is a legacy alias for --cost-date (a completed UTC day), not the management run date."] : [],
|
|
49
|
+
invalidate(name) {
|
|
50
|
+
if (checkpoint.entries[name]) { checkpoint.entries[name].ok = false; save(); }
|
|
51
|
+
},
|
|
52
|
+
collect(name, args, outputName, stage, run) {
|
|
53
|
+
const outputFile = path.join(dir, outputName);
|
|
54
|
+
const key = sha(JSON.stringify(args));
|
|
55
|
+
const cached = checkpoint.entries[name];
|
|
56
|
+
if (options.adminResume && cached?.ok && cached.requestHash === key && cached.file === outputName) {
|
|
57
|
+
try {
|
|
58
|
+
const bytes = fs.readFileSync(outputFile);
|
|
59
|
+
const json = JSON.parse(bytes);
|
|
60
|
+
if (sha(bytes) === cached.sha256 && json.ok === true && typeof json.status === "string")
|
|
61
|
+
return { ok: true, json, attempts: 0, durationMs: 0, capturedAt: cached.capturedAt, reused: true };
|
|
62
|
+
} catch { /* Missing/changed bytes require a fresh read, never a cached success. */ }
|
|
63
|
+
}
|
|
64
|
+
const remainingMs = maxSeconds * 1000 - (clock() - started);
|
|
65
|
+
const reason = phase !== "all" && phase !== stage ? `outside-${phase}-phase`
|
|
66
|
+
: executed >= maxSteps ? "step-budget" : remainingMs <= 0 ? "time-budget" : null;
|
|
67
|
+
if (reason) return { ok: false, deferred: true, reason, attempts: 0, durationMs: 0 };
|
|
68
|
+
executed++;
|
|
69
|
+
let result;
|
|
70
|
+
try { result = run(args, { outputFile, retryRead: true, timeoutMs: Math.max(1, Math.min(120000, Math.floor(remainingMs))) }); }
|
|
71
|
+
catch (error) { result = { ok: false, attempts: 1, json: { error: { message: String(error.message || error) } } }; }
|
|
72
|
+
const capturedAt = new Date(now()).toISOString();
|
|
73
|
+
let digest;
|
|
74
|
+
if (result.ok) {
|
|
75
|
+
try {
|
|
76
|
+
const bytes = fs.readFileSync(outputFile);
|
|
77
|
+
if (JSON.stringify(JSON.parse(bytes)) !== JSON.stringify(result.json)) throw new Error("receipt mismatch");
|
|
78
|
+
digest = sha(bytes);
|
|
79
|
+
} catch {
|
|
80
|
+
result = { ...result, ok: false, json: { error: { code: "CLI_DAILY_RECEIPT_INVALID", message: "Successful collection did not save its matching receipt." } } };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
checkpoint.entries[name] = { ok: result.ok, requestHash: key, file: outputName, capturedAt, ...(digest ? { sha256: digest } : {}) };
|
|
84
|
+
save();
|
|
85
|
+
return { ...result, capturedAt, reused: false };
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
package/lib/admin-daily.js
CHANGED
|
@@ -8,6 +8,7 @@ import { adminWorkDirectory } from "./admin-work-directory.js";
|
|
|
8
8
|
import { readAdminJsonFile, writeAdminJsonFile } from "./admin-news.js";
|
|
9
9
|
import { createDailyReviewState, dailyRunProgress } from "./admin-duties.js";
|
|
10
10
|
import { summarizeOwnerViewport, formatOwnerViewportReport } from "./admin-owner-viewport.js";
|
|
11
|
+
import { createDailyCollection } from "./admin-daily-collection.js";
|
|
11
12
|
export { dailyRunProgress };
|
|
12
13
|
|
|
13
14
|
// Multi-step conveniences for the full daily management run. Each one runs
|
|
@@ -22,7 +23,7 @@ const MARKED_ASSET = fileURLToPath(
|
|
|
22
23
|
|
|
23
24
|
export function runAdmin(
|
|
24
25
|
args,
|
|
25
|
-
{ outputFile, extraEnv, retryRead = false, spawnImpl = spawnSync } = {},
|
|
26
|
+
{ outputFile, extraEnv, retryRead = false, spawnImpl = spawnSync, timeoutMs } = {},
|
|
26
27
|
) {
|
|
27
28
|
const startedAt = performance.now();
|
|
28
29
|
const attemptFile = outputFile
|
|
@@ -33,6 +34,7 @@ export function runAdmin(
|
|
|
33
34
|
const result = spawnImpl(process.execPath, [LUMINE_BIN, ...full], {
|
|
34
35
|
encoding: "utf8",
|
|
35
36
|
maxBuffer: 256 * 1024 * 1024,
|
|
37
|
+
...(timeoutMs ? { timeout: timeoutMs } : {}),
|
|
36
38
|
env: { ...process.env, ...(extraEnv || {}) },
|
|
37
39
|
});
|
|
38
40
|
let parsed = null;
|
|
@@ -84,6 +86,9 @@ export function runAdmin(
|
|
|
84
86
|
sponsor: ["applications"],
|
|
85
87
|
"bot-output": [null],
|
|
86
88
|
"math-lab": ["queue"],
|
|
89
|
+
arena: ["queue", "today-queue"],
|
|
90
|
+
"word-games": ["status"],
|
|
91
|
+
nonogram: ["status"],
|
|
87
92
|
subjects: ["candidates"],
|
|
88
93
|
subject: ["get"],
|
|
89
94
|
};
|
|
@@ -98,8 +103,9 @@ export function runAdmin(
|
|
|
98
103
|
result.error ||
|
|
99
104
|
result.signal ||
|
|
100
105
|
parsed?.error?.code === "lumine_http_invalid_response";
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
const remainingMs = timeoutMs == null ? undefined : Math.floor(timeoutMs - (performance.now() - startedAt));
|
|
107
|
+
if (!ok && retryRead && readOnly && transient && (remainingMs == null || remainingMs > 0)) {
|
|
108
|
+
const retried = runAdmin(args, { outputFile, extraEnv, spawnImpl, timeoutMs: remainingMs });
|
|
103
109
|
return {
|
|
104
110
|
...retried,
|
|
105
111
|
durationMs: Math.round(performance.now() - startedAt),
|
|
@@ -166,16 +172,21 @@ export function renderBotOutput({ chatMessages, comments }, file) {
|
|
|
166
172
|
fs.writeFileSync(file, lines.join("\n") + "\n", { mode: 0o600 });
|
|
167
173
|
}
|
|
168
174
|
|
|
169
|
-
export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
175
|
+
export async function dailyRunGather(options, { run = runAdmin, now, clock } = {}) {
|
|
170
176
|
const startedAt = performance.now();
|
|
171
177
|
const dir = ensureDir(
|
|
172
178
|
path.resolve(options.adminOutputDir || options.adminDir || "."),
|
|
173
179
|
);
|
|
174
|
-
const
|
|
180
|
+
const collection = createDailyCollection(dir, options, { now, clock });
|
|
181
|
+
const yesterday = collection.costDate;
|
|
175
182
|
const steps = [];
|
|
183
|
+
const failLast = (error) => {
|
|
184
|
+
collection.invalidate(steps.at(-1).name);
|
|
185
|
+
Object.assign(steps.at(-1), { ok: false, error });
|
|
186
|
+
};
|
|
187
|
+
const evidenceNames = /^(ai-costs|media-costs|reward-activity|reward-diagnostics|owner-wake|bot-output)/;
|
|
176
188
|
const step = (name, args, file) => {
|
|
177
|
-
const
|
|
178
|
-
const res = run(args, { outputFile, retryRead: true });
|
|
189
|
+
const res = collection.collect(name, args, file, evidenceNames.test(name) ? "evidence" : "priority", run);
|
|
179
190
|
steps.push({
|
|
180
191
|
name,
|
|
181
192
|
file,
|
|
@@ -183,14 +194,49 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
183
194
|
attempts: res.attempts,
|
|
184
195
|
durationMs: res.durationMs,
|
|
185
196
|
recovered: Boolean(res.recovered),
|
|
186
|
-
|
|
197
|
+
capturedAt: res.capturedAt || null,
|
|
198
|
+
reused: Boolean(res.reused),
|
|
199
|
+
...(res.deferred ? { deferred: true, reason: res.reason } : res.ok ? {} : { error: res.json?.error || res.stderr }),
|
|
187
200
|
});
|
|
188
201
|
return res;
|
|
189
202
|
};
|
|
190
203
|
step("energy-budget", ["energy-budget"], "energy-budget.json");
|
|
191
204
|
step("news", ["news"], "news.json");
|
|
192
205
|
step("brief", ["brief"], "brief.json");
|
|
206
|
+
step("todos", ["todo", "list"], "todos.json");
|
|
207
|
+
step("escalations", ["escalation", "list"], "escalations.json");
|
|
193
208
|
step("math-lab-queue", ["math-lab", "queue"], "math-lab-queue.json");
|
|
209
|
+
step("arena-queue", ["arena", "queue"], "arena-queue.json");
|
|
210
|
+
step("word-games-status", ["word-games", "status"], "word-games-status.json");
|
|
211
|
+
step("nonogram-status", ["nonogram", "status"], "nonogram-status.json");
|
|
212
|
+
const personalQueue = { complete: false, pages: 0, members: 0 };
|
|
213
|
+
let personalCursor = 0;
|
|
214
|
+
const personalCursors = new Set();
|
|
215
|
+
for (;;) {
|
|
216
|
+
personalQueue.pages++;
|
|
217
|
+
const res = step(`arena-today-page-${personalQueue.pages}`,
|
|
218
|
+
["arena", "today-queue", "--cursor", String(personalCursor), "--limit", "100"],
|
|
219
|
+
`arena-today-queue-${personalQueue.pages}.json`);
|
|
220
|
+
if (!res.ok) break;
|
|
221
|
+
const data = res.json?.data;
|
|
222
|
+
const rawNext = data?.nextCursor;
|
|
223
|
+
const next = Number(rawNext || 0);
|
|
224
|
+
if (!Array.isArray(data?.members) || !Object.hasOwn(data, "nextCursor") ||
|
|
225
|
+
!(rawNext === null || (typeof rawNext === "number" && Number.isSafeInteger(rawNext)) || (typeof rawNext === "string" && /^\d+$/.test(rawNext))) ||
|
|
226
|
+
!Number.isSafeInteger(next) || next < 0) {
|
|
227
|
+
failLast("Invalid Arena personal queue page.");
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
personalQueue.members += data.members.length;
|
|
231
|
+
if (!next) { personalQueue.complete = true; break; }
|
|
232
|
+
if (personalCursors.has(next) || personalQueue.pages >= 100) {
|
|
233
|
+
collection.invalidate(steps.at(-1).name);
|
|
234
|
+
steps.push({ name: "arena-today-pagination", ok: false, error: "Arena personal queue repeated a cursor or reached its 100-page bound." });
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
personalCursors.add(next);
|
|
238
|
+
personalCursor = next;
|
|
239
|
+
}
|
|
194
240
|
step("ai-costs-monthly", ["ai-costs", "monthly"], "ai-monthly.json");
|
|
195
241
|
step("ai-costs-day", ["ai-costs", "day", yesterday], "ai-day.json");
|
|
196
242
|
step("media-costs", ["media-costs", "monthly"], "media.json");
|
|
@@ -222,10 +268,7 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
222
268
|
!(data.nextCursor === null ||
|
|
223
269
|
(typeof data.nextCursor === "string" && /^\d+(\.\d\.\d+)?$/.test(data.nextCursor)))
|
|
224
270
|
) {
|
|
225
|
-
|
|
226
|
-
ok: false,
|
|
227
|
-
error: "Invalid pending Build review page: expected filter, items and nextCursor.",
|
|
228
|
-
});
|
|
271
|
+
failLast("Invalid pending Build review page: expected filter, items and nextCursor.");
|
|
229
272
|
break;
|
|
230
273
|
}
|
|
231
274
|
buildReviews.items += data.items.length;
|
|
@@ -234,6 +277,7 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
234
277
|
break;
|
|
235
278
|
}
|
|
236
279
|
if (reviewCursors.has(data.nextCursor) || buildReviews.pages >= 100) {
|
|
280
|
+
collection.invalidate(steps.at(-1).name);
|
|
237
281
|
steps.push({
|
|
238
282
|
name: "build-reviews-pagination",
|
|
239
283
|
ok: false,
|
|
@@ -249,9 +293,7 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
249
293
|
// management approvals (birthdates, teacher signups, meetups) with their
|
|
250
294
|
// flags: a required report section (agent-guides/lumine-admin.md)
|
|
251
295
|
step("approvals-pending", ["approvals", "list", "--status", "pending"], "approvals-pending.json");
|
|
252
|
-
step("todos", ["todo", "list"], "todos.json");
|
|
253
296
|
step("personal-followups", ["followup", "list"], "personal-followups.json");
|
|
254
|
-
step("escalations", ["escalation", "list"], "escalations.json");
|
|
255
297
|
const wake = step("owner-wake", ["owner-trace", "--since", "24h", "--wake", "--limit", "2000"], "owner-wake.json");
|
|
256
298
|
// A phone tab can live longer than the daily window. Retain its version,
|
|
257
299
|
// browser and device context instead of silently calling it unknown.
|
|
@@ -284,6 +326,7 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
284
326
|
let cursor = "";
|
|
285
327
|
let pages = 0;
|
|
286
328
|
let botOk = true;
|
|
329
|
+
const botCursors = new Set();
|
|
287
330
|
for (;;) {
|
|
288
331
|
pages += 1;
|
|
289
332
|
const file = `bot-output-${pages}.json`;
|
|
@@ -297,12 +340,18 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
297
340
|
break;
|
|
298
341
|
}
|
|
299
342
|
const data = res.json?.data || {};
|
|
343
|
+
if (!Array.isArray(data.chatMessages) || !Array.isArray(data.comments) || typeof data.pagination?.exhausted !== "boolean") {
|
|
344
|
+
botOk = false;
|
|
345
|
+
failLast("Invalid bot-output page: rows and explicit exhaustion are required.");
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
300
348
|
chatMessages.push(...(data.chatMessages || []));
|
|
301
349
|
comments.push(...(data.comments || []));
|
|
302
350
|
const p = data.pagination || {};
|
|
303
351
|
if (p.exhausted && !data.chatTruncated && !data.commentsTruncated) break;
|
|
304
352
|
cursor = p.nextCursor || "";
|
|
305
|
-
if (!cursor || pages
|
|
353
|
+
if (!cursor || botCursors.has(cursor) || pages >= 50) {
|
|
354
|
+
collection.invalidate(steps.at(-1).name);
|
|
306
355
|
botOk = false;
|
|
307
356
|
steps.push({
|
|
308
357
|
name: "bot-output",
|
|
@@ -311,6 +360,7 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
311
360
|
});
|
|
312
361
|
break;
|
|
313
362
|
}
|
|
363
|
+
botCursors.add(cursor);
|
|
314
364
|
}
|
|
315
365
|
if (botOk) renderBotOutput({ chatMessages, comments }, path.join(dir, "bot-chats.txt"));
|
|
316
366
|
|
|
@@ -319,6 +369,9 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
319
369
|
durationMs: Math.round(performance.now() - startedAt),
|
|
320
370
|
dir,
|
|
321
371
|
yesterdayUtc: yesterday,
|
|
372
|
+
costDateUtc: yesterday,
|
|
373
|
+
collection: { id: collection.collectionId, startedAt: collection.startedAt, phase: collection.phase, warnings: collection.warnings },
|
|
374
|
+
personalQueue,
|
|
322
375
|
buildReviews,
|
|
323
376
|
ownerWake: {
|
|
324
377
|
coverage: ownerWake.coverage,
|
|
@@ -334,7 +387,8 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
334
387
|
readFile: botOk ? path.join(dir, "bot-chats.txt") : null,
|
|
335
388
|
},
|
|
336
389
|
steps,
|
|
337
|
-
failed: steps.filter((s) => !s.ok).map((s) => s.name),
|
|
390
|
+
failed: steps.filter((s) => !s.ok && !s.deferred).map((s) => s.name),
|
|
391
|
+
deferred: steps.filter((s) => s.deferred).map((s) => ({ name: s.name, reason: s.reason })),
|
|
338
392
|
};
|
|
339
393
|
fs.writeFileSync(
|
|
340
394
|
path.join(dir, "gather.json"),
|
|
@@ -351,8 +405,8 @@ export async function dailyRunGather(options, { run = runAdmin } = {}) {
|
|
|
351
405
|
{ privateFile: true },
|
|
352
406
|
);
|
|
353
407
|
return {
|
|
354
|
-
ok: summary.failed.length === 0,
|
|
355
|
-
status: summary.failed.length ? "partial_failure" : "success",
|
|
408
|
+
ok: summary.failed.length === 0 && summary.deferred.length === 0,
|
|
409
|
+
status: summary.failed.length ? "partial_failure" : summary.deferred.length ? "deferred" : "success",
|
|
356
410
|
data: summary,
|
|
357
411
|
};
|
|
358
412
|
}
|
|
@@ -450,10 +504,37 @@ nav{position:fixed;top:0;left:0;bottom:0;width:250px;overflow:auto;background:va
|
|
|
450
504
|
nav h4{margin:0 0 10px;font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}nav a{display:block;padding:4px 6px;border-radius:6px;color:var(--fg);text-decoration:none;font-size:13px}nav a:hover{background:var(--line)}
|
|
451
505
|
main{margin-left:250px;padding:28px 40px;max-width:1100px}a{color:var(--link)}table{border-collapse:collapse;margin:10px 0;display:block;overflow-x:auto}th,td{border:1px solid var(--line);padding:6px 9px;text-align:left;vertical-align:top}
|
|
452
506
|
code{background:var(--code);padding:1px 5px;border-radius:4px;font-size:.9em}pre{background:var(--code);padding:12px;border-radius:8px;overflow:auto}h2{border-bottom:1px solid var(--line);padding-bottom:4px;margin-top:34px}
|
|
507
|
+
.relay-actions{border:1px solid var(--line);border-radius:12px;padding:16px;margin:20px 0;background:var(--nav)}
|
|
508
|
+
.relay-actions h3{margin:0 0 8px}.relay-actions p{margin:8px 0}.relay-buttons{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,230px),1fr));gap:8px}
|
|
509
|
+
.relay-actions button{font:inherit;font-size:1rem;text-align:left;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--bg);color:var(--fg);cursor:pointer;overflow-wrap:anywhere}
|
|
510
|
+
.relay-actions button:hover{border-color:var(--link)}.relay-actions button:focus-visible{outline:2px solid var(--link);outline-offset:2px}.relay-actions button:disabled{cursor:wait;opacity:.65}
|
|
511
|
+
.relay-actions .relay-master{grid-column:1/-1;font-weight:650;border-color:var(--link);color:var(--link)}.relay-actions [role=status]{min-height:1.55em;color:var(--muted);font-size:1rem}
|
|
512
|
+
.relay-prompt-source{width:100%;min-height:180px;padding:12px;font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:8px}
|
|
453
513
|
@media (max-width:760px){nav{position:static;width:auto;height:auto;border-right:0;border-bottom:1px solid var(--line)}main{margin-left:0;padding:16px}}
|
|
454
514
|
</style></head><body><nav><h4>Sections</h4><div id="toc"></div><h4 style="margin-top:18px">Source</h4><a href="source.html">Markdown</a></nav><main id="content">Loading…</main>
|
|
455
515
|
<script src="marked.min.js"></script><script>
|
|
456
516
|
fetch('daily-management-report.md',{cache:'no-store'}).then(r=>{if(!r.ok)throw new Error('Report source unavailable');return r.text()}).then(md=>{const el=document.getElementById('content');el.innerHTML=marked.parse(md);const toc=document.getElementById('toc');let i=0;el.querySelectorAll('h1,h2').forEach(h=>{h.id='s'+(i++);if(h.tagName==='H2'){const a=document.createElement('a');a.href='#'+h.id;a.textContent=h.textContent;toc.appendChild(a)}})}).catch(()=>{document.getElementById('content').textContent='The report could not load. Open Markdown under Source, or reload this page.'});
|
|
517
|
+
document.getElementById('content').addEventListener('click',async event=>{
|
|
518
|
+
const button=event.target.closest('button[data-copy-prompt]');
|
|
519
|
+
if(!button||button.disabled)return;
|
|
520
|
+
const group=button.closest('.relay-actions');
|
|
521
|
+
const source=document.getElementById(button.dataset.copyPrompt);
|
|
522
|
+
const status=group?.querySelector('[role=status]');
|
|
523
|
+
if(!group||!status||!source?.matches('textarea.relay-prompt-source')||!group.contains(source))return;
|
|
524
|
+
const buttons=Array.from(group.querySelectorAll('button[data-copy-prompt]'));
|
|
525
|
+
buttons.forEach(b=>{b.disabled=true});
|
|
526
|
+
try{
|
|
527
|
+
await navigator.clipboard.writeText(source.value);
|
|
528
|
+
status.textContent=button.textContent.replace(/^Copy: /,'')+' copied. Paste it into the agent chat you choose.';
|
|
529
|
+
}catch{
|
|
530
|
+
source.hidden=false;
|
|
531
|
+
source.focus();
|
|
532
|
+
source.select();
|
|
533
|
+
status.textContent='Automatic copy is unavailable. The prompt is selected below; copy it with your keyboard.';
|
|
534
|
+
}finally{
|
|
535
|
+
buttons.forEach(b=>{b.disabled=false});
|
|
536
|
+
}
|
|
537
|
+
});
|
|
457
538
|
</script></body></html>`;
|
|
458
539
|
}
|
|
459
540
|
|
package/lib/admin-duties.js
CHANGED
|
@@ -28,6 +28,8 @@ export function createDailyReviewState(dir, gather, previous = {}) {
|
|
|
28
28
|
.map((step) => ({
|
|
29
29
|
...(step.ok ? fingerprint(dir, step.file) : { file: step.file }),
|
|
30
30
|
fetched: step.ok,
|
|
31
|
+
...(step.deferred ? { deferred: true, reason: step.reason } : {}),
|
|
32
|
+
...(step.capturedAt ? { capturedAt: step.capturedAt, reused: Boolean(step.reused) } : {}),
|
|
31
33
|
read: false,
|
|
32
34
|
}));
|
|
33
35
|
if (gather.botOutput.complete)
|
|
@@ -42,6 +44,10 @@ export function createDailyReviewState(dir, gather, previous = {}) {
|
|
|
42
44
|
if (gather.buildReviews && (
|
|
43
45
|
source.file === "rr-pending.json" || /^build-reviews-\d+\.json$/.test(source.file)
|
|
44
46
|
)) continue;
|
|
47
|
+
if (gather.personalQueue && /^arena-today-queue-\d+\.json$/.test(source.file)) continue;
|
|
48
|
+
if (gather.botOutput && (
|
|
49
|
+
source.file === "bot-chats.txt" || /^bot-output-\d+\.json$/.test(source.file)
|
|
50
|
+
)) continue;
|
|
45
51
|
if (!sources.some((current) => current.file === source.file)) sources.push({ ...source, retained: true, read: false });
|
|
46
52
|
}
|
|
47
53
|
return {
|
|
@@ -49,6 +55,7 @@ export function createDailyReviewState(dir, gather, previous = {}) {
|
|
|
49
55
|
scope: previous.scope || "collection",
|
|
50
56
|
sources,
|
|
51
57
|
collectionFailures: gather.failed || [],
|
|
58
|
+
collectionDeferred: gather.deferred || [],
|
|
52
59
|
requiredDuties: [...new Set(["effort-levels", "report-browser", ...(previous.requiredDuties || [])])],
|
|
53
60
|
note: "Collection is not a full review. Add every duty required by the run scope and its carryovers. Reading and browser checks require explicit evidence acknowledgments.",
|
|
54
61
|
};
|
|
@@ -157,8 +164,9 @@ export function summarizeDailyProgress(dir, state, evidence = {}) {
|
|
|
157
164
|
.filter((item) => item.status !== "completed")
|
|
158
165
|
.map((item) => item.id);
|
|
159
166
|
const collectionFailures = state.collectionFailures || [];
|
|
167
|
+
const collectionDeferred = state.collectionDeferred || [];
|
|
160
168
|
const complete =
|
|
161
|
-
collectionFailures.length === 0 && sources.every((item) => item.read) && incomplete.length === 0;
|
|
169
|
+
collectionFailures.length === 0 && collectionDeferred.length === 0 && sources.every((item) => item.read) && incomplete.length === 0;
|
|
162
170
|
return {
|
|
163
171
|
scope: state.scope || "declared-duties",
|
|
164
172
|
coverageBasis: "declared sources and duties only",
|
|
@@ -166,6 +174,7 @@ export function summarizeDailyProgress(dir, state, evidence = {}) {
|
|
|
166
174
|
effortAssignments,
|
|
167
175
|
sources,
|
|
168
176
|
collectionFailures,
|
|
177
|
+
collectionDeferred,
|
|
169
178
|
duties,
|
|
170
179
|
complete,
|
|
171
180
|
reading: {
|
package/lib/commands.js
CHANGED
|
@@ -2474,6 +2474,7 @@ export function parseArgs(args) {
|
|
|
2474
2474
|
const booleanFlags = new Set([
|
|
2475
2475
|
// lumine admin arena import --live
|
|
2476
2476
|
"live",
|
|
2477
|
+
"additive",
|
|
2477
2478
|
"remove",
|
|
2478
2479
|
"fault",
|
|
2479
2480
|
"noAnnounce",
|
|
@@ -2701,6 +2702,7 @@ export function parseArgs(args) {
|
|
|
2701
2702
|
// lumine admin arena (admin-arena.js)
|
|
2702
2703
|
adminArenaAudio: raw.audio ? String(raw.audio) : "",
|
|
2703
2704
|
adminArenaLive: parseBoolean(raw.live, false),
|
|
2705
|
+
adminArenaAdditive: parseBoolean(raw.additive, false),
|
|
2704
2706
|
adminArenaItem: raw.item ? String(raw.item) : "",
|
|
2705
2707
|
adminArenaTo: raw.to ? String(raw.to) : "",
|
|
2706
2708
|
adminClaimFile: raw.claim ? String(raw.claim) : "",
|
|
@@ -2851,6 +2853,10 @@ export function parseArgs(args) {
|
|
|
2851
2853
|
adminTarget: raw.target ? String(raw.target) : "",
|
|
2852
2854
|
adminActions: raw.actions ? String(raw.actions) : "",
|
|
2853
2855
|
adminDate: raw.date ? String(raw.date) : "",
|
|
2856
|
+
adminCostDate: raw.costDate ? String(raw.costDate) : "",
|
|
2857
|
+
adminPhase: raw.phase ? String(raw.phase) : "",
|
|
2858
|
+
adminMaxSteps: raw.maxSteps ?? "",
|
|
2859
|
+
adminMaxSeconds: raw.maxSeconds ?? "",
|
|
2854
2860
|
adminDays: raw.days ? String(raw.days) : "",
|
|
2855
2861
|
adminSpendingMonth: raw.month,
|
|
2856
2862
|
adminSpendingFrom: raw.from,
|
|
@@ -3339,7 +3345,7 @@ export function printHelp() {
|
|
|
3339
3345
|
lumine admin reward-review reject|revoke <review-id> --reason <text> [--json]
|
|
3340
3346
|
lumine admin recommendations list [--since-run|--after <date>|--include-legacy] [--all --checkpoint <file> [--resume]] [--content-types comment,dailyReflection] [--unviewed|--viewed] [--cursor <cursor>] [--json]
|
|
3341
3347
|
lumine admin builds candidates [--since-run|--after <date>|--include-legacy] [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--limit <number>] [--json]
|
|
3342
|
-
lumine admin daily-run gather --output-dir <dir> [--date YYYY-MM-DD] [--json] (
|
|
3348
|
+
lumine admin daily-run gather --output-dir <dir> [--cost-date YYYY-MM-DD] [--phase priority|evidence|all] [--max-steps 200] [--max-seconds 900] [--resume] [--json] (closed UTC cost day; resumable collection, never automatic review)
|
|
3343
3349
|
lumine admin daily-run progress --dir <dir> [--file <duty-evidence.json>] [--json] (reading, canonical effort counts, pending duties)
|
|
3344
3350
|
lumine admin featured candidates [--days 7] [--after <iso>] [--json] (recent, provably never-Featured Subjects with comment counts)
|
|
3345
3351
|
lumine admin report serve --file <report.md> [--port <n>] [--open] [--json] (loopback report page with section nav)
|