@pocketcoder/shared 0.0.2
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/LICENSE +53 -0
- package/dist/constants/__tests__/limits.test.d.ts +2 -0
- package/dist/constants/__tests__/limits.test.d.ts.map +1 -0
- package/dist/constants/__tests__/limits.test.js +68 -0
- package/dist/constants/__tests__/limits.test.js.map +1 -0
- package/dist/constants/errors.d.ts +48 -0
- package/dist/constants/errors.d.ts.map +1 -0
- package/dist/constants/errors.js +61 -0
- package/dist/constants/errors.js.map +1 -0
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +4 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/limits.d.ts +41 -0
- package/dist/constants/limits.d.ts.map +1 -0
- package/dist/constants/limits.js +46 -0
- package/dist/constants/limits.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types/__tests__/auth-messages.test.d.ts +2 -0
- package/dist/types/__tests__/auth-messages.test.d.ts.map +1 -0
- package/dist/types/__tests__/auth-messages.test.js +94 -0
- package/dist/types/__tests__/auth-messages.test.js.map +1 -0
- package/dist/types/__tests__/messages.test.d.ts +2 -0
- package/dist/types/__tests__/messages.test.d.ts.map +1 -0
- package/dist/types/__tests__/messages.test.js +329 -0
- package/dist/types/__tests__/messages.test.js.map +1 -0
- package/dist/types/__tests__/relay-types.test.d.ts +2 -0
- package/dist/types/__tests__/relay-types.test.d.ts.map +1 -0
- package/dist/types/__tests__/relay-types.test.js +69 -0
- package/dist/types/__tests__/relay-types.test.js.map +1 -0
- package/dist/types/auth-messages.d.ts +49 -0
- package/dist/types/auth-messages.d.ts.map +1 -0
- package/dist/types/auth-messages.js +9 -0
- package/dist/types/auth-messages.js.map +1 -0
- package/dist/types/database.d.ts +259 -0
- package/dist/types/database.d.ts.map +1 -0
- package/dist/types/database.js +6 -0
- package/dist/types/database.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/messages.d.ts +821 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +122 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/relay.d.ts +62 -0
- package/dist/types/relay.d.ts.map +1 -0
- package/dist/types/relay.js +8 -0
- package/dist/types/relay.js.map +1 -0
- package/package.json +49 -0
- package/src/constants/__tests__/limits.test.ts +93 -0
- package/src/constants/errors.ts +71 -0
- package/src/constants/index.ts +3 -0
- package/src/constants/limits.ts +53 -0
- package/src/index.ts +3 -0
- package/src/types/__tests__/auth-messages.test.ts +108 -0
- package/src/types/__tests__/messages.test.ts +382 -0
- package/src/types/__tests__/relay-types.test.ts +82 -0
- package/src/types/auth-messages.ts +66 -0
- package/src/types/database.ts +320 -0
- package/src/types/index.ts +5 -0
- package/src/types/messages.ts +1310 -0
- package/src/types/relay.ts +79 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isFsListRequest,
|
|
5
|
+
isFsReadRequest,
|
|
6
|
+
isGitStatusRequest,
|
|
7
|
+
isAgentMessageRequest,
|
|
8
|
+
isErrorResponse,
|
|
9
|
+
isMessage,
|
|
10
|
+
isRequestMessage,
|
|
11
|
+
isAgentInitEvent,
|
|
12
|
+
isAgentDoneEvent,
|
|
13
|
+
isAgentEvent,
|
|
14
|
+
} from "../messages.js";
|
|
15
|
+
import type { AgentEvent } from "../messages.js";
|
|
16
|
+
|
|
17
|
+
describe("message type guards", () => {
|
|
18
|
+
describe("isFsListRequest", () => {
|
|
19
|
+
it("should validate fs_list message", () => {
|
|
20
|
+
const msg = {
|
|
21
|
+
type: "fs_list",
|
|
22
|
+
requestId: "req_123",
|
|
23
|
+
sessionId: "proj_abc",
|
|
24
|
+
payload: { path: "src/components", depth: 1 },
|
|
25
|
+
};
|
|
26
|
+
expect(isFsListRequest(msg)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should reject message with wrong type", () => {
|
|
30
|
+
const msg = {
|
|
31
|
+
type: "fs_read",
|
|
32
|
+
requestId: "req_123",
|
|
33
|
+
sessionId: "proj_abc",
|
|
34
|
+
payload: { path: "src" },
|
|
35
|
+
};
|
|
36
|
+
expect(isFsListRequest(msg)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should reject message missing requestId", () => {
|
|
40
|
+
const msg = {
|
|
41
|
+
type: "fs_list",
|
|
42
|
+
sessionId: "proj_abc",
|
|
43
|
+
payload: { path: "" },
|
|
44
|
+
};
|
|
45
|
+
expect(isFsListRequest(msg)).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should reject message missing sessionId", () => {
|
|
49
|
+
const msg = {
|
|
50
|
+
type: "fs_list",
|
|
51
|
+
requestId: "req_123",
|
|
52
|
+
payload: { path: "" },
|
|
53
|
+
};
|
|
54
|
+
expect(isFsListRequest(msg)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should reject message missing payload.path", () => {
|
|
58
|
+
const msg = {
|
|
59
|
+
type: "fs_list",
|
|
60
|
+
requestId: "req_123",
|
|
61
|
+
sessionId: "proj_abc",
|
|
62
|
+
payload: {},
|
|
63
|
+
};
|
|
64
|
+
expect(isFsListRequest(msg)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should reject non-object values", () => {
|
|
68
|
+
expect(isFsListRequest(null)).toBe(false);
|
|
69
|
+
expect(isFsListRequest(undefined)).toBe(false);
|
|
70
|
+
expect(isFsListRequest("fs_list")).toBe(false);
|
|
71
|
+
expect(isFsListRequest(42)).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("isFsReadRequest", () => {
|
|
76
|
+
it("should validate fs_read message", () => {
|
|
77
|
+
const msg = {
|
|
78
|
+
type: "fs_read",
|
|
79
|
+
requestId: "req_456",
|
|
80
|
+
sessionId: "proj_abc",
|
|
81
|
+
payload: { path: "src/index.ts" },
|
|
82
|
+
};
|
|
83
|
+
expect(isFsReadRequest(msg)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should reject message with wrong type", () => {
|
|
87
|
+
const msg = {
|
|
88
|
+
type: "fs_list",
|
|
89
|
+
requestId: "req_456",
|
|
90
|
+
sessionId: "proj_abc",
|
|
91
|
+
payload: { path: "src/index.ts" },
|
|
92
|
+
};
|
|
93
|
+
expect(isFsReadRequest(msg)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should reject message missing path", () => {
|
|
97
|
+
const msg = {
|
|
98
|
+
type: "fs_read",
|
|
99
|
+
requestId: "req_456",
|
|
100
|
+
sessionId: "proj_abc",
|
|
101
|
+
payload: {},
|
|
102
|
+
};
|
|
103
|
+
expect(isFsReadRequest(msg)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("isGitStatusRequest", () => {
|
|
108
|
+
it("should validate git_status message", () => {
|
|
109
|
+
const msg = {
|
|
110
|
+
type: "git_status",
|
|
111
|
+
requestId: "req_100",
|
|
112
|
+
sessionId: "proj_abc",
|
|
113
|
+
payload: {},
|
|
114
|
+
};
|
|
115
|
+
expect(isGitStatusRequest(msg)).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should reject message with wrong type", () => {
|
|
119
|
+
const msg = {
|
|
120
|
+
type: "git_branches",
|
|
121
|
+
requestId: "req_100",
|
|
122
|
+
sessionId: "proj_abc",
|
|
123
|
+
payload: {},
|
|
124
|
+
};
|
|
125
|
+
expect(isGitStatusRequest(msg)).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("isAgentMessageRequest", () => {
|
|
130
|
+
it("should validate agent_message", () => {
|
|
131
|
+
const msg = {
|
|
132
|
+
type: "agent_message",
|
|
133
|
+
requestId: "req_202",
|
|
134
|
+
sessionId: "proj_abc",
|
|
135
|
+
payload: {
|
|
136
|
+
agentSessionId: "10ca3c7c-ece4-4c98-a251-365dad2a34ad",
|
|
137
|
+
content: "Now add tests for the component",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
expect(isAgentMessageRequest(msg)).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should reject message missing agentSessionId", () => {
|
|
144
|
+
const msg = {
|
|
145
|
+
type: "agent_message",
|
|
146
|
+
requestId: "req_202",
|
|
147
|
+
sessionId: "proj_abc",
|
|
148
|
+
payload: { content: "hello" },
|
|
149
|
+
};
|
|
150
|
+
expect(isAgentMessageRequest(msg)).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should reject message missing content", () => {
|
|
154
|
+
const msg = {
|
|
155
|
+
type: "agent_message",
|
|
156
|
+
requestId: "req_202",
|
|
157
|
+
sessionId: "proj_abc",
|
|
158
|
+
payload: { agentSessionId: "abc" },
|
|
159
|
+
};
|
|
160
|
+
expect(isAgentMessageRequest(msg)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("isErrorResponse", () => {
|
|
165
|
+
it("should validate error response", () => {
|
|
166
|
+
const msg = {
|
|
167
|
+
type: "error",
|
|
168
|
+
requestId: "req_xxx",
|
|
169
|
+
error: {
|
|
170
|
+
code: "FILE_TOO_LARGE",
|
|
171
|
+
message: "File exceeds maximum size limit",
|
|
172
|
+
details: { path: "large-file.bin", size: 15728640, limit: 10485760 },
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
expect(isErrorResponse(msg)).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should validate error response without details", () => {
|
|
179
|
+
const msg = {
|
|
180
|
+
type: "error",
|
|
181
|
+
requestId: "req_xxx",
|
|
182
|
+
error: {
|
|
183
|
+
code: "UNKNOWN_ERROR",
|
|
184
|
+
message: "Something went wrong",
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
expect(isErrorResponse(msg)).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should reject non-error type", () => {
|
|
191
|
+
const msg = {
|
|
192
|
+
type: "fs_list",
|
|
193
|
+
requestId: "req_xxx",
|
|
194
|
+
error: { code: "UNKNOWN", message: "test" },
|
|
195
|
+
};
|
|
196
|
+
expect(isErrorResponse(msg)).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should reject missing error.code", () => {
|
|
200
|
+
const msg = {
|
|
201
|
+
type: "error",
|
|
202
|
+
requestId: "req_xxx",
|
|
203
|
+
error: { message: "test" },
|
|
204
|
+
};
|
|
205
|
+
expect(isErrorResponse(msg)).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should reject missing requestId", () => {
|
|
209
|
+
const msg = {
|
|
210
|
+
type: "error",
|
|
211
|
+
error: { code: "UNKNOWN_ERROR", message: "test" },
|
|
212
|
+
};
|
|
213
|
+
expect(isErrorResponse(msg)).toBe(false);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("isMessage", () => {
|
|
218
|
+
it("should accept any object with a type field", () => {
|
|
219
|
+
expect(isMessage({ type: "fs_list" })).toBe(true);
|
|
220
|
+
expect(isMessage({ type: "error" })).toBe(true);
|
|
221
|
+
expect(isMessage({ type: "daemon_register" })).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should reject objects without type", () => {
|
|
225
|
+
expect(isMessage({})).toBe(false);
|
|
226
|
+
expect(isMessage({ requestId: "abc" })).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should reject non-objects", () => {
|
|
230
|
+
expect(isMessage(null)).toBe(false);
|
|
231
|
+
expect(isMessage(undefined)).toBe(false);
|
|
232
|
+
expect(isMessage("string")).toBe(false);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("isRequestMessage", () => {
|
|
237
|
+
it("should accept messages with type and requestId", () => {
|
|
238
|
+
expect(isRequestMessage({ type: "fs_list", requestId: "req_1" })).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should reject messages without requestId", () => {
|
|
242
|
+
expect(isRequestMessage({ type: "fs_changed" })).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe("isAgentInitEvent", () => {
|
|
247
|
+
it("should validate init AgentEvent", () => {
|
|
248
|
+
const event = {
|
|
249
|
+
type: "init",
|
|
250
|
+
sessionId: "session-abc-123",
|
|
251
|
+
tools: ["Read", "Write", "Edit", "Bash"],
|
|
252
|
+
model: "claude-sonnet-4-5-20250929",
|
|
253
|
+
};
|
|
254
|
+
expect(isAgentInitEvent(event)).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should reject init event missing sessionId", () => {
|
|
258
|
+
const event = {
|
|
259
|
+
type: "init",
|
|
260
|
+
tools: ["Read"],
|
|
261
|
+
model: "claude-sonnet-4-5-20250929",
|
|
262
|
+
};
|
|
263
|
+
expect(isAgentInitEvent(event)).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should reject init event missing tools", () => {
|
|
267
|
+
const event = {
|
|
268
|
+
type: "init",
|
|
269
|
+
sessionId: "session-abc",
|
|
270
|
+
model: "claude-sonnet-4-5-20250929",
|
|
271
|
+
};
|
|
272
|
+
expect(isAgentInitEvent(event)).toBe(false);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should reject init event missing model", () => {
|
|
276
|
+
const event = {
|
|
277
|
+
type: "init",
|
|
278
|
+
sessionId: "session-abc",
|
|
279
|
+
tools: ["Read"],
|
|
280
|
+
};
|
|
281
|
+
expect(isAgentInitEvent(event)).toBe(false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should reject non-init type", () => {
|
|
285
|
+
const event = { type: "text", content: "hello" };
|
|
286
|
+
expect(isAgentInitEvent(event)).toBe(false);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("should reject non-object values", () => {
|
|
290
|
+
expect(isAgentInitEvent(null)).toBe(false);
|
|
291
|
+
expect(isAgentInitEvent(undefined)).toBe(false);
|
|
292
|
+
expect(isAgentInitEvent("init")).toBe(false);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("isAgentDoneEvent", () => {
|
|
297
|
+
it("should validate done AgentEvent with usage", () => {
|
|
298
|
+
const event = {
|
|
299
|
+
type: "done",
|
|
300
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
301
|
+
};
|
|
302
|
+
expect(isAgentDoneEvent(event)).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should validate done AgentEvent with error", () => {
|
|
306
|
+
const event = {
|
|
307
|
+
type: "done",
|
|
308
|
+
error: "Something went wrong",
|
|
309
|
+
};
|
|
310
|
+
expect(isAgentDoneEvent(event)).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("should validate done AgentEvent with cancelled", () => {
|
|
314
|
+
const event = {
|
|
315
|
+
type: "done",
|
|
316
|
+
cancelled: true,
|
|
317
|
+
};
|
|
318
|
+
expect(isAgentDoneEvent(event)).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should validate minimal done AgentEvent (no optional fields)", () => {
|
|
322
|
+
const event = { type: "done" };
|
|
323
|
+
expect(isAgentDoneEvent(event)).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("should reject done event with invalid usage", () => {
|
|
327
|
+
const event = {
|
|
328
|
+
type: "done",
|
|
329
|
+
usage: { input_tokens: "not-a-number" },
|
|
330
|
+
};
|
|
331
|
+
expect(isAgentDoneEvent(event)).toBe(false);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should reject done event with non-string error", () => {
|
|
335
|
+
const event = {
|
|
336
|
+
type: "done",
|
|
337
|
+
error: 42,
|
|
338
|
+
};
|
|
339
|
+
expect(isAgentDoneEvent(event)).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("should reject non-done type", () => {
|
|
343
|
+
const event = { type: "text", content: "hello" };
|
|
344
|
+
expect(isAgentDoneEvent(event)).toBe(false);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe("isAgentEvent", () => {
|
|
349
|
+
it("should include init in AgentEvent union", () => {
|
|
350
|
+
const event: AgentEvent = {
|
|
351
|
+
type: "init",
|
|
352
|
+
sessionId: "s1",
|
|
353
|
+
tools: ["Read"],
|
|
354
|
+
model: "claude-sonnet-4-5-20250929",
|
|
355
|
+
};
|
|
356
|
+
expect(isAgentEvent(event)).toBe(true);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("should validate all AgentEvent types", () => {
|
|
360
|
+
expect(isAgentEvent({ type: "init", sessionId: "s", tools: [], model: "m" })).toBe(true);
|
|
361
|
+
expect(isAgentEvent({ type: "thinking", content: "..." })).toBe(true);
|
|
362
|
+
expect(isAgentEvent({ type: "text", content: "hello" })).toBe(true);
|
|
363
|
+
expect(isAgentEvent({ type: "tool_use", tool: "Read", input: {} })).toBe(true);
|
|
364
|
+
expect(isAgentEvent({ type: "tool_complete", toolUseId: "toolu_123" })).toBe(true);
|
|
365
|
+
expect(isAgentEvent({ type: "file_edit", path: "foo.ts" })).toBe(true);
|
|
366
|
+
expect(isAgentEvent({ type: "file_create", path: "bar.ts" })).toBe(true);
|
|
367
|
+
expect(isAgentEvent({ type: "file_delete", path: "baz.ts" })).toBe(true);
|
|
368
|
+
expect(isAgentEvent({ type: "prompt", question: "?" })).toBe(true);
|
|
369
|
+
expect(isAgentEvent({ type: "done" })).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should reject unknown event types", () => {
|
|
373
|
+
expect(isAgentEvent({ type: "unknown_type" })).toBe(false);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("should reject non-object values", () => {
|
|
377
|
+
expect(isAgentEvent(null)).toBe(false);
|
|
378
|
+
expect(isAgentEvent("text")).toBe(false);
|
|
379
|
+
expect(isAgentEvent(42)).toBe(false);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ClientAuthMessage,
|
|
5
|
+
ClientAuthResponse,
|
|
6
|
+
DaemonConnection,
|
|
7
|
+
DaemonStatusBatchMessage,
|
|
8
|
+
DaemonStatusMessage,
|
|
9
|
+
} from "../relay.js";
|
|
10
|
+
|
|
11
|
+
describe("relay types", () => {
|
|
12
|
+
it("should export DaemonConnection interface", () => {
|
|
13
|
+
const conn: DaemonConnection = {
|
|
14
|
+
ws: {}, // unknown in shared; relay casts to WebSocket
|
|
15
|
+
userId: "user_123",
|
|
16
|
+
machineKey: "machine_abc",
|
|
17
|
+
projects: new Set(["proj_1", "proj_2"]),
|
|
18
|
+
connectedAt: new Date("2026-02-14T20:00:00Z"),
|
|
19
|
+
lastPongAt: new Date("2026-02-14T20:01:00Z"),
|
|
20
|
+
};
|
|
21
|
+
expect(conn.userId).toBe("user_123");
|
|
22
|
+
expect(conn.machineKey).toBe("machine_abc");
|
|
23
|
+
expect(conn.projects.size).toBe(2);
|
|
24
|
+
expect(conn.projects.has("proj_1")).toBe(true);
|
|
25
|
+
expect(conn.connectedAt).toBeInstanceOf(Date);
|
|
26
|
+
expect(conn.lastPongAt).toBeInstanceOf(Date);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should export relay message types", () => {
|
|
30
|
+
// client_auth
|
|
31
|
+
const authMsg: ClientAuthMessage = {
|
|
32
|
+
type: "client_auth",
|
|
33
|
+
payload: { token: "eyJ..." },
|
|
34
|
+
};
|
|
35
|
+
expect(authMsg.type).toBe("client_auth");
|
|
36
|
+
expect(authMsg.payload.token).toBe("eyJ...");
|
|
37
|
+
|
|
38
|
+
// client_auth_response (success)
|
|
39
|
+
const authResp: ClientAuthResponse = {
|
|
40
|
+
type: "client_auth_response",
|
|
41
|
+
payload: { userId: "user_123", authenticated: true },
|
|
42
|
+
};
|
|
43
|
+
expect(authResp.payload.authenticated).toBe(true);
|
|
44
|
+
expect(authResp.payload.userId).toBe("user_123");
|
|
45
|
+
|
|
46
|
+
// client_auth_response (failure)
|
|
47
|
+
const authFail: ClientAuthResponse = {
|
|
48
|
+
type: "client_auth_response",
|
|
49
|
+
payload: { authenticated: false },
|
|
50
|
+
error: { code: "auth_failed", message: "Invalid token" },
|
|
51
|
+
};
|
|
52
|
+
expect(authFail.payload.authenticated).toBe(false);
|
|
53
|
+
expect(authFail.error?.code).toBe("auth_failed");
|
|
54
|
+
|
|
55
|
+
// daemon_status (single machine)
|
|
56
|
+
const status: DaemonStatusMessage = {
|
|
57
|
+
type: "daemon_status",
|
|
58
|
+
payload: {
|
|
59
|
+
machineKey: "machine_abc",
|
|
60
|
+
connected: false,
|
|
61
|
+
lastSeen: "2026-02-14T20:00:00Z",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
expect(status.type).toBe("daemon_status");
|
|
65
|
+
expect(status.payload.connected).toBe(false);
|
|
66
|
+
expect(status.payload.lastSeen).toBe("2026-02-14T20:00:00Z");
|
|
67
|
+
|
|
68
|
+
// daemon_status (batch, after client_auth)
|
|
69
|
+
const batch: DaemonStatusBatchMessage = {
|
|
70
|
+
type: "daemon_status",
|
|
71
|
+
payload: {
|
|
72
|
+
machines: [
|
|
73
|
+
{ machineKey: "m1", connected: true },
|
|
74
|
+
{ machineKey: "m2", connected: false, lastSeen: "2026-02-14T19:00:00Z" },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
expect(batch.payload.machines).toHaveLength(2);
|
|
79
|
+
expect(batch.payload.machines[0].connected).toBe(true);
|
|
80
|
+
expect(batch.payload.machines[1].lastSeen).toBe("2026-02-14T19:00:00Z");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth message types for daemon ↔ relay communication.
|
|
3
|
+
* The daemon exchanges OAuth codes and refreshes tokens via the Relay
|
|
4
|
+
* (never directly with Supabase).
|
|
5
|
+
*
|
|
6
|
+
* See PRD.md: "auth_exchange Message" and "token_refresh Message" sections.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { MessageError } from "./messages.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Auth user
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface AuthUser {
|
|
16
|
+
id: string;
|
|
17
|
+
email: string;
|
|
18
|
+
github_username: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// auth_exchange (Daemon → Relay → Daemon)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export interface AuthExchangeRequest {
|
|
26
|
+
type: "auth_exchange";
|
|
27
|
+
requestId: string;
|
|
28
|
+
payload: {
|
|
29
|
+
code: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AuthExchangeResponse {
|
|
34
|
+
type: "auth_exchange_response";
|
|
35
|
+
requestId: string;
|
|
36
|
+
payload?: {
|
|
37
|
+
access_token: string;
|
|
38
|
+
refresh_token: string;
|
|
39
|
+
expires_at: number;
|
|
40
|
+
user: AuthUser;
|
|
41
|
+
};
|
|
42
|
+
error?: MessageError;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// token_refresh (Daemon → Relay → Daemon)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export interface TokenRefreshRequest {
|
|
50
|
+
type: "token_refresh";
|
|
51
|
+
requestId: string;
|
|
52
|
+
payload: {
|
|
53
|
+
refresh_token: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface TokenRefreshResponse {
|
|
58
|
+
type: "token_refresh_response";
|
|
59
|
+
requestId: string;
|
|
60
|
+
payload?: {
|
|
61
|
+
access_token: string;
|
|
62
|
+
refresh_token: string;
|
|
63
|
+
expires_at: number;
|
|
64
|
+
};
|
|
65
|
+
error?: MessageError;
|
|
66
|
+
}
|