@av-pi-studio/protocol 0.0.116 → 0.0.118
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/dist/client-capabilities.d.ts +5 -0
- package/dist/client-capabilities.js +7 -0
- package/dist/messages.d.ts +1606 -1
- package/dist/messages.js +37 -1
- package/fixtures/external-clients/.signatures.json +174 -0
- package/fixtures/external-clients/CHANGELOG.md +28 -0
- package/fixtures/external-clients/README.md +52 -0
- package/fixtures/external-clients/binary/file_transfer.begin.json +18 -0
- package/fixtures/external-clients/binary/file_transfer.chunk.json +13 -0
- package/fixtures/external-clients/binary/file_transfer.end.json +13 -0
- package/fixtures/external-clients/binary/file_transfer.error.json +13 -0
- package/fixtures/external-clients/binary/manifest.json +59 -0
- package/fixtures/external-clients/binary/terminal.input.json +13 -0
- package/fixtures/external-clients/binary/terminal.output.json +13 -0
- package/fixtures/external-clients/binary/terminal.resize.json +14 -0
- package/fixtures/external-clients/binary/terminal.restore.json +13 -0
- package/fixtures/external-clients/binary/terminal.snapshot.json +13 -0
- package/fixtures/external-clients/capture_terminal_request.json +17 -0
- package/fixtures/external-clients/capture_terminal_response.json +19 -0
- package/fixtures/external-clients/create_terminal_request.json +22 -0
- package/fixtures/external-clients/create_terminal_response.json +31 -0
- package/fixtures/external-clients/file_download_request.json +18 -0
- package/fixtures/external-clients/file_download_response.json +19 -0
- package/fixtures/external-clients/file_download_token_request.json +17 -0
- package/fixtures/external-clients/file_download_token_response.json +19 -0
- package/fixtures/external-clients/file_upload_complete.json +19 -0
- package/fixtures/external-clients/file_upload_request.json +19 -0
- package/fixtures/external-clients/file_upload_response.json +18 -0
- package/fixtures/external-clients/hello.platform.json +20 -0
- package/fixtures/external-clients/kill_terminal_request.json +17 -0
- package/fixtures/external-clients/kill_terminal_response.json +18 -0
- package/fixtures/external-clients/list_terminals_request.json +16 -0
- package/fixtures/external-clients/list_terminals_response.json +33 -0
- package/fixtures/external-clients/manifest.json +115 -0
- package/fixtures/external-clients/rename_terminal_request.json +18 -0
- package/fixtures/external-clients/rename_terminal_response.json +18 -0
- package/fixtures/external-clients/subscribe_terminal_request.json +19 -0
- package/fixtures/external-clients/subscribe_terminal_response.json +21 -0
- package/fixtures/external-clients/terminals_update.json +31 -0
- package/package.json +1 -1
package/dist/messages.js
CHANGED
|
@@ -22,8 +22,15 @@ export const wireTimestampSchema = z.union([z.number(), isoTimestampSchema]);
|
|
|
22
22
|
// ===========================================================================
|
|
23
23
|
// Handshake
|
|
24
24
|
// ===========================================================================
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Client kinds that may connect. The enum is CLOSED on purpose: a daemon up to 0.0.117 fails `hello`
|
|
27
|
+
* validation on an unknown value (silently on a relay connection), so a new kind of client must
|
|
28
|
+
* connect as one of these and say what it is through `hello.platform`. The desktop app connects as
|
|
29
|
+
* `browser` with `platform: "desktop"`.
|
|
30
|
+
*/
|
|
26
31
|
export const clientTypeSchema = z.enum(["mobile", "browser", "cli", "mcp"]);
|
|
32
|
+
/** Longest `hello.platform` the daemon keeps; anything longer is dropped, not rejected. */
|
|
33
|
+
export const PLATFORM_MAX_LENGTH = 32;
|
|
27
34
|
/**
|
|
28
35
|
* Client → Server handshake. Must be the first frame on a connection. `capabilities` advertises
|
|
29
36
|
* the `CLIENT_CAPS.*` flags the client supports (a string→boolean map; see client-capabilities.ts).
|
|
@@ -33,6 +40,12 @@ export const helloSchema = z.object({
|
|
|
33
40
|
clientId: z.string(),
|
|
34
41
|
clientType: clientTypeSchema,
|
|
35
42
|
protocolVersion: z.number(),
|
|
43
|
+
/**
|
|
44
|
+
* Free-form, lowercase platform label (`"desktop"`). Informational: nothing branches on it. An
|
|
45
|
+
* over-long or non-string value is DROPPED (the handshake still succeeds), and daemons that
|
|
46
|
+
* predate the field ignore it, so it is safe to send to any daemon.
|
|
47
|
+
*/
|
|
48
|
+
platform: z.string().max(PLATFORM_MAX_LENGTH).optional().catch(undefined),
|
|
36
49
|
appVersion: z.string().optional(),
|
|
37
50
|
capabilities: z.record(z.string(), z.boolean()).optional(),
|
|
38
51
|
});
|
|
@@ -1074,6 +1087,8 @@ export const daemonClientInfoSchema = z
|
|
|
1074
1087
|
clientId: z.string(),
|
|
1075
1088
|
clientType: z.string(),
|
|
1076
1089
|
appVersion: z.string().optional(),
|
|
1090
|
+
/** The hello's `platform`, when the client sent one. */
|
|
1091
|
+
platform: z.string().optional(),
|
|
1077
1092
|
connectedAt: z.number(),
|
|
1078
1093
|
remoteAddress: z.string().optional(),
|
|
1079
1094
|
transport: z.enum(["direct", "relay"]),
|
|
@@ -1654,6 +1669,26 @@ export const agentTodosUpdateSchema = z
|
|
|
1654
1669
|
})
|
|
1655
1670
|
.passthrough();
|
|
1656
1671
|
// ===========================================================================
|
|
1672
|
+
// File transfer completion (sprint-096, features/desktop-client-protocol-followups.md § 2)
|
|
1673
|
+
// ===========================================================================
|
|
1674
|
+
// The `file_*` request/response RPCs and the binary frames carry no schemas of their own; this push
|
|
1675
|
+
// does because its two consumers (the SDK's `upload()` and external clients) both branch on `ok`.
|
|
1676
|
+
// Sent only to the session whose `file_upload_request` carried `ack: true`.
|
|
1677
|
+
/**
|
|
1678
|
+
* Sent once to the uploading session after the target file was closed (`ok: true`) or the upload
|
|
1679
|
+
* failed (`ok: false`, `error`). A partial file is left on disk after a failure.
|
|
1680
|
+
*/
|
|
1681
|
+
export const fileUploadCompleteSchema = z
|
|
1682
|
+
.object({
|
|
1683
|
+
type: z.literal("file_upload_complete"),
|
|
1684
|
+
transferId: z.string(),
|
|
1685
|
+
stream: z.number().int().nonnegative(),
|
|
1686
|
+
ok: z.boolean(),
|
|
1687
|
+
bytesWritten: z.number().int().nonnegative(),
|
|
1688
|
+
error: z.string().optional(),
|
|
1689
|
+
})
|
|
1690
|
+
.passthrough();
|
|
1691
|
+
// ===========================================================================
|
|
1657
1692
|
// RPC error
|
|
1658
1693
|
// ===========================================================================
|
|
1659
1694
|
export const rpcErrorSchema = z.object({
|
|
@@ -1775,6 +1810,7 @@ export const sessionMessageSchema = z.discriminatedUnion("type", [
|
|
|
1775
1810
|
agentTodosListRequestSchema,
|
|
1776
1811
|
agentTodosListResponseSchema,
|
|
1777
1812
|
agentTodosUpdateSchema,
|
|
1813
|
+
fileUploadCompleteSchema,
|
|
1778
1814
|
rpcErrorSchema,
|
|
1779
1815
|
]);
|
|
1780
1816
|
/**
|
|
@@ -323,6 +323,44 @@
|
|
|
323
323
|
"/message/type": "string",
|
|
324
324
|
"/type": "string"
|
|
325
325
|
},
|
|
326
|
+
"capture_terminal_request.json": {
|
|
327
|
+
"/message/requestId": "string",
|
|
328
|
+
"/message/slot": "number",
|
|
329
|
+
"/message/type": "string",
|
|
330
|
+
"/type": "string"
|
|
331
|
+
},
|
|
332
|
+
"capture_terminal_response.json": {
|
|
333
|
+
"/message/ok": "boolean",
|
|
334
|
+
"/message/requestId": "string",
|
|
335
|
+
"/message/screen": "string",
|
|
336
|
+
"/message/slot": "number",
|
|
337
|
+
"/message/type": "string",
|
|
338
|
+
"/type": "string"
|
|
339
|
+
},
|
|
340
|
+
"create_terminal_request.json": {
|
|
341
|
+
"/message/cols": "number",
|
|
342
|
+
"/message/cwd": "string",
|
|
343
|
+
"/message/name": "string",
|
|
344
|
+
"/message/requestId": "string",
|
|
345
|
+
"/message/rows": "number",
|
|
346
|
+
"/message/shell": "string",
|
|
347
|
+
"/message/type": "string",
|
|
348
|
+
"/message/workspaceId": "string",
|
|
349
|
+
"/type": "string"
|
|
350
|
+
},
|
|
351
|
+
"create_terminal_response.json": {
|
|
352
|
+
"/message/requestId": "string",
|
|
353
|
+
"/message/terminal/closed": "boolean",
|
|
354
|
+
"/message/terminal/cols": "number",
|
|
355
|
+
"/message/terminal/cwd": "string",
|
|
356
|
+
"/message/terminal/name": "string",
|
|
357
|
+
"/message/terminal/rows": "number",
|
|
358
|
+
"/message/terminal/shell": "string",
|
|
359
|
+
"/message/terminal/slot": "number",
|
|
360
|
+
"/message/terminal/workspaceId": "string",
|
|
361
|
+
"/message/type": "string",
|
|
362
|
+
"/type": "string"
|
|
363
|
+
},
|
|
326
364
|
"daemon_log_lines.json": {
|
|
327
365
|
"/message/dropped": "number",
|
|
328
366
|
"/message/lines/0/level": "string",
|
|
@@ -477,6 +515,58 @@
|
|
|
477
515
|
"/message/type": "string",
|
|
478
516
|
"/type": "string"
|
|
479
517
|
},
|
|
518
|
+
"file_download_request.json": {
|
|
519
|
+
"/message/requestId": "string",
|
|
520
|
+
"/message/stream": "number",
|
|
521
|
+
"/message/token": "string",
|
|
522
|
+
"/message/type": "string",
|
|
523
|
+
"/type": "string"
|
|
524
|
+
},
|
|
525
|
+
"file_download_response.json": {
|
|
526
|
+
"/message/ok": "boolean",
|
|
527
|
+
"/message/requestId": "string",
|
|
528
|
+
"/message/stream": "number",
|
|
529
|
+
"/message/transferId": "string",
|
|
530
|
+
"/message/type": "string",
|
|
531
|
+
"/type": "string"
|
|
532
|
+
},
|
|
533
|
+
"file_download_token_request.json": {
|
|
534
|
+
"/message/path": "string",
|
|
535
|
+
"/message/requestId": "string",
|
|
536
|
+
"/message/type": "string",
|
|
537
|
+
"/type": "string"
|
|
538
|
+
},
|
|
539
|
+
"file_download_token_response.json": {
|
|
540
|
+
"/message/expiresAt": "number",
|
|
541
|
+
"/message/ok": "boolean",
|
|
542
|
+
"/message/requestId": "string",
|
|
543
|
+
"/message/token": "string",
|
|
544
|
+
"/message/type": "string",
|
|
545
|
+
"/type": "string"
|
|
546
|
+
},
|
|
547
|
+
"file_upload_complete.json": {
|
|
548
|
+
"/message/bytesWritten": "number",
|
|
549
|
+
"/message/ok": "boolean",
|
|
550
|
+
"/message/stream": "number",
|
|
551
|
+
"/message/transferId": "string",
|
|
552
|
+
"/message/type": "string",
|
|
553
|
+
"/type": "string"
|
|
554
|
+
},
|
|
555
|
+
"file_upload_request.json": {
|
|
556
|
+
"/message/ack": "boolean",
|
|
557
|
+
"/message/path": "string",
|
|
558
|
+
"/message/requestId": "string",
|
|
559
|
+
"/message/transferId": "string",
|
|
560
|
+
"/message/type": "string",
|
|
561
|
+
"/type": "string"
|
|
562
|
+
},
|
|
563
|
+
"file_upload_response.json": {
|
|
564
|
+
"/message/ok": "boolean",
|
|
565
|
+
"/message/requestId": "string",
|
|
566
|
+
"/message/stream": "number",
|
|
567
|
+
"/message/type": "string",
|
|
568
|
+
"/type": "string"
|
|
569
|
+
},
|
|
480
570
|
"health.json": {
|
|
481
571
|
"/body/status": "string",
|
|
482
572
|
"/contentType": "string",
|
|
@@ -490,6 +580,15 @@
|
|
|
490
580
|
"/protocolVersion": "number",
|
|
491
581
|
"/type": "string"
|
|
492
582
|
},
|
|
583
|
+
"hello.platform.json": {
|
|
584
|
+
"/appVersion": "string",
|
|
585
|
+
"/capabilities/reasoning_merge_enum": "boolean",
|
|
586
|
+
"/clientId": "string",
|
|
587
|
+
"/clientType": "string",
|
|
588
|
+
"/platform": "string",
|
|
589
|
+
"/protocolVersion": "number",
|
|
590
|
+
"/type": "string"
|
|
591
|
+
},
|
|
493
592
|
"interrupt_agent.json": {
|
|
494
593
|
"/message/agentId": "string",
|
|
495
594
|
"/message/requestId": "string",
|
|
@@ -503,6 +602,19 @@
|
|
|
503
602
|
"/message/type": "string",
|
|
504
603
|
"/type": "string"
|
|
505
604
|
},
|
|
605
|
+
"kill_terminal_request.json": {
|
|
606
|
+
"/message/requestId": "string",
|
|
607
|
+
"/message/slot": "number",
|
|
608
|
+
"/message/type": "string",
|
|
609
|
+
"/type": "string"
|
|
610
|
+
},
|
|
611
|
+
"kill_terminal_response.json": {
|
|
612
|
+
"/message/ok": "boolean",
|
|
613
|
+
"/message/requestId": "string",
|
|
614
|
+
"/message/slot": "number",
|
|
615
|
+
"/message/type": "string",
|
|
616
|
+
"/type": "string"
|
|
617
|
+
},
|
|
506
618
|
"list_agents_request.json": {
|
|
507
619
|
"/message/requestId": "string",
|
|
508
620
|
"/message/type": "string",
|
|
@@ -522,6 +634,24 @@
|
|
|
522
634
|
"/message/type": "string",
|
|
523
635
|
"/type": "string"
|
|
524
636
|
},
|
|
637
|
+
"list_terminals_request.json": {
|
|
638
|
+
"/message/requestId": "string",
|
|
639
|
+
"/message/type": "string",
|
|
640
|
+
"/type": "string"
|
|
641
|
+
},
|
|
642
|
+
"list_terminals_response.json": {
|
|
643
|
+
"/message/requestId": "string",
|
|
644
|
+
"/message/terminals/0/closed": "boolean",
|
|
645
|
+
"/message/terminals/0/cols": "number",
|
|
646
|
+
"/message/terminals/0/cwd": "string",
|
|
647
|
+
"/message/terminals/0/name": "string",
|
|
648
|
+
"/message/terminals/0/rows": "number",
|
|
649
|
+
"/message/terminals/0/shell": "string",
|
|
650
|
+
"/message/terminals/0/slot": "number",
|
|
651
|
+
"/message/terminals/0/workspaceId": "string",
|
|
652
|
+
"/message/type": "string",
|
|
653
|
+
"/type": "string"
|
|
654
|
+
},
|
|
525
655
|
"ping.json": {
|
|
526
656
|
"/clientSentAt": "number",
|
|
527
657
|
"/requestId": "string",
|
|
@@ -534,6 +664,20 @@
|
|
|
534
664
|
"/serverSentAt": "number",
|
|
535
665
|
"/type": "string"
|
|
536
666
|
},
|
|
667
|
+
"rename_terminal_request.json": {
|
|
668
|
+
"/message/name": "string",
|
|
669
|
+
"/message/requestId": "string",
|
|
670
|
+
"/message/slot": "number",
|
|
671
|
+
"/message/type": "string",
|
|
672
|
+
"/type": "string"
|
|
673
|
+
},
|
|
674
|
+
"rename_terminal_response.json": {
|
|
675
|
+
"/message/ok": "boolean",
|
|
676
|
+
"/message/requestId": "string",
|
|
677
|
+
"/message/slot": "number",
|
|
678
|
+
"/message/type": "string",
|
|
679
|
+
"/type": "string"
|
|
680
|
+
},
|
|
537
681
|
"rpc_error.json": {
|
|
538
682
|
"/message/code": "string",
|
|
539
683
|
"/message/message": "string",
|
|
@@ -554,6 +698,36 @@
|
|
|
554
698
|
"/payload/status": "string",
|
|
555
699
|
"/payload/version": "string",
|
|
556
700
|
"/type": "string"
|
|
701
|
+
},
|
|
702
|
+
"subscribe_terminal_request.json": {
|
|
703
|
+
"/message/cols": "number",
|
|
704
|
+
"/message/requestId": "string",
|
|
705
|
+
"/message/rows": "number",
|
|
706
|
+
"/message/slot": "number",
|
|
707
|
+
"/message/type": "string",
|
|
708
|
+
"/type": "string"
|
|
709
|
+
},
|
|
710
|
+
"subscribe_terminal_response.json": {
|
|
711
|
+
"/message/cols": "number",
|
|
712
|
+
"/message/ok": "boolean",
|
|
713
|
+
"/message/requestId": "string",
|
|
714
|
+
"/message/restoreMode": "string",
|
|
715
|
+
"/message/rows": "number",
|
|
716
|
+
"/message/slot": "number",
|
|
717
|
+
"/message/type": "string",
|
|
718
|
+
"/type": "string"
|
|
719
|
+
},
|
|
720
|
+
"terminals_update.json": {
|
|
721
|
+
"/message/terminals/0/closed": "boolean",
|
|
722
|
+
"/message/terminals/0/cols": "number",
|
|
723
|
+
"/message/terminals/0/cwd": "string",
|
|
724
|
+
"/message/terminals/0/name": "string",
|
|
725
|
+
"/message/terminals/0/rows": "number",
|
|
726
|
+
"/message/terminals/0/shell": "string",
|
|
727
|
+
"/message/terminals/0/slot": "number",
|
|
728
|
+
"/message/terminals/0/workspaceId": "string",
|
|
729
|
+
"/message/type": "string",
|
|
730
|
+
"/type": "string"
|
|
557
731
|
}
|
|
558
732
|
}
|
|
559
733
|
}
|
|
@@ -16,3 +16,31 @@ Stage 2 fixtures, gated by `requiresFeature: "daemonObservability"` (pi-studio 0
|
|
|
16
16
|
`daemon_status_unsubscribe_request`/`_response`, `daemon_status_update`,
|
|
17
17
|
`daemon_logs_subscribe_request`/`_response`, `daemon_logs_unsubscribe_request`/`_response` and
|
|
18
18
|
`daemon_log_lines`. New files only; no existing fixture changed, no migration.
|
|
19
|
+
|
|
20
|
+
## Additive — 2026-11-14 (formatVersion stays 1)
|
|
21
|
+
|
|
22
|
+
`hello.platform.json` (pi-studio 0.0.118): a `hello` variant carrying the new optional
|
|
23
|
+
`platform` string. New file only; no existing fixture changed, no migration. `daemon_status`
|
|
24
|
+
client entries may now also carry `platform` — a field present only when the client sent one, so
|
|
25
|
+
the existing `daemon_status_*` fixtures are unchanged.
|
|
26
|
+
|
|
27
|
+
## Additive — 2026-11-14 (formatVersion stays 1)
|
|
28
|
+
|
|
29
|
+
File-transfer and terminal fixtures (pi-studio 0.0.118): `file_download_token_request`/`_response`,
|
|
30
|
+
`file_download_request`/`_response`, `file_upload_request`/`_response`, `file_upload_complete`
|
|
31
|
+
(`requiresFeature: "fileTransferAck"`, as is the upload request, which now shows `ack: true`),
|
|
32
|
+
`create_terminal_request`/`_response`, `subscribe_terminal_request`/`_response`,
|
|
33
|
+
`list_terminals_request`/`_response`, `rename_terminal_request`/`_response`,
|
|
34
|
+
`capture_terminal_request`/`_response`, `kill_terminal_request`/`_response` and `terminals_update`.
|
|
35
|
+
New files only; no existing fixture changed, no migration. The binary frames are fixtured separately
|
|
36
|
+
(next entry).
|
|
37
|
+
|
|
38
|
+
## Additive — 2026-11-14, binary frames (`binary/`, its own formatVersion 1)
|
|
39
|
+
|
|
40
|
+
`binary/` (pi-studio 0.0.118): `manifest.json` (`formatVersion` 1, independent of the JSON
|
|
41
|
+
manifest's) plus nine frame fixtures — file transfer `Begin`/`Chunk`/`End`/`Error` and terminal
|
|
42
|
+
`Output`/`Input`/`Resize`/`Snapshot`/`Restore` — each a complete frame as `hex` with its `decoded`
|
|
43
|
+
form. New files in a new subdirectory only: `manifest.json`, its `formatVersion` and every JSON
|
|
44
|
+
fixture are untouched, no migration. A consumer that vendors this directory wholesale needs no
|
|
45
|
+
change; one that lists top-level `*.json` against `manifest.json` is unaffected because the new
|
|
46
|
+
files are not top-level.
|
|
@@ -61,6 +61,58 @@ contract the Zod-only protocol cannot give: many list/stat/orchestration respons
|
|
|
61
61
|
never be an exception. Current exceptions: `agent_status`, `agent_ui_resolved.cancelled`.
|
|
62
62
|
- The tool-permission family (`agent_permission_*`, `agent.permission.respond.*`) was retired in
|
|
63
63
|
sprint-086 and deliberately has no fixtures.
|
|
64
|
+
- File-transfer and terminal fixtures (since 0.0.118) cover the JSON control frames only: the
|
|
65
|
+
`file_download_*`/`file_upload_*` RPCs, `file_upload_complete` (gated by `fileTransferAck`, as is
|
|
66
|
+
the `ack` upload request) and the `*_terminal_*` RPCs plus the `terminals_update` broadcast.
|
|
67
|
+
Most of these have no Zod schema, so Layer B is their guard. Download tokens appear as the literal
|
|
68
|
+
`"FAKE"` (volatile). The binary frames are fixtured separately, see "Binary frames" below.
|
|
69
|
+
|
|
70
|
+
## Binary frames
|
|
71
|
+
|
|
72
|
+
File-transfer and terminal data travel in binary frames, not JSON. Their fixtures live in the
|
|
73
|
+
**`binary/` subdirectory** (since 0.0.118), with their own manifest and their own `formatVersion`
|
|
74
|
+
(starting at 1), so `manifest.json` and the JSON `formatVersion` are untouched. They are in a
|
|
75
|
+
subdirectory on purpose: a consumer that checks "every top-level `*.json` is listed in
|
|
76
|
+
`manifest.json`" (the Rust dashboard's vendored-fixture test does) would fail on top-level binary
|
|
77
|
+
files. A plain `rsync -a --delete` of this directory carries `binary/` along harmlessly.
|
|
78
|
+
|
|
79
|
+
| File | Purpose |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `binary/manifest.json` | `{"formatVersion": 1, "frames": [{"file","family","opcode","direction"}]}` — lists the nine frame fixtures; the test fails on an unlisted or missing file |
|
|
82
|
+
| `binary/<family>.<opcode>.json` | One fixture per frame: `file_transfer.{begin,chunk,end,error}`, `terminal.{output,input,resize,snapshot,restore}` |
|
|
83
|
+
|
|
84
|
+
```jsonc
|
|
85
|
+
{
|
|
86
|
+
"family": "file_transfer", // | "terminal"
|
|
87
|
+
"opcode": "Chunk",
|
|
88
|
+
"direction": "both", // | "server_to_client" | "client_to_server"
|
|
89
|
+
"description": "…",
|
|
90
|
+
"since": "0.0.118",
|
|
91
|
+
"hex": "11 03 68 65 6c 6c 6f", // the COMPLETE frame: opcode, stream/slot, payload
|
|
92
|
+
"decoded": { "opcode": "Chunk", "stream": 3, "dataHex": "68 65 6c 6c 6f" }
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- Frame layout is `[opcode][stream or slot][payload]`. File-transfer opcodes: `Begin` 0x10,
|
|
97
|
+
`Chunk` 0x11, `End` 0x12, `Error` 0x13. Terminal opcodes: `Output` 0x01, `Input` 0x02,
|
|
98
|
+
`Resize` 0x03, `Snapshot` 0x04, `Restore` 0x05. File-transfer frames are valid in **both**
|
|
99
|
+
directions (a download is daemon → client, an upload client → daemon).
|
|
100
|
+
- `decoded` is the typed frame. Raw payloads (`Chunk`, `Output`, `Input`, `Snapshot`, `Restore`)
|
|
101
|
+
appear as `dataHex`; `Begin` carries its metadata object, `End` an `ok` boolean, `Error` a
|
|
102
|
+
`message`, `Resize` `rows` and `cols`.
|
|
103
|
+
- **How to use them.** Decode every `hex` with your own codec and compare with `decoded`; encode
|
|
104
|
+
`decoded` with your own codec and compare with `hex`. For `Begin` and `Resize`, whose payload is
|
|
105
|
+
JSON, compare the *parsed* payload rather than the bytes: key order and whitespace are not part of
|
|
106
|
+
the contract (the fixtures show what the TypeScript codec writes). `Snapshot` and `Restore`
|
|
107
|
+
payloads are opaque to the codec; the bytes shown are stand-ins.
|
|
108
|
+
- Layer A for binary frames is `src/external-binary-fixtures.test.ts`: it decodes each `hex` with the
|
|
109
|
+
real codec, encodes `decoded` back to the same bytes, checks the first byte against a table of the
|
|
110
|
+
wire opcode values, and fails on an unlisted or missing file. There is no live-daemon layer.
|
|
111
|
+
- **Breaking-change rule.** A change to an existing frame's `hex` (a different opcode byte, header
|
|
112
|
+
or payload encoding) or to the `decoded` shape is breaking: bump `formatVersion` in
|
|
113
|
+
`binary/manifest.json` and add a dated `CHANGELOG.md` entry naming the file. A new frame fixture
|
|
114
|
+
is additive. This rule is enforced by review and by the round-trip test (a codec edit that moves
|
|
115
|
+
the bytes fails it); there is no signature guard for binary fixtures.
|
|
64
116
|
|
|
65
117
|
## Compatibility rule ("bump + note")
|
|
66
118
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "file_transfer",
|
|
3
|
+
"opcode": "Begin",
|
|
4
|
+
"direction": "both",
|
|
5
|
+
"description": "Opens a transfer: JSON metadata after the two-byte header. Sent by the daemon before a download and by the client before an upload. `transferId` ties an upload to its `file_upload_request`; `totalBytes` is optional.",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "10 03 7b 22 74 72 61 6e 73 66 65 72 49 64 22 3a 22 74 72 61 6e 73 66 65 72 2d 31 22 2c 22 66 69 6c 65 4e 61 6d 65 22 3a 22 68 65 6c 6c 6f 2e 74 78 74 22 2c 22 6d 69 6d 65 54 79 70 65 22 3a 22 74 65 78 74 2f 70 6c 61 69 6e 22 2c 22 74 6f 74 61 6c 42 79 74 65 73 22 3a 35 7d",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "Begin",
|
|
10
|
+
"stream": 3,
|
|
11
|
+
"meta": {
|
|
12
|
+
"transferId": "transfer-1",
|
|
13
|
+
"fileName": "hello.txt",
|
|
14
|
+
"mimeType": "text/plain",
|
|
15
|
+
"totalBytes": 5
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "file_transfer",
|
|
3
|
+
"opcode": "Chunk",
|
|
4
|
+
"direction": "both",
|
|
5
|
+
"description": "Raw file bytes after the header. Senders cut chunks at 32 KiB; the codec accepts any length.",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "11 03 68 65 6c 6c 6f",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "Chunk",
|
|
10
|
+
"stream": 3,
|
|
11
|
+
"dataHex": "68 65 6c 6c 6f"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "file_transfer",
|
|
3
|
+
"opcode": "End",
|
|
4
|
+
"direction": "both",
|
|
5
|
+
"description": "Completion marker: one payload byte, 1 = ok, 0 = aborted. The daemon sends no End after an Error.",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "12 03 01",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "End",
|
|
10
|
+
"stream": 3,
|
|
11
|
+
"ok": true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "file_transfer",
|
|
3
|
+
"opcode": "Error",
|
|
4
|
+
"direction": "both",
|
|
5
|
+
"description": "Aborts a transfer: UTF-8 message after the header. The daemon sends it on a download read error (message `read_failed`) and a client sends it to cancel an upload; no End follows.",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "13 03 72 65 61 64 5f 66 61 69 6c 65 64",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "Error",
|
|
10
|
+
"stream": 3,
|
|
11
|
+
"message": "read_failed"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"formatVersion": 1,
|
|
3
|
+
"frames": [
|
|
4
|
+
{
|
|
5
|
+
"file": "file_transfer.begin.json",
|
|
6
|
+
"family": "file_transfer",
|
|
7
|
+
"opcode": "Begin",
|
|
8
|
+
"direction": "both"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"file": "file_transfer.chunk.json",
|
|
12
|
+
"family": "file_transfer",
|
|
13
|
+
"opcode": "Chunk",
|
|
14
|
+
"direction": "both"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"file": "file_transfer.end.json",
|
|
18
|
+
"family": "file_transfer",
|
|
19
|
+
"opcode": "End",
|
|
20
|
+
"direction": "both"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"file": "file_transfer.error.json",
|
|
24
|
+
"family": "file_transfer",
|
|
25
|
+
"opcode": "Error",
|
|
26
|
+
"direction": "both"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"file": "terminal.output.json",
|
|
30
|
+
"family": "terminal",
|
|
31
|
+
"opcode": "Output",
|
|
32
|
+
"direction": "server_to_client"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"file": "terminal.input.json",
|
|
36
|
+
"family": "terminal",
|
|
37
|
+
"opcode": "Input",
|
|
38
|
+
"direction": "client_to_server"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"file": "terminal.resize.json",
|
|
42
|
+
"family": "terminal",
|
|
43
|
+
"opcode": "Resize",
|
|
44
|
+
"direction": "client_to_server"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"file": "terminal.snapshot.json",
|
|
48
|
+
"family": "terminal",
|
|
49
|
+
"opcode": "Snapshot",
|
|
50
|
+
"direction": "server_to_client"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"file": "terminal.restore.json",
|
|
54
|
+
"family": "terminal",
|
|
55
|
+
"opcode": "Restore",
|
|
56
|
+
"direction": "server_to_client"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "terminal",
|
|
3
|
+
"opcode": "Input",
|
|
4
|
+
"direction": "client_to_server",
|
|
5
|
+
"description": "Keystrokes for one slot, passed through verbatim to the PTY.",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "02 01 6c 73 0d",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "Input",
|
|
10
|
+
"slot": 1,
|
|
11
|
+
"dataHex": "6c 73 0d"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "terminal",
|
|
3
|
+
"opcode": "Output",
|
|
4
|
+
"direction": "server_to_client",
|
|
5
|
+
"description": "Terminal output bytes for one subscribed slot, passed through verbatim (here: green text, then a CRLF).",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "01 01 1b 5b 33 32 6d 6f 6b 1b 5b 30 6d 0d 0a",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "Output",
|
|
10
|
+
"slot": 1,
|
|
11
|
+
"dataHex": "1b 5b 33 32 6d 6f 6b 1b 5b 30 6d 0d 0a"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "terminal",
|
|
3
|
+
"opcode": "Resize",
|
|
4
|
+
"direction": "client_to_server",
|
|
5
|
+
"description": "Window size for one slot: UTF-8 JSON `{rows, cols}` after the header.",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "03 01 7b 22 72 6f 77 73 22 3a 32 34 2c 22 63 6f 6c 73 22 3a 38 30 7d",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "Resize",
|
|
10
|
+
"slot": 1,
|
|
11
|
+
"rows": 24,
|
|
12
|
+
"cols": 80
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "terminal",
|
|
3
|
+
"opcode": "Restore",
|
|
4
|
+
"direction": "server_to_client",
|
|
5
|
+
"description": "Reflowable-tier restore, sent only to a client that advertised `terminal_reflowable_snapshot`: a serialization of the daemon screen model. The payload is opaque to the codec; the text here is a stand-in.",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "05 01 72 65 73 74 6f 72 65 2d 70 61 79 6c 6f 61 64",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "Restore",
|
|
10
|
+
"slot": 1,
|
|
11
|
+
"dataHex": "72 65 73 74 6f 72 65 2d 70 61 79 6c 6f 61 64"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "terminal",
|
|
3
|
+
"opcode": "Snapshot",
|
|
4
|
+
"direction": "server_to_client",
|
|
5
|
+
"description": "Raw-tier restore for a subscription that did not negotiate `terminal_reflowable_snapshot`: the screen as bytes to replay. The payload is opaque to the codec.",
|
|
6
|
+
"since": "0.0.118",
|
|
7
|
+
"hex": "04 01 24 20",
|
|
8
|
+
"decoded": {
|
|
9
|
+
"opcode": "Snapshot",
|
|
10
|
+
"slot": 1,
|
|
11
|
+
"dataHex": "24 20"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "capture_terminal_request",
|
|
3
|
+
"direction": "client_to_server",
|
|
4
|
+
"description": "One-shot screen text of a terminal; does not subscribe.",
|
|
5
|
+
"since": "0.0.118",
|
|
6
|
+
"stage": 3,
|
|
7
|
+
"requiresFeature": null,
|
|
8
|
+
"volatile": ["/message/requestId", "/message/slot"],
|
|
9
|
+
"frame": {
|
|
10
|
+
"type": "session",
|
|
11
|
+
"message": {
|
|
12
|
+
"type": "capture_terminal_request",
|
|
13
|
+
"requestId": "req-1",
|
|
14
|
+
"slot": 1
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "capture_terminal_response",
|
|
3
|
+
"direction": "server_to_client",
|
|
4
|
+
"description": "Current screen text; `ok:false` with an empty `screen` when the slot does not exist.",
|
|
5
|
+
"since": "0.0.118",
|
|
6
|
+
"stage": 3,
|
|
7
|
+
"requiresFeature": null,
|
|
8
|
+
"volatile": ["/message/requestId", "/message/slot", "/message/screen"],
|
|
9
|
+
"frame": {
|
|
10
|
+
"type": "session",
|
|
11
|
+
"message": {
|
|
12
|
+
"type": "capture_terminal_response",
|
|
13
|
+
"slot": 1,
|
|
14
|
+
"ok": true,
|
|
15
|
+
"screen": "$ ",
|
|
16
|
+
"requestId": "req-1"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "create_terminal_request",
|
|
3
|
+
"direction": "client_to_server",
|
|
4
|
+
"description": "Spawn a PTY terminal. `shell`, `cwd`, `name`, `cols` and `rows` are optional.",
|
|
5
|
+
"since": "0.0.118",
|
|
6
|
+
"stage": 3,
|
|
7
|
+
"requiresFeature": null,
|
|
8
|
+
"volatile": ["/message/requestId", "/message/cwd"],
|
|
9
|
+
"frame": {
|
|
10
|
+
"type": "session",
|
|
11
|
+
"message": {
|
|
12
|
+
"type": "create_terminal_request",
|
|
13
|
+
"requestId": "req-1",
|
|
14
|
+
"workspaceId": "workspace-1",
|
|
15
|
+
"cwd": "/tmp/pi-studio-fixture",
|
|
16
|
+
"shell": "/bin/sh",
|
|
17
|
+
"name": "terminal-1",
|
|
18
|
+
"cols": 80,
|
|
19
|
+
"rows": 24
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|