@stage5/lumine 0.2.56 → 0.2.58

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.
@@ -0,0 +1,1817 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash, randomBytes } from "node:crypto";
3
+ import fs from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+
7
+ import { notifyBuildOwnerOfContribution } from "./api.js";
8
+ import { ensureAuth, resolveAuth, writeAuthFile } from "./auth.js";
9
+ import { readCompleteBuildForumSnapshot } from "./forum.js";
10
+ import { requestJson } from "./http.js";
11
+ import { sleep } from "./util.js";
12
+ import { collectProjectFiles } from "./workspace.js";
13
+
14
+ const SPONSOR_PATH = "/cli/sponsor";
15
+ const EXECUTION_MODE = "agent_session_v1";
16
+ const STATE_VERSION = 1;
17
+ const PROVIDERS = new Set(["codex", "claude-code"]);
18
+ const PERSONAS = new Set(["zero", "ciel"]);
19
+ const DEFAULT_DUTY_POLL_MS = 3_000;
20
+ const DEFAULT_DUTY_WATCH_MS = 20_000;
21
+ const MAX_DUTY_WATCH_MS = 60_000;
22
+ const MAX_FORUM_CONTEXT_CHARS = 8_000;
23
+
24
+ export async function sponsorDutyCommand(options, args, commandServices) {
25
+ const action = String(args[0] || "status")
26
+ .trim()
27
+ .toLowerCase();
28
+ if (action === "status") {
29
+ if (args.length > 1) throw new Error(sponsorDutyUsage());
30
+ await printDutyStatus(options);
31
+ return;
32
+ }
33
+ if (args.length > 1 || options.sponsorPersona) {
34
+ throw new Error(
35
+ "Sponsor duty is shared by Zero and Ciel. Remove the persona argument.",
36
+ );
37
+ }
38
+ if (["pause", "resume", "stop"].includes(action)) {
39
+ await withSponsorStateLock(options, () =>
40
+ changeDutyState(options, action),
41
+ );
42
+ return;
43
+ }
44
+ if (action === "watch") {
45
+ await withSponsorStateLock(options, () =>
46
+ watchDuty(options, commandServices),
47
+ );
48
+ return;
49
+ }
50
+ if (action !== "start") throw new Error(sponsorDutyUsage());
51
+ await withSponsorStateLock(options, () => startDuty(options));
52
+ }
53
+
54
+ export async function sponsorJobCommand(options, args, commandServices) {
55
+ const action = String(args[0] || "status")
56
+ .trim()
57
+ .toLowerCase();
58
+ const jobId = positiveInteger(args[1], "Workshop job ID");
59
+ if (action === "status" || action === "pulse") {
60
+ assertNoExtraArgs(args, 2);
61
+ await withSponsorStateLock(options, () => showJob(options, jobId));
62
+ return;
63
+ }
64
+ if (action === "begin") {
65
+ assertNoExtraArgs(args, 2);
66
+ await withSponsorStateLock(options, () => beginJob(options, jobId));
67
+ return;
68
+ }
69
+ if (action === "relay-applied") {
70
+ const relayIds = args
71
+ .slice(2)
72
+ .map((value) => positiveInteger(value, "Workshop relay ID"));
73
+ if (relayIds.length === 0) {
74
+ throw new Error(
75
+ "List the approved relay IDs you actually applied to the workspace.",
76
+ );
77
+ }
78
+ await withSponsorStateLock(options, () =>
79
+ markRelaysApplied(options, jobId, relayIds),
80
+ );
81
+ return;
82
+ }
83
+ if (action === "helper-start") {
84
+ assertNoExtraArgs(args, 2);
85
+ await withSponsorStateLock(options, () => startHelper(options, jobId));
86
+ return;
87
+ }
88
+ if (action === "helper-complete") {
89
+ assertNoExtraArgs(args, 2);
90
+ await withSponsorStateLock(options, () =>
91
+ completeHelper(options, jobId),
92
+ );
93
+ return;
94
+ }
95
+ if (action === "complete") {
96
+ assertNoExtraArgs(args, 2);
97
+ await withSponsorStateLock(options, () =>
98
+ completeJob(options, jobId, commandServices),
99
+ );
100
+ return;
101
+ }
102
+ if (action === "fail") {
103
+ const positionalReason = args.slice(2).join(" ").trim();
104
+ await withSponsorStateLock(options, () =>
105
+ failJob(
106
+ options,
107
+ jobId,
108
+ options.sponsorFailureReason || positionalReason,
109
+ ),
110
+ );
111
+ return;
112
+ }
113
+ throw new Error(sponsorJobUsage());
114
+ }
115
+
116
+ async function startDuty(options) {
117
+ const operatorSession = detectSponsorAgentSession();
118
+ if (!operatorSession.runtimeVersion) {
119
+ throw new Error(
120
+ `Lumine could not verify the active ${displayProvider(operatorSession.provider)} runtime version. Confirm that its CLI is available in this agent session before starting duty.`,
121
+ );
122
+ }
123
+ const provider = normalizeDutyProvider(options.provider, operatorSession);
124
+ const requestedModel = requiredRuntimeSetting(options.model, "--model", 160);
125
+ const requestedEffort = requiredRuntimeSetting(
126
+ options.sponsorEffort,
127
+ "--effort",
128
+ 40,
129
+ );
130
+ if (provider !== "codex" && options.sponsorServiceTier) {
131
+ throw new Error(
132
+ "--service-tier is currently supported only for a Codex duty session.",
133
+ );
134
+ }
135
+ const auth = await ensureSponsorAuth(options);
136
+ const existingState = await readSponsorState(options, { required: false });
137
+ if (existingState) {
138
+ await reconcileExistingStateBeforeStart({ options, auth, existingState });
139
+ }
140
+ const started = await sponsorRequest({
141
+ options,
142
+ auth,
143
+ method: "POST",
144
+ path: "/duty/start",
145
+ body: {
146
+ scope: "shared",
147
+ provider,
148
+ requestedModel,
149
+ requestedEffort,
150
+ requestedServiceTier: options.sponsorServiceTier || null,
151
+ cliVersion: options.lumineCli?.version || null,
152
+ operatorSession,
153
+ },
154
+ });
155
+ const dutyId = Number(started.duty?.id || 0);
156
+ const leaseToken = String(started.leaseToken || "");
157
+ if (!dutyId || !leaseToken || started.duty?.scope !== "shared") {
158
+ throw new Error("Twinkle did not return a valid shared sponsor duty lease.");
159
+ }
160
+ const state = {
161
+ version: STATE_VERSION,
162
+ apiUrl: options.apiUrl,
163
+ sponsorUserId: Number(started.duty.sponsorUserId || auth.userId || 0),
164
+ operatorSession,
165
+ duty: {
166
+ ...started.duty,
167
+ leaseToken,
168
+ heartbeatEverySeconds: Number(started.heartbeatEverySeconds || 20),
169
+ },
170
+ jobs: {},
171
+ preservedWorkspaces: [],
172
+ updatedAt: new Date().toISOString(),
173
+ };
174
+ try {
175
+ await writeSponsorState(options, state);
176
+ } catch (error) {
177
+ await sponsorRequest({
178
+ options,
179
+ auth,
180
+ method: "POST",
181
+ path: "/duty/state",
182
+ body: { dutySessionId: dutyId, state: "stopped" },
183
+ }).catch(() => undefined);
184
+ throw error;
185
+ }
186
+ printJsonOrLines(
187
+ options,
188
+ {
189
+ duty: started.duty,
190
+ executionMode: EXECUTION_MODE,
191
+ operatorSession: publicOperatorSession(operatorSession),
192
+ nextCommand: "lumine sponsor duty watch --json",
193
+ },
194
+ [
195
+ `Shared Zero/Ciel sponsor duty #${dutyId} is open under this ${displayProvider(provider)} session.`,
196
+ `Declared runtime: model=${requestedModel}, effort=${requestedEffort}, service-tier=${options.sponsorServiceTier || "provider default"}.`,
197
+ "This same live agent session must keep checking in, receive approved plans, and perform the work itself.",
198
+ "Do not leave an unattended heartbeat running and do not hand jobs to a newly spawned provider process.",
199
+ "Run `lumine sponsor duty watch` repeatedly to stay present and receive an assignment.",
200
+ ],
201
+ );
202
+ }
203
+
204
+ async function reconcileExistingStateBeforeStart({
205
+ options,
206
+ auth,
207
+ existingState,
208
+ }) {
209
+ assertSponsorStateAccount(existingState, auth);
210
+ const status = await sponsorRequest({ options, auth, path: "/status" });
211
+ const canonicalDuty = (status.duties || []).find(
212
+ (duty) => Number(duty.id) === Number(existingState.duty.id),
213
+ );
214
+ const now = Math.floor(Date.now() / 1_000);
215
+ const canonicallyOpen =
216
+ canonicalDuty &&
217
+ ["active", "paused"].includes(String(canonicalDuty.state || ""));
218
+ if (canonicallyOpen && Number(canonicalDuty.expiresAt || 0) > now) {
219
+ throw new Error(
220
+ `Sponsor duty #${canonicalDuty.id} is still live. Resume it with \`lumine sponsor duty watch\`, or stop it before starting another.`,
221
+ );
222
+ }
223
+ if (canonicallyOpen) {
224
+ await sponsorRequest({
225
+ options,
226
+ auth,
227
+ method: "POST",
228
+ path: "/duty/state",
229
+ body: {
230
+ dutySessionId: Number(canonicalDuty.id),
231
+ state: "stopped",
232
+ },
233
+ });
234
+ }
235
+ const preservedWorkspaces = dutyWorkspacePaths(existingState);
236
+ if (preservedWorkspaces.length > 0) {
237
+ const archivePath = await archiveSponsorState(options);
238
+ console.error(
239
+ `lumine: preserved the expired duty record at ${archivePath}.`,
240
+ );
241
+ for (const workspace of preservedWorkspaces) {
242
+ console.error(`lumine: preserved expired workspace ${workspace}`);
243
+ }
244
+ } else {
245
+ await removeSponsorState(options);
246
+ }
247
+ }
248
+
249
+ async function watchDuty(options, commandServices) {
250
+ const auth = await ensureSponsorAuth(options);
251
+ let state = await loadOwnedState({ options, auth });
252
+ const waitMs = normalizeWatchMs(options.sponsorWaitMs);
253
+ const deadline = Date.now() + waitMs;
254
+ let lastDutyHeartbeatAt = 0;
255
+ let lastJobHeartbeatAt = 0;
256
+
257
+ while (true) {
258
+ const now = Date.now();
259
+ const dutyEveryMs = Math.max(
260
+ 5_000,
261
+ Number(state.duty.heartbeatEverySeconds || 15) * 1_000,
262
+ );
263
+ if (!lastDutyHeartbeatAt || now - lastDutyHeartbeatAt >= dutyEveryMs) {
264
+ state = await heartbeatDuty({ options, auth, state });
265
+ lastDutyHeartbeatAt = Date.now();
266
+ }
267
+ if (state.duty.state !== "active") {
268
+ printJsonOrLines(
269
+ options,
270
+ { duty: publicDuty(state.duty), assignment: null },
271
+ [
272
+ `Sponsor duty #${state.duty.id} is ${state.duty.state}. Resume it before watching for work.`,
273
+ ],
274
+ );
275
+ return;
276
+ }
277
+
278
+ const jobEveryMs = Math.max(
279
+ 10_000,
280
+ minimumJobHeartbeatSeconds(state) * 1_000,
281
+ );
282
+ if (!lastJobHeartbeatAt || now - lastJobHeartbeatAt >= jobEveryMs) {
283
+ const relays = await heartbeatAllJobs({ options, auth, state });
284
+ state = relays.state;
285
+ lastJobHeartbeatAt = Date.now();
286
+ if (relays.newRelayCount > 0) {
287
+ const assignments = activeJobSummaries(state);
288
+ printJsonOrLines(options, { assignments }, [
289
+ `Received ${relays.newRelayCount} new approved Workshop follow-up${relays.newRelayCount === 1 ? "" : "s"}.`,
290
+ ...assignments.map(formatAssignmentLine),
291
+ ]);
292
+ return;
293
+ }
294
+ }
295
+
296
+ const unpreparedJob = Object.values(state.jobs || {}).find(
297
+ (jobState) => !jobState.preparedAt,
298
+ );
299
+ if (unpreparedJob) {
300
+ const prepared = await prepareClaimedJob({
301
+ options,
302
+ auth,
303
+ state,
304
+ jobId: Number(unpreparedJob.job.id),
305
+ commandServices,
306
+ });
307
+ state = prepared.state;
308
+ const assignment = jobSummary(prepared.jobState);
309
+ printJsonOrLines(options, { assignment }, [
310
+ `Recovered Workshop job #${assignment.job.id} for this live agent session.`,
311
+ `Workspace: ${assignment.workspaceDir}`,
312
+ `Approved assignment: ${assignment.assignmentPath}`,
313
+ `Begin it with: lumine sponsor job begin ${assignment.job.id}`,
314
+ ]);
315
+ return;
316
+ }
317
+
318
+ let claim;
319
+ try {
320
+ claim = await sponsorRequest({
321
+ options,
322
+ auth,
323
+ method: "POST",
324
+ path: "/jobs/claim",
325
+ body: {
326
+ dutySessionId: Number(state.duty.id),
327
+ leaseToken: state.duty.leaseToken,
328
+ operatorSession: state.operatorSession,
329
+ },
330
+ });
331
+ } catch (error) {
332
+ if (!isRetryableSponsorRequestError(error)) throw error;
333
+ if (Date.now() >= deadline) throw error;
334
+ console.error(
335
+ `Workshop check-in failed; retrying while this watch is active: ${error?.message || error}`,
336
+ );
337
+ await sleep(
338
+ Math.min(
339
+ options.sponsorPollMs || DEFAULT_DUTY_POLL_MS,
340
+ 3_000,
341
+ Math.max(0, deadline - Date.now()),
342
+ ),
343
+ );
344
+ continue;
345
+ }
346
+ if (claim.job) {
347
+ state = await recordClaim({ options, state, claim });
348
+ const prepared = await prepareClaimedJob({
349
+ options,
350
+ auth,
351
+ state,
352
+ jobId: Number(claim.job.id),
353
+ commandServices,
354
+ });
355
+ state = prepared.state;
356
+ const assignment = jobSummary(prepared.jobState);
357
+ printJsonOrLines(options, { assignment }, [
358
+ `Workshop job #${assignment.job.id} is assigned to this live ${displayProvider(state.operatorSession.provider)} session.`,
359
+ `Workspace: ${assignment.workspaceDir}`,
360
+ `Approved assignment: ${assignment.assignmentPath}`,
361
+ `Begin it with: lumine sponsor job begin ${assignment.job.id}`,
362
+ ]);
363
+ return;
364
+ }
365
+ if (Date.now() >= deadline) {
366
+ printJsonOrLines(
367
+ options,
368
+ {
369
+ duty: publicDuty(state.duty),
370
+ assignment: null,
371
+ activeJobs: activeJobSummaries(state),
372
+ nextCommand: "lumine sponsor duty watch --json",
373
+ },
374
+ [
375
+ `No new Workshop assignment during this ${Math.ceil(waitMs / 1000)}-second watch.`,
376
+ "Run `lumine sponsor duty watch` again now to remain visibly on duty.",
377
+ ],
378
+ );
379
+ return;
380
+ }
381
+ await sleep(
382
+ Math.min(
383
+ options.sponsorPollMs || DEFAULT_DUTY_POLL_MS,
384
+ Math.max(0, deadline - Date.now()),
385
+ ),
386
+ );
387
+ }
388
+ }
389
+
390
+ async function changeDutyState(options, action) {
391
+ const auth = await ensureSponsorAuth(options);
392
+ const status = await sponsorRequest({ options, auth, path: "/status" });
393
+ const duties = (status.duties || []).filter((duty) =>
394
+ ["active", "paused"].includes(String(duty.state || "")),
395
+ );
396
+ if (duties.length === 0) {
397
+ if (action === "stop") {
398
+ const localState = await readSponsorState(options, { required: false });
399
+ if (!localState) {
400
+ printJsonOrLines(
401
+ options,
402
+ { changed: false, duty: null },
403
+ ["No active sponsor duty session."],
404
+ );
405
+ return;
406
+ }
407
+ assertSponsorStateAccount(localState, auth);
408
+ const preservedWorkspaces = dutyWorkspacePaths(localState);
409
+ const localArchive =
410
+ preservedWorkspaces.length > 0
411
+ ? await archiveSponsorState(options)
412
+ : null;
413
+ if (!localArchive) await removeSponsorState(options);
414
+ printJsonOrLines(
415
+ options,
416
+ {
417
+ changed: false,
418
+ duty: null,
419
+ ...(localArchive ? { localArchive, preservedWorkspaces } : {}),
420
+ },
421
+ [
422
+ "No active sponsor duty session.",
423
+ ...(localArchive
424
+ ? [
425
+ `Preserved the expired job record at ${localArchive}.`,
426
+ ...preservedWorkspaces.map(
427
+ (workspace) => `Preserved workspace: ${workspace}`,
428
+ ),
429
+ ]
430
+ : ["Removed the stale local duty record."]),
431
+ ],
432
+ );
433
+ return;
434
+ }
435
+ throw new Error("No active sponsor duty session was found.");
436
+ }
437
+ if (duties.length > 1) {
438
+ throw new Error(
439
+ `Multiple legacy duty sessions remain open (${duties.map((duty) => `#${duty.id}`).join(", ")}). Stop them before using shared duty.`,
440
+ );
441
+ }
442
+ const dutyId = Number(duties[0].id || 0);
443
+ let localState = await readSponsorState(options, { required: false });
444
+ if (localState) assertSponsorStateAccount(localState, auth);
445
+ if (action === "resume") {
446
+ localState = await loadOwnedState({ options, auth, state: localState });
447
+ if (Number(localState.duty.id) !== dutyId) {
448
+ throw new Error("The local agent-session lease does not own this duty.");
449
+ }
450
+ }
451
+ const nextState =
452
+ action === "resume" ? "active" : action === "stop" ? "stopped" : "paused";
453
+ const result = await sponsorRequest({
454
+ options,
455
+ auth,
456
+ method: "POST",
457
+ path: "/duty/state",
458
+ body: {
459
+ dutySessionId: dutyId,
460
+ state: nextState,
461
+ ...(nextState === "active"
462
+ ? {
463
+ dutyLeaseToken: localState.duty.leaseToken,
464
+ operatorSession: localState.operatorSession,
465
+ }
466
+ : {}),
467
+ },
468
+ });
469
+ let localArchive = null;
470
+ let preservedWorkspaces = [];
471
+ if (localState && Number(localState.duty?.id) === dutyId) {
472
+ if (nextState === "stopped") {
473
+ preservedWorkspaces = dutyWorkspacePaths(localState);
474
+ if (preservedWorkspaces.length > 0) {
475
+ localArchive = await archiveSponsorState(options);
476
+ } else {
477
+ await removeSponsorState(options);
478
+ }
479
+ } else {
480
+ localState.duty = {
481
+ ...result.duty,
482
+ leaseToken: localState.duty.leaseToken,
483
+ heartbeatEverySeconds: localState.duty.heartbeatEverySeconds,
484
+ };
485
+ await writeSponsorState(options, localState);
486
+ }
487
+ }
488
+ printJsonOrLines(
489
+ options,
490
+ {
491
+ ...result,
492
+ ...(localArchive
493
+ ? { localArchive, preservedWorkspaces }
494
+ : {}),
495
+ },
496
+ [
497
+ `Sponsor duty is now ${nextState}.`,
498
+ ...(localArchive
499
+ ? [
500
+ `Preserved the expired job record at ${localArchive}.`,
501
+ ...preservedWorkspaces.map(
502
+ (workspace) => `Preserved workspace: ${workspace}`,
503
+ ),
504
+ ]
505
+ : []),
506
+ ],
507
+ );
508
+ }
509
+
510
+ async function showJob(options, jobId) {
511
+ const auth = await ensureSponsorAuth(options);
512
+ let state = await loadOwnedState({ options, auth });
513
+ const refreshed = await heartbeatJob({ options, auth, state, jobId });
514
+ state = refreshed.state;
515
+ const jobState = requireJobState(state, jobId);
516
+ printJsonOrLines(options, jobSummary(jobState), [
517
+ `Workshop job #${jobId}: ${jobState.job.status}`,
518
+ `Workspace: ${jobState.workspaceDir}`,
519
+ `Approved assignment: ${jobState.assignmentPath}`,
520
+ `${unappliedRelayIds(jobState).length} approved relay(s) still need an explicit applied receipt.`,
521
+ ]);
522
+ }
523
+
524
+ async function beginJob(options, jobId) {
525
+ const auth = await ensureSponsorAuth(options);
526
+ let state = await loadOwnedState({ options, auth });
527
+ ({ state } = await heartbeatJob({ options, auth, state, jobId }));
528
+ const jobState = requireJobState(state, jobId);
529
+ if (!jobState.preparedAt) {
530
+ throw new Error(
531
+ `Job #${jobId} is not prepared. Run \`lumine sponsor duty watch\` again.`,
532
+ );
533
+ }
534
+ const agent = await ensureCoordinator({ options, auth, state, jobState });
535
+ jobState.coordinator = agent;
536
+ await writeSponsorState(options, state);
537
+ ({ state } = await heartbeatJob({ options, auth, state, jobId }));
538
+ const canonicalJobState = requireJobState(state, jobId);
539
+ printJsonOrLines(options, { job: canonicalJobState.job, coordinator: agent }, [
540
+ `Workshop job #${jobId} is now being handled by this same on-duty agent session.`,
541
+ `Apply the approved plan in ${jobState.workspaceDir}.`,
542
+ `Check in with: lumine sponsor job pulse ${jobId}`,
543
+ ]);
544
+ }
545
+
546
+ async function startHelper(options, jobId) {
547
+ const auth = await ensureSponsorAuth(options);
548
+ let state = await loadOwnedState({ options, auth });
549
+ ({ state } = await heartbeatJob({ options, auth, state, jobId }));
550
+ const jobState = requireJobState(state, jobId);
551
+ const coordinator = await ensureCoordinator({
552
+ options,
553
+ auth,
554
+ state,
555
+ jobState,
556
+ });
557
+ jobState.coordinator = coordinator;
558
+ const ordinal = normalizeHelperOrdinal(options.sponsorAgentOrdinal, jobState);
559
+ const existing = jobState.helpers?.[String(ordinal)];
560
+ if (existing) {
561
+ throw new Error(`Helper ${ordinal} is already registered for job #${jobId}.`);
562
+ }
563
+ const helper = await sponsorJobRequest({
564
+ options,
565
+ auth,
566
+ state,
567
+ jobState,
568
+ path: "/agents",
569
+ body: {
570
+ role: "helper",
571
+ ordinal,
572
+ parentAgentId: coordinator.agentId,
573
+ provider: state.operatorSession.provider,
574
+ requestedModel: jobState.runtime.requestedModel,
575
+ requestedEffort: jobState.runtime.requestedEffort,
576
+ requestedServiceTier: jobState.runtime.requestedServiceTier,
577
+ },
578
+ });
579
+ jobState.helpers = { ...(jobState.helpers || {}), [String(ordinal)]: helper };
580
+ await writeSponsorState(options, state);
581
+ printJsonOrLines(options, { helper, jobId }, [
582
+ `Registered helper ${ordinal} for Workshop job #${jobId}.`,
583
+ "Spawn and supervise that helper from this agent session; the CLI does not launch a replacement provider.",
584
+ ]);
585
+ }
586
+
587
+ async function completeHelper(options, jobId) {
588
+ const outcome = String(options.sponsorOutcome || "").trim();
589
+ if (!outcome) {
590
+ throw new Error("Describe the helper's actual result with --outcome <text>.");
591
+ }
592
+ const ordinal = positiveInteger(options.sponsorAgentOrdinal, "--ordinal");
593
+ const auth = await ensureSponsorAuth(options);
594
+ let state = await loadOwnedState({ options, auth });
595
+ ({ state } = await heartbeatJob({ options, auth, state, jobId }));
596
+ const jobState = requireJobState(state, jobId);
597
+ const helper = jobState.helpers?.[String(ordinal)];
598
+ if (!helper?.agentId) {
599
+ throw new Error(`Helper ${ordinal} is not registered for job #${jobId}.`);
600
+ }
601
+ const result = await completeAgent({
602
+ options,
603
+ auth,
604
+ state,
605
+ jobState,
606
+ agent: helper,
607
+ outcome: {
608
+ finalText: outcome.slice(0, 2000),
609
+ changedPaths: [],
610
+ agentSessionBound: true,
611
+ },
612
+ });
613
+ jobState.helpers[String(ordinal)] = { ...helper, ...result };
614
+ await writeSponsorState(options, state);
615
+ printJsonOrLines(options, result, [
616
+ `Helper ${ordinal} provenance is complete for Workshop job #${jobId}.`,
617
+ ]);
618
+ }
619
+
620
+ async function markRelaysApplied(options, jobId, relayIds) {
621
+ const auth = await ensureSponsorAuth(options);
622
+ let state = await loadOwnedState({ options, auth });
623
+ ({ state } = await heartbeatJob({ options, auth, state, jobId }));
624
+ const jobState = requireJobState(state, jobId);
625
+ const availableIds = new Set((jobState.relays || []).map((relay) => Number(relay.id)));
626
+ if (relayIds.some((relayId) => !availableIds.has(relayId))) {
627
+ throw new Error(
628
+ "Every applied relay ID must come from this job's approved assignment.",
629
+ );
630
+ }
631
+ const result = await sponsorJobRequest({
632
+ options,
633
+ auth,
634
+ state,
635
+ jobState,
636
+ path: "/relays/applied",
637
+ body: { relayIds: Array.from(new Set(relayIds)) },
638
+ });
639
+ jobState.appliedRelayIds = Array.from(
640
+ new Set([
641
+ ...(jobState.appliedRelayIds || []),
642
+ ...(result.appliedRelayIds || []).map(Number),
643
+ ]),
644
+ );
645
+ await writeAssignment(jobState, state);
646
+ await writeSponsorState(options, state);
647
+ printJsonOrLines(options, result, [
648
+ `Twinkle confirmed ${result.appliedRelayIds?.length || 0} applied relay receipt(s) for job #${jobId}.`,
649
+ ]);
650
+ }
651
+
652
+ async function completeJob(options, jobId, commandServices) {
653
+ const summary = String(options.summary || "").trim();
654
+ if (!summary) {
655
+ throw new Error("Summarize the actual completed work with --summary <text>.");
656
+ }
657
+ const auth = await ensureSponsorAuth(options);
658
+ let state = await loadOwnedState({ options, auth });
659
+ ({ state } = await heartbeatJob({ options, auth, state, jobId }));
660
+ const jobState = requireJobState(state, jobId);
661
+ if (!jobState.coordinator?.agentId) {
662
+ throw new Error(
663
+ `Run \`lumine sponsor job begin ${jobId}\` before completing this job.`,
664
+ );
665
+ }
666
+ const unapplied = unappliedRelayIds(jobState);
667
+ if (unapplied.length > 0) {
668
+ throw new Error(
669
+ `Approved relay${unapplied.length === 1 ? "" : "s"} ${unapplied.join(", ")} still need to be applied and acknowledged with \`lumine sponsor job relay-applied ${jobId} <relay-id...>\`.`,
670
+ );
671
+ }
672
+ const closure = await sponsorJobRequest({
673
+ options,
674
+ auth,
675
+ state,
676
+ jobState,
677
+ path: "/relays/close",
678
+ });
679
+ const newRelayCount = mergeCanonicalRelays(jobState, closure.relays || []);
680
+ if (!closure.closed) {
681
+ await writeAssignment(jobState, state);
682
+ await writeSponsorState(options, state);
683
+ throw new Error(
684
+ `${newRelayCount || closure.relays?.length || 1} approved follow-up relay(s) arrived. Read the refreshed assignment, apply them, and acknowledge their IDs before completing.`,
685
+ );
686
+ }
687
+ jobState.relaysClosedAt = Number(closure.closedAt || 0) || true;
688
+ await writeSponsorState(options, state);
689
+
690
+ const jobOptions = jobWorkspaceOptions(options, state, jobState, { summary });
691
+ const currentFiles = await collectProjectFiles(jobState.workspaceDir);
692
+ const currentDigest = digestProjectFiles(currentFiles);
693
+ let savedArtifact = jobState.savedArtifact;
694
+ if (!savedArtifact || savedArtifact.localFilesDigest !== currentDigest) {
695
+ const saveResult = await commandServices.saveWorkspace(jobOptions);
696
+ if (!saveResult?.artifactVersion?.versionId || !saveResult?.filesHash) {
697
+ throw new Error(
698
+ "The same-session agent did not produce a new canonical saved artifact for this Workshop job.",
699
+ );
700
+ }
701
+ savedArtifact = {
702
+ artifactVersionId: Number(saveResult.artifactVersion.versionId),
703
+ filesHash: String(saveResult.filesHash),
704
+ localFilesDigest: currentDigest,
705
+ };
706
+ jobState.savedArtifact = savedArtifact;
707
+ await writeSponsorState(options, state);
708
+ }
709
+ const changedPaths = listChangedPaths(
710
+ jobState.initialFileHashes || {},
711
+ hashProjectFiles(currentFiles),
712
+ );
713
+ const coordinatorResult = await completeAgent({
714
+ options,
715
+ auth,
716
+ state,
717
+ jobState,
718
+ agent: jobState.coordinator,
719
+ outcome: {
720
+ finalText: summary.slice(0, 2000),
721
+ changedPathCount: changedPaths.length,
722
+ changedPaths: changedPaths.slice(0, 50),
723
+ agentSessionBound: true,
724
+ },
725
+ });
726
+ jobState.coordinator = {
727
+ ...jobState.coordinator,
728
+ ...coordinatorResult,
729
+ };
730
+ await writeSponsorState(options, state);
731
+
732
+ const jobAuth = await resolveAuth(jobOptions);
733
+ const branchNoticeMessageId = await ensureCanonicalBranchNotice({
734
+ options,
735
+ auth,
736
+ state,
737
+ jobState,
738
+ jobOptions,
739
+ jobAuth,
740
+ outcomeSummary: summary,
741
+ });
742
+ const result = await retrySponsorTransport(() =>
743
+ sponsorJobRequest({
744
+ options,
745
+ auth,
746
+ state,
747
+ jobState,
748
+ path: "/complete",
749
+ body: {
750
+ artifactVersionId: savedArtifact.artifactVersionId,
751
+ branchNoticeMessageId,
752
+ outcomeSummary: summary,
753
+ reportedFilesHash: savedArtifact.filesHash,
754
+ },
755
+ }),
756
+ );
757
+ await removeCompletedJob({ options, state, jobId });
758
+ printJsonOrLines(options, result, [
759
+ `Completed Workshop job #${jobId}; ${displayPersona(jobState.job.persona)} sent the canonical branch update.`,
760
+ ]);
761
+ }
762
+
763
+ async function failJob(options, jobId, rawReason) {
764
+ const reason = String(rawReason || "").trim();
765
+ if (!reason) {
766
+ throw new Error("Explain the concrete failure with --reason <text>.");
767
+ }
768
+ const auth = await ensureSponsorAuth(options);
769
+ let state = await loadOwnedState({ options, auth });
770
+ ({ state } = await heartbeatJob({ options, auth, state, jobId }));
771
+ const jobState = requireJobState(state, jobId);
772
+ const result = await retrySponsorTransport(() =>
773
+ sponsorJobRequest({
774
+ options,
775
+ auth,
776
+ state,
777
+ jobState,
778
+ path: "/fail",
779
+ body: {
780
+ failureCode: "agent_session_failed",
781
+ failureReason: reason.slice(0, 1000),
782
+ },
783
+ }),
784
+ );
785
+ const preservedWorkspace = await preserveFailedJobWorkspace({
786
+ options,
787
+ state,
788
+ jobId,
789
+ reason,
790
+ });
791
+ printJsonOrLines(options, { ...result, preservedWorkspace }, [
792
+ `Workshop job #${jobId} was ended safely.`,
793
+ `Preserved the unfinished workspace at ${preservedWorkspace}.`,
794
+ ]);
795
+ }
796
+
797
+ async function heartbeatDuty({ options, auth, state }) {
798
+ const duty = await retrySponsorCheckIn(() =>
799
+ sponsorRequest({
800
+ options,
801
+ auth,
802
+ method: "POST",
803
+ path: `/duty/${Number(state.duty.id)}/heartbeat`,
804
+ body: {
805
+ leaseToken: state.duty.leaseToken,
806
+ operatorSession: state.operatorSession,
807
+ },
808
+ }),
809
+ );
810
+ state.duty = {
811
+ ...duty,
812
+ leaseToken: state.duty.leaseToken,
813
+ heartbeatEverySeconds: state.duty.heartbeatEverySeconds,
814
+ };
815
+ await writeSponsorState(options, state);
816
+ return state;
817
+ }
818
+
819
+ async function heartbeatAllJobs({ options, auth, state }) {
820
+ let newRelayCount = 0;
821
+ for (const key of Object.keys(state.jobs || {})) {
822
+ const jobId = Number(key);
823
+ const refreshed = await heartbeatJob({ options, auth, state, jobId });
824
+ state = refreshed.state;
825
+ newRelayCount += refreshed.newRelayCount;
826
+ }
827
+ return { state, newRelayCount };
828
+ }
829
+
830
+ async function heartbeatJob({ options, auth, state, jobId }) {
831
+ const jobState = requireJobState(state, jobId);
832
+ const result = await retrySponsorCheckIn(() =>
833
+ sponsorJobRequest({
834
+ options,
835
+ auth,
836
+ state,
837
+ jobState,
838
+ path: "/heartbeat",
839
+ }),
840
+ );
841
+ jobState.job = result.job;
842
+ jobState.leaseExpiresAt = Number(result.leaseExpiresAt || 0) || null;
843
+ const newRelayCount = mergeCanonicalRelays(jobState, result.relays || []);
844
+ if (newRelayCount > 0 && jobState.preparedAt) {
845
+ await writeAssignment(jobState, state);
846
+ }
847
+ await writeSponsorState(options, state);
848
+ return { state, newRelayCount };
849
+ }
850
+
851
+ async function recordClaim({ options, state, claim }) {
852
+ const jobId = Number(claim.job?.id || 0);
853
+ const persona = normalizeJobPersona(claim.job?.persona);
854
+ const attemptToken = String(claim.attempt?.token || "");
855
+ const workspaceToken = String(claim.workspaceToken?.accessToken || "");
856
+ if (!jobId || !attemptToken || !workspaceToken) {
857
+ throw new Error("Twinkle returned an incomplete Workshop job lease.");
858
+ }
859
+ if (state.jobs?.[String(jobId)]) {
860
+ throw new Error(`Workshop job #${jobId} is already recorded locally.`);
861
+ }
862
+ const runtime = {
863
+ provider: String(claim.runtime?.provider || ""),
864
+ requestedModel: String(claim.runtime?.requestedModel || "").trim(),
865
+ requestedEffort: String(claim.runtime?.requestedEffort || "").trim(),
866
+ requestedServiceTier:
867
+ String(claim.runtime?.requestedServiceTier || "").trim() || null,
868
+ };
869
+ if (
870
+ runtime.provider !== state.operatorSession.provider ||
871
+ runtime.requestedModel !== String(state.duty.requestedModel || "") ||
872
+ runtime.requestedEffort !== String(state.duty.requestedEffort || "")
873
+ ) {
874
+ throw new Error(
875
+ "Twinkle returned a Workshop runtime that does not match this live agent session.",
876
+ );
877
+ }
878
+ const tempDir = await fs.mkdtemp(
879
+ path.join(os.tmpdir(), `lumine-${persona}-job-${jobId}-`),
880
+ );
881
+ await fs.chmod(tempDir, 0o700);
882
+ const jobState = {
883
+ job: claim.job,
884
+ attempt: {
885
+ id: Number(claim.attempt?.id || 0),
886
+ number: Number(claim.attempt?.number || 0),
887
+ token: attemptToken,
888
+ },
889
+ runtime,
890
+ relays: Array.isArray(claim.relays) ? claim.relays : [],
891
+ appliedRelayIds: [],
892
+ heartbeatEverySeconds: Number(claim.heartbeatEverySeconds || 40),
893
+ leaseExpiresAt: Number(claim.job?.leaseExpiresAt || 0) || null,
894
+ workspaceToken: {
895
+ accessToken: workspaceToken,
896
+ expiresAt: Number(claim.workspaceToken?.expiresAt || 0),
897
+ user: claim.workspaceToken?.user || null,
898
+ },
899
+ tempDir,
900
+ workspaceDir: path.join(tempDir, "workspace"),
901
+ authFile: path.join(tempDir, "job-auth.json"),
902
+ assignmentPath: path.join(tempDir, "WORKSHOP_ASSIGNMENT.md"),
903
+ preparedAt: null,
904
+ initialFileHashes: null,
905
+ forumContext: "",
906
+ coordinator: null,
907
+ helpers: {},
908
+ savedArtifact: null,
909
+ };
910
+ state.jobs = { ...(state.jobs || {}), [String(jobId)]: jobState };
911
+ await writeSponsorState(options, state);
912
+ return state;
913
+ }
914
+
915
+ async function prepareClaimedJob({
916
+ options,
917
+ auth,
918
+ state,
919
+ jobId,
920
+ commandServices,
921
+ }) {
922
+ const jobState = requireJobState(state, jobId);
923
+ if (jobState.preparedAt) return { state, jobState };
924
+ if (jobState.workspaceToken?.accessToken) {
925
+ await writeAuthFile(jobWorkspaceOptions(options, state, jobState), {
926
+ token: jobState.workspaceToken.accessToken,
927
+ username:
928
+ jobState.workspaceToken.user?.username ||
929
+ displayPersona(jobState.job.persona),
930
+ userId:
931
+ jobState.workspaceToken.user?.id || Number(jobState.job.personaUserId),
932
+ expiresAt: Number(jobState.workspaceToken.expiresAt || 0) * 1_000,
933
+ apiUrl: options.apiUrl,
934
+ createdAt: new Date().toISOString(),
935
+ });
936
+ jobState.workspaceToken = null;
937
+ await writeSponsorState(options, state);
938
+ }
939
+ const jobOptions = jobWorkspaceOptions(options, state, jobState);
940
+ const jobAuth = await resolveAuth(jobOptions);
941
+ await commandServices.pullWorkspace({
942
+ options: jobOptions,
943
+ auth: jobAuth,
944
+ buildId: Number(jobState.job.contributionBuild.id),
945
+ });
946
+ jobState.forumContext = await loadForumContext({
947
+ options: jobOptions,
948
+ auth: jobAuth,
949
+ buildId: Number(jobState.job.contributionBuild.id),
950
+ });
951
+ jobState.initialFileHashes = hashProjectFiles(
952
+ await collectProjectFiles(jobState.workspaceDir),
953
+ );
954
+ jobState.preparedAt = new Date().toISOString();
955
+ await writeAssignment(jobState, state);
956
+ await writeSponsorState(options, state);
957
+ return { state, jobState };
958
+ }
959
+
960
+ async function ensureCoordinator({ options, auth, state, jobState }) {
961
+ if (jobState.coordinator?.agentId) return jobState.coordinator;
962
+ return await sponsorJobRequest({
963
+ options,
964
+ auth,
965
+ state,
966
+ jobState,
967
+ path: "/agents",
968
+ body: {
969
+ role: "coordinator",
970
+ ordinal: 0,
971
+ parentAgentId: null,
972
+ provider: state.operatorSession.provider,
973
+ requestedModel: jobState.runtime.requestedModel,
974
+ requestedEffort: jobState.runtime.requestedEffort,
975
+ requestedServiceTier: jobState.runtime.requestedServiceTier,
976
+ },
977
+ });
978
+ }
979
+
980
+ async function completeAgent({
981
+ options,
982
+ auth,
983
+ state,
984
+ jobState,
985
+ agent,
986
+ outcome,
987
+ }) {
988
+ return await retrySponsorTransport(() =>
989
+ sponsorJobRequest({
990
+ options,
991
+ auth,
992
+ state,
993
+ jobState,
994
+ path: `/agents/${Number(agent.agentId)}/complete`,
995
+ body: {
996
+ status: "completed",
997
+ resolvedModel:
998
+ options.sponsorResolvedModel || jobState.runtime.requestedModel,
999
+ resolvedEffort:
1000
+ options.sponsorResolvedEffort || jobState.runtime.requestedEffort,
1001
+ resolvedServiceTier:
1002
+ options.sponsorResolvedServiceTier ||
1003
+ jobState.runtime.requestedServiceTier,
1004
+ runtimeVersion: state.operatorSession.runtimeVersion || null,
1005
+ evidenceTier: "provider_reported",
1006
+ usage: {
1007
+ executionMode: EXECUTION_MODE,
1008
+ agentSessionFingerprintHash:
1009
+ state.operatorSession.fingerprintHash,
1010
+ bindingEvidence: state.operatorSession.bindingEvidence,
1011
+ },
1012
+ outcome,
1013
+ },
1014
+ }),
1015
+ );
1016
+ }
1017
+
1018
+ async function ensureCanonicalBranchNotice({
1019
+ options,
1020
+ auth,
1021
+ state,
1022
+ jobState,
1023
+ jobOptions,
1024
+ jobAuth,
1025
+ outcomeSummary,
1026
+ }) {
1027
+ const lookup = () =>
1028
+ sponsorJobRequest({
1029
+ options,
1030
+ auth,
1031
+ state,
1032
+ jobState,
1033
+ path: "/branch-notice",
1034
+ });
1035
+ const existing = await lookup();
1036
+ const existingMessageId = Number(existing.branchNotice?.id || 0);
1037
+ if (existingMessageId) return existingMessageId;
1038
+ try {
1039
+ const notice = await notifyBuildOwnerOfContribution({
1040
+ options: jobOptions,
1041
+ auth: jobAuth,
1042
+ rootBuildId: Number(jobState.job.rootBuild.id),
1043
+ contributionBuildId: Number(jobState.job.contributionBuild.id),
1044
+ note: outcomeSummary,
1045
+ });
1046
+ const messageId = Number(notice?.message?.id || 0);
1047
+ if (!messageId) {
1048
+ throw new Error("Twinkle did not confirm the branch update message.");
1049
+ }
1050
+ return messageId;
1051
+ } catch (error) {
1052
+ for (const delayMs of [250, 750, 1_500]) {
1053
+ await sleep(delayMs);
1054
+ const recovered = await lookup().catch(() => null);
1055
+ const recoveredMessageId = Number(recovered?.branchNotice?.id || 0);
1056
+ if (recoveredMessageId) return recoveredMessageId;
1057
+ }
1058
+ throw error;
1059
+ }
1060
+ }
1061
+
1062
+ async function sponsorJobRequest({
1063
+ options,
1064
+ auth,
1065
+ state,
1066
+ jobState,
1067
+ path: suffix,
1068
+ body = {},
1069
+ }) {
1070
+ return await sponsorRequest({
1071
+ options,
1072
+ auth,
1073
+ method: "POST",
1074
+ path: `/jobs/${Number(jobState.job.id)}${suffix}`,
1075
+ body: {
1076
+ dutySessionId: Number(state.duty.id),
1077
+ attemptToken: jobState.attempt.token,
1078
+ operatorSession: state.operatorSession,
1079
+ dutyLeaseToken: state.duty.leaseToken,
1080
+ ...body,
1081
+ },
1082
+ });
1083
+ }
1084
+
1085
+ async function sponsorRequest({
1086
+ options,
1087
+ auth,
1088
+ method = "GET",
1089
+ path: suffix,
1090
+ body,
1091
+ }) {
1092
+ return await requestJson({
1093
+ method,
1094
+ url: `${options.apiUrl}${SPONSOR_PATH}${suffix}`,
1095
+ authToken: auth.token,
1096
+ body,
1097
+ timeoutMs: options.timeoutMs,
1098
+ });
1099
+ }
1100
+
1101
+ async function ensureSponsorAuth(options) {
1102
+ const auth = await ensureAuth(options);
1103
+ if (Number(auth.userId || 0) > 0) return auth;
1104
+ const session = await requestJson({
1105
+ url: `${options.apiUrl}/cli/session`,
1106
+ authToken: auth.token,
1107
+ timeoutMs: options.timeoutMs,
1108
+ });
1109
+ const userId = Number(session?.userId || 0);
1110
+ if (!Number.isSafeInteger(userId) || userId <= 0) {
1111
+ throw new Error(
1112
+ "Twinkle could not verify which account owns this sponsor duty login.",
1113
+ );
1114
+ }
1115
+ return {
1116
+ ...auth,
1117
+ userId,
1118
+ username: String(session?.username || ""),
1119
+ };
1120
+ }
1121
+
1122
+ async function retrySponsorTransport(operation) {
1123
+ let lastError;
1124
+ for (const delayMs of [0, 250, 750, 1_500]) {
1125
+ if (delayMs) await sleep(delayMs);
1126
+ try {
1127
+ return await operation();
1128
+ } catch (error) {
1129
+ if (Number(error?.status || 0) > 0) throw error;
1130
+ lastError = error;
1131
+ }
1132
+ }
1133
+ throw lastError;
1134
+ }
1135
+
1136
+ async function retrySponsorCheckIn(operation) {
1137
+ let lastError;
1138
+ for (const delayMs of [0, 250, 750, 1_500]) {
1139
+ if (delayMs) await sleep(delayMs);
1140
+ try {
1141
+ return await operation();
1142
+ } catch (error) {
1143
+ if (!isRetryableSponsorRequestError(error)) throw error;
1144
+ lastError = error;
1145
+ }
1146
+ }
1147
+ throw lastError;
1148
+ }
1149
+
1150
+ function isRetryableSponsorRequestError(error) {
1151
+ const status = Number(error?.status || 0);
1152
+ if (!status) return true;
1153
+ return status === 408 || status === 425 || status === 429 || status >= 500;
1154
+ }
1155
+
1156
+ async function loadForumContext({ options, auth, buildId }) {
1157
+ try {
1158
+ const snapshot = await readCompleteBuildForumSnapshot({
1159
+ options: { ...options, limit: 100 },
1160
+ auth,
1161
+ buildId,
1162
+ maxPages: 100,
1163
+ });
1164
+ const events = (snapshot.events || []).slice(-50);
1165
+ return events.length > 0
1166
+ ? JSON.stringify(events).slice(0, MAX_FORUM_CONTEXT_CHARS)
1167
+ : "";
1168
+ } catch (error) {
1169
+ console.error(
1170
+ `lumine: scoped Forum context unavailable (${error?.message || error})`,
1171
+ );
1172
+ return "";
1173
+ }
1174
+ }
1175
+
1176
+ async function writeAssignment(jobState, state) {
1177
+ const unapplied = new Set(unappliedRelayIds(jobState));
1178
+ const relays = (jobState.relays || [])
1179
+ .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")}`;
1191
+ })
1192
+ .join("\n\n");
1193
+ const content = `# Lumine Build Workshop assignment #${jobState.job.id}
1194
+
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.
1196
+
1197
+ 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
+
1199
+ - User: @${jobState.job.requester.username}
1200
+ - Visible teammate: ${displayPersona(jobState.job.persona)}
1201
+ - Main project: ${jobState.job.rootBuild.title} (#${jobState.job.rootBuild.id})
1202
+ - Assigned contribution branch: ${jobState.job.contributionBuild.title} (#${jobState.job.contributionBuild.id})
1203
+ - Workspace: ${jobState.workspaceDir}
1204
+
1205
+ ${relays || "No approved relay text was supplied."}
1206
+
1207
+ ${jobState.forumContext ? `## Scoped Build Forum snapshot\n\n${jobState.forumContext}\n` : ""}
1208
+ ## Duty protocol
1209
+
1210
+ 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 "..."\`.
1215
+ `;
1216
+ await fs.writeFile(jobState.assignmentPath, content, {
1217
+ encoding: "utf8",
1218
+ mode: 0o600,
1219
+ });
1220
+ await fs.chmod(jobState.assignmentPath, 0o600);
1221
+ }
1222
+
1223
+ function mergeCanonicalRelays(jobState, relays) {
1224
+ const byId = new Map(
1225
+ (jobState.relays || []).map((relay) => [Number(relay.id), relay]),
1226
+ );
1227
+ let newRelayCount = 0;
1228
+ for (const relay of relays) {
1229
+ const relayId = Number(relay?.id || 0);
1230
+ if (!relayId) continue;
1231
+ if (!byId.has(relayId)) newRelayCount += 1;
1232
+ byId.set(relayId, relay);
1233
+ }
1234
+ jobState.relays = Array.from(byId.values()).sort(
1235
+ (a, b) => Number(a.id) - Number(b.id),
1236
+ );
1237
+ return newRelayCount;
1238
+ }
1239
+
1240
+ function unappliedRelayIds(jobState) {
1241
+ const applied = new Set((jobState.appliedRelayIds || []).map(Number));
1242
+ return (jobState.relays || [])
1243
+ .map((relay) => Number(relay.id))
1244
+ .filter((relayId) => relayId > 0 && !applied.has(relayId));
1245
+ }
1246
+
1247
+ function jobWorkspaceOptions(options, state, jobState, overrides = {}) {
1248
+ return {
1249
+ ...options,
1250
+ ...overrides,
1251
+ authToken: null,
1252
+ authFile: jobState.authFile,
1253
+ dir: jobState.workspaceDir,
1254
+ target: String(jobState.job.contributionBuild.id),
1255
+ buildIdFlag: String(jobState.job.contributionBuild.id),
1256
+ externalAgentProvider: state.operatorSession.provider,
1257
+ openBrowser: false,
1258
+ quiet: true,
1259
+ publish: false,
1260
+ force: false,
1261
+ skipAssetManifest: true,
1262
+ };
1263
+ }
1264
+
1265
+ async function loadOwnedState({ options, auth, state = null }) {
1266
+ const loaded = state || (await readSponsorState(options));
1267
+ if (loaded.apiUrl !== options.apiUrl) {
1268
+ throw new Error("The saved sponsor duty belongs to a different Twinkle API.");
1269
+ }
1270
+ assertSponsorStateAccount(loaded, auth);
1271
+ const currentSession = detectSponsorAgentSession();
1272
+ if (
1273
+ currentSession.provider !== loaded.operatorSession?.provider ||
1274
+ currentSession.fingerprintHash !==
1275
+ loaded.operatorSession?.fingerprintHash
1276
+ ) {
1277
+ throw new Error(
1278
+ `Sponsor duty #${loaded.duty?.id || "unknown"} belongs to a different live agent session. Stop it from the sponsor account or return to the session that started it.`,
1279
+ );
1280
+ }
1281
+ return loaded;
1282
+ }
1283
+
1284
+ export function sponsorDutyStatePath(options) {
1285
+ const authFile = path.resolve(options.authFile);
1286
+ const contextHash = createHash("sha256")
1287
+ .update(String(options.apiUrl || ""))
1288
+ .update("\0")
1289
+ .update(authFile)
1290
+ .digest("hex")
1291
+ .slice(0, 12);
1292
+ return path.join(
1293
+ path.dirname(authFile),
1294
+ `lumine-sponsor-duty-${contextHash}.json`,
1295
+ );
1296
+ }
1297
+
1298
+ function assertSponsorStateAccount(state, auth) {
1299
+ if (
1300
+ auth.userId &&
1301
+ state?.sponsorUserId &&
1302
+ Number(auth.userId) !== Number(state.sponsorUserId)
1303
+ ) {
1304
+ throw new Error(
1305
+ `The saved sponsor duty belongs to account user ${state.sponsorUserId}. Use a separate --auth-file or return to that account; this login will not alter its lease record.`,
1306
+ );
1307
+ }
1308
+ }
1309
+
1310
+ async function readSponsorState(options, { required = true } = {}) {
1311
+ const filePath = sponsorDutyStatePath(options);
1312
+ try {
1313
+ const state = JSON.parse(await fs.readFile(filePath, "utf8"));
1314
+ if (
1315
+ Number(state?.version) !== STATE_VERSION ||
1316
+ !state?.duty?.id ||
1317
+ !state?.duty?.leaseToken
1318
+ ) {
1319
+ throw new Error(
1320
+ `The saved sponsor duty state at ${filePath} is invalid. Stop the canonical duty before replacing it.`,
1321
+ );
1322
+ }
1323
+ return state;
1324
+ } catch (error) {
1325
+ if (error.code === "ENOENT" && !required) return null;
1326
+ if (error.code === "ENOENT") {
1327
+ throw new Error(
1328
+ "No local agent-owned duty exists. Start one from the Codex or Claude Code session that will do the work.",
1329
+ );
1330
+ }
1331
+ throw error;
1332
+ }
1333
+ }
1334
+
1335
+ async function writeSponsorState(options, state) {
1336
+ const filePath = sponsorDutyStatePath(options);
1337
+ const directory = path.dirname(filePath);
1338
+ await fs.mkdir(directory, { recursive: true, mode: 0o700 });
1339
+ const nextState = {
1340
+ ...state,
1341
+ updatedAt: new Date().toISOString(),
1342
+ };
1343
+ const temporaryPath = `${filePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
1344
+ try {
1345
+ await fs.writeFile(temporaryPath, JSON.stringify(nextState, null, 2), {
1346
+ encoding: "utf8",
1347
+ mode: 0o600,
1348
+ });
1349
+ await fs.chmod(temporaryPath, 0o600);
1350
+ await fs.rename(temporaryPath, filePath);
1351
+ await fs.chmod(filePath, 0o600);
1352
+ } finally {
1353
+ await fs.unlink(temporaryPath).catch((error) => {
1354
+ if (error.code !== "ENOENT") throw error;
1355
+ });
1356
+ }
1357
+ }
1358
+
1359
+ async function removeSponsorState(options) {
1360
+ await fs.unlink(sponsorDutyStatePath(options)).catch((error) => {
1361
+ if (error.code !== "ENOENT") throw error;
1362
+ });
1363
+ }
1364
+
1365
+ async function withSponsorStateLock(options, operation) {
1366
+ const lockPath = `${sponsorDutyStatePath(options)}.lock`;
1367
+ const lockDirectory = path.dirname(lockPath);
1368
+ await fs.mkdir(lockDirectory, { recursive: true, mode: 0o700 });
1369
+ let handle;
1370
+ try {
1371
+ handle = await fs.open(lockPath, "wx", 0o600);
1372
+ } catch (error) {
1373
+ if (error.code !== "EEXIST") throw error;
1374
+ const stale = await sponsorLockIsStale(lockPath);
1375
+ if (!stale) {
1376
+ throw new Error(
1377
+ "Another sponsor duty command is still running for this login. Wait for it to finish instead of starting overlapping watchers or job mutations.",
1378
+ );
1379
+ }
1380
+ await fs.unlink(lockPath);
1381
+ handle = await fs.open(lockPath, "wx", 0o600);
1382
+ }
1383
+ try {
1384
+ await handle.writeFile(
1385
+ JSON.stringify({ pid: process.pid, createdAt: Date.now() }),
1386
+ "utf8",
1387
+ );
1388
+ await handle.chmod(0o600);
1389
+ return await operation();
1390
+ } finally {
1391
+ await handle.close().catch(() => undefined);
1392
+ await fs.unlink(lockPath).catch((error) => {
1393
+ if (error.code !== "ENOENT") throw error;
1394
+ });
1395
+ }
1396
+ }
1397
+
1398
+ async function sponsorLockIsStale(lockPath) {
1399
+ try {
1400
+ const lock = JSON.parse(await fs.readFile(lockPath, "utf8"));
1401
+ const pid = Number(lock?.pid || 0);
1402
+ if (!Number.isSafeInteger(pid) || pid <= 0) return true;
1403
+ try {
1404
+ process.kill(pid, 0);
1405
+ return false;
1406
+ } catch (error) {
1407
+ return error.code === "ESRCH";
1408
+ }
1409
+ } catch (error) {
1410
+ if (error.code === "ENOENT") return true;
1411
+ return false;
1412
+ }
1413
+ }
1414
+
1415
+ async function archiveSponsorState(options) {
1416
+ const filePath = sponsorDutyStatePath(options);
1417
+ const archivePath = `${filePath}.stopped-${Date.now()}`;
1418
+ await fs.rename(filePath, archivePath);
1419
+ await fs.chmod(archivePath, 0o600);
1420
+ return archivePath;
1421
+ }
1422
+
1423
+ async function removeCompletedJob({ options, state, jobId }) {
1424
+ const jobState = requireJobState(state, jobId);
1425
+ const nextJobs = { ...(state.jobs || {}) };
1426
+ delete nextJobs[String(jobId)];
1427
+ state.jobs = nextJobs;
1428
+ await writeSponsorState(options, state);
1429
+ await cleanupJobFiles(jobState).catch((error) => {
1430
+ console.error(
1431
+ `lumine: could not remove completed Workshop directory ${jobState.tempDir} (${error?.message || error})`,
1432
+ );
1433
+ });
1434
+ }
1435
+
1436
+ async function preserveFailedJobWorkspace({ options, state, jobId, reason }) {
1437
+ const jobState = requireJobState(state, jobId);
1438
+ const nextJobs = { ...(state.jobs || {}) };
1439
+ delete nextJobs[String(jobId)];
1440
+ state.jobs = nextJobs;
1441
+ state.preservedWorkspaces = [
1442
+ ...(Array.isArray(state.preservedWorkspaces)
1443
+ ? state.preservedWorkspaces.filter(
1444
+ (item) => String(item?.workspaceDir || "") !== jobState.workspaceDir,
1445
+ )
1446
+ : []),
1447
+ {
1448
+ jobId,
1449
+ workspaceDir: jobState.workspaceDir,
1450
+ reason: reason.slice(0, 1000),
1451
+ preservedAt: new Date().toISOString(),
1452
+ },
1453
+ ];
1454
+ await writeSponsorState(options, state);
1455
+ return jobState.workspaceDir;
1456
+ }
1457
+
1458
+ function dutyWorkspacePaths(state) {
1459
+ return Array.from(
1460
+ new Set(
1461
+ [
1462
+ ...Object.values(state.jobs || {}).map((jobState) =>
1463
+ String(jobState.workspaceDir || ""),
1464
+ ),
1465
+ ...(Array.isArray(state.preservedWorkspaces)
1466
+ ? state.preservedWorkspaces.map((item) =>
1467
+ String(item?.workspaceDir || ""),
1468
+ )
1469
+ : []),
1470
+ ].filter(Boolean),
1471
+ ),
1472
+ );
1473
+ }
1474
+
1475
+ async function cleanupJobFiles(jobState) {
1476
+ const tempRoot = path.resolve(os.tmpdir());
1477
+ const tempDir = path.resolve(String(jobState?.tempDir || ""));
1478
+ if (
1479
+ !tempDir.startsWith(`${tempRoot}${path.sep}`) ||
1480
+ !path.basename(tempDir).startsWith("lumine-") ||
1481
+ !path.basename(tempDir).includes("-job-")
1482
+ ) {
1483
+ throw new Error("Refused to remove an unrecognized Workshop directory.");
1484
+ }
1485
+ await fs.rm(tempDir, { recursive: true, force: true });
1486
+ }
1487
+
1488
+ export function detectSponsorAgentSession({
1489
+ environment = process.env,
1490
+ ancestry = null,
1491
+ } = {}) {
1492
+ const codexSessionId = firstNonEmpty(
1493
+ environment.CODEX_SESSION_ID,
1494
+ environment.CODEX_THREAD_ID,
1495
+ );
1496
+ const claudeSessionId = firstNonEmpty(
1497
+ environment.CLAUDE_CODE_SESSION_ID,
1498
+ environment.CLAUDE_SESSION_ID,
1499
+ environment.CLAUDE_RUNNER_SESSION_ID,
1500
+ environment.CLAUDE_CODE_REMOTE_SESSION_ID,
1501
+ );
1502
+ const detectedAncestry = ancestry || readAgentProcessAncestry();
1503
+ if (codexSessionId && claudeSessionId) {
1504
+ throw new Error(
1505
+ "Lumine found both Codex and Claude Code session IDs. Start duty from a single, directly owning agent session.",
1506
+ );
1507
+ }
1508
+ const codexIdentity = claudeSessionId
1509
+ ? null
1510
+ : codexSessionId || detectedAncestry.codex || null;
1511
+ const claudeIdentity = codexSessionId
1512
+ ? null
1513
+ : claudeSessionId ||
1514
+ (String(environment.CLAUDECODE || "").trim() === "1"
1515
+ ? detectedAncestry.claude
1516
+ : null) ||
1517
+ detectedAncestry.claude ||
1518
+ null;
1519
+ if (codexIdentity && claudeIdentity) {
1520
+ throw new Error(
1521
+ "Lumine found both Codex and Claude Code session signals. Start duty from a single, directly owning agent session.",
1522
+ );
1523
+ }
1524
+ const provider = codexIdentity
1525
+ ? "codex"
1526
+ : claudeIdentity
1527
+ ? "claude-code"
1528
+ : null;
1529
+ const identity = codexIdentity || claudeIdentity;
1530
+ if (!provider || !identity) {
1531
+ throw new Error(
1532
+ "Sponsor duty must be started from an active Codex or Claude Code agent session; a standalone terminal or background supervisor cannot advertise Workshop availability.",
1533
+ );
1534
+ }
1535
+ const bindingEvidence =
1536
+ (provider === "codex" && codexSessionId) ||
1537
+ (provider === "claude-code" && claudeSessionId)
1538
+ ? "runtime_session_id"
1539
+ : "agent_process_ancestry";
1540
+ return {
1541
+ mode: EXECUTION_MODE,
1542
+ provider,
1543
+ fingerprintHash: createHash("sha256")
1544
+ .update(`lumine-sponsor-agent-session\0${provider}\0${identity}`)
1545
+ .digest("hex"),
1546
+ bindingEvidence,
1547
+ runtimeVersion: detectAgentRuntimeVersion(provider),
1548
+ };
1549
+ }
1550
+
1551
+ function readAgentProcessAncestry() {
1552
+ const found = { codex: null, claude: null };
1553
+ let currentPid = process.ppid;
1554
+ const visited = new Set();
1555
+ for (let depth = 0; depth < 12 && currentPid > 1; depth += 1) {
1556
+ if (visited.has(currentPid)) break;
1557
+ visited.add(currentPid);
1558
+ try {
1559
+ const line = execFileSync(
1560
+ "ps",
1561
+ ["-o", "pid=,ppid=,comm=", "-p", String(currentPid)],
1562
+ { encoding: "utf8", timeout: 2_000 },
1563
+ ).trim();
1564
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.+)$/);
1565
+ if (!match) break;
1566
+ const pid = Number(match[1]);
1567
+ const parentPid = Number(match[2]);
1568
+ const command = path.basename(String(match[3] || "").trim()).toLowerCase();
1569
+ if (!found.claude && (command === "claude" || command.startsWith("claude-"))) {
1570
+ found.claude = `process:${pid}`;
1571
+ }
1572
+ if (!found.codex && (command === "codex" || command.startsWith("codex-"))) {
1573
+ found.codex = `process:${pid}`;
1574
+ }
1575
+ currentPid = parentPid;
1576
+ } catch {
1577
+ break;
1578
+ }
1579
+ }
1580
+ return found;
1581
+ }
1582
+
1583
+ function detectAgentRuntimeVersion(provider) {
1584
+ const binary = provider === "claude-code" ? "claude" : "codex";
1585
+ try {
1586
+ return String(
1587
+ execFileSync(binary, ["--version"], {
1588
+ encoding: "utf8",
1589
+ timeout: 5_000,
1590
+ stdio: ["ignore", "pipe", "ignore"],
1591
+ }),
1592
+ )
1593
+ .trim()
1594
+ .slice(0, 64) || null;
1595
+ } catch {
1596
+ return null;
1597
+ }
1598
+ }
1599
+
1600
+ function normalizeDutyProvider(value, operatorSession) {
1601
+ const supplied = String(value || "")
1602
+ .trim()
1603
+ .toLowerCase();
1604
+ if (supplied && !PROVIDERS.has(supplied)) {
1605
+ throw new Error("Pass --provider codex or --provider claude-code.");
1606
+ }
1607
+ if (supplied && supplied !== operatorSession.provider) {
1608
+ throw new Error(
1609
+ `This is a ${displayProvider(operatorSession.provider)} session, so it cannot advertise ${displayProvider(supplied)} duty.`,
1610
+ );
1611
+ }
1612
+ return operatorSession.provider;
1613
+ }
1614
+
1615
+ function requiredRuntimeSetting(value, flag, maximum) {
1616
+ const setting = String(value || "").trim();
1617
+ if (!setting) {
1618
+ throw new Error(
1619
+ `${flag} is required so the actual on-duty agent runtime is recorded.`,
1620
+ );
1621
+ }
1622
+ if (setting.length > maximum) {
1623
+ throw new Error(`${flag} is too long.`);
1624
+ }
1625
+ return setting;
1626
+ }
1627
+
1628
+ function normalizeHelperOrdinal(value, jobState) {
1629
+ const limit = Math.max(0, Number(jobState.job.requestedSubagents || 0));
1630
+ if (limit === 0) {
1631
+ throw new Error("This duty capacity does not allow helpers for this job.");
1632
+ }
1633
+ if (value !== undefined && value !== null && value !== "") {
1634
+ const ordinal = positiveInteger(value, "--ordinal");
1635
+ if (ordinal > limit) {
1636
+ throw new Error(`--ordinal cannot exceed this job's helper limit (${limit}).`);
1637
+ }
1638
+ return ordinal;
1639
+ }
1640
+ for (let ordinal = 1; ordinal <= limit; ordinal += 1) {
1641
+ if (!jobState.helpers?.[String(ordinal)]) return ordinal;
1642
+ }
1643
+ throw new Error(`All ${limit} helper slot(s) are already registered.`);
1644
+ }
1645
+
1646
+ function normalizeWatchMs(value) {
1647
+ const selected = Number(value || DEFAULT_DUTY_WATCH_MS);
1648
+ if (!Number.isSafeInteger(selected) || selected < 1_000) {
1649
+ throw new Error("--wait-ms must be an integer of at least 1000.");
1650
+ }
1651
+ return Math.min(selected, MAX_DUTY_WATCH_MS);
1652
+ }
1653
+
1654
+ function positiveInteger(value, label) {
1655
+ const number = Number(value);
1656
+ if (!Number.isSafeInteger(number) || number <= 0) {
1657
+ throw new Error(`${label} must be a positive integer.`);
1658
+ }
1659
+ return number;
1660
+ }
1661
+
1662
+ function assertNoExtraArgs(args, maximum) {
1663
+ if (args.length > maximum) throw new Error(sponsorJobUsage());
1664
+ }
1665
+
1666
+ function requireJobState(state, jobId) {
1667
+ const jobState = state.jobs?.[String(jobId)];
1668
+ if (!jobState) {
1669
+ throw new Error(
1670
+ `Workshop job #${jobId} is not assigned to this local agent session.`,
1671
+ );
1672
+ }
1673
+ return jobState;
1674
+ }
1675
+
1676
+ function normalizeJobPersona(value) {
1677
+ const persona = String(value || "")
1678
+ .trim()
1679
+ .toLowerCase();
1680
+ if (!PERSONAS.has(persona)) {
1681
+ throw new Error("Twinkle returned an invalid Workshop job assistant.");
1682
+ }
1683
+ return persona;
1684
+ }
1685
+
1686
+ function minimumJobHeartbeatSeconds(state) {
1687
+ const values = Object.values(state.jobs || {})
1688
+ .map((jobState) => Number(jobState.heartbeatEverySeconds || 40))
1689
+ .filter((value) => Number.isFinite(value) && value > 0);
1690
+ return values.length > 0 ? Math.min(...values) : 40;
1691
+ }
1692
+
1693
+ function hashProjectFiles(files) {
1694
+ return Object.fromEntries(
1695
+ files.map((file) => [
1696
+ String(file.path),
1697
+ createHash("sha256").update(String(file.content || "")).digest("hex"),
1698
+ ]),
1699
+ );
1700
+ }
1701
+
1702
+ function digestProjectFiles(files) {
1703
+ const hash = createHash("sha256");
1704
+ for (const file of files) {
1705
+ hash.update(String(file.path));
1706
+ hash.update("\0");
1707
+ hash.update(String(file.content || ""));
1708
+ hash.update("\0");
1709
+ }
1710
+ return hash.digest("hex");
1711
+ }
1712
+
1713
+ function listChangedPaths(before, after) {
1714
+ return Array.from(new Set([...Object.keys(before), ...Object.keys(after)]))
1715
+ .filter((filePath) => before[filePath] !== after[filePath])
1716
+ .sort();
1717
+ }
1718
+
1719
+ function publicOperatorSession(session) {
1720
+ return {
1721
+ mode: session.mode,
1722
+ provider: session.provider,
1723
+ bindingEvidence: session.bindingEvidence,
1724
+ runtimeVersion: session.runtimeVersion || null,
1725
+ fingerprint: session.fingerprintHash.slice(0, 12),
1726
+ };
1727
+ }
1728
+
1729
+ function publicDuty(duty) {
1730
+ const { leaseToken: _leaseToken, ...visibleDuty } = duty || {};
1731
+ return visibleDuty;
1732
+ }
1733
+
1734
+ function jobSummary(jobState) {
1735
+ return {
1736
+ job: jobState.job,
1737
+ workspaceDir: jobState.workspaceDir,
1738
+ assignmentPath: jobState.assignmentPath,
1739
+ relays: jobState.relays || [],
1740
+ appliedRelayIds: jobState.appliedRelayIds || [],
1741
+ unappliedRelayIds: unappliedRelayIds(jobState),
1742
+ coordinator: jobState.coordinator || null,
1743
+ helpers: Object.values(jobState.helpers || {}),
1744
+ };
1745
+ }
1746
+
1747
+ function activeJobSummaries(state) {
1748
+ return Object.values(state.jobs || {}).map(jobSummary);
1749
+ }
1750
+
1751
+ function formatAssignmentLine(assignment) {
1752
+ return `Job #${assignment.job.id}: ${assignment.assignmentPath}`;
1753
+ }
1754
+
1755
+ function displayPersona(persona) {
1756
+ return String(persona || "").toLowerCase() === "ciel" ? "Ciel" : "Zero";
1757
+ }
1758
+
1759
+ function displayProvider(provider) {
1760
+ return provider === "claude-code" ? "Claude Code" : "Codex";
1761
+ }
1762
+
1763
+ function firstNonEmpty(...values) {
1764
+ for (const value of values) {
1765
+ const normalized = String(value || "").trim();
1766
+ if (normalized) return normalized;
1767
+ }
1768
+ return null;
1769
+ }
1770
+
1771
+ async function printDutyStatus(options) {
1772
+ const auth = await ensureSponsorAuth(options);
1773
+ const status = await sponsorRequest({ options, auth, path: "/status" });
1774
+ if (options.json) {
1775
+ console.log(JSON.stringify(status, null, 2));
1776
+ return;
1777
+ }
1778
+ const active = (status.duties || []).filter((duty) =>
1779
+ ["active", "paused"].includes(String(duty.state || "")),
1780
+ );
1781
+ if (active.length === 0) {
1782
+ console.log("No active sponsor duty session.");
1783
+ return;
1784
+ }
1785
+ for (const duty of active) {
1786
+ console.log(
1787
+ `Duty #${duty.id}: ${duty.state} · ${duty.provider} · model=${duty.requestedModel || "missing"} · effort=${duty.requestedEffort || "missing"}`,
1788
+ );
1789
+ }
1790
+ }
1791
+
1792
+ function printJsonOrLines(options, value, lines) {
1793
+ if (options.json) console.log(JSON.stringify(value, null, 2));
1794
+ else for (const line of lines) console.log(line);
1795
+ }
1796
+
1797
+ function sponsorDutyUsage() {
1798
+ return [
1799
+ "Usage:",
1800
+ " lumine sponsor duty start [--provider <codex|claude-code>] --model <name> --effort <level> [--service-tier <tier>]",
1801
+ " lumine sponsor duty watch [--wait-ms <1000-60000>] [--json]",
1802
+ " lumine sponsor duty status|pause|resume|stop",
1803
+ ].join("\n");
1804
+ }
1805
+
1806
+ function sponsorJobUsage() {
1807
+ return [
1808
+ "Usage:",
1809
+ " lumine sponsor job status|pulse <job-id>",
1810
+ " lumine sponsor job begin <job-id>",
1811
+ " lumine sponsor job relay-applied <job-id> <relay-id...>",
1812
+ " lumine sponsor job helper-start <job-id> [--ordinal <n>]",
1813
+ " lumine sponsor job helper-complete <job-id> --ordinal <n> --outcome <text> [--resolved-model <name>] [--resolved-effort <level>]",
1814
+ " lumine sponsor job complete <job-id> --summary <text> [--resolved-model <name>] [--resolved-effort <level>] [--resolved-service-tier <tier>]",
1815
+ " lumine sponsor job fail <job-id> --reason <text>",
1816
+ ].join("\n");
1817
+ }