@parall/daemon 1.30.0 → 1.32.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.
Files changed (53) hide show
  1. package/bundle/manifest.json +17 -11
  2. package/bundle/package.json +1 -0
  3. package/bundle/parall-claude-agent.js +27671 -337
  4. package/bundle/parall-codex-agent.js +27715 -375
  5. package/bundle/parall-daemon.js +30421 -1355
  6. package/bundle/parall-openclaw-agent.js +51 -26
  7. package/dist/cli.d.ts +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +122 -72
  10. package/dist/clip-runtime/clip-installer.d.ts +44 -0
  11. package/dist/clip-runtime/clip-installer.d.ts.map +1 -0
  12. package/dist/clip-runtime/clip-installer.js +501 -0
  13. package/dist/clip-runtime/clip-provider.d.ts +76 -0
  14. package/dist/clip-runtime/clip-provider.d.ts.map +1 -0
  15. package/dist/clip-runtime/clip-provider.js +402 -0
  16. package/dist/clip-runtime/index.d.ts +7 -0
  17. package/dist/clip-runtime/index.d.ts.map +1 -0
  18. package/dist/clip-runtime/index.js +5 -0
  19. package/dist/clip-runtime/ipc.d.ts +94 -0
  20. package/dist/clip-runtime/ipc.d.ts.map +1 -0
  21. package/dist/clip-runtime/ipc.js +98 -0
  22. package/dist/clip-runtime/manifest.d.ts +74 -0
  23. package/dist/clip-runtime/manifest.d.ts.map +1 -0
  24. package/dist/clip-runtime/manifest.js +181 -0
  25. package/dist/clip-runtime/process-manager.d.ts +57 -0
  26. package/dist/clip-runtime/process-manager.d.ts.map +1 -0
  27. package/dist/clip-runtime/process-manager.js +354 -0
  28. package/dist/clip-runtime/process.d.ts +59 -0
  29. package/dist/clip-runtime/process.d.ts.map +1 -0
  30. package/dist/clip-runtime/process.js +350 -0
  31. package/dist/config.d.ts +13 -0
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +36 -19
  34. package/dist/filesystem.d.ts +1 -1
  35. package/dist/filesystem.d.ts.map +1 -1
  36. package/dist/filesystem.js +51 -53
  37. package/dist/index.js +46 -14
  38. package/dist/runtimes.d.ts +10 -8
  39. package/dist/runtimes.d.ts.map +1 -1
  40. package/dist/runtimes.js +63 -95
  41. package/dist/supervisor.d.ts +12 -3
  42. package/dist/supervisor.d.ts.map +1 -1
  43. package/dist/supervisor.js +272 -71
  44. package/dist/updater-manifest.d.ts +41 -0
  45. package/dist/updater-manifest.d.ts.map +1 -0
  46. package/dist/updater-manifest.js +94 -0
  47. package/dist/updater.d.ts +60 -0
  48. package/dist/updater.d.ts.map +1 -0
  49. package/dist/updater.js +427 -0
  50. package/dist/workspace.d.ts +2 -2
  51. package/dist/workspace.d.ts.map +1 -1
  52. package/dist/workspace.js +112 -112
  53. package/package.json +6 -6
