@stage5/lumine 0.2.99 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/admin.js +182 -3
- package/lib/commands.js +4 -0
- package/package.json +1 -1
- package/sdk/LUMINE_ADMIN.md +68 -0
package/lib/admin.js
CHANGED
|
@@ -640,6 +640,8 @@ export async function adminCommand(options) {
|
|
|
640
640
|
let runScope = null;
|
|
641
641
|
let correctionSessionId = 0;
|
|
642
642
|
let correctionSession = null;
|
|
643
|
+
let commentSessionId = 0;
|
|
644
|
+
let commentSession = null;
|
|
643
645
|
if (adminOperationRequiresRun(operation)) {
|
|
644
646
|
if (operation.correctionEligible) {
|
|
645
647
|
const correctionStatus = await requestJson({
|
|
@@ -657,6 +659,15 @@ export async function adminCommand(options) {
|
|
|
657
659
|
correctionSession = correction;
|
|
658
660
|
}
|
|
659
661
|
}
|
|
662
|
+
if (!correctionSessionId && adminOperationUsesCommentSession(operation)) {
|
|
663
|
+
commentSession = await loadActiveCommentSession({
|
|
664
|
+
options,
|
|
665
|
+
authToken: auth.token,
|
|
666
|
+
});
|
|
667
|
+
commentSessionId = Number(commentSession?.id || 0);
|
|
668
|
+
}
|
|
669
|
+
// Look up the daily run too: when both are active the caller must choose
|
|
670
|
+
// one (--via), and exactly one authority is sent.
|
|
660
671
|
const runStatus = correctionSessionId
|
|
661
672
|
? null
|
|
662
673
|
: await requestJson({
|
|
@@ -671,9 +682,39 @@ export async function adminCommand(options) {
|
|
|
671
682
|
].includes(operation.name)
|
|
672
683
|
? runStatus?.data?.lastRun || null
|
|
673
684
|
: null;
|
|
674
|
-
|
|
685
|
+
let selectedRun = activeRun || retryableFinishedRun;
|
|
686
|
+
const via = parseAdminVia(options.adminVia, operation);
|
|
687
|
+
if (via && correctionSessionId) {
|
|
688
|
+
throw cliValidationError(
|
|
689
|
+
`Correction session #${correctionSessionId} is active for this comment and handles it; drop --via or complete the correction first.`,
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
if (commentSessionId && selectedRun) {
|
|
693
|
+
let choice = via;
|
|
694
|
+
if (!choice) {
|
|
695
|
+
if (COMMENT_SESSION_WRITE_OPERATIONS.has(operation.name)) {
|
|
696
|
+
throw cliValidationError(
|
|
697
|
+
`Both comment session #${commentSessionId} (${commentSession.identity?.key || "unknown"}) and daily run #${selectedRun.id} (${selectedRun.identity?.key || "unknown"}) are active. Choose which one this ${operation.name} uses with --via session or --via run.`,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
// Reads create nothing; without --via they keep using the daily run.
|
|
701
|
+
choice = "run";
|
|
702
|
+
}
|
|
703
|
+
if (choice === "session") {
|
|
704
|
+
selectedRun = null;
|
|
705
|
+
} else {
|
|
706
|
+
commentSessionId = 0;
|
|
707
|
+
commentSession = null;
|
|
708
|
+
}
|
|
709
|
+
} else if (via === "session" && !commentSessionId) {
|
|
710
|
+
throw cliValidationError(
|
|
711
|
+
"--via session: no comment session is active. Start one with lumine admin comment session start.",
|
|
712
|
+
);
|
|
713
|
+
} else if (via === "run" && !selectedRun && !correctionSessionId) {
|
|
714
|
+
throw noActiveRunError();
|
|
715
|
+
}
|
|
675
716
|
runId = Number(selectedRun?.id || 0);
|
|
676
|
-
if (!runId && !correctionSessionId) {
|
|
717
|
+
if (!runId && !correctionSessionId && !commentSessionId) {
|
|
677
718
|
if (operation.correctionEligible) {
|
|
678
719
|
const error = new Error(
|
|
679
720
|
`Start a correction session first: lumine admin correction start ${operation.correctionCommentId}.`,
|
|
@@ -687,7 +728,17 @@ export async function adminCommand(options) {
|
|
|
687
728
|
runScope = canonicalAdminRunScope(selectedRun);
|
|
688
729
|
assertAdminOperationAllowedForRunScope({ operation, runScope });
|
|
689
730
|
}
|
|
690
|
-
if (options.adminIdentity &&
|
|
731
|
+
if (options.adminIdentity && commentSession) {
|
|
732
|
+
const requestedIdentity = parseIdentity(options.adminIdentity);
|
|
733
|
+
if (
|
|
734
|
+
requestedIdentity !== "auto" &&
|
|
735
|
+
requestedIdentity !== commentSession.identity?.key
|
|
736
|
+
) {
|
|
737
|
+
throw cliValidationError(
|
|
738
|
+
`--identity ${requestedIdentity} does not match comment session #${commentSessionId} (${commentSession.identity?.key || "unknown"}).`,
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
} else if (options.adminIdentity && (selectedRun || correctionSession)) {
|
|
691
742
|
const requestedIdentity = parseIdentity(options.adminIdentity);
|
|
692
743
|
const canonicalIdentity = correctionSession
|
|
693
744
|
? correctionSession.identity?.key
|
|
@@ -722,6 +773,11 @@ export async function adminCommand(options) {
|
|
|
722
773
|
String(correctionSessionId),
|
|
723
774
|
}
|
|
724
775
|
: {}),
|
|
776
|
+
...(commentSessionId
|
|
777
|
+
? {
|
|
778
|
+
"x-lumine-admin-comment-session-id": String(commentSessionId),
|
|
779
|
+
}
|
|
780
|
+
: {}),
|
|
725
781
|
...(requestId ? { "x-lumine-idempotency-key": requestId } : {}),
|
|
726
782
|
},
|
|
727
783
|
// Long operations (reward-bank seed) carry their own timeout.
|
|
@@ -1529,6 +1585,81 @@ export function assertAdminOperationAllowedForRunScope({
|
|
|
1529
1585
|
);
|
|
1530
1586
|
}
|
|
1531
1587
|
|
|
1588
|
+
// Commands a standalone comment session can serve: drafting, replying,
|
|
1589
|
+
// publishing and editing its own comments, plus the reads needed to compose
|
|
1590
|
+
// on its targets. The API still enforces the session's target lock on each.
|
|
1591
|
+
const COMMENT_SESSION_WRITE_OPERATIONS = new Set([
|
|
1592
|
+
"comment.draft",
|
|
1593
|
+
"comment.post",
|
|
1594
|
+
"comment.edit",
|
|
1595
|
+
]);
|
|
1596
|
+
const COMMENT_SESSION_OPERATIONS = new Set([
|
|
1597
|
+
...COMMENT_SESSION_WRITE_OPERATIONS,
|
|
1598
|
+
"subject.get",
|
|
1599
|
+
"subject.comments",
|
|
1600
|
+
"post.get",
|
|
1601
|
+
"post.comments",
|
|
1602
|
+
]);
|
|
1603
|
+
|
|
1604
|
+
export function adminOperationUsesCommentSession(operation) {
|
|
1605
|
+
return COMMENT_SESSION_OPERATIONS.has(operation?.name);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
export function parseAdminVia(value, operation) {
|
|
1609
|
+
const via = String(value || "").trim().toLowerCase();
|
|
1610
|
+
if (!via) return "";
|
|
1611
|
+
if (via !== "session" && via !== "run") {
|
|
1612
|
+
throw cliValidationError("--via must be session or run.");
|
|
1613
|
+
}
|
|
1614
|
+
if (!adminOperationUsesCommentSession(operation)) {
|
|
1615
|
+
throw cliValidationError(
|
|
1616
|
+
"--via applies only to comment draft/reply/post/edit and the subject/post/comment reads.",
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
return via;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
async function loadActiveCommentSession({ options, authToken }) {
|
|
1623
|
+
let status;
|
|
1624
|
+
try {
|
|
1625
|
+
status = await requestJson({
|
|
1626
|
+
url: `${options.apiUrl}/cli/admin/comment-sessions/status`,
|
|
1627
|
+
authToken,
|
|
1628
|
+
timeoutMs: options.timeoutMs,
|
|
1629
|
+
});
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
// An API without comment sessions yet: keep daily runs working.
|
|
1632
|
+
if (error?.status === 404) return null;
|
|
1633
|
+
throw error;
|
|
1634
|
+
}
|
|
1635
|
+
const session = status?.data?.commentSession || null;
|
|
1636
|
+
return session?.status === "active" && Number(session.id) > 0
|
|
1637
|
+
? session
|
|
1638
|
+
: null;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
export function parseCommentSessionTargets(values) {
|
|
1642
|
+
const raw = values
|
|
1643
|
+
.flatMap((value) => String(value || "").split(","))
|
|
1644
|
+
.map((value) => value.trim())
|
|
1645
|
+
.filter(Boolean);
|
|
1646
|
+
if (!raw.length) {
|
|
1647
|
+
throw cliValidationError(
|
|
1648
|
+
"Name the targets with --target <subject:ID|comment:ID|build:ID|aiStory:ID|dailyReflection:ID>[,...].",
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
const seen = new Set();
|
|
1652
|
+
const targets = [];
|
|
1653
|
+
for (const value of raw) {
|
|
1654
|
+
const target = parseAdminCommentTarget({ target: value });
|
|
1655
|
+
const key = `${target.type}:${target.id}`;
|
|
1656
|
+
if (seen.has(key)) continue;
|
|
1657
|
+
seen.add(key);
|
|
1658
|
+
targets.push(target);
|
|
1659
|
+
}
|
|
1660
|
+
return targets;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1532
1663
|
function adminOperationRequiresRun(operation) {
|
|
1533
1664
|
if (typeof operation.requiresRun === "boolean") {
|
|
1534
1665
|
return operation.requiresRun;
|
|
@@ -1547,6 +1678,9 @@ function adminOperationRequiresRun(operation) {
|
|
|
1547
1678
|
"correction.start",
|
|
1548
1679
|
"correction.status",
|
|
1549
1680
|
"correction.complete",
|
|
1681
|
+
"comment-session.start",
|
|
1682
|
+
"comment-session.status",
|
|
1683
|
+
"comment-session.close",
|
|
1550
1684
|
"escalation.list",
|
|
1551
1685
|
"escalation.set",
|
|
1552
1686
|
"notable.add",
|
|
@@ -3361,6 +3495,51 @@ export function parseAdminOperation(options) {
|
|
|
3361
3495
|
}
|
|
3362
3496
|
|
|
3363
3497
|
if (namespace === "comment") {
|
|
3498
|
+
if (action === "session") {
|
|
3499
|
+
const sessionAction = target || "status";
|
|
3500
|
+
if (sessionAction === "status") {
|
|
3501
|
+
return readOperation(
|
|
3502
|
+
"comment-session.status",
|
|
3503
|
+
"/cli/admin/comment-sessions/status",
|
|
3504
|
+
);
|
|
3505
|
+
}
|
|
3506
|
+
if (sessionAction === "start") {
|
|
3507
|
+
const identity = String(options.adminIdentity || "")
|
|
3508
|
+
.trim()
|
|
3509
|
+
.toLowerCase();
|
|
3510
|
+
if (identity !== "zero" && identity !== "ciel") {
|
|
3511
|
+
throw cliValidationError(
|
|
3512
|
+
"comment session start needs --identity zero|ciel.",
|
|
3513
|
+
);
|
|
3514
|
+
}
|
|
3515
|
+
const targets = parseCommentSessionTargets([
|
|
3516
|
+
options.adminTarget,
|
|
3517
|
+
...(options.positional || []).slice(3),
|
|
3518
|
+
]);
|
|
3519
|
+
const reason = String(options.adminReason || "").trim();
|
|
3520
|
+
return writeOperation(
|
|
3521
|
+
"comment-session.start",
|
|
3522
|
+
"POST",
|
|
3523
|
+
"/cli/admin/comment-sessions",
|
|
3524
|
+
{
|
|
3525
|
+
identity,
|
|
3526
|
+
targets,
|
|
3527
|
+
...(reason ? { reason } : {}),
|
|
3528
|
+
},
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3531
|
+
if (sessionAction === "close") {
|
|
3532
|
+
return writeOperation(
|
|
3533
|
+
"comment-session.close",
|
|
3534
|
+
"POST",
|
|
3535
|
+
"/cli/admin/comment-sessions/close",
|
|
3536
|
+
{},
|
|
3537
|
+
);
|
|
3538
|
+
}
|
|
3539
|
+
throw cliValidationError(
|
|
3540
|
+
"Usage: lumine admin comment session start --identity zero|ciel --target <type:id>[,<type:id>...] [--reason <text>] | comment session status | comment session close.",
|
|
3541
|
+
);
|
|
3542
|
+
}
|
|
3364
3543
|
if (action === "draft" || action === "reply") {
|
|
3365
3544
|
const parsedTarget = parseAdminCommentTarget({
|
|
3366
3545
|
target,
|
package/lib/commands.js
CHANGED
|
@@ -2597,6 +2597,7 @@ export function parseArgs(args) {
|
|
|
2597
2597
|
adminUnviewed: Boolean(raw.unviewed),
|
|
2598
2598
|
adminViewed: Boolean(raw.viewed),
|
|
2599
2599
|
adminIdentity: raw.identity ? String(raw.identity) : "",
|
|
2600
|
+
adminVia: raw.via ? String(raw.via) : "",
|
|
2600
2601
|
adminScope: raw.scope ? String(raw.scope) : "",
|
|
2601
2602
|
commentMode: raw.commentMode ? String(raw.commentMode) : "",
|
|
2602
2603
|
runKey: raw.runKey ? String(raw.runKey) : "",
|
|
@@ -3062,6 +3063,9 @@ export function printHelp() {
|
|
|
3062
3063
|
lumine admin comment draft <target> [--type subject|comment|build|aiStory|dailyReflection] [--file <comment.md>] [--review-receipt <review.json>|--reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--identity zero|ciel|auto] [--json]
|
|
3063
3064
|
lumine admin comment reply comment:<id> [--file <reply.md>] [--reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--identity zero|ciel|auto] [--json]
|
|
3064
3065
|
lumine admin comment post --draft-id <id> [--json]
|
|
3066
|
+
lumine admin comment session start --identity zero|ciel --target <subject:ID|comment:ID|build:ID|aiStory:ID>[,...] [--reason <text>] [--json]
|
|
3067
|
+
lumine admin comment session status|close [--json]
|
|
3068
|
+
(with both a comment session and a daily run active, comment draft/reply/post/edit need --via session|run)
|
|
3065
3069
|
lumine admin comment edit <comment-id> --file <comment.md> [--review-receipt <review.json> | --reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--json]
|
|
3066
3070
|
lumine admin brief [--days <1..30>] [--json]
|
|
3067
3071
|
lumine admin ai-costs monthly [--output <file.json>] [--json] (no active run required)
|
package/package.json
CHANGED
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -4171,6 +4171,74 @@ canonical review record ID before claiming success. Older APIs that still block
|
|
|
4171
4171
|
Build edits must be deployed first; do not silently substitute a duplicate reply
|
|
4172
4172
|
when Mikey requested an edit.
|
|
4173
4173
|
|
|
4174
|
+
### Standalone comment sessions
|
|
4175
|
+
|
|
4176
|
+
Posting a few new comments or replies as Zero or Ciel does not require a full
|
|
4177
|
+
daily run. An approved operator names the identity and the exact targets:
|
|
4178
|
+
|
|
4179
|
+
```bash
|
|
4180
|
+
lumine admin comment session start --identity ciel \
|
|
4181
|
+
--target subject:123,comment:456 --reason "Mikey asked for a reply" --json
|
|
4182
|
+
lumine admin subject get 123 --include-comments --json
|
|
4183
|
+
lumine admin comment draft subject:123 --file comment.md --json
|
|
4184
|
+
lumine admin comment reply comment:456 --file reply.md --json
|
|
4185
|
+
lumine admin comment post --draft-id <id> --json
|
|
4186
|
+
lumine admin comment edit <commentId> --file corrected.md --json
|
|
4187
|
+
lumine admin comment session status --json
|
|
4188
|
+
lumine admin comment session close --json
|
|
4189
|
+
```
|
|
4190
|
+
|
|
4191
|
+
`--target` takes a comma-separated list (extra space-separated targets after
|
|
4192
|
+
it also count); each is `subject:ID`, `comment:ID`, `build:ID`, `aiStory:ID` or
|
|
4193
|
+
`dailyReflection:ID`, at most ten. Pass them in one `--target`: a repeated
|
|
4194
|
+
`--target` flag keeps only its last value. The server checks every target when
|
|
4195
|
+
the session starts and refuses one the draft path would refuse later.
|
|
4196
|
+
|
|
4197
|
+
What the session authorizes:
|
|
4198
|
+
|
|
4199
|
+
- **Scopes:** only `content:read`, `comment:draft` and `comment:post`. No chat,
|
|
4200
|
+
Featured, reward, recommendation, subject, newspaper or run-completion
|
|
4201
|
+
command works through it; those still need a daily run.
|
|
4202
|
+
- **Target lock:** drafts, replies and the reads above work only on the named
|
|
4203
|
+
targets. A reply target counts when it is a named `comment:ID`, or a comment
|
|
4204
|
+
sitting directly in a named subject, Build, AI Story or Daily Reflection.
|
|
4205
|
+
Anything else fails with `CLI_ADMIN_COMMENT_SESSION_TARGET_FORBIDDEN` and
|
|
4206
|
+
the allowed list. `comment post` accepts only drafts created in the session.
|
|
4207
|
+
- **Edits:** `comment edit <commentId>` works under the session only for a
|
|
4208
|
+
comment this session itself posted, as the same identity
|
|
4209
|
+
(`CLI_ADMIN_COMMENT_SESSION_EDIT_FORBIDDEN` otherwise). The usual edit rules
|
|
4210
|
+
apply unchanged, including fresh Build review evidence for a Build comment.
|
|
4211
|
+
Edits do not count toward the post cap.
|
|
4212
|
+
- **Limits:** 30 minutes, at most five published comments
|
|
4213
|
+
(`CLI_ADMIN_COMMENT_SESSION_POST_CAP`), one active session per operator.
|
|
4214
|
+
`close` or the TTL ends it; start a new session for more.
|
|
4215
|
+
- **Same bar as a daily run:** drafting and publishing use the exact daily-run
|
|
4216
|
+
draft/publish path. Build comments still require composed text, a review
|
|
4217
|
+
receipt or `--reviewed-version/--reviewed-via` evidence of the current
|
|
4218
|
+
published version, and `--review-context`; context and persona revisions
|
|
4219
|
+
are checked at publish time as usual.
|
|
4220
|
+
- **Identity:** the operator must be an approved delegated administrator, and
|
|
4221
|
+
the session posts only as the identity named at start (`--identity auto` is
|
|
4222
|
+
not accepted). It does not change the Bangkok calendar assignment or the
|
|
4223
|
+
identity preference.
|
|
4224
|
+
|
|
4225
|
+
A comment session and a daily run can be active together, but every request
|
|
4226
|
+
carries exactly one authority. The session never claims the daily-run slot.
|
|
4227
|
+
|
|
4228
|
+
- When only one of them is active, the CLI uses it.
|
|
4229
|
+
- When both are active, `comment draft`, `comment reply`, `comment post` and
|
|
4230
|
+
`comment edit` refuse to run until you choose with `--via session` or
|
|
4231
|
+
`--via run`. Target reads (`subject get`, `post get`, `comments get`, their
|
|
4232
|
+
comment lists) default to the daily run and take `--via session` to read
|
|
4233
|
+
through the session instead.
|
|
4234
|
+
- The API refuses a request that names a comment session together with a
|
|
4235
|
+
daily run or correction session (`CLI_ADMIN_AMBIGUOUS_AUTHORITY`); a session
|
|
4236
|
+
request outside the session is refused, never handed to the run.
|
|
4237
|
+
- A draft records the authority that created it, and only that authority can
|
|
4238
|
+
publish it (`CLI_ADMIN_DRAFT_AUTHORITY_MISMATCH`).
|
|
4239
|
+
- A correction session still takes precedence for its one comment; `--via` is
|
|
4240
|
+
refused while it is active for that comment.
|
|
4241
|
+
|
|
4174
4242
|
**Editing the bot's own comments.** `comment edit <commentId> --file
|
|
4175
4243
|
<comment.md>` replaces the text of a comment the acting bot itself authored —
|
|
4176
4244
|
for correcting a factual error, an unfulfillable claim, or outdated guidance
|