@poping/yome 0.0.4 → 0.0.6
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/commands/mesh.d.ts +15 -0
- package/dist/commands/mesh.js +282 -0
- package/dist/commands/mesh.js.map +1 -0
- package/dist/daemon/index.js +7 -2
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/systemd.d.ts +7 -0
- package/dist/daemon/systemd.js +87 -0
- package/dist/daemon/systemd.js.map +1 -0
- package/dist/index.js +29 -3
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts +14 -2
- package/dist/llm.js +266 -201
- package/dist/llm.js.map +1 -1
- package/dist/mesh/capabilities.d.ts +6 -0
- package/dist/mesh/capabilities.js +66 -0
- package/dist/mesh/capabilities.js.map +1 -0
- package/dist/mesh/device-id.d.ts +28 -0
- package/dist/mesh/device-id.js +105 -0
- package/dist/mesh/device-id.js.map +1 -0
- package/dist/mesh/device-registrar.d.ts +33 -0
- package/dist/mesh/device-registrar.js +131 -0
- package/dist/mesh/device-registrar.js.map +1 -0
- package/dist/mesh/index.d.ts +28 -0
- package/dist/mesh/index.js +71 -0
- package/dist/mesh/index.js.map +1 -0
- package/dist/mesh/partykit-client.d.ts +43 -0
- package/dist/mesh/partykit-client.js +195 -0
- package/dist/mesh/partykit-client.js.map +1 -0
- package/dist/mesh/rpc-handler.d.ts +28 -0
- package/dist/mesh/rpc-handler.js +201 -0
- package/dist/mesh/rpc-handler.js.map +1 -0
- package/dist/mesh/thread-stream.d.ts +109 -0
- package/dist/mesh/thread-stream.js +110 -0
- package/dist/mesh/thread-stream.js.map +1 -0
- package/dist/mesh/ticket.d.ts +33 -0
- package/dist/mesh/ticket.js +127 -0
- package/dist/mesh/ticket.js.map +1 -0
- package/dist/mesh/types.d.ts +83 -0
- package/dist/mesh/types.js +9 -0
- package/dist/mesh/types.js.map +1 -0
- package/dist/ui/MeshApp.d.ts +9 -0
- package/dist/ui/MeshApp.js +226 -0
- package/dist/ui/MeshApp.js.map +1 -0
- package/dist/withRetry.d.ts +14 -0
- package/dist/withRetry.js +106 -0
- package/dist/withRetry.js.map +1 -0
- package/dist/yomeSkills/cli.d.ts +5 -1
- package/dist/yomeSkills/cli.js +24 -2
- package/dist/yomeSkills/cli.js.map +1 -1
- package/dist/yomeSkills/webLogin.d.ts +37 -0
- package/dist/yomeSkills/webLogin.js +114 -0
- package/dist/yomeSkills/webLogin.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// PartyKit WebSocket client.
|
|
2
|
+
//
|
|
3
|
+
// Port of Yome/Shared/Sync/PartyKitClient.swift to Node. Behaviour
|
|
4
|
+
// matches the Swift client one-for-one so the Cloud sees no difference
|
|
5
|
+
// between a macOS device, an iOS device, and a Linux box:
|
|
6
|
+
//
|
|
7
|
+
// - URL shape: wss://{host}/parties/main/{roomId}?type=...&userId=...
|
|
8
|
+
// &deviceId=...&token=...&locale=...
|
|
9
|
+
// - Keep-alive: WS ping every 30s; receive-loop drives `isConnected`.
|
|
10
|
+
// - Reconnect: exponential backoff capped at 30s, max 10 attempts,
|
|
11
|
+
// resets on successful receive.
|
|
12
|
+
// - All inbound messages emit `onMessage(text)`.
|
|
13
|
+
//
|
|
14
|
+
// Differs from Swift in:
|
|
15
|
+
// - Uses `ws` npm package (works on Node ≥18 without DOM).
|
|
16
|
+
// - Token is fetched lazily via the TicketCache so reconnects
|
|
17
|
+
// after the ticket expires automatically mint a new one.
|
|
18
|
+
import WebSocket from 'ws';
|
|
19
|
+
export class PartyKitClient {
|
|
20
|
+
opts;
|
|
21
|
+
ws = null;
|
|
22
|
+
isConnected = false;
|
|
23
|
+
reconnectAttempts = 0;
|
|
24
|
+
maxReconnectAttempts = 10;
|
|
25
|
+
reconnectTimer = null;
|
|
26
|
+
pingTimer = null;
|
|
27
|
+
intentionalClose = false;
|
|
28
|
+
listeners = new Set();
|
|
29
|
+
onConnectCb = null;
|
|
30
|
+
onDisconnectCb = null;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.opts = opts;
|
|
33
|
+
}
|
|
34
|
+
onMessage(cb) {
|
|
35
|
+
this.listeners.add(cb);
|
|
36
|
+
return () => this.listeners.delete(cb);
|
|
37
|
+
}
|
|
38
|
+
onConnect(cb) { this.onConnectCb = cb; }
|
|
39
|
+
onDisconnect(cb) { this.onDisconnectCb = cb; }
|
|
40
|
+
async connect() {
|
|
41
|
+
this.intentionalClose = false;
|
|
42
|
+
this.reconnectAttempts = 0;
|
|
43
|
+
await this.establish();
|
|
44
|
+
}
|
|
45
|
+
disconnect() {
|
|
46
|
+
this.intentionalClose = true;
|
|
47
|
+
if (this.reconnectTimer) {
|
|
48
|
+
clearTimeout(this.reconnectTimer);
|
|
49
|
+
this.reconnectTimer = null;
|
|
50
|
+
}
|
|
51
|
+
if (this.pingTimer) {
|
|
52
|
+
clearInterval(this.pingTimer);
|
|
53
|
+
this.pingTimer = null;
|
|
54
|
+
}
|
|
55
|
+
if (this.ws) {
|
|
56
|
+
try {
|
|
57
|
+
this.ws.close(1000, 'client-shutdown');
|
|
58
|
+
}
|
|
59
|
+
catch { /* ignore */ }
|
|
60
|
+
this.ws = null;
|
|
61
|
+
}
|
|
62
|
+
this.isConnected = false;
|
|
63
|
+
}
|
|
64
|
+
/** Throws if not connected. Caller chooses to await or fire-and-forget. */
|
|
65
|
+
async send(frame) {
|
|
66
|
+
const ws = this.ws;
|
|
67
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
68
|
+
throw new Error('PartyKit not connected');
|
|
69
|
+
}
|
|
70
|
+
const text = typeof frame === 'string' ? frame : JSON.stringify(frame);
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
ws.send(text, (err) => err ? reject(err) : resolve());
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
get connected() {
|
|
76
|
+
return this.isConnected;
|
|
77
|
+
}
|
|
78
|
+
log(level, msg, meta) {
|
|
79
|
+
if (this.opts.log) {
|
|
80
|
+
this.opts.log(level, msg, meta);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const line = meta ? `${msg} ${JSON.stringify(meta)}` : msg;
|
|
84
|
+
if (level === 'error')
|
|
85
|
+
console.error(`[mesh] ${line}`);
|
|
86
|
+
else if (level === 'warn')
|
|
87
|
+
console.warn(`[mesh] ${line}`);
|
|
88
|
+
else
|
|
89
|
+
console.log(`[mesh] ${line}`);
|
|
90
|
+
}
|
|
91
|
+
async establish() {
|
|
92
|
+
let ticket;
|
|
93
|
+
try {
|
|
94
|
+
ticket = await this.opts.ticketCache.get();
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
this.log('error', 'Ticket mint failed; will retry', { err: err.message });
|
|
98
|
+
this.scheduleReconnect();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const host = this.opts.partykitHostOverride ?? ticket.partykit_host;
|
|
102
|
+
const room = this.opts.roomIdOverride ?? ticket.userId;
|
|
103
|
+
const type = this.opts.clientType ?? 'desktop';
|
|
104
|
+
const locale = this.opts.locale ?? 'zh';
|
|
105
|
+
const url = `wss://${host}/parties/main/${encodeURIComponent(room)}` +
|
|
106
|
+
`?type=${encodeURIComponent(type)}` +
|
|
107
|
+
`&userId=${encodeURIComponent(ticket.userId)}` +
|
|
108
|
+
`&deviceId=${encodeURIComponent(ticket.deviceId)}` +
|
|
109
|
+
`&token=${encodeURIComponent(ticket.ws_token)}` +
|
|
110
|
+
`&locale=${encodeURIComponent(locale)}`;
|
|
111
|
+
const ws = this.opts.wsFactory ? this.opts.wsFactory(url) : new WebSocket(url);
|
|
112
|
+
this.ws = ws;
|
|
113
|
+
ws.on('open', () => {
|
|
114
|
+
this.log('info', 'WS open', { host, room });
|
|
115
|
+
this.isConnected = true;
|
|
116
|
+
this.reconnectAttempts = 0;
|
|
117
|
+
this.startPing();
|
|
118
|
+
this.onConnectCb?.();
|
|
119
|
+
});
|
|
120
|
+
ws.on('message', (raw) => {
|
|
121
|
+
// Per protocol the server always sends text frames; tolerate
|
|
122
|
+
// binary just in case (decode as utf-8).
|
|
123
|
+
const text = typeof raw === 'string'
|
|
124
|
+
? raw
|
|
125
|
+
: Buffer.isBuffer(raw)
|
|
126
|
+
? raw.toString('utf-8')
|
|
127
|
+
: Buffer.from(raw).toString('utf-8');
|
|
128
|
+
for (const cb of this.listeners) {
|
|
129
|
+
try {
|
|
130
|
+
cb(text);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
this.log('error', 'listener threw', { err: err.message });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
ws.on('close', (code, reasonBuf) => {
|
|
138
|
+
this.log('warn', 'WS close', { code, reason: reasonBuf?.toString() ?? '' });
|
|
139
|
+
this.cleanupSocket();
|
|
140
|
+
if (!this.intentionalClose) {
|
|
141
|
+
// 4001 = invalid token (see Server/party/yome.ts). Invalidate the
|
|
142
|
+
// ticket cache before reconnecting so we re-mint a fresh one.
|
|
143
|
+
if (code === 4001)
|
|
144
|
+
this.opts.ticketCache.invalidate();
|
|
145
|
+
this.scheduleReconnect();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
ws.on('error', (err) => {
|
|
149
|
+
this.log('error', 'WS error', { err: err.message });
|
|
150
|
+
// Don't tear down here; the 'close' handler runs on its own and
|
|
151
|
+
// owns the reconnect path. ws guarantees 'close' fires after 'error'.
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
startPing() {
|
|
155
|
+
if (this.pingTimer)
|
|
156
|
+
clearInterval(this.pingTimer);
|
|
157
|
+
this.pingTimer = setInterval(() => {
|
|
158
|
+
const ws = this.ws;
|
|
159
|
+
if (!ws || ws.readyState !== WebSocket.OPEN)
|
|
160
|
+
return;
|
|
161
|
+
try {
|
|
162
|
+
ws.ping();
|
|
163
|
+
}
|
|
164
|
+
catch { /* ignore */ }
|
|
165
|
+
}, 30_000);
|
|
166
|
+
}
|
|
167
|
+
cleanupSocket() {
|
|
168
|
+
const wasConnected = this.isConnected;
|
|
169
|
+
this.isConnected = false;
|
|
170
|
+
if (this.pingTimer) {
|
|
171
|
+
clearInterval(this.pingTimer);
|
|
172
|
+
this.pingTimer = null;
|
|
173
|
+
}
|
|
174
|
+
this.ws = null;
|
|
175
|
+
if (wasConnected)
|
|
176
|
+
this.onDisconnectCb?.();
|
|
177
|
+
}
|
|
178
|
+
scheduleReconnect() {
|
|
179
|
+
if (this.reconnectTimer)
|
|
180
|
+
return;
|
|
181
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
182
|
+
this.log('error', 'Max reconnect attempts reached; giving up');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.reconnectAttempts += 1;
|
|
186
|
+
const delaySec = Math.min(Math.pow(2, this.reconnectAttempts), 30);
|
|
187
|
+
this.log('info', `Reconnecting in ${delaySec}s (attempt ${this.reconnectAttempts})`);
|
|
188
|
+
this.reconnectTimer = setTimeout(() => {
|
|
189
|
+
this.reconnectTimer = null;
|
|
190
|
+
// Async fire-and-forget; errors flow back into ws 'error'/'close'.
|
|
191
|
+
void this.establish();
|
|
192
|
+
}, delaySec * 1000);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=partykit-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partykit-client.js","sourceRoot":"","sources":["../../src/mesh/partykit-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PartyKitClient } from './partykit-client.js';
|
|
2
|
+
export interface RpcHandlerOpts {
|
|
3
|
+
log?: (level: 'info' | 'warn' | 'error', msg: string, meta?: Record<string, unknown>) => void;
|
|
4
|
+
}
|
|
5
|
+
export declare class RpcHandler {
|
|
6
|
+
private client;
|
|
7
|
+
private opts;
|
|
8
|
+
private detach;
|
|
9
|
+
constructor(client: PartyKitClient, opts?: RpcHandlerOpts);
|
|
10
|
+
start(): void;
|
|
11
|
+
stop(): void;
|
|
12
|
+
private handleRequest;
|
|
13
|
+
private dispatch;
|
|
14
|
+
/**
|
|
15
|
+
* `bash exec --cmd="..."` or any other action where args.cmd is the
|
|
16
|
+
* shell line. We deliberately only honour --cmd (not raw `command`)
|
|
17
|
+
* to match the existing domain-RPC parser shape that other domains
|
|
18
|
+
* use; if cmd is absent we fall back to req.command.
|
|
19
|
+
*/
|
|
20
|
+
private handleBash;
|
|
21
|
+
/**
|
|
22
|
+
* `fs <action> --path=... --content=...` minimal port of the same
|
|
23
|
+
* actions the macOS FileBridge exposes (Server/agent/commands/fsCommands.ts
|
|
24
|
+
* lists the canonical action set). Stage A: cat / ls / mkdir / write.
|
|
25
|
+
*/
|
|
26
|
+
private handleFs;
|
|
27
|
+
private log;
|
|
28
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Dispatch incoming rpc:cal-request frames to local Linux tools and
|
|
2
|
+
// send the result back as rpc:cal-response.
|
|
3
|
+
//
|
|
4
|
+
// Mirror of the macOS path in Yome/YomeApp.swift::BridgeMessage.request,
|
|
5
|
+
// which routes by `parsed.domain` to a per-domain bridge. Here on Linux:
|
|
6
|
+
//
|
|
7
|
+
// domain=bash → spawn /bin/sh and stream output
|
|
8
|
+
// domain=fs → file system operations (mkdir / ls / cat / write)
|
|
9
|
+
//
|
|
10
|
+
// Stage A intentionally ships only 'bash' + 'fs' to prove the pipe.
|
|
11
|
+
// Other domains (git / docker / k8s / systemd / pkg / log / svc / net)
|
|
12
|
+
// are advertised as capabilities but currently return a friendly
|
|
13
|
+
// "not implemented yet" so the Cloud agent can fall back gracefully.
|
|
14
|
+
import { spawn } from 'child_process';
|
|
15
|
+
import { promises as fsp } from 'fs';
|
|
16
|
+
import { join, resolve as resolvePath, isAbsolute } from 'path';
|
|
17
|
+
const BASH_TIMEOUT_MS = 60_000;
|
|
18
|
+
const MAX_STDOUT_CHARS = 64_000;
|
|
19
|
+
export class RpcHandler {
|
|
20
|
+
client;
|
|
21
|
+
opts;
|
|
22
|
+
detach = null;
|
|
23
|
+
constructor(client, opts = {}) {
|
|
24
|
+
this.client = client;
|
|
25
|
+
this.opts = opts;
|
|
26
|
+
}
|
|
27
|
+
start() {
|
|
28
|
+
this.detach = this.client.onMessage((frame) => {
|
|
29
|
+
let parsed;
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(frame);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const obj = parsed;
|
|
37
|
+
if (obj?.type !== 'rpc:cal-request')
|
|
38
|
+
return;
|
|
39
|
+
const req = obj;
|
|
40
|
+
// Don't await: each RPC handled independently so a slow bash
|
|
41
|
+
// command doesn't block the receive loop.
|
|
42
|
+
void this.handleRequest(req);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
stop() {
|
|
46
|
+
this.detach?.();
|
|
47
|
+
this.detach = null;
|
|
48
|
+
}
|
|
49
|
+
async handleRequest(req) {
|
|
50
|
+
this.log('info', 'rpc:cal-request', {
|
|
51
|
+
requestId: req.requestId, command: req.command, domain: req.parsed?.domain,
|
|
52
|
+
});
|
|
53
|
+
let result;
|
|
54
|
+
try {
|
|
55
|
+
result = await this.dispatch(req);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
result = { stdout: '', stderr: `[handler] ${err.message}`, exitCode: 1 };
|
|
59
|
+
}
|
|
60
|
+
// Cap stdout to avoid blowing past WS frame limits.
|
|
61
|
+
if (result.stdout.length > MAX_STDOUT_CHARS) {
|
|
62
|
+
result.stdout = result.stdout.slice(0, MAX_STDOUT_CHARS) + `\n[stdout capped at ${MAX_STDOUT_CHARS} chars]`;
|
|
63
|
+
}
|
|
64
|
+
const response = {
|
|
65
|
+
type: 'rpc:cal-response',
|
|
66
|
+
requestId: req.requestId,
|
|
67
|
+
stdout: result.stdout,
|
|
68
|
+
stderr: result.stderr,
|
|
69
|
+
exitCode: result.exitCode,
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
await this.client.send(response);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
this.log('error', 'failed to send rpc response', { err: err.message });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async dispatch(req) {
|
|
79
|
+
const domain = req.parsed?.domain;
|
|
80
|
+
switch (domain) {
|
|
81
|
+
case 'bash':
|
|
82
|
+
return this.handleBash(req);
|
|
83
|
+
case 'fs':
|
|
84
|
+
return this.handleFs(req);
|
|
85
|
+
// Capabilities we advertise but haven't implemented yet:
|
|
86
|
+
case 'git':
|
|
87
|
+
case 'docker':
|
|
88
|
+
case 'k8s':
|
|
89
|
+
case 'systemd':
|
|
90
|
+
case 'pkg':
|
|
91
|
+
case 'log':
|
|
92
|
+
case 'net':
|
|
93
|
+
case 'svc':
|
|
94
|
+
return {
|
|
95
|
+
stdout: '',
|
|
96
|
+
stderr: `[mesh] domain '${domain}' not implemented on linux cli yet — falling back to bash via Cloud agent`,
|
|
97
|
+
exitCode: 127,
|
|
98
|
+
};
|
|
99
|
+
default:
|
|
100
|
+
return {
|
|
101
|
+
stdout: '',
|
|
102
|
+
stderr: `[mesh] unknown domain: ${domain}`,
|
|
103
|
+
exitCode: 127,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* `bash exec --cmd="..."` or any other action where args.cmd is the
|
|
109
|
+
* shell line. We deliberately only honour --cmd (not raw `command`)
|
|
110
|
+
* to match the existing domain-RPC parser shape that other domains
|
|
111
|
+
* use; if cmd is absent we fall back to req.command.
|
|
112
|
+
*/
|
|
113
|
+
handleBash(req) {
|
|
114
|
+
const shellLine = req.parsed?.args?.cmd ?? req.command ?? '';
|
|
115
|
+
if (!shellLine.trim()) {
|
|
116
|
+
return Promise.resolve({ stdout: '', stderr: '[bash] empty command', exitCode: 2 });
|
|
117
|
+
}
|
|
118
|
+
return new Promise((resolveP) => {
|
|
119
|
+
const proc = spawn('sh', ['-c', shellLine], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
120
|
+
let stdout = '';
|
|
121
|
+
let stderr = '';
|
|
122
|
+
let killed = false;
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
killed = true;
|
|
125
|
+
try {
|
|
126
|
+
proc.kill('SIGTERM');
|
|
127
|
+
}
|
|
128
|
+
catch { /* ignore */ }
|
|
129
|
+
}, BASH_TIMEOUT_MS);
|
|
130
|
+
proc.stdout.on('data', (b) => { stdout += b.toString('utf-8'); });
|
|
131
|
+
proc.stderr.on('data', (b) => { stderr += b.toString('utf-8'); });
|
|
132
|
+
proc.on('close', (code) => {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
if (killed) {
|
|
135
|
+
resolveP({ stdout, stderr: `[bash] timed out after ${BASH_TIMEOUT_MS / 1000}s`, exitCode: 124 });
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
resolveP({ stdout, stderr, exitCode: code ?? 1 });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
proc.on('error', (err) => {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
resolveP({ stdout: '', stderr: `[bash] spawn error: ${err.message}`, exitCode: 1 });
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `fs <action> --path=... --content=...` minimal port of the same
|
|
149
|
+
* actions the macOS FileBridge exposes (Server/agent/commands/fsCommands.ts
|
|
150
|
+
* lists the canonical action set). Stage A: cat / ls / mkdir / write.
|
|
151
|
+
*/
|
|
152
|
+
async handleFs(req) {
|
|
153
|
+
const action = req.parsed?.action ?? '';
|
|
154
|
+
const args = req.parsed?.args ?? {};
|
|
155
|
+
const path = typeof args.path === 'string' ? args.path : '';
|
|
156
|
+
const safeJoinedPath = path && isAbsolute(path) ? path : resolvePath(process.cwd(), path);
|
|
157
|
+
try {
|
|
158
|
+
switch (action) {
|
|
159
|
+
case 'cat':
|
|
160
|
+
case 'read': {
|
|
161
|
+
const content = await fsp.readFile(safeJoinedPath, 'utf-8');
|
|
162
|
+
return { stdout: content, stderr: '', exitCode: 0 };
|
|
163
|
+
}
|
|
164
|
+
case 'ls': {
|
|
165
|
+
const entries = await fsp.readdir(safeJoinedPath, { withFileTypes: true });
|
|
166
|
+
const lines = entries.map((e) => `${e.isDirectory() ? 'd' : '-'} ${e.name}`);
|
|
167
|
+
return { stdout: lines.join('\n'), stderr: '', exitCode: 0 };
|
|
168
|
+
}
|
|
169
|
+
case 'mkdir': {
|
|
170
|
+
await fsp.mkdir(safeJoinedPath, { recursive: true });
|
|
171
|
+
return { stdout: `created ${safeJoinedPath}`, stderr: '', exitCode: 0 };
|
|
172
|
+
}
|
|
173
|
+
case 'write': {
|
|
174
|
+
const content = typeof args.content === 'string' ? args.content : '';
|
|
175
|
+
await fsp.mkdir(join(safeJoinedPath, '..'), { recursive: true });
|
|
176
|
+
await fsp.writeFile(safeJoinedPath, content, 'utf-8');
|
|
177
|
+
return { stdout: `wrote ${content.length} bytes to ${safeJoinedPath}`, stderr: '', exitCode: 0 };
|
|
178
|
+
}
|
|
179
|
+
default:
|
|
180
|
+
return { stdout: '', stderr: `[fs] unknown action: ${action}`, exitCode: 127 };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
return { stdout: '', stderr: `[fs] ${err.message}`, exitCode: 1 };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
log(level, msg, meta) {
|
|
188
|
+
if (this.opts.log) {
|
|
189
|
+
this.opts.log(level, msg, meta);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const line = meta ? `${msg} ${JSON.stringify(meta)}` : msg;
|
|
193
|
+
if (level === 'error')
|
|
194
|
+
console.error(`[rpc] ${line}`);
|
|
195
|
+
else if (level === 'warn')
|
|
196
|
+
console.warn(`[rpc] ${line}`);
|
|
197
|
+
else
|
|
198
|
+
console.log(`[rpc] ${line}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=rpc-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-handler.js","sourceRoot":"","sources":["../../src/mesh/rpc-handler.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,4CAA4C;AAC5C,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,EAAE;AACF,kDAAkD;AAClD,oEAAoE;AACpE,EAAE;AACF,oEAAoE;AACpE,uEAAuE;AACvE,iEAAiE;AACjE,qEAAqE;AAErE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,WAAW,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAIhE,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAMhC,MAAM,OAAO,UAAU;IAED;IAAgC;IAD5C,MAAM,GAAwB,IAAI,CAAC;IAC3C,YAAoB,MAAsB,EAAU,OAAuB,EAAE;QAAzD,WAAM,GAAN,MAAM,CAAgB;QAAU,SAAI,GAAJ,IAAI,CAAqB;IAAG,CAAC;IAEjF,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;YAC5C,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO;YAAC,CAAC;YACrD,MAAM,GAAG,GAAG,MAA2B,CAAC;YACxC,IAAI,GAAG,EAAE,IAAI,KAAK,iBAAiB;gBAAE,OAAO;YAC5C,MAAM,GAAG,GAAG,GAA8B,CAAC;YAC3C,6DAA6D;YAC7D,0CAA0C;YAC1C,KAAK,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,GAAiB;QAC3C,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE;YAClC,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM;SAC3E,CAAC,CAAC;QACH,IAAI,MAA4D,CAAC;QACjE,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,aAAc,GAAa,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QACtF,CAAC;QACD,oDAAoD;QACpD,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;YAC5C,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,uBAAuB,gBAAgB,SAAS,CAAC;QAC9G,CAAC;QACD,MAAM,QAAQ,GAAkB;YAC9B,IAAI,EAAE,kBAAkB;YACxB,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC;QACF,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,6BAA6B,EAAE,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,GAAiB;QACtC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;QAClC,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,MAAM;gBACT,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAC9B,KAAK,IAAI;gBACP,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC5B,yDAAyD;YACzD,KAAK,KAAK,CAAC;YACX,KAAK,QAAQ,CAAC;YACd,KAAK,KAAK,CAAC;YACX,KAAK,SAAS,CAAC;YACf,KAAK,KAAK,CAAC;YACX,KAAK,KAAK,CAAC;YACX,KAAK,KAAK,CAAC;YACX,KAAK,KAAK;gBACR,OAAO;oBACL,MAAM,EAAE,EAAE;oBACV,MAAM,EAAE,kBAAkB,MAAM,2EAA2E;oBAC3G,QAAQ,EAAE,GAAG;iBACd,CAAC;YACJ;gBACE,OAAO;oBACL,MAAM,EAAE,EAAE;oBACV,MAAM,EAAE,0BAA0B,MAAM,EAAE;oBAC1C,QAAQ,EAAE,GAAG;iBACd,CAAC;QACN,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACK,UAAU,CAAC,GAAiB;QAClC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;QAC7D,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;YACtB,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,sBAAsB,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YACnF,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,KAAK,CAAC;YACnB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,MAAM,GAAG,IAAI,CAAC;gBACd,IAAI,CAAC;oBAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;YACtD,CAAC,EAAE,eAAe,CAAC,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAClE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAClE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBACxB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,IAAI,MAAM,EAAE,CAAC;oBACX,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,eAAe,GAAG,IAAI,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;gBACnG,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;gBACpD,CAAC;YACH,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACvB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,uBAAuB,GAAG,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;YACtF,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,QAAQ,CAAC,GAAiB;QACtC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,MAAM,cAAc,GAAG,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;QAE1F,IAAI,CAAC;YACH,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,KAAK,CAAC;gBACX,KAAK,MAAM,CAAC,CAAC,CAAC;oBACZ,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;oBAC5D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;gBACtD,CAAC;gBACD,KAAK,IAAI,CAAC,CAAC,CAAC;oBACV,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC3E,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;oBAC7E,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;gBAC/D,CAAC;gBACD,KAAK,OAAO,CAAC,CAAC,CAAC;oBACb,MAAM,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;oBACrD,OAAO,EAAE,MAAM,EAAE,WAAW,cAAc,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;gBAC1E,CAAC;gBACD,KAAK,OAAO,CAAC,CAAC,CAAC;oBACb,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrE,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;oBACjE,MAAM,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;oBACtD,OAAO,EAAE,MAAM,EAAE,SAAS,OAAO,CAAC,MAAM,aAAa,cAAc,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;gBACnG,CAAC;gBACD;oBACE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,wBAAwB,MAAM,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;YACnF,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAS,GAAa,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QAC/E,CAAC;IACH,CAAC;IAEO,GAAG,CAAC,KAAgC,EAAE,GAAW,EAAE,IAA8B;QACvF,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAC3D,IAAI,KAAK,KAAK,OAAO;YAAE,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;aACjD,IAAI,KAAK,KAAK,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;;YACpD,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACpC,CAAC;CACF"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { PartyKitClient } from './partykit-client.js';
|
|
2
|
+
export type ThreadEvent = {
|
|
3
|
+
type: 'thread:sync';
|
|
4
|
+
threadId: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'thread:created';
|
|
7
|
+
threadId: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
} | {
|
|
10
|
+
type: 'thread:user-message';
|
|
11
|
+
threadId: string;
|
|
12
|
+
content: string;
|
|
13
|
+
imageURLs?: string[];
|
|
14
|
+
} | {
|
|
15
|
+
type: 'agent:start';
|
|
16
|
+
runId: string;
|
|
17
|
+
agentType?: string;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
threadId?: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'agent:user-message';
|
|
22
|
+
runId: string;
|
|
23
|
+
content: string;
|
|
24
|
+
threadId?: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'agent:text-delta';
|
|
27
|
+
runId: string;
|
|
28
|
+
delta: string;
|
|
29
|
+
agentType?: string;
|
|
30
|
+
threadId?: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'agent:text-done';
|
|
33
|
+
runId: string;
|
|
34
|
+
fullText?: string;
|
|
35
|
+
threadId?: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'agent:tool-use';
|
|
38
|
+
runId: string;
|
|
39
|
+
toolUseId: string;
|
|
40
|
+
name: string;
|
|
41
|
+
input: Record<string, unknown>;
|
|
42
|
+
threadId?: string;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'agent:tool-result';
|
|
45
|
+
runId: string;
|
|
46
|
+
toolUseId: string;
|
|
47
|
+
exitCode: number;
|
|
48
|
+
result?: string;
|
|
49
|
+
threadId?: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'agent:done';
|
|
52
|
+
runId: string;
|
|
53
|
+
threadId?: string;
|
|
54
|
+
} | {
|
|
55
|
+
type: 'agent:error';
|
|
56
|
+
runId: string;
|
|
57
|
+
message: string;
|
|
58
|
+
threadId?: string;
|
|
59
|
+
} | {
|
|
60
|
+
type: 'agent:retry';
|
|
61
|
+
runId: string;
|
|
62
|
+
attempt?: number;
|
|
63
|
+
threadId?: string;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'agent:quota-exceeded';
|
|
66
|
+
runId: string;
|
|
67
|
+
reason: string;
|
|
68
|
+
threadId?: string;
|
|
69
|
+
} | {
|
|
70
|
+
type: 'device-updated';
|
|
71
|
+
devices: Array<Record<string, unknown>>;
|
|
72
|
+
} | {
|
|
73
|
+
type: string;
|
|
74
|
+
[k: string]: unknown;
|
|
75
|
+
};
|
|
76
|
+
export type ThreadEventHandler = (event: ThreadEvent) => void;
|
|
77
|
+
export interface ThreadStreamOpts {
|
|
78
|
+
client: PartyKitClient;
|
|
79
|
+
/** Initial thread id (if resuming). Otherwise the first `thread:sync` wins. */
|
|
80
|
+
initialThreadId?: string;
|
|
81
|
+
log?: (level: 'info' | 'warn' | 'error', msg: string, meta?: Record<string, unknown>) => void;
|
|
82
|
+
}
|
|
83
|
+
export declare class ThreadStream {
|
|
84
|
+
private readonly client;
|
|
85
|
+
private readonly handlers;
|
|
86
|
+
private currentThreadId;
|
|
87
|
+
private unsubscribe;
|
|
88
|
+
private readonly log;
|
|
89
|
+
constructor(opts: ThreadStreamOpts);
|
|
90
|
+
/** Begin dispatching ws frames to subscribers. Safe to call multiple times. */
|
|
91
|
+
start(): void;
|
|
92
|
+
stop(): void;
|
|
93
|
+
/** Subscribe to all thread/agent events. Returns an unsubscribe fn. */
|
|
94
|
+
on(handler: ThreadEventHandler): () => void;
|
|
95
|
+
get threadId(): string | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Send a chat message as if it were typed in iOS / macOS Yome.app.
|
|
98
|
+
* The server treats the connection's authenticated userId as the
|
|
99
|
+
* sender; no extra identity bookkeeping needed here.
|
|
100
|
+
*
|
|
101
|
+
* If we don't yet have a threadId, omit it: the server will create or
|
|
102
|
+
* resolve the default thread, then broadcast `thread:sync` which we
|
|
103
|
+
* latch onto in handleFrame.
|
|
104
|
+
*/
|
|
105
|
+
sendUserMessage(content: string, opts?: {
|
|
106
|
+
threadTitle?: string;
|
|
107
|
+
}): Promise<void>;
|
|
108
|
+
private handleFrame;
|
|
109
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Thread event stream — typed wrapper over PartyKitClient for the TUI.
|
|
2
|
+
//
|
|
3
|
+
// PartyKit broadcasts the canonical thread/agent event protocol (see
|
|
4
|
+
// Server/party/yome.ts handleUserMessage + YomeAgentRuntime). This
|
|
5
|
+
// module is the read/write surface the TUI uses to participate as a
|
|
6
|
+
// thin client, identical to how iOS / macOS Yome.app participate:
|
|
7
|
+
//
|
|
8
|
+
// incoming (subscribe):
|
|
9
|
+
// thread:sync { threadId }
|
|
10
|
+
// thread:created { threadId, title }
|
|
11
|
+
// thread:user-message { threadId, content, imageURLs?, ... }
|
|
12
|
+
// agent:start { runId, agentType, sessionId? }
|
|
13
|
+
// agent:user-message { runId, content }
|
|
14
|
+
// agent:text-delta { runId, delta, agentType }
|
|
15
|
+
// agent:text-done { runId, fullText? }
|
|
16
|
+
// agent:tool-use { runId, toolUseId, name, input }
|
|
17
|
+
// agent:tool-result { runId, toolUseId, exitCode, result? }
|
|
18
|
+
// agent:done { runId }
|
|
19
|
+
// agent:error { runId, message }
|
|
20
|
+
// agent:retry { runId, attempt }
|
|
21
|
+
// agent:quota-exceeded { runId, reason, ... }
|
|
22
|
+
// device-updated { devices: [...] }
|
|
23
|
+
//
|
|
24
|
+
// outgoing (publish):
|
|
25
|
+
// { type: 'message', content, threadId?, ... } // user types a line
|
|
26
|
+
//
|
|
27
|
+
// The cli stays Stage A's bash/fs RPC executor _while also_ being a
|
|
28
|
+
// chat client — both directions share the same WS via PartyKitClient,
|
|
29
|
+
// because that's exactly how iOS Yome.app does it.
|
|
30
|
+
export class ThreadStream {
|
|
31
|
+
client;
|
|
32
|
+
handlers = new Set();
|
|
33
|
+
currentThreadId;
|
|
34
|
+
unsubscribe = null;
|
|
35
|
+
log;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
this.client = opts.client;
|
|
38
|
+
this.currentThreadId = opts.initialThreadId;
|
|
39
|
+
this.log = opts.log ?? (() => { });
|
|
40
|
+
}
|
|
41
|
+
/** Begin dispatching ws frames to subscribers. Safe to call multiple times. */
|
|
42
|
+
start() {
|
|
43
|
+
if (this.unsubscribe)
|
|
44
|
+
return;
|
|
45
|
+
this.unsubscribe = this.client.onMessage((frame) => this.handleFrame(frame));
|
|
46
|
+
}
|
|
47
|
+
stop() {
|
|
48
|
+
this.unsubscribe?.();
|
|
49
|
+
this.unsubscribe = null;
|
|
50
|
+
this.handlers.clear();
|
|
51
|
+
}
|
|
52
|
+
/** Subscribe to all thread/agent events. Returns an unsubscribe fn. */
|
|
53
|
+
on(handler) {
|
|
54
|
+
this.handlers.add(handler);
|
|
55
|
+
return () => this.handlers.delete(handler);
|
|
56
|
+
}
|
|
57
|
+
get threadId() {
|
|
58
|
+
return this.currentThreadId;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Send a chat message as if it were typed in iOS / macOS Yome.app.
|
|
62
|
+
* The server treats the connection's authenticated userId as the
|
|
63
|
+
* sender; no extra identity bookkeeping needed here.
|
|
64
|
+
*
|
|
65
|
+
* If we don't yet have a threadId, omit it: the server will create or
|
|
66
|
+
* resolve the default thread, then broadcast `thread:sync` which we
|
|
67
|
+
* latch onto in handleFrame.
|
|
68
|
+
*/
|
|
69
|
+
async sendUserMessage(content, opts) {
|
|
70
|
+
const frame = {
|
|
71
|
+
type: 'message',
|
|
72
|
+
content,
|
|
73
|
+
};
|
|
74
|
+
if (this.currentThreadId)
|
|
75
|
+
frame.threadId = this.currentThreadId;
|
|
76
|
+
if (opts?.threadTitle)
|
|
77
|
+
frame.threadTitle = opts.threadTitle;
|
|
78
|
+
await this.client.send(frame);
|
|
79
|
+
}
|
|
80
|
+
handleFrame(text) {
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(text);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Non-JSON frames (e.g. ping/pong text) — ignore.
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!parsed || typeof parsed !== 'object' || typeof parsed.type !== 'string')
|
|
90
|
+
return;
|
|
91
|
+
// Latch threadId from the first authoritative server frame so the
|
|
92
|
+
// next user message round-trips cleanly even before the user
|
|
93
|
+
// explicitly switches threads.
|
|
94
|
+
if (parsed.type === 'thread:sync' && typeof parsed.threadId === 'string') {
|
|
95
|
+
this.currentThreadId = parsed.threadId;
|
|
96
|
+
}
|
|
97
|
+
if (parsed.type === 'thread:created' && !this.currentThreadId && typeof parsed.threadId === 'string') {
|
|
98
|
+
this.currentThreadId = parsed.threadId;
|
|
99
|
+
}
|
|
100
|
+
for (const h of this.handlers) {
|
|
101
|
+
try {
|
|
102
|
+
h(parsed);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
this.log('error', 'ThreadStream handler threw', { err: err.message });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=thread-stream.js.map
|