@stage5/lumine 0.2.59 → 0.2.60

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 CHANGED
@@ -283,13 +283,18 @@ works:
283
283
 
284
284
  ```bash
285
285
  lumine sponsor job begin 42
286
+ lumine sponsor job update 42 --file /path/to/lumine-update.txt --phase building
286
287
  lumine sponsor job pulse 42
287
288
  lumine sponsor job relay-applied 42 101
288
289
  lumine sponsor job complete 42 --summary "Implemented and tested the approved change"
289
290
  ```
290
291
 
291
292
  `pulse` renews the duty/job leases and returns any newly approved follow-up.
292
- Run it between substantial work steps. `relay-applied` is an explicit receipt;
293
+ `update` sends the exact file text back to Zero or Ciel as a user-visible
294
+ message from Lumine, their added brain. Use it for concise, deliberate
295
+ milestones only; never send hidden reasoning, raw terminal output, credentials,
296
+ tokens, private paths, or unrelated data. Run `pulse` between substantial work
297
+ steps. `relay-applied` is an explicit receipt;
293
298
  completion is rejected until every delivered relay has actually been applied
294
299
  and acknowledged. Use `helper-start` and `helper-complete` to record native
295
300
  same-session subagents within the configured helper limit—the CLI records them
package/lib/commands.js CHANGED
@@ -2409,6 +2409,8 @@ export function parseArgs(args) {
2409
2409
  sponsorAgentOrdinal: raw.ordinal,
2410
2410
  sponsorOutcome: raw.outcome ? String(raw.outcome) : "",
2411
2411
  sponsorFailureReason: raw.reason ? String(raw.reason) : "",
2412
+ sponsorUpdateFile: raw.file ? String(raw.file) : "",
2413
+ sponsorUpdatePhase: raw.phase ? String(raw.phase) : "",
2412
2414
  sponsorResolvedModel: raw.resolvedModel
2413
2415
  ? String(raw.resolvedModel)
2414
2416
  : "",
@@ -2749,6 +2751,7 @@ export function printHelp() {
2749
2751
  lumine sponsor duty watch [--wait-ms <ms>] [--json]
2750
2752
  lumine sponsor duty pause|resume|stop
2751
2753
  lumine sponsor job status|pulse|begin <job-id>
2754
+ lumine sponsor job update <job-id> --file <path> [--phase <name>]
2752
2755
  lumine sponsor job relay-applied <job-id> <relay-id...>
2753
2756
  lumine sponsor job helper-start|helper-complete <job-id> [options]
2754
2757
  lumine sponsor job complete <job-id> --summary <text>
@@ -66,6 +66,13 @@ export async function sponsorJobCommand(options, args, commandServices) {
66
66
  await withSponsorStateLock(options, () => beginJob(options, jobId));
67
67
  return;
68
68
  }
69
+ if (action === "update") {
70
+ assertNoExtraArgs(args, 2);
71
+ await withSponsorStateLock(options, () =>
72
+ publishDialogueUpdate(options, jobId),
73
+ );
74
+ return;
75
+ }
69
76
  if (action === "relay-applied") {
70
77
  const relayIds = args
71
78
  .slice(2)
@@ -539,10 +546,61 @@ async function beginJob(options, jobId) {
539
546
  printJsonOrLines(options, { job: canonicalJobState.job, coordinator: agent }, [
540
547
  `Workshop job #${jobId} is now being handled by this same on-duty agent session.`,
541
548
  `Apply the approved plan in ${jobState.workspaceDir}.`,
549
+ `Introduce yourself to ${displayPersona(jobState.job.persona)} as Lumine with: lumine sponsor job update ${jobId} --file <message-file> --phase starting`,
542
550
  `Check in with: lumine sponsor job pulse ${jobId}`,
543
551
  ]);
544
552
  }
545
553
 
554
+ async function publishDialogueUpdate(options, jobId) {
555
+ const messageFile = String(options.sponsorUpdateFile || "").trim();
556
+ if (!messageFile) {
557
+ throw new Error(
558
+ "Write the deliberate user-facing Lumine update to a file and pass --file <path>.",
559
+ );
560
+ }
561
+ const phase = String(options.sponsorUpdatePhase || "").trim();
562
+ if (phase.length > 40) {
563
+ throw new Error("--phase must be at most 40 characters.");
564
+ }
565
+ const messagePath = path.resolve(messageFile);
566
+ const messageStat = await fs.stat(messagePath);
567
+ if (!messageStat.isFile() || messageStat.size > 8_000) {
568
+ throw new Error("The Lumine update file must be a text file under 8 KB.");
569
+ }
570
+ const message = String(await fs.readFile(messagePath, "utf8")).trim();
571
+ if (!message) throw new Error("The Lumine update file is empty.");
572
+ if (message.length > 2_000) {
573
+ throw new Error("The Lumine update must be at most 2,000 characters.");
574
+ }
575
+
576
+ const auth = await ensureSponsorAuth(options);
577
+ let state = await loadOwnedState({ options, auth });
578
+ ({ state } = await heartbeatJob({ options, auth, state, jobId }));
579
+ const jobState = requireJobState(state, jobId);
580
+ const clientUpdateKey = randomBytes(18).toString("base64url");
581
+ const result = await retrySponsorCheckIn(() =>
582
+ sponsorJobRequest({
583
+ options,
584
+ auth,
585
+ state,
586
+ jobState,
587
+ path: "/dialogue",
588
+ body: {
589
+ clientUpdateKey,
590
+ phase: phase || null,
591
+ message,
592
+ },
593
+ }),
594
+ );
595
+ if (!result?.update?.message) {
596
+ throw new Error("Twinkle did not confirm the canonical Lumine update.");
597
+ }
598
+ printJsonOrLines(options, result, [
599
+ `Lumine → ${displayPersona(jobState.job.persona)}:`,
600
+ result.update.message,
601
+ ]);
602
+ }
603
+
546
604
  async function startHelper(options, jobId) {
547
605
  const auth = await ensureSponsorAuth(options);
548
606
  let state = await loadOwnedState({ options, auth });
@@ -1177,25 +1235,36 @@ async function writeAssignment(jobState, state) {
1177
1235
  const unapplied = new Set(unappliedRelayIds(jobState));
1178
1236
  const relays = (jobState.relays || [])
1179
1237
  .map((relay) => {
1180
- const details = [
1181
- relay.summary,
1182
- relay.requestedOutcome ? `What to deliver: ${relay.requestedOutcome}` : "",
1183
- relay.constraints?.length
1184
- ? `Constraints: ${relay.constraints.join("; ")}`
1185
- : "",
1186
- relay.acceptanceCriteria?.length
1187
- ? `Done means: ${relay.acceptanceCriteria.join("; ")}`
1188
- : "",
1189
- ].filter(Boolean);
1190
- return `## Relay #${relay.id}${unapplied.has(Number(relay.id)) ? " (not yet acknowledged as applied)" : " (applied receipt confirmed)"}\n\n${details.join("\n\n")}`;
1238
+ const dialogueText =
1239
+ String(relay.dialogueText || "").trim() ||
1240
+ [
1241
+ relay.summary,
1242
+ relay.projectTitleHint
1243
+ ? `Project: ${relay.projectTitleHint}`
1244
+ : "",
1245
+ relay.requestedOutcome
1246
+ ? `What to build: ${relay.requestedOutcome}`
1247
+ : "",
1248
+ relay.constraints?.length
1249
+ ? `Keeping in mind:\n${relay.constraints.map((item) => `• ${item}`).join("\n")}`
1250
+ : "",
1251
+ relay.acceptanceCriteria?.length
1252
+ ? `Done means:\n${relay.acceptanceCriteria.map((item) => `• ${item}`).join("\n")}`
1253
+ : "",
1254
+ ]
1255
+ .filter(Boolean)
1256
+ .join("\n\n");
1257
+ return `## Relay #${relay.id}${unapplied.has(Number(relay.id)) ? " (not yet acknowledged as applied)" : " (applied receipt confirmed)"}\n\n${dialogueText}`;
1191
1258
  })
1192
1259
  .join("\n\n");
1193
1260
  const content = `# Lumine Build Workshop assignment #${jobState.job.id}
1194
1261
 
1195
- You are the same live ${displayProvider(state.operatorSession.provider)} agent session that opened sponsor duty. Zero or Ciel is the user's visible teammate; you are lending that teammate an added brain. Perform this work in this session. Do not launch a replacement coding provider or leave an unattended heartbeat process standing in for you.
1262
+ You are the same live ${displayProvider(state.operatorSession.provider)} agent session that opened sponsor duty. Zero or Ciel is the user's visible teammate; you are lending that teammate an added brain. In every user-facing Workshop update, you are Lumine: the added brain talking with that teammate. Perform this work in this session. Do not launch a replacement coding provider or leave an unattended heartbeat process standing in for you.
1196
1263
 
1197
1264
  The user approved sharing only this structured plan, active-job follow-ups, the assigned Build project, and its scoped Forum. Never inspect or infer from their private Zero/Ciel chat. Edit only contribution Build #${jobState.job.contributionBuild.id}; never merge Main, publish, or contact the user directly.
1198
1265
 
1266
+ Lumine updates are a deliberate public channel. Write concise messages about what you are checking, what you found, or what happens next. Never publish hidden chain-of-thought, raw terminal output, credentials, tokens, private paths, or unrelated data. The exact file text you submit is shown in Twinkle and echoed back by the CLI.
1267
+
1199
1268
  - User: @${jobState.job.requester.username}
1200
1269
  - Visible teammate: ${displayPersona(jobState.job.persona)}
1201
1270
  - Main project: ${jobState.job.rootBuild.title} (#${jobState.job.rootBuild.id})
@@ -1208,10 +1277,11 @@ ${jobState.forumContext ? `## Scoped Build Forum snapshot\n\n${jobState.forumCon
1208
1277
  ## Duty protocol
1209
1278
 
1210
1279
  1. Run \`lumine sponsor job begin ${jobState.job.id}\` before editing.
1211
- 2. Work only in the workspace above. Use your native same-session subagents only after registering each with \`helper-start\`. The coordinator alone runs sponsor CLI commands; helpers report their results back to it.
1212
- 3. Run \`lumine sponsor job pulse ${jobState.job.id}\` between substantial work steps to keep the lease alive and receive approved follow-ups.
1213
- 4. After applying a relay, record its exact ID with \`lumine sponsor job relay-applied ${jobState.job.id} <relay-id...>\`.
1214
- 5. Finish with \`lumine sponsor job complete ${jobState.job.id} --summary "..."\`.
1280
+ 2. Introduce yourself and publish meaningful milestones with \`lumine sponsor job update ${jobState.job.id} --file <message-file> --phase <name>\`. Speak as Lumine, never as the underlying provider.
1281
+ 3. Work only in the workspace above. Use your native same-session subagents only after registering each with \`helper-start\`. The coordinator alone runs sponsor CLI commands; helpers report their results back to it.
1282
+ 4. Run \`lumine sponsor job pulse ${jobState.job.id}\` between substantial work steps to keep the lease alive and receive approved follow-ups.
1283
+ 5. After applying a relay, record its exact ID with \`lumine sponsor job relay-applied ${jobState.job.id} <relay-id...>\`.
1284
+ 6. Finish with \`lumine sponsor job complete ${jobState.job.id} --summary "..."\`.
1215
1285
  `;
1216
1286
  await fs.writeFile(jobState.assignmentPath, content, {
1217
1287
  encoding: "utf8",
@@ -1865,6 +1935,7 @@ function sponsorJobUsage() {
1865
1935
  "Usage:",
1866
1936
  " lumine sponsor job status|pulse <job-id>",
1867
1937
  " lumine sponsor job begin <job-id>",
1938
+ " lumine sponsor job update <job-id> --file <path> [--phase <name>]",
1868
1939
  " lumine sponsor job relay-applied <job-id> <relay-id...>",
1869
1940
  " lumine sponsor job helper-start <job-id> [--ordinal <n>]",
1870
1941
  " lumine sponsor job helper-complete <job-id> --ordinal <n> --outcome <text> [--resolved-model <name>] [--resolved-effort <level>]",
package/lib/sponsor.js CHANGED
@@ -391,6 +391,7 @@ function sponsorUsage() {
391
391
  " lumine sponsor duty watch [--wait-ms <ms>] [--json]",
392
392
  " lumine sponsor duty status|pause|resume|stop",
393
393
  " lumine sponsor job status|pulse|begin <job-id>",
394
+ " lumine sponsor job update <job-id> --file <path> [--phase <name>]",
394
395
  " lumine sponsor job relay-applied <job-id> <relay-id...>",
395
396
  " lumine sponsor job helper-start|helper-complete <job-id> [options]",
396
397
  " lumine sponsor job complete <job-id> --summary <text>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.59",
3
+ "version": "0.2.60",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {