warpmetal 0.8.13 → 0.9.1

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,800 @@
1
+ import {
2
+ booleanOption,
3
+ integerOption,
4
+ rejectUnknownOptions,
5
+ stringOption,
6
+ } from "./args.js";
7
+ import {
8
+ projectWork,
9
+ projectWorkContent,
10
+ projectWorkList,
11
+ projectWorkOperation,
12
+ projectWorkPolicy,
13
+ projectWorkSources,
14
+ projectWorkStateOperation,
15
+ WORK_BOX_ID,
16
+ WORK_ID,
17
+ WORK_OPERATION_ID,
18
+ WORK_REQUEST_ID,
19
+ workContentQuery,
20
+ workContinuityBody,
21
+ workListQuery,
22
+ workMutationBody,
23
+ workOperationBody,
24
+ workStateBody,
25
+ } from "./contracts/work.js";
26
+ import {
27
+ projectWorkHandoff,
28
+ projectWorkHandoffTargets,
29
+ workHandoffBody,
30
+ } from "./contracts/work-handoff.js";
31
+ import { projectSessionHandoff } from "./contracts/session-handoff.js";
32
+ import { CliError } from "./errors.js";
33
+
34
+ const COMMON_OPTIONS = [
35
+ "base-url",
36
+ "state-dir",
37
+ "token-file",
38
+ "json",
39
+ "help",
40
+ ];
41
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
42
+ const IDEMPOTENCY_KEY = /^[A-Za-z0-9._:-]{8,160}$/;
43
+ const OPERATION_KINDS = new Set([
44
+ "checkpoint",
45
+ "continue",
46
+ "restore",
47
+ "handoff",
48
+ ]);
49
+
50
+ function invalid(message) {
51
+ throw new CliError(message, { exitCode: 2 });
52
+ }
53
+
54
+ function invalidResponse(label = "Work") {
55
+ throw new CliError(`WarpMetal returned invalid ${label} data.`, {
56
+ exitCode: 3,
57
+ });
58
+ }
59
+
60
+ function exactPositionals(positionals, action, count) {
61
+ if (positionals.length !== count + 1 || positionals[0] !== action) {
62
+ invalid(`work ${action} received invalid positional arguments.`);
63
+ }
64
+ return positionals.slice(1);
65
+ }
66
+
67
+ function validateScope(serverId, sandboxId, workId) {
68
+ if (!SAFE_ID.test(serverId) || !WORK_BOX_ID.test(sandboxId)) {
69
+ invalid("Work requires valid SERVER and BOX identifiers.");
70
+ }
71
+ if (workId !== undefined && !WORK_ID.test(workId)) {
72
+ invalid("Work requires a valid WORK identifier.");
73
+ }
74
+ }
75
+
76
+ function fixedPath(serverId, sandboxId, suffix) {
77
+ return `/servers/${encodeURIComponent(serverId)}/sandboxes/${encodeURIComponent(sandboxId)}${suffix}`;
78
+ }
79
+
80
+ function workPath(serverId, sandboxId, workId, suffix = "") {
81
+ return fixedPath(
82
+ serverId,
83
+ sandboxId,
84
+ `/work/${encodeURIComponent(workId)}${suffix}`,
85
+ );
86
+ }
87
+
88
+ function checkedIdempotencyKey(options, body) {
89
+ const key = stringOption(options, "idempotency-key") || body.requestId;
90
+ if (!IDEMPOTENCY_KEY.test(key)) {
91
+ invalid("--idempotency-key must be 8 to 160 safe characters.");
92
+ }
93
+ return key;
94
+ }
95
+
96
+ function checkedRequestId(value, label) {
97
+ if (!WORK_REQUEST_ID.test(value || "")) {
98
+ invalid(`${label} requires a valid request ID.`);
99
+ }
100
+ return value;
101
+ }
102
+
103
+ function checkedOperationId(value, label) {
104
+ if (!WORK_OPERATION_ID.test(value || "")) {
105
+ invalid(`${label} requires a valid operation ID.`);
106
+ }
107
+ return value;
108
+ }
109
+
110
+ function projected(result, project, label) {
111
+ const data = project(result?.data);
112
+ if (!data) invalidResponse(label);
113
+ return { ...result, data };
114
+ }
115
+
116
+ function workHuman(value) {
117
+ const work = value.work;
118
+ return `${work.workId}: ${work.title ?? work.lifecycle} (revision ${work.revision}, ${work.lifecycle})`;
119
+ }
120
+
121
+ function operationHuman(kind, state) {
122
+ if (state === "accepted") {
123
+ if (kind === "checkpoint") return "Checkpoint saved.";
124
+ if (kind === "restore") return "Workspace restored.";
125
+ if (kind === "continue")
126
+ return "Continuation admitted; task outcome pending.";
127
+ return "Handoff admitted; task outcome pending.";
128
+ }
129
+ return `${kind} operation: ${state}`;
130
+ }
131
+
132
+ function emitOperation(services, kind, result) {
133
+ const state = result.data.operation.state;
134
+ services.emit(
135
+ services.context.stdout,
136
+ result.data,
137
+ services.context.json,
138
+ operationHuman(kind, state),
139
+ );
140
+ if (result.status === 202 || ["pending", "reported", "outcome_unknown"].includes(state)) {
141
+ return 8;
142
+ }
143
+ if (["failed", "superseded"].includes(state)) return 5;
144
+ if (state !== "accepted") invalidResponse(`${kind} operation`);
145
+ return 0;
146
+ }
147
+
148
+ function workResult(result, sandboxId, workId) {
149
+ return projected(
150
+ result,
151
+ (value) => {
152
+ const work = value?.work;
153
+ const projectedWork = projectWork(work, sandboxId, workId);
154
+ return projectedWork ? { work: projectedWork } : null;
155
+ },
156
+ "Work",
157
+ );
158
+ }
159
+
160
+ function exactWorkMutationResult(result, sandboxId, workId, body, create) {
161
+ const checked = workResult(result, sandboxId, workId);
162
+ const work = checked.data.work;
163
+ const expectedRevision = create ? 1 : body.expectedRevision + 1;
164
+ if (
165
+ work.revision !== expectedRevision ||
166
+ work.continuityEnabled !== body.continuityEnabled ||
167
+ (body.title !== undefined && work.title !== body.title) ||
168
+ (create &&
169
+ (work.projectId !== body.projectId ||
170
+ work.workspaceEpoch !== body.workspaceEpoch ||
171
+ work.binding.bindingId !== body.managedSession.bindingId ||
172
+ work.binding.registeredSourceId !==
173
+ body.managedSession.registeredSourceId ||
174
+ work.binding.serviceRegistrationId !==
175
+ body.managedSession.serviceRegistrationId ||
176
+ work.binding.nativeSessionId !== body.managedSession.nativeSessionId ||
177
+ work.binding.nativeProjectId !== body.managedSession.nativeProjectId ||
178
+ work.binding.nativeLocationDigest !==
179
+ body.managedSession.nativeLocationDigest))
180
+ ) {
181
+ invalidResponse("Work mutation receipt");
182
+ }
183
+ return checked;
184
+ }
185
+
186
+ async function exactContent(
187
+ services,
188
+ token,
189
+ serverId,
190
+ sandboxId,
191
+ workId,
192
+ revision,
193
+ expected,
194
+ ) {
195
+ const path = `${workPath(serverId, sandboxId, workId, "/content")}?revision=${revision}`;
196
+ const result = projected(
197
+ await services.client.request("GET", path, { token }),
198
+ (value) => projectWorkContent(value, workId, String(revision)),
199
+ "Work content",
200
+ );
201
+ const content = result.data.content;
202
+ if (
203
+ content.objective !== expected.objective ||
204
+ content.constraints !== expected.constraints ||
205
+ content.context !== expected.context
206
+ ) {
207
+ invalidResponse("Work mutation reconciliation");
208
+ }
209
+ }
210
+
211
+ async function readWorkMutationBody(services, options, create) {
212
+ const file = stringOption(options, "file", { required: true });
213
+ const input = await services.readInput(file, 2 * 1024 * 1024);
214
+ const body = workMutationBody(input, create);
215
+ if (!body) invalid("The Work request file is invalid.");
216
+ return body;
217
+ }
218
+
219
+ async function handleList(positionals, options, services) {
220
+ rejectUnknownOptions(options, [...COMMON_OPTIONS, "cursor", "limit"]);
221
+ const [serverId, sandboxId] = exactPositionals(positionals, "list", 2);
222
+ validateScope(serverId, sandboxId);
223
+ const params = new URLSearchParams();
224
+ const cursor = stringOption(options, "cursor");
225
+ const limit = integerOption(options, "limit", undefined);
226
+ if (cursor !== undefined) params.set("cursor", cursor);
227
+ if (limit !== undefined) params.set("limit", String(limit));
228
+ const query = workListQuery(params);
229
+ if (query === null) invalid("The Work list query is invalid.");
230
+ const token = await services.requireServerToken(
231
+ services.store,
232
+ serverId,
233
+ options,
234
+ services.context.env,
235
+ );
236
+ const result = projected(
237
+ await services.client.request(
238
+ "GET",
239
+ `${fixedPath(serverId, sandboxId, "/work")}${query}`,
240
+ { token },
241
+ ),
242
+ (value) => projectWorkList(value, sandboxId),
243
+ "Work list",
244
+ );
245
+ services.emit(
246
+ services.context.stdout,
247
+ result.data,
248
+ services.context.json,
249
+ result.data.work.map((work) => workHuman({ work })).join("\n") ||
250
+ "No retained Work.",
251
+ );
252
+ return 0;
253
+ }
254
+
255
+ async function handleShow(positionals, options, services) {
256
+ rejectUnknownOptions(options, COMMON_OPTIONS);
257
+ const [serverId, sandboxId, workId] = exactPositionals(
258
+ positionals,
259
+ "show",
260
+ 3,
261
+ );
262
+ validateScope(serverId, sandboxId, workId);
263
+ const token = await services.requireServerToken(
264
+ services.store,
265
+ serverId,
266
+ options,
267
+ services.context.env,
268
+ );
269
+ const result = workResult(
270
+ await services.client.request("GET", workPath(serverId, sandboxId, workId), {
271
+ token,
272
+ }),
273
+ sandboxId,
274
+ workId,
275
+ );
276
+ services.emit(
277
+ services.context.stdout,
278
+ result.data,
279
+ services.context.json,
280
+ workHuman(result.data),
281
+ );
282
+ return 0;
283
+ }
284
+
285
+ async function handleContent(positionals, options, services) {
286
+ rejectUnknownOptions(options, [...COMMON_OPTIONS, "revision"]);
287
+ const [serverId, sandboxId, workId] = exactPositionals(
288
+ positionals,
289
+ "content",
290
+ 3,
291
+ );
292
+ validateScope(serverId, sandboxId, workId);
293
+ const revision = integerOption(options, "revision", undefined);
294
+ const params = new URLSearchParams();
295
+ if (revision !== undefined) params.set("revision", String(revision));
296
+ const query = workContentQuery(params);
297
+ if (query === null) invalid("The Work content revision is invalid.");
298
+ const token = await services.requireServerToken(
299
+ services.store,
300
+ serverId,
301
+ options,
302
+ services.context.env,
303
+ );
304
+ const result = projected(
305
+ await services.client.request(
306
+ "GET",
307
+ `${workPath(serverId, sandboxId, workId, "/content")}${query}`,
308
+ { token },
309
+ ),
310
+ (value) =>
311
+ projectWorkContent(
312
+ value,
313
+ workId,
314
+ revision === undefined ? null : String(revision),
315
+ ),
316
+ "Work content",
317
+ );
318
+ services.emit(
319
+ services.context.stdout,
320
+ result.data,
321
+ services.context.json,
322
+ [
323
+ `Objective:\n${result.data.content.objective}`,
324
+ `Constraints:\n${result.data.content.constraints}`,
325
+ `Context:\n${result.data.content.context}`,
326
+ ].join("\n\n"),
327
+ );
328
+ return 0;
329
+ }
330
+
331
+ async function handleSources(positionals, options, services) {
332
+ rejectUnknownOptions(options, [...COMMON_OPTIONS, "cursor", "limit"]);
333
+ const [serverId, sandboxId] = exactPositionals(positionals, "sources", 2);
334
+ validateScope(serverId, sandboxId);
335
+ const params = new URLSearchParams();
336
+ const cursor = stringOption(options, "cursor");
337
+ const limit = integerOption(options, "limit", undefined);
338
+ if (cursor !== undefined) params.set("cursor", cursor);
339
+ if (limit !== undefined) params.set("limit", String(limit));
340
+ const query = workListQuery(params);
341
+ if (query === null) invalid("The Work source query is invalid.");
342
+ const token = await services.requireServerToken(
343
+ services.store,
344
+ serverId,
345
+ options,
346
+ services.context.env,
347
+ );
348
+ const result = projected(
349
+ await services.client.request(
350
+ "GET",
351
+ `${fixedPath(serverId, sandboxId, "/work-sources")}${query}`,
352
+ { token },
353
+ ),
354
+ (value) => projectWorkSources(value, sandboxId),
355
+ "Work source",
356
+ );
357
+ services.emit(
358
+ services.context.stdout,
359
+ result.data,
360
+ services.context.json,
361
+ result.data.sources
362
+ .map(
363
+ (source) =>
364
+ `${source.registeredSourceId}: ${source.role} (${source.availability})`,
365
+ )
366
+ .join("\n") || "No managed Work sources.",
367
+ );
368
+ return 0;
369
+ }
370
+
371
+ async function handlePolicy(positionals, options, services) {
372
+ rejectUnknownOptions(options, COMMON_OPTIONS);
373
+ const [serverId, sandboxId] = exactPositionals(positionals, "policy", 2);
374
+ validateScope(serverId, sandboxId);
375
+ const token = await services.requireServerToken(
376
+ services.store,
377
+ serverId,
378
+ options,
379
+ services.context.env,
380
+ );
381
+ const result = projected(
382
+ await services.client.request(
383
+ "GET",
384
+ fixedPath(serverId, sandboxId, "/work-policy"),
385
+ { token },
386
+ ),
387
+ projectWorkPolicy,
388
+ "Work policy",
389
+ );
390
+ services.emit(
391
+ services.context.stdout,
392
+ result.data,
393
+ services.context.json,
394
+ `Continuity: ${result.data.continuityOptIn.state}\nCheckpoint limit: ${result.data.storagePolicy.maxCheckpointsPerBox} per box`,
395
+ );
396
+ return 0;
397
+ }
398
+
399
+ async function handleWorkMutation(positionals, options, services, create) {
400
+ const action = create ? "create" : "update";
401
+ rejectUnknownOptions(options, [
402
+ ...COMMON_OPTIONS,
403
+ "file",
404
+ "idempotency-key",
405
+ ]);
406
+ const values = exactPositionals(positionals, action, create ? 2 : 3);
407
+ const [serverId, sandboxId, positionalWorkId] = values;
408
+ validateScope(serverId, sandboxId, positionalWorkId);
409
+ const body = await readWorkMutationBody(services, options, create);
410
+ const workId = create ? body.workId : positionalWorkId;
411
+ if (!create && workId !== positionalWorkId) invalid("Work identity changed.");
412
+ const token = await services.requireServerToken(
413
+ services.store,
414
+ serverId,
415
+ options,
416
+ services.context.env,
417
+ );
418
+ const path = create
419
+ ? fixedPath(serverId, sandboxId, "/work")
420
+ : workPath(serverId, sandboxId, workId);
421
+ const result = await services.runMutation({
422
+ serverId,
423
+ sandboxId,
424
+ kind: `work-${action}`,
425
+ path,
426
+ requestId: body.requestId,
427
+ body,
428
+ submit: async () =>
429
+ exactWorkMutationResult(
430
+ await services.client.request(create ? "POST" : "PATCH", path, {
431
+ token,
432
+ body,
433
+ idempotencyKey: checkedIdempotencyKey(options, body),
434
+ }),
435
+ sandboxId,
436
+ workId,
437
+ body,
438
+ create,
439
+ ),
440
+ reconcile: async () => {
441
+ const expectedRevision = create ? 1 : body.expectedRevision + 1;
442
+ const detail = exactWorkMutationResult(
443
+ await services.client.request(
444
+ "GET",
445
+ workPath(serverId, sandboxId, workId),
446
+ { token },
447
+ ),
448
+ sandboxId,
449
+ workId,
450
+ body,
451
+ create,
452
+ );
453
+ await exactContent(
454
+ services,
455
+ token,
456
+ serverId,
457
+ sandboxId,
458
+ workId,
459
+ expectedRevision,
460
+ body.content,
461
+ );
462
+ return detail;
463
+ },
464
+ });
465
+ services.emit(
466
+ services.context.stdout,
467
+ result.data,
468
+ services.context.json,
469
+ `${create ? "Created" : "Updated"} ${workHuman(result.data)}`,
470
+ );
471
+ return 0;
472
+ }
473
+
474
+ async function handleEnable(positionals, options, services) {
475
+ rejectUnknownOptions(options, [
476
+ ...COMMON_OPTIONS,
477
+ "file",
478
+ "idempotency-key",
479
+ ]);
480
+ const [serverId, sandboxId, workId] = exactPositionals(
481
+ positionals,
482
+ "enable",
483
+ 3,
484
+ );
485
+ validateScope(serverId, sandboxId, workId);
486
+ const file = stringOption(options, "file", { required: true });
487
+ const body = workContinuityBody(await services.readInput(file, 64 * 1024));
488
+ if (!body) invalid("The Work continuity request file is invalid.");
489
+ const token = await services.requireServerToken(
490
+ services.store,
491
+ serverId,
492
+ options,
493
+ services.context.env,
494
+ );
495
+ const path = workPath(serverId, sandboxId, workId, "/continuity");
496
+ const verify = (result) => {
497
+ const checked = workResult(result, sandboxId, workId);
498
+ if (
499
+ checked.data.work.revision !== body.expectedRevision + 1 ||
500
+ checked.data.work.continuityEnabled !== body.continuityEnabled
501
+ ) {
502
+ invalidResponse("Work continuity receipt");
503
+ }
504
+ return checked;
505
+ };
506
+ const result = await services.runMutation({
507
+ serverId,
508
+ sandboxId,
509
+ kind: "work-enable",
510
+ path,
511
+ requestId: body.requestId,
512
+ body,
513
+ submit: async () =>
514
+ verify(
515
+ await services.client.request("PATCH", path, {
516
+ token,
517
+ body,
518
+ idempotencyKey: checkedIdempotencyKey(options, body),
519
+ }),
520
+ ),
521
+ reconcile: async () =>
522
+ verify(
523
+ await services.client.request(
524
+ "GET",
525
+ workPath(serverId, sandboxId, workId),
526
+ { token },
527
+ ),
528
+ ),
529
+ });
530
+ services.emit(
531
+ services.context.stdout,
532
+ result.data,
533
+ services.context.json,
534
+ `Continuity ${body.continuityEnabled ? "enabled" : "disabled"} for ${workId}.`,
535
+ );
536
+ return 0;
537
+ }
538
+
539
+ function operationRoute(kind) {
540
+ if (kind === "checkpoint") return "operations";
541
+ if (kind === "continue") return "continuations";
542
+ if (kind === "restore") return "restores";
543
+ return "handoffs";
544
+ }
545
+
546
+ function operationProjector(kind, workId, operationId, requestId) {
547
+ if (kind === "checkpoint") {
548
+ return (value) =>
549
+ projectWorkOperation(value, workId, operationId, requestId);
550
+ }
551
+ if (kind === "handoff") {
552
+ return (value) => projectWorkHandoff(value, workId, operationId, requestId);
553
+ }
554
+ return (value) =>
555
+ projectWorkStateOperation(
556
+ value,
557
+ workId,
558
+ kind,
559
+ operationId,
560
+ requestId,
561
+ );
562
+ }
563
+
564
+ async function handleOperationMutation(
565
+ positionals,
566
+ options,
567
+ services,
568
+ kind,
569
+ ) {
570
+ rejectUnknownOptions(options, [
571
+ ...COMMON_OPTIONS,
572
+ "file",
573
+ "idempotency-key",
574
+ ]);
575
+ const [serverId, sandboxId, workId] = exactPositionals(
576
+ positionals,
577
+ kind,
578
+ 3,
579
+ );
580
+ validateScope(serverId, sandboxId, workId);
581
+ const file = stringOption(options, "file", { required: true });
582
+ const input = await services.readInput(file, 64 * 1024);
583
+ const body =
584
+ kind === "checkpoint"
585
+ ? workOperationBody(input)
586
+ : kind === "handoff"
587
+ ? workHandoffBody(input)
588
+ : workStateBody(input, kind);
589
+ if (!body) invalid(`The Work ${kind} request file is invalid.`);
590
+ const token = await services.requireServerToken(
591
+ services.store,
592
+ serverId,
593
+ options,
594
+ services.context.env,
595
+ );
596
+ const route = operationRoute(kind);
597
+ const path = workPath(serverId, sandboxId, workId, `/${route}`);
598
+ const project = operationProjector(
599
+ kind,
600
+ workId,
601
+ body.operationId,
602
+ body.requestId,
603
+ );
604
+ const result = await services.runMutation({
605
+ serverId,
606
+ sandboxId,
607
+ kind: `work-${kind}`,
608
+ path,
609
+ requestId: body.requestId,
610
+ body,
611
+ submit: async () =>
612
+ projected(
613
+ await services.client.request("POST", path, {
614
+ token,
615
+ body,
616
+ idempotencyKey: checkedIdempotencyKey(options, body),
617
+ }),
618
+ project,
619
+ `Work ${kind} receipt`,
620
+ ),
621
+ reconcile: async () => {
622
+ const query = new URLSearchParams({ requestId: body.requestId });
623
+ return projected(
624
+ await services.client.request("GET", `${path}?${query}`, { token }),
625
+ project,
626
+ `Work ${kind} receipt`,
627
+ );
628
+ },
629
+ });
630
+ return emitOperation(services, kind, result);
631
+ }
632
+
633
+ async function handleTargets(positionals, options, services) {
634
+ rejectUnknownOptions(options, COMMON_OPTIONS);
635
+ const [serverId, sandboxId, workId] = exactPositionals(
636
+ positionals,
637
+ "targets",
638
+ 3,
639
+ );
640
+ validateScope(serverId, sandboxId, workId);
641
+ const token = await services.requireServerToken(
642
+ services.store,
643
+ serverId,
644
+ options,
645
+ services.context.env,
646
+ );
647
+ const result = projected(
648
+ await services.client.request(
649
+ "GET",
650
+ workPath(serverId, sandboxId, workId, "/handoff-targets"),
651
+ { token },
652
+ ),
653
+ projectWorkHandoffTargets,
654
+ "Work handoff target",
655
+ );
656
+ services.emit(
657
+ services.context.stdout,
658
+ result.data,
659
+ services.context.json,
660
+ result.data.targets
661
+ .map((target) => `${target.memberId}: ${target.label} (${target.role})`)
662
+ .join("\n") || "No handoff targets.",
663
+ );
664
+ return 0;
665
+ }
666
+
667
+ async function handleStatus(positionals, options, services) {
668
+ rejectUnknownOptions(options, [
669
+ ...COMMON_OPTIONS,
670
+ "kind",
671
+ "request",
672
+ "operation",
673
+ ]);
674
+ const [serverId, sandboxId, workId] = exactPositionals(
675
+ positionals,
676
+ "status",
677
+ 3,
678
+ );
679
+ validateScope(serverId, sandboxId, workId);
680
+ const kind = stringOption(options, "kind", { required: true });
681
+ if (!OPERATION_KINDS.has(kind)) invalid("--kind is invalid.");
682
+ const requestId = stringOption(options, "request");
683
+ const operationId = stringOption(options, "operation");
684
+ if ((requestId === undefined) === (operationId === undefined)) {
685
+ invalid("work status requires exactly one --request or --operation.");
686
+ }
687
+ if (requestId !== undefined) checkedRequestId(requestId, "work status");
688
+ if (operationId !== undefined)
689
+ checkedOperationId(operationId, "work status");
690
+ const token = await services.requireServerToken(
691
+ services.store,
692
+ serverId,
693
+ options,
694
+ services.context.env,
695
+ );
696
+ const route = operationRoute(kind);
697
+ const base = workPath(serverId, sandboxId, workId, `/${route}`);
698
+ const path =
699
+ operationId !== undefined
700
+ ? `${base}/${encodeURIComponent(operationId)}`
701
+ : `${base}?${new URLSearchParams({ requestId })}`;
702
+ const result = projected(
703
+ await services.client.request("GET", path, { token }),
704
+ operationProjector(kind, workId, operationId, requestId),
705
+ `Work ${kind} status`,
706
+ );
707
+ return emitOperation(services, kind, result);
708
+ }
709
+
710
+ async function handleOpen(positionals, options, services) {
711
+ rejectUnknownOptions(options, [
712
+ ...COMMON_OPTIONS,
713
+ "connection-file",
714
+ "identity",
715
+ ]);
716
+ const [serverId, sandboxId, workId] = exactPositionals(
717
+ positionals,
718
+ "open",
719
+ 3,
720
+ );
721
+ validateScope(serverId, sandboxId, workId);
722
+ const token = await services.requireServerToken(
723
+ services.store,
724
+ serverId,
725
+ options,
726
+ services.context.env,
727
+ );
728
+ const result = projected(
729
+ await services.client.request(
730
+ "GET",
731
+ workPath(serverId, sandboxId, workId, "/session-handoff"),
732
+ { token },
733
+ ),
734
+ (value) => projectSessionHandoff(value, serverId, sandboxId, workId),
735
+ "Work session handoff",
736
+ );
737
+ if (services.context.json) {
738
+ services.emit(
739
+ services.context.stdout,
740
+ result.data,
741
+ true,
742
+ "",
743
+ );
744
+ return result.data.capability === "exact_session" ? 0 : 5;
745
+ }
746
+ if (result.data.capability !== "exact_session") {
747
+ services.emit(
748
+ services.context.stdout,
749
+ result.data,
750
+ false,
751
+ `Session unavailable: ${result.data.reason}`,
752
+ );
753
+ return 5;
754
+ }
755
+ const code = await services.openSessionHandoff(result.data, {
756
+ connectionFile: stringOption(options, "connection-file"),
757
+ identityPath: stringOption(options, "identity"),
758
+ context: services.context,
759
+ });
760
+ return Number.isInteger(code) ? code : 0;
761
+ }
762
+
763
+ export async function handleWork(positionals, options, services) {
764
+ if (Array.isArray(positionals) && positionals[0] === "work") {
765
+ positionals = positionals.slice(1);
766
+ }
767
+ if (!Array.isArray(positionals) || positionals.length === 0) {
768
+ invalid("A Work command is required.");
769
+ }
770
+ if (
771
+ !services?.client ||
772
+ !services.store ||
773
+ !services.context ||
774
+ typeof services.requireServerToken !== "function" ||
775
+ typeof services.emit !== "function" ||
776
+ typeof services.readInput !== "function" ||
777
+ typeof services.runMutation !== "function" ||
778
+ typeof services.openSessionHandoff !== "function"
779
+ ) {
780
+ throw new CliError("The Work command is unavailable.", { exitCode: 1 });
781
+ }
782
+ const action = positionals[0];
783
+ if (action === "list") return handleList(positionals, options, services);
784
+ if (action === "show") return handleShow(positionals, options, services);
785
+ if (action === "content") return handleContent(positionals, options, services);
786
+ if (action === "sources") return handleSources(positionals, options, services);
787
+ if (action === "policy") return handlePolicy(positionals, options, services);
788
+ if (action === "create")
789
+ return handleWorkMutation(positionals, options, services, true);
790
+ if (action === "update")
791
+ return handleWorkMutation(positionals, options, services, false);
792
+ if (action === "enable") return handleEnable(positionals, options, services);
793
+ if (["checkpoint", "continue", "restore", "handoff"].includes(action)) {
794
+ return handleOperationMutation(positionals, options, services, action);
795
+ }
796
+ if (action === "targets") return handleTargets(positionals, options, services);
797
+ if (action === "status") return handleStatus(positionals, options, services);
798
+ if (action === "open") return handleOpen(positionals, options, services);
799
+ invalid(`Unknown Work command: ${action}`);
800
+ }