@ccmsg/protocol 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/common/hello.ts +15 -0
- package/src/fixtures/common.ts +306 -0
- package/src/fixtures/control.ts +557 -0
- package/src/fixtures/envelope.ts +15 -0
- package/src/fixtures/ids.ts +19 -0
- package/src/fixtures/index.ts +166 -0
- package/src/fixtures/messaging.ts +83 -0
- package/src/fixtures/topics.ts +438 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ccmsg/protocol",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Wire contract (schema + types + op attribute table) shared by the ccmsg daemon and web UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kawaz",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"type": "module",
|
|
15
15
|
"main": "./src/index.ts",
|
|
16
16
|
"exports": {
|
|
17
|
-
".": "./src/index.ts"
|
|
17
|
+
".": "./src/index.ts",
|
|
18
|
+
"./fixtures": "./src/fixtures/index.ts"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
21
|
"typecheck": "tsc --noEmit",
|
package/src/common/hello.ts
CHANGED
|
@@ -129,6 +129,21 @@ export const HelloResult = Type.Object({
|
|
|
129
129
|
/** The daemon build, for display. */
|
|
130
130
|
version: Type.String(),
|
|
131
131
|
started_at: Timestamp,
|
|
132
|
+
/** Where a person opens the terminal a session runs in: the base URL of the
|
|
133
|
+
* gateway that fronts this instance's terminals. A session's terminal names
|
|
134
|
+
* itself in `terminal_id` on the `agents` topic, and the gateway's URL for it
|
|
135
|
+
* is `<terminal_gateway>/sessions/<terminal_id>` — so the base URL carries no
|
|
136
|
+
* trailing slash, the path below it being the gateway's spelling and not this
|
|
137
|
+
* contract's.
|
|
138
|
+
*
|
|
139
|
+
* Stated by the instance because only it knows which gateway stands in front
|
|
140
|
+
* of the machine its sessions run on; a client has no way to derive one from
|
|
141
|
+
* the endpoint it reached. Absent where the instance cannot reach its
|
|
142
|
+
* sessions' terminals at all, which is the same condition that leaves the
|
|
143
|
+
* `terminal` capability out of the set above. */
|
|
144
|
+
terminal_gateway: Type.Optional(
|
|
145
|
+
Type.String({ pattern: "^https?://[^/?#\\s]+(/[^?#\\s]*[^/?#\\s])?$" }),
|
|
146
|
+
),
|
|
132
147
|
/** When this connection's authorization runs out, after which the instance
|
|
133
148
|
* closes it. Present on a connection an access token opened; absent where
|
|
134
149
|
* reaching the instance is itself the permission (the Unix socket) or where
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import type { Static } from "@sinclair/typebox";
|
|
2
|
+
import type {
|
|
3
|
+
AuthAssertRequest,
|
|
4
|
+
AuthAssertResponse,
|
|
5
|
+
AuthChallengeRequest,
|
|
6
|
+
AuthChallengeResponse,
|
|
7
|
+
AuthRefreshRequest,
|
|
8
|
+
AuthRefreshResponse,
|
|
9
|
+
AuthRefreshTokenRequest,
|
|
10
|
+
AuthRefreshTokenResponse,
|
|
11
|
+
AuthRegisterRequest,
|
|
12
|
+
AuthRegisterResponse,
|
|
13
|
+
AuthResolveRequest,
|
|
14
|
+
AuthResolveResponse,
|
|
15
|
+
AuthRotateRequest,
|
|
16
|
+
AuthRotateResponse,
|
|
17
|
+
} from "../common/auth.ts";
|
|
18
|
+
import type { HelloRequest, HelloResponse } from "../common/hello.ts";
|
|
19
|
+
import type { InstancePingRequest, InstancePingResponse } from "../common/ping.ts";
|
|
20
|
+
import type {
|
|
21
|
+
InstanceShutdownRequest,
|
|
22
|
+
InstanceShutdownResponse,
|
|
23
|
+
SessionStoppingRequest,
|
|
24
|
+
SessionStoppingResponse,
|
|
25
|
+
} from "../common/shutdown.ts";
|
|
26
|
+
import type {
|
|
27
|
+
TopicSubscribeRequest,
|
|
28
|
+
TopicSubscribeResponse,
|
|
29
|
+
TopicUnsubscribeRequest,
|
|
30
|
+
TopicUnsubscribeResponse,
|
|
31
|
+
} from "../common/topics.ts";
|
|
32
|
+
import { FIXTURE_IDS, FIXTURE_NOW } from "./ids.ts";
|
|
33
|
+
|
|
34
|
+
const { sid, instance, other_instance, endpoint, other_endpoint, request_id } = FIXTURE_IDS;
|
|
35
|
+
|
|
36
|
+
export const HELLO_REQUEST: Static<typeof HelloRequest> = {
|
|
37
|
+
request_id,
|
|
38
|
+
op: "hello",
|
|
39
|
+
role: "session",
|
|
40
|
+
protocol_version: 3,
|
|
41
|
+
sid,
|
|
42
|
+
client_version: "0.1.0",
|
|
43
|
+
repo: "ccmsg-protocol",
|
|
44
|
+
ws: "main",
|
|
45
|
+
cwd: "/repos/kawaz/ccmsg-protocol/main",
|
|
46
|
+
repo_root: "/repos/kawaz/ccmsg-protocol",
|
|
47
|
+
branch: "main",
|
|
48
|
+
transcript_path: "/transcripts/6f1a2b3c.jsonl",
|
|
49
|
+
title: "contract fixtures",
|
|
50
|
+
model: "claude-opus-5",
|
|
51
|
+
effort: "high",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** The other side of `hello`: an instance greeting a peer, which carries the
|
|
55
|
+
* mesh claim in place of a session's meta. */
|
|
56
|
+
export const HELLO_MESH_REQUEST: Static<typeof HelloRequest> = {
|
|
57
|
+
request_id,
|
|
58
|
+
op: "hello",
|
|
59
|
+
role: "instance",
|
|
60
|
+
protocol_version: 3,
|
|
61
|
+
mesh: {
|
|
62
|
+
ver: 1,
|
|
63
|
+
iss: other_endpoint,
|
|
64
|
+
aud: endpoint,
|
|
65
|
+
id: other_instance,
|
|
66
|
+
kid: "9f2c7a5e1b4d8036af51c9e27d604b18",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const HELLO_RESPONSE: Static<typeof HelloResponse> = {
|
|
71
|
+
ok: true,
|
|
72
|
+
request_id,
|
|
73
|
+
protocol_version: 3,
|
|
74
|
+
instance,
|
|
75
|
+
endpoint,
|
|
76
|
+
auth_expires_at: FIXTURE_NOW + 10_000_000,
|
|
77
|
+
instances: [
|
|
78
|
+
{ id: instance, endpoint, host: "mba", reachable: true },
|
|
79
|
+
{ id: other_instance, endpoint: other_endpoint, host: "nuc", reachable: false },
|
|
80
|
+
],
|
|
81
|
+
capabilities: ["fork", "launcher", "terminal"],
|
|
82
|
+
terminal_gateway: "https://mba.example.ts.net/hyoui",
|
|
83
|
+
version: "0.1.0",
|
|
84
|
+
started_at: FIXTURE_NOW - 3_600_000,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const INSTANCE_PING_REQUEST: Static<typeof InstancePingRequest> = {
|
|
88
|
+
request_id,
|
|
89
|
+
op: "instance_ping",
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const INSTANCE_PING_RESPONSE: Static<typeof InstancePingResponse> = {
|
|
93
|
+
ok: true,
|
|
94
|
+
request_id,
|
|
95
|
+
instance,
|
|
96
|
+
version: "0.1.0",
|
|
97
|
+
pid: 4821,
|
|
98
|
+
started_at: FIXTURE_NOW - 3_600_000,
|
|
99
|
+
clients: 3,
|
|
100
|
+
http: ["127.0.0.1:8787"],
|
|
101
|
+
network: "online",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const INSTANCE_SHUTDOWN_REQUEST: Static<typeof InstanceShutdownRequest> = {
|
|
105
|
+
request_id,
|
|
106
|
+
op: "instance_shutdown",
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const INSTANCE_SHUTDOWN_RESPONSE: Static<typeof InstanceShutdownResponse> = {
|
|
110
|
+
ok: true,
|
|
111
|
+
request_id,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const SESSION_STOPPING_REQUEST: Static<typeof SessionStoppingRequest> = {
|
|
115
|
+
request_id,
|
|
116
|
+
op: "session_stopping",
|
|
117
|
+
reason: "prompt_input_exit",
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const SESSION_STOPPING_RESPONSE: Static<typeof SessionStoppingResponse> = {
|
|
121
|
+
ok: true,
|
|
122
|
+
request_id,
|
|
123
|
+
stopped_at: FIXTURE_NOW - 1_000_000,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const TOPIC_SUBSCRIBE_REQUEST: Static<typeof TopicSubscribeRequest> = {
|
|
127
|
+
request_id,
|
|
128
|
+
op: "topic_subscribe",
|
|
129
|
+
topic: "peers",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const TOPIC_SUBSCRIBE_RESPONSE: Static<typeof TopicSubscribeResponse> = {
|
|
133
|
+
ok: true,
|
|
134
|
+
request_id,
|
|
135
|
+
topic: "peers",
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** A subscription naming one session, which is how the session-scoped topics
|
|
139
|
+
* are spelled. */
|
|
140
|
+
export const TOPIC_SUBSCRIBE_SESSION_REQUEST: Static<typeof TopicSubscribeRequest> = {
|
|
141
|
+
request_id,
|
|
142
|
+
op: "topic_subscribe",
|
|
143
|
+
topic: `transcript:${sid}`,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const TOPIC_UNSUBSCRIBE_REQUEST: Static<typeof TopicUnsubscribeRequest> = {
|
|
147
|
+
request_id,
|
|
148
|
+
op: "topic_unsubscribe",
|
|
149
|
+
topic: "peers",
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const TOPIC_UNSUBSCRIBE_RESPONSE: Static<typeof TopicUnsubscribeResponse> = {
|
|
153
|
+
ok: true,
|
|
154
|
+
request_id,
|
|
155
|
+
topic: "peers",
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const CHALLENGE = {
|
|
159
|
+
challenge: "Zm9vYmFyYmF6cXV4MTIzNDU2Nzg5MA",
|
|
160
|
+
issuer: instance,
|
|
161
|
+
expires_at: FIXTURE_NOW + 300_000,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const ACCESS = { value: "YWNjZXNzLXRva2Vu", expires_at: FIXTURE_NOW + 10_000_000 };
|
|
165
|
+
const REFRESH = { value: "cmVmcmVzaA", expires_at: FIXTURE_NOW + 600_000_000 };
|
|
166
|
+
const REGISTER_TOKEN = "eyJhbGciOiJIUzI1NiJ9.e30.c2ln";
|
|
167
|
+
const REGISTER_CODE = "048213";
|
|
168
|
+
const SUBJECT = "personal-1";
|
|
169
|
+
|
|
170
|
+
export const AUTH_CHALLENGE_REQUEST: Static<typeof AuthChallengeRequest> = {
|
|
171
|
+
request_id,
|
|
172
|
+
op: "auth_challenge",
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const AUTH_CHALLENGE_RESPONSE: Static<typeof AuthChallengeResponse> = {
|
|
176
|
+
ok: true,
|
|
177
|
+
request_id,
|
|
178
|
+
...CHALLENGE,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const AUTH_REGISTER_REQUEST: Static<typeof AuthRegisterRequest> = {
|
|
182
|
+
request_id,
|
|
183
|
+
op: "auth_register",
|
|
184
|
+
token: REGISTER_TOKEN,
|
|
185
|
+
code: REGISTER_CODE,
|
|
186
|
+
device_label: "work laptop",
|
|
187
|
+
challenge: CHALLENGE,
|
|
188
|
+
credential: {
|
|
189
|
+
id: "Y3JlZC1pZA",
|
|
190
|
+
raw_id: "Y3JlZC1pZA",
|
|
191
|
+
client_data_json: "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0",
|
|
192
|
+
attestation_object: "o2NmbXRkbm9uZQ",
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const AUTH_REGISTER_RESPONSE: Static<typeof AuthRegisterResponse> = {
|
|
197
|
+
ok: true,
|
|
198
|
+
request_id,
|
|
199
|
+
sub: SUBJECT,
|
|
200
|
+
access: ACCESS,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export const AUTH_ASSERT_REQUEST: Static<typeof AuthAssertRequest> = {
|
|
204
|
+
request_id,
|
|
205
|
+
op: "auth_assert",
|
|
206
|
+
challenge: CHALLENGE,
|
|
207
|
+
credential: {
|
|
208
|
+
raw_id: "Y3JlZC1pZA",
|
|
209
|
+
client_data_json: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0In0",
|
|
210
|
+
authenticator_data: "YXV0aC1kYXRh",
|
|
211
|
+
signature: "c2lnbmF0dXJl",
|
|
212
|
+
user_handle: "dXNlci1oYW5kbGU",
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const AUTH_ASSERT_RESPONSE: Static<typeof AuthAssertResponse> = {
|
|
217
|
+
ok: true,
|
|
218
|
+
request_id,
|
|
219
|
+
sub: SUBJECT,
|
|
220
|
+
access: ACCESS,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const AUTH_REFRESH_TOKEN_REQUEST: Static<typeof AuthRefreshTokenRequest> = {
|
|
224
|
+
request_id,
|
|
225
|
+
op: "auth_refresh_token",
|
|
226
|
+
reason: "reload",
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const AUTH_REFRESH_TOKEN_RESPONSE: Static<typeof AuthRefreshTokenResponse> = {
|
|
230
|
+
ok: true,
|
|
231
|
+
request_id,
|
|
232
|
+
sub: SUBJECT,
|
|
233
|
+
access: ACCESS,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const AUTH_REFRESH_REQUEST: Static<typeof AuthRefreshRequest> = {
|
|
237
|
+
request_id,
|
|
238
|
+
op: "auth_refresh",
|
|
239
|
+
access_token: ACCESS.value,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const AUTH_REFRESH_RESPONSE: Static<typeof AuthRefreshResponse> = {
|
|
243
|
+
ok: true,
|
|
244
|
+
request_id,
|
|
245
|
+
auth_expires_at: FIXTURE_NOW + 20_000_000,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export const AUTH_RESOLVE_REQUEST = {
|
|
249
|
+
request_id,
|
|
250
|
+
op: "auth_resolve",
|
|
251
|
+
to_instance: instance,
|
|
252
|
+
kind: "register",
|
|
253
|
+
token: REGISTER_TOKEN,
|
|
254
|
+
code: REGISTER_CODE,
|
|
255
|
+
} satisfies Static<typeof AuthResolveRequest>;
|
|
256
|
+
|
|
257
|
+
export const AUTH_RESOLVE_RESPONSE = {
|
|
258
|
+
ok: true,
|
|
259
|
+
request_id,
|
|
260
|
+
kind: "register",
|
|
261
|
+
claims: {
|
|
262
|
+
iss: instance,
|
|
263
|
+
sub: SUBJECT,
|
|
264
|
+
unit: "personal",
|
|
265
|
+
endpoint,
|
|
266
|
+
rp_id: "mba.example.ts.net",
|
|
267
|
+
expires_at: FIXTURE_NOW + 600_000,
|
|
268
|
+
jti: "01J9Z3W2Q",
|
|
269
|
+
user_id: "dXNlci1oYW5kbGU",
|
|
270
|
+
issued_label: "for kawaz",
|
|
271
|
+
},
|
|
272
|
+
} satisfies Static<typeof AuthResolveResponse>;
|
|
273
|
+
|
|
274
|
+
/** The other half of the resolve union: spending a challenge, which answers
|
|
275
|
+
* nothing beyond having spent it. */
|
|
276
|
+
export const AUTH_RESOLVE_CHALLENGE_REQUEST = {
|
|
277
|
+
request_id,
|
|
278
|
+
op: "auth_resolve",
|
|
279
|
+
to_instance: instance,
|
|
280
|
+
kind: "challenge",
|
|
281
|
+
challenge: CHALLENGE.challenge,
|
|
282
|
+
} satisfies Static<typeof AuthResolveRequest>;
|
|
283
|
+
|
|
284
|
+
export const AUTH_RESOLVE_CHALLENGE_RESPONSE = {
|
|
285
|
+
ok: true,
|
|
286
|
+
request_id,
|
|
287
|
+
kind: "challenge",
|
|
288
|
+
} satisfies Static<typeof AuthResolveResponse>;
|
|
289
|
+
|
|
290
|
+
export const AUTH_ROTATE_REQUEST: Static<typeof AuthRotateRequest> = {
|
|
291
|
+
request_id,
|
|
292
|
+
op: "auth_rotate",
|
|
293
|
+
to_instance: instance,
|
|
294
|
+
refresh_token: REFRESH.value,
|
|
295
|
+
reason: "reconnect",
|
|
296
|
+
ip: "203.0.113.7",
|
|
297
|
+
user_agent: "Mozilla/5.0",
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export const AUTH_ROTATE_RESPONSE: Static<typeof AuthRotateResponse> = {
|
|
301
|
+
ok: true,
|
|
302
|
+
request_id,
|
|
303
|
+
sub: SUBJECT,
|
|
304
|
+
access: ACCESS,
|
|
305
|
+
refresh: REFRESH,
|
|
306
|
+
};
|