@@ -0,0 +1,402 @@
1
+ /**
2
+ * ClipProvider — Connect-RPC client that connects the Daemon to the Clip
3
+ * Service (Hub) as a Provider. Uses Node.js http2 directly with Connect
4
+ * protocol bidi streaming (envelope-framed JSON over HTTP/2).
5
+ *
6
+ * Protocol: POST /clip.v1.ClipHubService/ProviderStream
7
+ * Content-Type: application/connect+json
8
+ * Authorization: Bearer mck_xxx
9
+ * Body: envelope-framed ProviderMessage stream
10
+ * Response: envelope-framed HubMessage stream
11
+ *
12
+ * Connect envelope format (JSON mode):
13
+ * [flags:1][length:4 big-endian][JSON payload]
14
+ * flags=0x00 data, flags=0x02 end-of-stream (trailers)
15
+ */
16
+ import * as http2 from 'node:http2';
17
+ import { ClipCommandError } from './process.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Connect envelope helpers
20
+ // ---------------------------------------------------------------------------
21
+ const ENVELOPE_FLAG_DATA = 0x00;
22
+ const ENVELOPE_FLAG_TRAILER = 0x02;
23
+ function encodeEnvelope(msg) {
24
+ const json = Buffer.from(JSON.stringify(msg), 'utf-8');
25
+ const header = Buffer.alloc(5);
26
+ header[0] = ENVELOPE_FLAG_DATA;
27
+ header.writeUInt32BE(json.length, 1);
28
+ return Buffer.concat([header, json]);
29
+ }
30
+ /**
31
+ * Incremental envelope decoder. Feed chunks via push(), receive decoded
32
+ * messages from flush(). Handles partial reads across chunk boundaries.
33
+ */
34
+ class EnvelopeDecoder {
35
+ buf = Buffer.alloc(0);
36
+ push(chunk) {
37
+ this.buf = this.buf.length === 0 ? Buffer.from(chunk) : Buffer.concat([this.buf, chunk]);
38
+ }
39
+ /** Drain all complete envelopes from the internal buffer. */
40
+ static MAX_FRAME_LENGTH = 16 * 1024 * 1024; // 16 MiB
41
+ *flush() {
42
+ while (this.buf.length >= 5) {
43
+ const flags = this.buf[0];
44
+ const length = this.buf.readUInt32BE(1);
45
+ if (length > EnvelopeDecoder.MAX_FRAME_LENGTH) {
46
+ this.buf = Buffer.alloc(0);
47
+ throw new Error(`envelope frame too large: ${length} bytes`);
48
+ }
49
+ if (this.buf.length < 5 + length)
50
+ break; // incomplete frame
51
+ const payload = this.buf.subarray(5, 5 + length);
52
+ this.buf = Buffer.from(this.buf.subarray(5 + length));
53
+ yield { flags, payload };
54
+ }
55
+ }
56
+ }
57
+ export class ClipProvider {
58
+ opts;
59
+ session = null;
60
+ stream = null;
61
+ stopped = false;
62
+ connected = false;
63
+ reconnectAttempt = 0;
64
+ reconnectTimer = null;
65
+ heartbeatTimer = null;
66
+ statusUnsubscribe = null;
67
+ manifestUnsubscribe = null;
68
+ needsReregister = false;
69
+ intentionalClose = false;
70
+ static RECONNECT_BASE_MS = 5_000;
71
+ static RECONNECT_MAX_MS = 60_000;
72
+ static HEARTBEAT_INTERVAL_MS = 30_000;
73
+ constructor(opts) {
74
+ this.opts = opts;
75
+ }
76
+ /** Connect to the Clip Service and register local clips. Reconnects on failure. */
77
+ async connect() {
78
+ this.stopped = false;
79
+ // Subscribe to clip status changes so we can forward them to the Hub
80
+ this.subscribeStatusChanges();
81
+ await this.openStream();
82
+ }
83
+ /** Disconnect and stop reconnection. */
84
+ async disconnect() {
85
+ this.stopped = true;
86
+ this.unsubscribeStatusChanges();
87
+ this.clearReconnectTimer();
88
+ this.clearHeartbeatTimer();
89
+ this.closeStream();
90
+ this.closeSession();
91
+ this.connected = false;
92
+ }
93
+ isConnected() {
94
+ return this.connected;
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // Stream lifecycle
98
+ // ---------------------------------------------------------------------------
99
+ async openStream() {
100
+ if (this.stopped)
101
+ return;
102
+ try {
103
+ this.closeStream();
104
+ this.closeSession();
105
+ const url = new URL(this.opts.serviceUrl);
106
+ const authority = url.origin;
107
+ this.session = http2.connect(authority);
108
+ this.session.on('error', (err) => {
109
+ this.opts.log.warn(`[clip-provider] h2 session error: ${String(err)}`);
110
+ this.handleDisconnect();
111
+ });
112
+ this.session.on('close', () => {
113
+ this.handleDisconnect();
114
+ });
115
+ this.stream = this.session.request({
116
+ ':method': 'POST',
117
+ ':path': '/clip.v1.ClipHubService/ProviderStream',
118
+ 'content-type': 'application/connect+json',
119
+ 'connect-protocol-version': '1',
120
+ authorization: `Bearer ${this.opts.authKey}`,
121
+ });
122
+ this.stream.on('error', (err) => {
123
+ this.opts.log.warn(`[clip-provider] stream error: ${String(err)}`);
124
+ this.handleDisconnect();
125
+ });
126
+ // Read response envelopes
127
+ const decoder = new EnvelopeDecoder();
128
+ this.stream.on('data', (chunk) => {
129
+ decoder.push(chunk);
130
+ for (const envelope of decoder.flush()) {
131
+ if (envelope.flags === ENVELOPE_FLAG_TRAILER) {
132
+ // End-of-stream trailers — connection closing
133
+ this.opts.log.info('[clip-provider] received end-of-stream trailer');
134
+ continue;
135
+ }
136
+ try {
137
+ const msg = JSON.parse(envelope.payload.toString('utf-8'));
138
+ this.handleHubMessage(msg);
139
+ }
140
+ catch (err) {
141
+ this.opts.log.warn(`[clip-provider] failed to parse hub message: ${String(err)}`);
142
+ }
143
+ }
144
+ });
145
+ this.stream.on('end', () => {
146
+ this.opts.log.info('[clip-provider] stream ended by hub');
147
+ this.handleDisconnect();
148
+ });
149
+ this.stream.on('close', () => {
150
+ this.handleDisconnect();
151
+ });
152
+ // Send ProviderRegister as the first message
153
+ await this.sendRegister();
154
+ }
155
+ catch (err) {
156
+ this.opts.log.warn(`[clip-provider] connect failed: ${String(err)}`);
157
+ this.scheduleReconnect();
158
+ }
159
+ }
160
+ handleDisconnect() {
161
+ if (this.stopped || this.intentionalClose)
162
+ return;
163
+ if (!this.connected && this.reconnectTimer)
164
+ return; // already scheduling
165
+ this.connected = false;
166
+ this.clearHeartbeatTimer();
167
+ this.scheduleReconnect();
168
+ }
169
+ scheduleReconnect() {
170
+ if (this.stopped || this.reconnectTimer)
171
+ return;
172
+ const delay = Math.min(ClipProvider.RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempt), ClipProvider.RECONNECT_MAX_MS);
173
+ this.reconnectAttempt++;
174
+ this.opts.log.info(`[clip-provider] reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
175
+ this.reconnectTimer = setTimeout(() => {
176
+ this.reconnectTimer = null;
177
+ void this.openStream();
178
+ }, delay);
179
+ this.reconnectTimer.unref?.();
180
+ }
181
+ clearReconnectTimer() {
182
+ if (this.reconnectTimer) {
183
+ clearTimeout(this.reconnectTimer);
184
+ this.reconnectTimer = null;
185
+ }
186
+ }
187
+ clearHeartbeatTimer() {
188
+ if (this.heartbeatTimer) {
189
+ clearInterval(this.heartbeatTimer);
190
+ this.heartbeatTimer = null;
191
+ }
192
+ }
193
+ startHeartbeat() {
194
+ this.clearHeartbeatTimer();
195
+ this.heartbeatTimer = setInterval(() => {
196
+ // If a clip manifest was populated since last heartbeat, reconnect to
197
+ // re-register all clips with their now-available commands.
198
+ if (this.needsReregister) {
199
+ this.needsReregister = false;
200
+ this.opts.log.info('[clip-provider] manifest updated — reconnecting to re-register clips');
201
+ this.intentionalClose = true;
202
+ this.closeStream();
203
+ this.closeSession();
204
+ this.intentionalClose = false;
205
+ this.connected = false;
206
+ this.clearHeartbeatTimer();
207
+ this.clearReconnectTimer();
208
+ void this.openStream();
209
+ return;
210
+ }
211
+ this.sendProviderMessage({ heartbeat: {} }).catch((err) => {
212
+ this.opts.log.warn(`[clip-provider] heartbeat send failed: ${String(err)}`);
213
+ });
214
+ }, ClipProvider.HEARTBEAT_INTERVAL_MS);
215
+ this.heartbeatTimer.unref?.();
216
+ }
217
+ closeStream() {
218
+ if (this.stream) {
219
+ try {
220
+ this.stream.close();
221
+ }
222
+ catch {
223
+ /* already closed */
224
+ }
225
+ this.stream = null;
226
+ }
227
+ }
228
+ closeSession() {
229
+ if (this.session) {
230
+ try {
231
+ this.session.close();
232
+ }
233
+ catch {
234
+ /* already closed */
235
+ }
236
+ this.session = null;
237
+ }
238
+ }
239
+ // ---------------------------------------------------------------------------
240
+ // Status change forwarding
241
+ // ---------------------------------------------------------------------------
242
+ subscribeStatusChanges() {
243
+ const mgr = this.opts.clipManager;
244
+ // Forward clip status changes to the Hub
245
+ this.statusUnsubscribe = mgr.addStatusListener((name, status, _message) => {
246
+ if (!this.connected)
247
+ return;
248
+ const protoStatus = status === 'running'
249
+ ? 'CLIP_ONLINE_STATUS_ONLINE'
250
+ : status === 'error'
251
+ ? 'CLIP_ONLINE_STATUS_ERROR'
252
+ : 'CLIP_ONLINE_STATUS_OFFLINE';
253
+ this.sendProviderMessage({
254
+ clipStatusChanged: { alias: name, status: protoStatus },
255
+ }).catch((err) => {
256
+ this.opts.log.warn(`[clip-provider] status change send failed: ${String(err)}`);
257
+ });
258
+ });
259
+ // Listen for manifest updates so we can re-register with populated commands
260
+ this.manifestUnsubscribe = mgr.addManifestListener(() => {
261
+ this.needsReregister = true;
262
+ });
263
+ }
264
+ unsubscribeStatusChanges() {
265
+ this.statusUnsubscribe?.();
266
+ this.statusUnsubscribe = null;
267
+ this.manifestUnsubscribe?.();
268
+ this.manifestUnsubscribe = null;
269
+ }
270
+ // ---------------------------------------------------------------------------
271
+ // Message handling
272
+ // ---------------------------------------------------------------------------
273
+ handleHubMessage(msg) {
274
+ if (msg.registered) {
275
+ this.handleRegistered(msg.registered);
276
+ }
277
+ else if (msg.invokeCommand) {
278
+ void this.handleInvokeCommand(msg.invokeCommand);
279
+ }
280
+ else if (msg.heartbeat !== undefined) {
281
+ // Respond to hub heartbeat with provider heartbeat
282
+ this.sendProviderMessage({ heartbeat: {} }).catch(() => { });
283
+ }
284
+ }
285
+ handleRegistered(reg) {
286
+ if (reg.accepted) {
287
+ this.opts.log.info(`[clip-provider] registered with hub (org=${reg.orgId})`);
288
+ this.connected = true;
289
+ this.reconnectAttempt = 0;
290
+ this.startHeartbeat();
291
+ }
292
+ else {
293
+ this.opts.log.error(`[clip-provider] registration rejected: ${reg.message}`);
294
+ // Don't reconnect on rejection — likely a permanent auth error
295
+ this.stopped = true;
296
+ this.closeStream();
297
+ this.closeSession();
298
+ }
299
+ }
300
+ async handleInvokeCommand(cmd) {
301
+ const { requestId, clipAlias, commandName } = cmd;
302
+ const timeoutMs = cmd.timeoutMs ?? 30_000;
303
+ try {
304
+ let input;
305
+ if (cmd.input) {
306
+ const decoded = Buffer.from(cmd.input, 'base64').toString('utf-8');
307
+ try {
308
+ input = JSON.parse(decoded);
309
+ }
310
+ catch {
311
+ input = decoded;
312
+ }
313
+ }
314
+ let timer;
315
+ const output = await Promise.race([
316
+ this.opts.clipManager.invoke(clipAlias, commandName, input),
317
+ new Promise((_, reject) => {
318
+ timer = setTimeout(() => reject(new Error('invoke timeout')), timeoutMs);
319
+ }),
320
+ ]).finally(() => {
321
+ if (timer)
322
+ clearTimeout(timer);
323
+ });
324
+ // Encode output as base64 bytes
325
+ const outputBytes = Buffer.from(typeof output === 'string' ? output : JSON.stringify(output ?? null), 'utf-8').toString('base64');
326
+ await this.sendProviderMessage({
327
+ invokeResult: { requestId, output: outputBytes, done: true },
328
+ });
329
+ }
330
+ catch (err) {
331
+ // Only a ClipCommandError is a clip application-level error safe to
332
+ // surface; send its bare message (no "ClipCommandError:" prefix) and flag
333
+ // it. Everything else (spawn failure, crash, timeout) is an internal error
334
+ // the Hub maps to a generic 500 without leaking the message.
335
+ const isCommandError = err instanceof ClipCommandError;
336
+ await this.sendProviderMessage({
337
+ invokeResult: {
338
+ requestId,
339
+ error: isCommandError ? err.message : String(err),
340
+ errorIsCommand: isCommandError,
341
+ done: true,
342
+ },
343
+ }).catch(() => { }); // best-effort error response
344
+ }
345
+ }
346
+ // ---------------------------------------------------------------------------
347
+ // Sending
348
+ // ---------------------------------------------------------------------------
349
+ async sendRegister() {
350
+ const clips = this.buildClipRegistrations();
351
+ await this.sendProviderMessage({
352
+ register: {
353
+ providerName: this.opts.providerName,
354
+ clips,
355
+ orgId: this.opts.orgId,
356
+ },
357
+ });
358
+ }
359
+ async sendProviderMessage(msg) {
360
+ if (!this.stream || this.stream.closed || this.stream.destroyed) {
361
+ throw new Error('stream not open');
362
+ }
363
+ const envelope = encodeEnvelope(msg);
364
+ return new Promise((resolve, reject) => {
365
+ this.stream.write(envelope, (err) => {
366
+ if (err)
367
+ reject(err);
368
+ else
369
+ resolve();
370
+ });
371
+ });
372
+ }
373
+ // ---------------------------------------------------------------------------
374
+ // Clip registration helpers
375
+ // ---------------------------------------------------------------------------
376
+ buildClipRegistrations() {
377
+ const clips = this.opts.clipManager.getRegisteredClips();
378
+ return clips.map((clip) => this.clipConfigToRegistration(clip));
379
+ }
380
+ clipConfigToRegistration(clip) {
381
+ const manifest = clip.manifest;
382
+ const commands = manifest
383
+ ? manifest.commandDetails.map((cmd) => this.commandDetailToInfo(cmd))
384
+ : [];
385
+ return {
386
+ alias: clip.name,
387
+ name: manifest?.package ?? clip.package ?? clip.name,
388
+ version: manifest?.version ?? clip.version ?? '',
389
+ commands,
390
+ };
391
+ }
392
+ commandDetailToInfo(detail) {
393
+ return {
394
+ name: detail.name,
395
+ description: detail.description ?? '',
396
+ inputSchema: detail.input ? Buffer.from(detail.input, 'utf-8').toString('base64') : undefined,
397
+ outputSchema: detail.output
398
+ ? Buffer.from(detail.output, 'utf-8').toString('base64')
399
+ : undefined,
400
+ };
401
+ }
402
+ }
@@ -0,0 +1,7 @@
1
+ export { ClipProcessManager, type ClipProcessManagerOptions } from './process-manager.js';
2
+ export { ClipProcess, ClipCommandError, type ClipProcessStatus, type InvokeEvent, type InvokeResult, } from './process.js';
3
+ export { type IpcMessage, type IpcManifest, type ListClipInfo, type ListCommandInfo, type IpcError, MessageType, NdjsonReader, NdjsonWriter, } from './ipc.js';
4
+ export { type ClipConfig, type ManifestCache, type CommandDetail, type ClipJson, } from './manifest.js';
5
+ export { ClipProvider, type ClipProviderOptions } from './clip-provider.js';
6
+ export { installClip, removeClip, parseSource, type InstallOptions, type InstallResult, } from './clip-installer.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { ClipProcessManager } from './process-manager.js';
2
+ export { ClipProcess, ClipCommandError, } from './process.js';
3
+ export { MessageType, NdjsonReader, NdjsonWriter, } from './ipc.js';
4
+ export { ClipProvider } from './clip-provider.js';
5
+ export { installClip, removeClip, parseSource, } from './clip-installer.js';
@@ -0,0 +1,94 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ export declare const MessageType: {
3
+ readonly Register: "register";
4
+ readonly Registered: "registered";
5
+ readonly Invoke: "invoke";
6
+ readonly Result: "result";
7
+ readonly Error: "error";
8
+ readonly Chunk: "chunk";
9
+ readonly Done: "done";
10
+ readonly ListClips: "list_clips";
11
+ readonly ListClipsResult: "list_clips_result";
12
+ readonly Data: "data";
13
+ readonly DataResult: "data_result";
14
+ readonly Heartbeat: "heartbeat";
15
+ };
16
+ export interface IpcMessage {
17
+ id?: string;
18
+ type: string;
19
+ alias?: string;
20
+ clip?: string;
21
+ command?: string;
22
+ input?: unknown;
23
+ output?: unknown;
24
+ error?: string;
25
+ manifest?: IpcManifest;
26
+ clips?: ListClipInfo[];
27
+ operation?: string;
28
+ path?: string;
29
+ content?: string;
30
+ mime?: string;
31
+ uri?: string;
32
+ entries?: unknown;
33
+ stat?: unknown;
34
+ }
35
+ export interface IpcManifest {
36
+ package?: string;
37
+ version?: string;
38
+ domain?: string;
39
+ description?: string;
40
+ commands?: unknown;
41
+ has_web?: boolean;
42
+ dependencies?: Record<string, {
43
+ package?: string;
44
+ version?: string;
45
+ }>;
46
+ patterns?: string[];
47
+ entities?: Record<string, unknown>;
48
+ }
49
+ export interface ListClipInfo {
50
+ name: string;
51
+ package?: string;
52
+ version?: string;
53
+ domain?: string;
54
+ description?: string;
55
+ patterns?: string[];
56
+ commands?: ListCommandInfo[];
57
+ }
58
+ export interface ListCommandInfo {
59
+ name: string;
60
+ description?: string;
61
+ input?: string;
62
+ output?: string;
63
+ }
64
+ export interface IpcError {
65
+ message: string;
66
+ code?: string;
67
+ }
68
+ export declare const IPC_CLOSED_ERROR: Error;
69
+ /**
70
+ * NDJSON reader for ChildProcess stdout.
71
+ * Corresponds to Pinix process.go readLoop's bufio.Scanner (8 MB line limit).
72
+ */
73
+ export declare class NdjsonReader {
74
+ private rl;
75
+ private closed;
76
+ constructor(stream: Readable);
77
+ [Symbol.asyncIterator](): AsyncIterableIterator<IpcMessage>;
78
+ close(): void;
79
+ }
80
+ /**
81
+ * NDJSON writer for ChildProcess stdin.
82
+ * Uses a Promise chain to serialize writes (corresponds to Pinix sendMu sync.Mutex).
83
+ */
84
+ export declare class NdjsonWriter {
85
+ private stream;
86
+ private chain;
87
+ private _closed;
88
+ constructor(stream: Writable);
89
+ get closed(): boolean;
90
+ send(message: IpcMessage): Promise<void>;
91
+ private doWrite;
92
+ close(): void;
93
+ }
94
+ //# sourceMappingURL=ipc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/ipc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import { createInterface } from 'node:readline';
2
+ // Message type constants — must match Pinix ipc.go exactly
3
+ export const MessageType = {
4
+ Register: 'register',
5
+ Registered: 'registered',
6
+ Invoke: 'invoke',
7
+ Result: 'result',
8
+ Error: 'error',
9
+ Chunk: 'chunk',
10
+ Done: 'done',
11
+ ListClips: 'list_clips',
12
+ ListClipsResult: 'list_clips_result',
13
+ Data: 'data',
14
+ DataResult: 'data_result',
15
+ Heartbeat: 'heartbeat',
16
+ };
17
+ export const IPC_CLOSED_ERROR = new Error('ipc client closed');
18
+ /**
19
+ * NDJSON reader for ChildProcess stdout.
20
+ * Corresponds to Pinix process.go readLoop's bufio.Scanner (8 MB line limit).
21
+ */
22
+ export class NdjsonReader {
23
+ rl;
24
+ closed = false;
25
+ constructor(stream) {
26
+ this.rl = createInterface({ input: stream, crlfDelay: Infinity });
27
+ }
28
+ async *[Symbol.asyncIterator]() {
29
+ for await (const line of this.rl) {
30
+ if (this.closed)
31
+ break;
32
+ const trimmed = line.trim();
33
+ if (!trimmed)
34
+ continue;
35
+ try {
36
+ const msg = JSON.parse(trimmed);
37
+ if (!msg.type) {
38
+ console.warn("[clip-ipc] message missing 'type' field, skipping");
39
+ continue;
40
+ }
41
+ yield msg;
42
+ }
43
+ catch {
44
+ console.warn('[clip-ipc] failed to parse NDJSON line, skipping');
45
+ }
46
+ }
47
+ }
48
+ close() {
49
+ this.closed = true;
50
+ this.rl.close();
51
+ }
52
+ }
53
+ /**
54
+ * NDJSON writer for ChildProcess stdin.
55
+ * Uses a Promise chain to serialize writes (corresponds to Pinix sendMu sync.Mutex).
56
+ */
57
+ export class NdjsonWriter {
58
+ stream;
59
+ chain = Promise.resolve();
60
+ _closed = false;
61
+ constructor(stream) {
62
+ this.stream = stream;
63
+ }
64
+ get closed() {
65
+ return this._closed;
66
+ }
67
+ async send(message) {
68
+ if (this._closed)
69
+ throw IPC_CLOSED_ERROR;
70
+ const p = this.chain.then(() => this.doWrite(message));
71
+ this.chain = p.catch(() => { });
72
+ return p;
73
+ }
74
+ doWrite(message) {
75
+ return new Promise((resolve, reject) => {
76
+ if (this._closed) {
77
+ reject(IPC_CLOSED_ERROR);
78
+ return;
79
+ }
80
+ const data = JSON.stringify(message) + '\n';
81
+ this.stream.write(data, (err) => {
82
+ if (err) {
83
+ this._closed = true;
84
+ reject(err);
85
+ }
86
+ else {
87
+ resolve();
88
+ }
89
+ });
90
+ });
91
+ }
92
+ close() {
93
+ this._closed = true;
94
+ if (!this.stream.destroyed) {
95
+ this.stream.end();
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,74 @@
1
+ import type { IpcManifest } from './ipc.js';
2
+ export interface ClipConfig {
3
+ name: string;
4
+ package?: string;
5
+ version?: string;
6
+ source: string;
7
+ path: string;
8
+ token?: string;
9
+ manifest?: ManifestCache;
10
+ }
11
+ export interface ManifestCache {
12
+ name: string;
13
+ package?: string;
14
+ version?: string;
15
+ domain?: string;
16
+ description?: string;
17
+ commands: string[];
18
+ commandDetails: CommandDetail[];
19
+ hasWeb?: boolean;
20
+ dependencies?: Record<string, {
21
+ package?: string;
22
+ version?: string;
23
+ }>;
24
+ patterns?: string[];
25
+ entities?: Record<string, unknown>;
26
+ }
27
+ export interface CommandDetail {
28
+ name: string;
29
+ description?: string;
30
+ input?: string;
31
+ output?: string;
32
+ }
33
+ export interface ClipJson {
34
+ name?: string;
35
+ version?: string;
36
+ description?: string;
37
+ runtime?: string;
38
+ main?: string;
39
+ web?: string;
40
+ author?: string;
41
+ license?: string;
42
+ repository?: string;
43
+ }
44
+ interface ProjectMetadata {
45
+ package?: string;
46
+ version?: string;
47
+ description?: string;
48
+ domain?: string;
49
+ main?: string;
50
+ web?: string;
51
+ dependencies?: Record<string, {
52
+ package?: string;
53
+ version?: string;
54
+ }>;
55
+ patterns?: string[];
56
+ commands?: CommandDetail[];
57
+ }
58
+ export declare function loadClipJson(dir: string): ClipJson | null;
59
+ export declare function loadProjectMetadata(clip: ClipConfig): ProjectMetadata;
60
+ /**
61
+ * Parse IPC commands in any of the three Pinix-supported formats:
62
+ * 1. string[] → ["add", "remove"]
63
+ * 2. CommandDetail[] → [{ name: "add", description: "..." }]
64
+ * 3. Record<string, object> → { "add": { description: "..." } }
65
+ */
66
+ export declare function parseIpcCommands(data: unknown): CommandDetail[];
67
+ export declare function enrichManifest(clip: ClipConfig, manifest: ManifestCache): ManifestCache;
68
+ /**
69
+ * Build a ManifestCache from IPC register message manifest data.
70
+ */
71
+ export declare function manifestFromIpc(ipcManifest: IpcManifest): ManifestCache;
72
+ export declare function resolveEntrypoint(clip: ClipConfig): string;
73
+ export {};
74
+ //# sourceMappingURL=manifest.d.ts.map