@oh-my-pi/pi-wire 15.11.8
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/CHANGELOG.md +9 -0
- package/README.md +31 -0
- package/dist/types/index.d.ts +370 -0
- package/package.json +56 -0
- package/src/index.ts +360 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @oh-my-pi/pi-wire
|
|
2
|
+
|
|
3
|
+
Shared TypeScript wire contracts for omp collab live sessions.
|
|
4
|
+
|
|
5
|
+
The package contains only JSON-safe protocol shapes and constants. It has no runtime dependencies and is consumed by both the host CLI (`@oh-my-pi/pi-coding-agent`) and browser guest (`@oh-my-pi/collab-web`).
|
|
6
|
+
|
|
7
|
+
## Exports
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import type { GuestFrame, HostFrame, SessionEntry } from "@oh-my-pi/pi-wire";
|
|
11
|
+
import { COLLAB_PROTO, DEFAULT_RELAY_URL, ENVELOPE_HEADER_LENGTH } from "@oh-my-pi/pi-wire";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Key groups:
|
|
15
|
+
|
|
16
|
+
- message and transcript entry shapes rendered by collab guests,
|
|
17
|
+
- live agent event and task-subagent bus payload shapes,
|
|
18
|
+
- `GuestFrame`, `HostFrame`, and `WireFrame` unions for AES-GCM sealed payloads,
|
|
19
|
+
- relay control TEXT messages,
|
|
20
|
+
- link/envelope constants shared by host, guest, and local relay code.
|
|
21
|
+
|
|
22
|
+
## Protocol boundary
|
|
23
|
+
|
|
24
|
+
`pi-wire` does not encode, decode, validate, encrypt, or route frames. It defines the shared contract used at those boundaries:
|
|
25
|
+
|
|
26
|
+
1. callers build a `GuestFrame` or `HostFrame`,
|
|
27
|
+
2. transport code serializes it as JSON inside an encrypted payload,
|
|
28
|
+
3. relay code routes opaque envelopes using the plaintext peer-id prefix,
|
|
29
|
+
4. receivers switch on `frame.t` and tolerate unknown future fields.
|
|
30
|
+
|
|
31
|
+
Keep protocol changes backward-aware: bump `COLLAB_PROTO` only when old hosts and guests must reject each other.
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire types for the omp collab live-session protocol.
|
|
3
|
+
*
|
|
4
|
+
* Dependency-free JSON shapes produced by `@oh-my-pi/pi-coding-agent`
|
|
5
|
+
* (`src/collab/protocol.ts` and friends). Browser and test clients import this
|
|
6
|
+
* package instead of depending on the coding-agent runtime; conformance is
|
|
7
|
+
* asserted type-only in `packages/coding-agent/test/collab/web-wire.types.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Unknown entry/event variants arrive over the wire as plain JSON. The unions
|
|
10
|
+
* below cover only the variants this client renders; consumers cast at the
|
|
11
|
+
* JSON boundary and every `switch` keeps a tolerant `default:` branch.
|
|
12
|
+
*/
|
|
13
|
+
export interface TextContent {
|
|
14
|
+
type: "text";
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ImageContent {
|
|
18
|
+
type: "image";
|
|
19
|
+
/** Base64-encoded image data. */
|
|
20
|
+
data: string;
|
|
21
|
+
/** e.g. "image/png". */
|
|
22
|
+
mimeType: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ThinkingContent {
|
|
25
|
+
type: "thinking";
|
|
26
|
+
thinking: string;
|
|
27
|
+
}
|
|
28
|
+
export interface RedactedThinkingContent {
|
|
29
|
+
type: "redactedThinking";
|
|
30
|
+
data: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ToolCallContent {
|
|
33
|
+
type: "toolCall";
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
arguments: Record<string, unknown>;
|
|
37
|
+
intent?: string;
|
|
38
|
+
}
|
|
39
|
+
export type AssistantContent = TextContent | ThinkingContent | RedactedThinkingContent | ToolCallContent;
|
|
40
|
+
export type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
|
|
41
|
+
export interface WireUsage {
|
|
42
|
+
input: number;
|
|
43
|
+
output: number;
|
|
44
|
+
cacheRead: number;
|
|
45
|
+
cacheWrite: number;
|
|
46
|
+
totalTokens: number;
|
|
47
|
+
cost: {
|
|
48
|
+
total: number;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export interface UserMessage {
|
|
52
|
+
role: "user";
|
|
53
|
+
content: string | (TextContent | ImageContent)[];
|
|
54
|
+
/** True if the message was injected by the system (e.g. auto-continue). */
|
|
55
|
+
synthetic?: boolean;
|
|
56
|
+
/** Unix timestamp in milliseconds. */
|
|
57
|
+
timestamp: number;
|
|
58
|
+
}
|
|
59
|
+
export interface DeveloperMessage {
|
|
60
|
+
role: "developer";
|
|
61
|
+
content: string | (TextContent | ImageContent)[];
|
|
62
|
+
timestamp: number;
|
|
63
|
+
}
|
|
64
|
+
export interface AssistantMessage {
|
|
65
|
+
role: "assistant";
|
|
66
|
+
content: AssistantContent[];
|
|
67
|
+
model: string;
|
|
68
|
+
usage: WireUsage;
|
|
69
|
+
stopReason: StopReason;
|
|
70
|
+
errorMessage?: string;
|
|
71
|
+
timestamp: number;
|
|
72
|
+
}
|
|
73
|
+
export interface ToolResultMessage {
|
|
74
|
+
role: "toolResult";
|
|
75
|
+
toolCallId: string;
|
|
76
|
+
toolName: string;
|
|
77
|
+
content: (TextContent | ImageContent)[];
|
|
78
|
+
details?: unknown;
|
|
79
|
+
isError: boolean;
|
|
80
|
+
timestamp: number;
|
|
81
|
+
}
|
|
82
|
+
export type WireMessage = UserMessage | DeveloperMessage | AssistantMessage | ToolResultMessage;
|
|
83
|
+
export interface SessionHeader {
|
|
84
|
+
type: "session";
|
|
85
|
+
id: string;
|
|
86
|
+
title?: string;
|
|
87
|
+
timestamp: string;
|
|
88
|
+
cwd: string;
|
|
89
|
+
}
|
|
90
|
+
export interface EntryBase {
|
|
91
|
+
id: string;
|
|
92
|
+
parentId: string | null;
|
|
93
|
+
timestamp: string;
|
|
94
|
+
}
|
|
95
|
+
export interface MessageEntry extends EntryBase {
|
|
96
|
+
type: "message";
|
|
97
|
+
message: WireMessage;
|
|
98
|
+
}
|
|
99
|
+
export interface CustomMessageEntry extends EntryBase {
|
|
100
|
+
type: "custom_message";
|
|
101
|
+
customType: string;
|
|
102
|
+
content: string | (TextContent | ImageContent)[];
|
|
103
|
+
details?: unknown;
|
|
104
|
+
display: boolean;
|
|
105
|
+
}
|
|
106
|
+
export interface CompactionEntry extends EntryBase {
|
|
107
|
+
type: "compaction";
|
|
108
|
+
summary: string;
|
|
109
|
+
shortSummary?: string;
|
|
110
|
+
firstKeptEntryId: string;
|
|
111
|
+
tokensBefore: number;
|
|
112
|
+
}
|
|
113
|
+
export interface BranchSummaryEntry extends EntryBase {
|
|
114
|
+
type: "branch_summary";
|
|
115
|
+
fromId: string;
|
|
116
|
+
summary: string;
|
|
117
|
+
}
|
|
118
|
+
export interface ModelChangeEntry extends EntryBase {
|
|
119
|
+
type: "model_change";
|
|
120
|
+
/** Model in "provider/modelId" format. */
|
|
121
|
+
model: string;
|
|
122
|
+
role?: string;
|
|
123
|
+
}
|
|
124
|
+
export interface ThinkingLevelChangeEntry extends EntryBase {
|
|
125
|
+
type: "thinking_level_change";
|
|
126
|
+
thinkingLevel?: string | null;
|
|
127
|
+
}
|
|
128
|
+
export type SessionEntry = MessageEntry | CustomMessageEntry | CompactionEntry | BranchSummaryEntry | ModelChangeEntry | ThinkingLevelChangeEntry;
|
|
129
|
+
/** customType of collab guest prompts injected on the host. */
|
|
130
|
+
export declare const COLLAB_PROMPT_MESSAGE_TYPE = "collab-prompt";
|
|
131
|
+
/** `details` shape of `custom_message` entries with `customType === "collab-prompt"`. */
|
|
132
|
+
export interface CollabPromptDetails {
|
|
133
|
+
from?: string;
|
|
134
|
+
}
|
|
135
|
+
export type AgentEvent = {
|
|
136
|
+
type: "agent_start";
|
|
137
|
+
} | {
|
|
138
|
+
type: "agent_end";
|
|
139
|
+
} | {
|
|
140
|
+
type: "turn_start";
|
|
141
|
+
} | {
|
|
142
|
+
type: "turn_end";
|
|
143
|
+
} | {
|
|
144
|
+
type: "message_start";
|
|
145
|
+
message: WireMessage;
|
|
146
|
+
}
|
|
147
|
+
/** Carries the FULL accumulating partial message — no delta tracking needed. */
|
|
148
|
+
| {
|
|
149
|
+
type: "message_update";
|
|
150
|
+
message: WireMessage;
|
|
151
|
+
} | {
|
|
152
|
+
type: "message_end";
|
|
153
|
+
message: WireMessage;
|
|
154
|
+
} | {
|
|
155
|
+
type: "tool_execution_start";
|
|
156
|
+
toolCallId: string;
|
|
157
|
+
toolName: string;
|
|
158
|
+
args: unknown;
|
|
159
|
+
intent?: string;
|
|
160
|
+
} | {
|
|
161
|
+
type: "tool_execution_update";
|
|
162
|
+
toolCallId: string;
|
|
163
|
+
toolName: string;
|
|
164
|
+
args: unknown;
|
|
165
|
+
partialResult: unknown;
|
|
166
|
+
} | {
|
|
167
|
+
type: "tool_execution_end";
|
|
168
|
+
toolCallId: string;
|
|
169
|
+
toolName: string;
|
|
170
|
+
result: unknown;
|
|
171
|
+
isError?: boolean;
|
|
172
|
+
} | {
|
|
173
|
+
type: "notice";
|
|
174
|
+
level: "info" | "warning" | "error";
|
|
175
|
+
message: string;
|
|
176
|
+
source?: string;
|
|
177
|
+
} | {
|
|
178
|
+
type: "auto_compaction_start";
|
|
179
|
+
reason: string;
|
|
180
|
+
action: string;
|
|
181
|
+
} | {
|
|
182
|
+
type: "auto_compaction_end";
|
|
183
|
+
aborted: boolean;
|
|
184
|
+
willRetry: boolean;
|
|
185
|
+
errorMessage?: string;
|
|
186
|
+
skipped?: boolean;
|
|
187
|
+
} | {
|
|
188
|
+
type: "auto_retry_start";
|
|
189
|
+
attempt: number;
|
|
190
|
+
maxAttempts: number;
|
|
191
|
+
delayMs: number;
|
|
192
|
+
errorMessage: string;
|
|
193
|
+
} | {
|
|
194
|
+
type: "auto_retry_end";
|
|
195
|
+
success: boolean;
|
|
196
|
+
attempt: number;
|
|
197
|
+
finalError?: string;
|
|
198
|
+
} | {
|
|
199
|
+
type: "thinking_level_changed";
|
|
200
|
+
thinkingLevel?: string;
|
|
201
|
+
};
|
|
202
|
+
export interface WireModel {
|
|
203
|
+
id: string;
|
|
204
|
+
name: string;
|
|
205
|
+
provider: string;
|
|
206
|
+
contextWindow: number;
|
|
207
|
+
}
|
|
208
|
+
export interface ContextUsage {
|
|
209
|
+
tokens: number | null;
|
|
210
|
+
contextWindow: number;
|
|
211
|
+
percent: number | null;
|
|
212
|
+
}
|
|
213
|
+
export interface Participant {
|
|
214
|
+
name: string;
|
|
215
|
+
role: "host" | "guest";
|
|
216
|
+
}
|
|
217
|
+
/** Debounced footer snapshot broadcast by the host. */
|
|
218
|
+
export interface SessionState {
|
|
219
|
+
isStreaming: boolean;
|
|
220
|
+
queuedMessageCount: number;
|
|
221
|
+
sessionName?: string;
|
|
222
|
+
/** Host cwd — display only; the guest never chdirs. */
|
|
223
|
+
cwd: string;
|
|
224
|
+
model?: WireModel;
|
|
225
|
+
thinkingLevel?: string;
|
|
226
|
+
contextUsage?: ContextUsage;
|
|
227
|
+
participants: Participant[];
|
|
228
|
+
}
|
|
229
|
+
export interface AgentSnapshot {
|
|
230
|
+
id: string;
|
|
231
|
+
displayName: string;
|
|
232
|
+
kind: "main" | "sub";
|
|
233
|
+
parentId?: string;
|
|
234
|
+
status: "running" | "idle" | "parked" | "aborted";
|
|
235
|
+
/** Whether the host has a transcript file for this agent (gates remote transcript fetch). */
|
|
236
|
+
hasSessionFile: boolean;
|
|
237
|
+
createdAt: number;
|
|
238
|
+
lastActivity: number;
|
|
239
|
+
}
|
|
240
|
+
export interface AgentProgress {
|
|
241
|
+
index: number;
|
|
242
|
+
id: string;
|
|
243
|
+
agent: string;
|
|
244
|
+
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
245
|
+
task: string;
|
|
246
|
+
description?: string;
|
|
247
|
+
lastIntent?: string;
|
|
248
|
+
currentTool?: string;
|
|
249
|
+
currentToolArgs?: string;
|
|
250
|
+
currentToolStartMs?: number;
|
|
251
|
+
recentTools: {
|
|
252
|
+
tool: string;
|
|
253
|
+
args: string;
|
|
254
|
+
endMs: number;
|
|
255
|
+
}[];
|
|
256
|
+
recentOutput: string[];
|
|
257
|
+
toolCount: number;
|
|
258
|
+
requests: number;
|
|
259
|
+
tokens: number;
|
|
260
|
+
contextTokens?: number;
|
|
261
|
+
contextWindow?: number;
|
|
262
|
+
cost: number;
|
|
263
|
+
durationMs: number;
|
|
264
|
+
resolvedModel?: string;
|
|
265
|
+
}
|
|
266
|
+
export interface SubagentProgressPayload {
|
|
267
|
+
index: number;
|
|
268
|
+
agent: string;
|
|
269
|
+
task: string;
|
|
270
|
+
parentToolCallId?: string;
|
|
271
|
+
assignment?: string;
|
|
272
|
+
progress: AgentProgress;
|
|
273
|
+
sessionFile?: string;
|
|
274
|
+
}
|
|
275
|
+
export interface SubagentLifecyclePayload {
|
|
276
|
+
id: string;
|
|
277
|
+
agent: string;
|
|
278
|
+
description?: string;
|
|
279
|
+
status: "started" | "completed" | "failed" | "aborted";
|
|
280
|
+
sessionFile?: string;
|
|
281
|
+
parentToolCallId?: string;
|
|
282
|
+
index: number;
|
|
283
|
+
}
|
|
284
|
+
export type GuestFrame = {
|
|
285
|
+
t: "hello";
|
|
286
|
+
proto: number;
|
|
287
|
+
name: string;
|
|
288
|
+
} | {
|
|
289
|
+
t: "prompt";
|
|
290
|
+
text: string;
|
|
291
|
+
images?: ImageContent[];
|
|
292
|
+
} | {
|
|
293
|
+
t: "abort";
|
|
294
|
+
} | {
|
|
295
|
+
t: "agent-cmd";
|
|
296
|
+
cmd: "chat" | "kill" | "revive";
|
|
297
|
+
agentId: string;
|
|
298
|
+
text?: string;
|
|
299
|
+
} | {
|
|
300
|
+
t: "fetch-transcript";
|
|
301
|
+
reqId: number;
|
|
302
|
+
agentId: string;
|
|
303
|
+
fromByte: number;
|
|
304
|
+
};
|
|
305
|
+
/** EventBus channels mirrored to guests (task subagent traffic only). */
|
|
306
|
+
export type BusChannel = "task:subagent:progress" | "task:subagent:lifecycle";
|
|
307
|
+
export type HostFrame = {
|
|
308
|
+
t: "welcome";
|
|
309
|
+
proto: number;
|
|
310
|
+
header: SessionHeader;
|
|
311
|
+
entries: SessionEntry[];
|
|
312
|
+
state: SessionState;
|
|
313
|
+
agents: AgentSnapshot[];
|
|
314
|
+
} | {
|
|
315
|
+
t: "entry";
|
|
316
|
+
entry: SessionEntry;
|
|
317
|
+
} | {
|
|
318
|
+
t: "event";
|
|
319
|
+
event: AgentEvent;
|
|
320
|
+
} | {
|
|
321
|
+
t: "state";
|
|
322
|
+
state: SessionState;
|
|
323
|
+
}
|
|
324
|
+
/** Mirrored EventBus traffic (task subagent lifecycle/progress channels only). */
|
|
325
|
+
| {
|
|
326
|
+
t: "bus";
|
|
327
|
+
channel: BusChannel;
|
|
328
|
+
data: unknown;
|
|
329
|
+
} | {
|
|
330
|
+
t: "agents";
|
|
331
|
+
agents: AgentSnapshot[];
|
|
332
|
+
}
|
|
333
|
+
/** Targeted reply to fetch-transcript; `text` is decoded JSONL from `fromByte`, `newSize` the next offset base. */
|
|
334
|
+
| {
|
|
335
|
+
t: "transcript";
|
|
336
|
+
reqId: number;
|
|
337
|
+
text: string;
|
|
338
|
+
newSize: number;
|
|
339
|
+
error?: string;
|
|
340
|
+
} | {
|
|
341
|
+
t: "bye";
|
|
342
|
+
reason: string;
|
|
343
|
+
} | {
|
|
344
|
+
t: "error";
|
|
345
|
+
message: string;
|
|
346
|
+
};
|
|
347
|
+
export type WireFrame = GuestFrame | HostFrame;
|
|
348
|
+
/** Wire protocol version carried in `hello`; the host rejects mismatches. */
|
|
349
|
+
export declare const COLLAB_PROTO = 1;
|
|
350
|
+
/** Plaintext envelope prefix: `[4B uint32 BE peerId][sealed payload]`. */
|
|
351
|
+
export declare const ENVELOPE_HEADER_LENGTH = 4;
|
|
352
|
+
export declare const ROOM_ID_BYTES = 16;
|
|
353
|
+
/** Default public relay; bare `<roomId>#<key>` links resolve against it. */
|
|
354
|
+
export declare const DEFAULT_RELAY_URL = "wss://relay.omp.sh";
|
|
355
|
+
export interface ParsedCollabLink {
|
|
356
|
+
/** wss://host[:port]/r/<roomId> — no query, no fragment. */
|
|
357
|
+
wsUrl: string;
|
|
358
|
+
roomId: string;
|
|
359
|
+
key: Uint8Array;
|
|
360
|
+
}
|
|
361
|
+
/** Relay → host control message. */
|
|
362
|
+
export type RelayControlToHost = {
|
|
363
|
+
t: "peer-joined" | "peer-left";
|
|
364
|
+
peer: number;
|
|
365
|
+
};
|
|
366
|
+
/** Relay → guest control message. */
|
|
367
|
+
export type RelayControlToGuest = {
|
|
368
|
+
t: "room-closed";
|
|
369
|
+
};
|
|
370
|
+
export type RelayControlMessage = RelayControlToHost | RelayControlToGuest;
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@oh-my-pi/pi-wire",
|
|
4
|
+
"version": "15.11.8",
|
|
5
|
+
"description": "Shared wire protocol types for Oh My Pi packages",
|
|
6
|
+
"homepage": "https://omp.sh",
|
|
7
|
+
"author": "Can Boluk",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/can1357/oh-my-pi.git",
|
|
12
|
+
"directory": "packages/wire"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/can1357/oh-my-pi/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"wire",
|
|
19
|
+
"protocol",
|
|
20
|
+
"types",
|
|
21
|
+
"collab"
|
|
22
|
+
],
|
|
23
|
+
"main": "./src/index.ts",
|
|
24
|
+
"types": "./dist/types/index.d.ts",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"check": "biome check . && bun run check:types",
|
|
27
|
+
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
28
|
+
"lint": "biome lint .",
|
|
29
|
+
"fix": "biome check --write --unsafe .",
|
|
30
|
+
"fmt": "biome format --write .",
|
|
31
|
+
"test": "bun test --parallel"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/bun": "^1.3.14"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"bun": ">=1.3.14"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src",
|
|
41
|
+
"README.md",
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"dist/types"
|
|
44
|
+
],
|
|
45
|
+
"exports": {
|
|
46
|
+
".": {
|
|
47
|
+
"types": "./dist/types/index.d.ts",
|
|
48
|
+
"import": "./src/index.ts"
|
|
49
|
+
},
|
|
50
|
+
"./*": {
|
|
51
|
+
"types": "./dist/types/*.d.ts",
|
|
52
|
+
"import": "./src/*.ts"
|
|
53
|
+
},
|
|
54
|
+
"./*.js": "./src/*.ts"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire types for the omp collab live-session protocol.
|
|
3
|
+
*
|
|
4
|
+
* Dependency-free JSON shapes produced by `@oh-my-pi/pi-coding-agent`
|
|
5
|
+
* (`src/collab/protocol.ts` and friends). Browser and test clients import this
|
|
6
|
+
* package instead of depending on the coding-agent runtime; conformance is
|
|
7
|
+
* asserted type-only in `packages/coding-agent/test/collab/web-wire.types.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Unknown entry/event variants arrive over the wire as plain JSON. The unions
|
|
10
|
+
* below cover only the variants this client renders; consumers cast at the
|
|
11
|
+
* JSON boundary and every `switch` keeps a tolerant `default:` branch.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
+
// Content blocks
|
|
16
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
17
|
+
|
|
18
|
+
export interface TextContent {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ImageContent {
|
|
24
|
+
type: "image";
|
|
25
|
+
/** Base64-encoded image data. */
|
|
26
|
+
data: string;
|
|
27
|
+
/** e.g. "image/png". */
|
|
28
|
+
mimeType: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ThinkingContent {
|
|
32
|
+
type: "thinking";
|
|
33
|
+
thinking: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RedactedThinkingContent {
|
|
37
|
+
type: "redactedThinking";
|
|
38
|
+
data: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ToolCallContent {
|
|
42
|
+
type: "toolCall";
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
arguments: Record<string, unknown>;
|
|
46
|
+
intent?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type AssistantContent = TextContent | ThinkingContent | RedactedThinkingContent | ToolCallContent;
|
|
50
|
+
|
|
51
|
+
export type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
|
|
52
|
+
|
|
53
|
+
export interface WireUsage {
|
|
54
|
+
input: number;
|
|
55
|
+
output: number;
|
|
56
|
+
cacheRead: number;
|
|
57
|
+
cacheWrite: number;
|
|
58
|
+
totalTokens: number;
|
|
59
|
+
cost: { total: number };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
63
|
+
// Messages
|
|
64
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
65
|
+
|
|
66
|
+
export interface UserMessage {
|
|
67
|
+
role: "user";
|
|
68
|
+
content: string | (TextContent | ImageContent)[];
|
|
69
|
+
/** True if the message was injected by the system (e.g. auto-continue). */
|
|
70
|
+
synthetic?: boolean;
|
|
71
|
+
/** Unix timestamp in milliseconds. */
|
|
72
|
+
timestamp: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DeveloperMessage {
|
|
76
|
+
role: "developer";
|
|
77
|
+
content: string | (TextContent | ImageContent)[];
|
|
78
|
+
timestamp: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface AssistantMessage {
|
|
82
|
+
role: "assistant";
|
|
83
|
+
content: AssistantContent[];
|
|
84
|
+
model: string;
|
|
85
|
+
usage: WireUsage;
|
|
86
|
+
stopReason: StopReason;
|
|
87
|
+
errorMessage?: string;
|
|
88
|
+
timestamp: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ToolResultMessage {
|
|
92
|
+
role: "toolResult";
|
|
93
|
+
toolCallId: string;
|
|
94
|
+
toolName: string;
|
|
95
|
+
content: (TextContent | ImageContent)[];
|
|
96
|
+
details?: unknown;
|
|
97
|
+
isError: boolean;
|
|
98
|
+
timestamp: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type WireMessage = UserMessage | DeveloperMessage | AssistantMessage | ToolResultMessage;
|
|
102
|
+
|
|
103
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
104
|
+
// Session entries (rendered subset; cast `as SessionEntry` at the JSON
|
|
105
|
+
// boundary and skip unknown `type`s in a tolerant `default:`)
|
|
106
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
107
|
+
|
|
108
|
+
export interface SessionHeader {
|
|
109
|
+
type: "session";
|
|
110
|
+
id: string;
|
|
111
|
+
title?: string;
|
|
112
|
+
timestamp: string;
|
|
113
|
+
cwd: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface EntryBase {
|
|
117
|
+
id: string;
|
|
118
|
+
parentId: string | null;
|
|
119
|
+
timestamp: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface MessageEntry extends EntryBase {
|
|
123
|
+
type: "message";
|
|
124
|
+
message: WireMessage;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface CustomMessageEntry extends EntryBase {
|
|
128
|
+
type: "custom_message";
|
|
129
|
+
customType: string;
|
|
130
|
+
content: string | (TextContent | ImageContent)[];
|
|
131
|
+
details?: unknown;
|
|
132
|
+
display: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface CompactionEntry extends EntryBase {
|
|
136
|
+
type: "compaction";
|
|
137
|
+
summary: string;
|
|
138
|
+
shortSummary?: string;
|
|
139
|
+
firstKeptEntryId: string;
|
|
140
|
+
tokensBefore: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface BranchSummaryEntry extends EntryBase {
|
|
144
|
+
type: "branch_summary";
|
|
145
|
+
fromId: string;
|
|
146
|
+
summary: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ModelChangeEntry extends EntryBase {
|
|
150
|
+
type: "model_change";
|
|
151
|
+
/** Model in "provider/modelId" format. */
|
|
152
|
+
model: string;
|
|
153
|
+
role?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface ThinkingLevelChangeEntry extends EntryBase {
|
|
157
|
+
type: "thinking_level_change";
|
|
158
|
+
thinkingLevel?: string | null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export type SessionEntry =
|
|
162
|
+
| MessageEntry
|
|
163
|
+
| CustomMessageEntry
|
|
164
|
+
| CompactionEntry
|
|
165
|
+
| BranchSummaryEntry
|
|
166
|
+
| ModelChangeEntry
|
|
167
|
+
| ThinkingLevelChangeEntry;
|
|
168
|
+
|
|
169
|
+
/** customType of collab guest prompts injected on the host. */
|
|
170
|
+
export const COLLAB_PROMPT_MESSAGE_TYPE = "collab-prompt";
|
|
171
|
+
|
|
172
|
+
/** `details` shape of `custom_message` entries with `customType === "collab-prompt"`. */
|
|
173
|
+
export interface CollabPromptDetails {
|
|
174
|
+
from?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
178
|
+
// Events (handled subset)
|
|
179
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
180
|
+
|
|
181
|
+
export type AgentEvent =
|
|
182
|
+
| { type: "agent_start" }
|
|
183
|
+
| { type: "agent_end" }
|
|
184
|
+
| { type: "turn_start" }
|
|
185
|
+
| { type: "turn_end" }
|
|
186
|
+
| { type: "message_start"; message: WireMessage }
|
|
187
|
+
/** Carries the FULL accumulating partial message — no delta tracking needed. */
|
|
188
|
+
| { type: "message_update"; message: WireMessage }
|
|
189
|
+
| { type: "message_end"; message: WireMessage }
|
|
190
|
+
| { type: "tool_execution_start"; toolCallId: string; toolName: string; args: unknown; intent?: string }
|
|
191
|
+
| { type: "tool_execution_update"; toolCallId: string; toolName: string; args: unknown; partialResult: unknown }
|
|
192
|
+
| { type: "tool_execution_end"; toolCallId: string; toolName: string; result: unknown; isError?: boolean }
|
|
193
|
+
| { type: "notice"; level: "info" | "warning" | "error"; message: string; source?: string }
|
|
194
|
+
| { type: "auto_compaction_start"; reason: string; action: string }
|
|
195
|
+
| { type: "auto_compaction_end"; aborted: boolean; willRetry: boolean; errorMessage?: string; skipped?: boolean }
|
|
196
|
+
| { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
|
|
197
|
+
| { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
|
|
198
|
+
| { type: "thinking_level_changed"; thinkingLevel?: string };
|
|
199
|
+
|
|
200
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
201
|
+
// State & agents
|
|
202
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
203
|
+
|
|
204
|
+
export interface WireModel {
|
|
205
|
+
id: string;
|
|
206
|
+
name: string;
|
|
207
|
+
provider: string;
|
|
208
|
+
contextWindow: number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface ContextUsage {
|
|
212
|
+
tokens: number | null;
|
|
213
|
+
contextWindow: number;
|
|
214
|
+
percent: number | null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface Participant {
|
|
218
|
+
name: string;
|
|
219
|
+
role: "host" | "guest";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Debounced footer snapshot broadcast by the host. */
|
|
223
|
+
export interface SessionState {
|
|
224
|
+
isStreaming: boolean;
|
|
225
|
+
queuedMessageCount: number;
|
|
226
|
+
sessionName?: string;
|
|
227
|
+
/** Host cwd — display only; the guest never chdirs. */
|
|
228
|
+
cwd: string;
|
|
229
|
+
model?: WireModel;
|
|
230
|
+
thinkingLevel?: string;
|
|
231
|
+
contextUsage?: ContextUsage;
|
|
232
|
+
participants: Participant[];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface AgentSnapshot {
|
|
236
|
+
id: string;
|
|
237
|
+
displayName: string;
|
|
238
|
+
kind: "main" | "sub";
|
|
239
|
+
parentId?: string;
|
|
240
|
+
status: "running" | "idle" | "parked" | "aborted";
|
|
241
|
+
/** Whether the host has a transcript file for this agent (gates remote transcript fetch). */
|
|
242
|
+
hasSessionFile: boolean;
|
|
243
|
+
createdAt: number;
|
|
244
|
+
lastActivity: number;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
248
|
+
// Bus payloads (task subagent lifecycle/progress channels)
|
|
249
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
250
|
+
|
|
251
|
+
export interface AgentProgress {
|
|
252
|
+
index: number;
|
|
253
|
+
id: string;
|
|
254
|
+
agent: string;
|
|
255
|
+
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
256
|
+
task: string;
|
|
257
|
+
description?: string;
|
|
258
|
+
lastIntent?: string;
|
|
259
|
+
currentTool?: string;
|
|
260
|
+
currentToolArgs?: string;
|
|
261
|
+
currentToolStartMs?: number;
|
|
262
|
+
recentTools: { tool: string; args: string; endMs: number }[];
|
|
263
|
+
recentOutput: string[];
|
|
264
|
+
toolCount: number;
|
|
265
|
+
requests: number;
|
|
266
|
+
tokens: number;
|
|
267
|
+
contextTokens?: number;
|
|
268
|
+
contextWindow?: number;
|
|
269
|
+
cost: number;
|
|
270
|
+
durationMs: number;
|
|
271
|
+
resolvedModel?: string;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface SubagentProgressPayload {
|
|
275
|
+
index: number;
|
|
276
|
+
agent: string;
|
|
277
|
+
task: string;
|
|
278
|
+
parentToolCallId?: string;
|
|
279
|
+
assignment?: string;
|
|
280
|
+
progress: AgentProgress;
|
|
281
|
+
sessionFile?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export interface SubagentLifecyclePayload {
|
|
285
|
+
id: string;
|
|
286
|
+
agent: string;
|
|
287
|
+
description?: string;
|
|
288
|
+
status: "started" | "completed" | "failed" | "aborted";
|
|
289
|
+
sessionFile?: string;
|
|
290
|
+
parentToolCallId?: string;
|
|
291
|
+
index: number;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
295
|
+
// Frames (JSON inside the AES-GCM seal)
|
|
296
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
297
|
+
|
|
298
|
+
export type GuestFrame =
|
|
299
|
+
| { t: "hello"; proto: number; name: string }
|
|
300
|
+
| { t: "prompt"; text: string; images?: ImageContent[] }
|
|
301
|
+
| { t: "abort" }
|
|
302
|
+
| { t: "agent-cmd"; cmd: "chat" | "kill" | "revive"; agentId: string; text?: string }
|
|
303
|
+
| { t: "fetch-transcript"; reqId: number; agentId: string; fromByte: number };
|
|
304
|
+
|
|
305
|
+
/** EventBus channels mirrored to guests (task subagent traffic only). */
|
|
306
|
+
export type BusChannel = "task:subagent:progress" | "task:subagent:lifecycle";
|
|
307
|
+
|
|
308
|
+
export type HostFrame =
|
|
309
|
+
| {
|
|
310
|
+
t: "welcome";
|
|
311
|
+
proto: number;
|
|
312
|
+
header: SessionHeader;
|
|
313
|
+
entries: SessionEntry[];
|
|
314
|
+
state: SessionState;
|
|
315
|
+
agents: AgentSnapshot[];
|
|
316
|
+
}
|
|
317
|
+
| { t: "entry"; entry: SessionEntry }
|
|
318
|
+
| { t: "event"; event: AgentEvent }
|
|
319
|
+
| { t: "state"; state: SessionState }
|
|
320
|
+
/** Mirrored EventBus traffic (task subagent lifecycle/progress channels only). */
|
|
321
|
+
| { t: "bus"; channel: BusChannel; data: unknown }
|
|
322
|
+
| { t: "agents"; agents: AgentSnapshot[] }
|
|
323
|
+
/** Targeted reply to fetch-transcript; `text` is decoded JSONL from `fromByte`, `newSize` the next offset base. */
|
|
324
|
+
| { t: "transcript"; reqId: number; text: string; newSize: number; error?: string }
|
|
325
|
+
| { t: "bye"; reason: string }
|
|
326
|
+
| { t: "error"; message: string };
|
|
327
|
+
|
|
328
|
+
export type WireFrame = GuestFrame | HostFrame;
|
|
329
|
+
|
|
330
|
+
/** Wire protocol version carried in `hello`; the host rejects mismatches. */
|
|
331
|
+
export const COLLAB_PROTO = 1;
|
|
332
|
+
|
|
333
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
334
|
+
// Envelope & link constants
|
|
335
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
336
|
+
|
|
337
|
+
/** Plaintext envelope prefix: `[4B uint32 BE peerId][sealed payload]`. */
|
|
338
|
+
export const ENVELOPE_HEADER_LENGTH = 4;
|
|
339
|
+
|
|
340
|
+
export const ROOM_ID_BYTES = 16;
|
|
341
|
+
|
|
342
|
+
/** Default public relay; bare `<roomId>#<key>` links resolve against it. */
|
|
343
|
+
export const DEFAULT_RELAY_URL = "wss://relay.omp.sh";
|
|
344
|
+
|
|
345
|
+
export interface ParsedCollabLink {
|
|
346
|
+
/** wss://host[:port]/r/<roomId> — no query, no fragment. */
|
|
347
|
+
wsUrl: string;
|
|
348
|
+
roomId: string;
|
|
349
|
+
key: Uint8Array;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
353
|
+
// Relay control messages (TEXT JSON, unencrypted, no session data)
|
|
354
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
355
|
+
|
|
356
|
+
/** Relay → host control message. */
|
|
357
|
+
export type RelayControlToHost = { t: "peer-joined" | "peer-left"; peer: number };
|
|
358
|
+
/** Relay → guest control message. */
|
|
359
|
+
export type RelayControlToGuest = { t: "room-closed" };
|
|
360
|
+
export type RelayControlMessage = RelayControlToHost | RelayControlToGuest;
|