@parall/parall 1.31.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 (47) hide show
  1. package/dist/accounts.d.ts +1 -1
  2. package/dist/accounts.js +4 -4
  3. package/dist/channel.d.ts +2 -2
  4. package/dist/channel.d.ts.map +1 -1
  5. package/dist/channel.js +10 -10
  6. package/dist/config-manager.d.ts +1 -1
  7. package/dist/config-manager.d.ts.map +1 -1
  8. package/dist/config-manager.js +70 -30
  9. package/dist/fork.d.ts.map +1 -1
  10. package/dist/fork.js +17 -17
  11. package/dist/gateway.d.ts +3 -3
  12. package/dist/gateway.d.ts.map +1 -1
  13. package/dist/gateway.js +147 -127
  14. package/dist/hooks.d.ts +1 -1
  15. package/dist/hooks.d.ts.map +1 -1
  16. package/dist/hooks.js +52 -26
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +7 -7
  19. package/dist/oc-session.d.ts.map +1 -1
  20. package/dist/oc-session.js +35 -27
  21. package/dist/outbound.d.ts +2 -2
  22. package/dist/outbound.d.ts.map +1 -1
  23. package/dist/outbound.js +13 -14
  24. package/dist/routing.d.ts +2 -2
  25. package/dist/routing.js +1 -1
  26. package/dist/runtime.d.ts +5 -5
  27. package/dist/runtime.d.ts.map +1 -1
  28. package/dist/runtime.js +2 -2
  29. package/dist/session.js +4 -4
  30. package/dist/wiki-helper.d.ts.map +1 -1
  31. package/dist/wiki-helper.js +27 -27
  32. package/openclaw.plugin.json +4 -1
  33. package/package.json +3 -3
  34. package/skills/parall-clips/SKILL.md +48 -0
  35. package/src/accounts.ts +5 -5
  36. package/src/channel.ts +15 -14
  37. package/src/config-manager.ts +79 -34
  38. package/src/fork.ts +26 -22
  39. package/src/gateway.ts +177 -134
  40. package/src/hooks.ts +109 -50
  41. package/src/index.ts +8 -8
  42. package/src/oc-session.ts +43 -35
  43. package/src/outbound.ts +18 -17
  44. package/src/routing.ts +2 -2
  45. package/src/runtime.ts +11 -7
  46. package/src/session.ts +4 -4
  47. package/src/wiki-helper.ts +47 -34
package/src/gateway.ts CHANGED
@@ -1,26 +1,30 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
2
- import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
1
+ import type { PluginRuntime } from 'openclaw/plugin-sdk';
2
+ import type { ChannelPlugin } from 'openclaw/plugin-sdk/core';
3
3
 
4
4
  /** Extract ChannelGatewayAdapter from ChannelPlugin (removed from public SDK exports in 2026.3.24). */
5
- type ChannelGatewayAdapter<T = unknown> = NonNullable<ChannelPlugin<T>["gateway"]>;
5
+ type ChannelGatewayAdapter<T = unknown> = NonNullable<ChannelPlugin<T>['gateway']>;
6
6
  import {
7
7
  ParallAgentGateway,
8
8
  parseShutdownDeadlineMs,
9
+ parseForkDeadlineMs,
10
+ parseDispatchDeadlineMs,
11
+ initAgentTelemetry,
12
+ createOtelLogger,
9
13
  type DispatchAdapter,
10
14
  type ParallEvent,
11
15
  type RuntimeEvent,
12
- } from "@parall/agent-core";
16
+ } from '@parall/agent-core';
13
17
  import {
14
18
  appendPreparedLocalAttachmentRefs,
15
19
  ensureLocalAttachmentGitExclude,
16
20
  pinLocalAttachmentPaths,
17
- } from "@parall/agent-core/internal/attachment-input";
18
- import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
19
- import type { ParallChannelConfig } from "./types.js";
20
- import * as crypto from "node:crypto";
21
- import * as os from "node:os";
22
- import * as path from "node:path";
23
- import { resolveParallAccount } from "./accounts.js";
21
+ } from '@parall/agent-core/internal/attachment-input';
22
+ import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
23
+ import type { ParallChannelConfig } from './types.js';
24
+ import * as crypto from 'node:crypto';
25
+ import * as os from 'node:os';
26
+ import * as path from 'node:path';
27
+ import { resolveParallAccount } from './accounts.js';
24
28
  import {
25
29
  getParallRuntime,
26
30
  removeParallAccountState,
@@ -28,18 +32,24 @@ import {
28
32
  setAgentSessionBinding,
29
33
  setDispatchGroupKey,
30
34
  setParallAccountState,
31
- } from "./runtime.js";
32
- import { buildOrchestratorSessionKey } from "./session.js";
33
- import type { ResolvedParallAccount } from "./types.js";
34
- import { fetchAndApplyPlatformConfig } from "./config-manager.js";
35
- import { startWikiHelper } from "./wiki-helper.js";
36
- import { SessionManager } from "./oc-session.js";
37
- import { cleanupForkSession, clearSessionState, forkOrchestratorSession, resolveSessionId, resolveTranscriptFile } from "./fork.js";
35
+ } from './runtime.js';
36
+ import { buildOrchestratorSessionKey } from './session.js';
37
+ import type { ResolvedParallAccount } from './types.js';
38
+ import { fetchAndApplyPlatformConfig } from './config-manager.js';
39
+ import { startWikiHelper } from './wiki-helper.js';
40
+ import { SessionManager } from './oc-session.js';
41
+ import {
42
+ cleanupForkSession,
43
+ clearSessionState,
44
+ forkOrchestratorSession,
45
+ resolveSessionId,
46
+ resolveTranscriptFile,
47
+ } from './fork.js';
38
48
 
39
49
  function resolveWsUrl(account: ResolvedParallAccount): string {
40
50
  if (account.config.ws_url) return account.config.ws_url;
41
- const base = account.config.parall_url.replace(/\/$/, "");
42
- const wsBase = base.replace(/^http/, "ws");
51
+ const base = account.config.parall_url.replace(/\/$/, '');
52
+ const wsBase = base.replace(/^http/, 'ws');
43
53
  return `${wsBase}/ws`;
44
54
  }
45
55
 
@@ -75,15 +85,15 @@ function buildInboundCtx(
75
85
  To: `parall:orchestrator`,
76
86
  SessionKey: sessionKey,
77
87
  AccountId: accountId,
78
- ChatType: event.targetType ?? "unknown",
88
+ ChatType: event.targetType ?? 'unknown',
79
89
  SenderName: event.senderName,
80
90
  SenderId: event.senderId,
81
- Provider: "parall" as const,
82
- Surface: "parall" as const,
91
+ Provider: 'parall' as const,
92
+ Surface: 'parall' as const,
83
93
  MessageSid: event.messageId,
84
94
  Timestamp: Date.now(),
85
95
  CommandAuthorized: true,
86
- OriginatingChannel: "parall" as const,
96
+ OriginatingChannel: 'parall' as const,
87
97
  OriginatingTo: `parall:orchestrator`,
88
98
  });
89
99
  }
@@ -96,13 +106,13 @@ function buildInboundHistory(events: ParallEvent[]): Array<{ sender: string; bod
96
106
  if (event.threadRootId) meta.push(`[Thread: ${event.threadRootId}]`);
97
107
  if (event.attachments?.length) {
98
108
  for (const att of event.attachments) {
99
- const safeMime = att.mimeType.replace(/[\r\n[\]|]/g, " ").trim();
109
+ const safeMime = att.mimeType.replace(/[\r\n[\]|]/g, ' ').trim();
100
110
  meta.push(`[Attachment: prll://${att.id} | ${safeMime}]`);
101
111
  }
102
112
  }
103
113
  return {
104
114
  sender: event.senderName,
105
- body: meta.length > 0 ? `${meta.join(" ")}\n${event.body}` : event.body,
115
+ body: meta.length > 0 ? `${meta.join(' ')}\n${event.body}` : event.body,
106
116
  };
107
117
  });
108
118
  }
@@ -192,7 +202,13 @@ function createOpenClawDispatchAdapter(opts: {
192
202
  sessionsDir: string;
193
203
  workspaceDir: string;
194
204
  }): DispatchAdapter {
205
+ const activeStreams = new Map<string, ReturnType<typeof createRuntimeEventStream>>();
206
+
195
207
  return {
208
+ abortDispatch(sessionKey: string): void {
209
+ activeStreams.get(sessionKey)?.fail(new Error('dispatch deadline exceeded'));
210
+ },
211
+
196
212
  async *dispatch({ event, earlierEvents = [], bodyForAgent, sessionKey, context }) {
197
213
  let promptBody = bodyForAgent;
198
214
  let releasePreparedAttachments = () => {};
@@ -209,18 +225,26 @@ function createOpenClawDispatchAdapter(opts: {
209
225
 
210
226
  try {
211
227
  const stream = createRuntimeEventStream();
212
- let reasoningBuffer = "";
213
- let turnGroupKey = "";
228
+ activeStreams.set(sessionKey, stream);
229
+ let reasoningBuffer = '';
230
+ let turnGroupKey = '';
214
231
 
215
232
  const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
216
- ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, promptBody, earlierEvents),
233
+ ctx: buildInboundCtx(
234
+ opts.core,
235
+ opts.accountId,
236
+ event,
237
+ sessionKey,
238
+ promptBody,
239
+ earlierEvents,
240
+ ),
217
241
  cfg: opts.cfg,
218
242
  dispatcherOptions: {
219
243
  deliver: async (payload: { text?: string }) => {
220
244
  const replyText = payload.text?.trim();
221
245
  if (!replyText) return;
222
246
  stream.push({
223
- type: "text",
247
+ type: 'text',
224
248
  text: replyText,
225
249
  project: false,
226
250
  groupKey: turnGroupKey || undefined,
@@ -233,16 +257,16 @@ function createOpenClawDispatchAdapter(opts: {
233
257
  },
234
258
  replyOptions: {
235
259
  onReasoningStream: (payload: { text?: string }) => {
236
- reasoningBuffer += payload.text ?? "";
260
+ reasoningBuffer += payload.text ?? '';
237
261
  },
238
262
  onReasoningEnd: async () => {
239
263
  if (!reasoningBuffer) return;
240
264
  stream.push({
241
- type: "thinking",
265
+ type: 'thinking',
242
266
  text: reasoningBuffer,
243
267
  groupKey: turnGroupKey || undefined,
244
268
  });
245
- reasoningBuffer = "";
269
+ reasoningBuffer = '';
246
270
  },
247
271
  },
248
272
  });
@@ -252,7 +276,12 @@ function createOpenClawDispatchAdapter(opts: {
252
276
  try {
253
277
  sessionId = await Promise.race([
254
278
  waitForOpenClawSessionId(opts.sessionsDir, sessionKey),
255
- run.then(() => null as string | null, (err) => { throw err; }),
279
+ run.then(
280
+ () => null as string | null,
281
+ (err) => {
282
+ throw err;
283
+ },
284
+ ),
256
285
  ]);
257
286
  } catch {
258
287
  sessionId = null;
@@ -261,13 +290,14 @@ function createOpenClawDispatchAdapter(opts: {
261
290
  stream.fail(new Error(`OpenClaw did not expose a sessionId for ${sessionKey}`));
262
291
  } else {
263
292
  yield {
264
- type: "runtime_session",
293
+ type: 'runtime_session',
265
294
  runtimeSessionId: sessionId,
266
295
  runtimeLaneKey: sessionKey,
267
296
  };
268
297
  }
269
298
  yield* stream.stream();
270
299
  } finally {
300
+ activeStreams.delete(sessionKey);
271
301
  releasePreparedAttachments();
272
302
  }
273
303
  },
@@ -296,7 +326,7 @@ function createOpenClawDispatchAdapter(opts: {
296
326
  },
297
327
 
298
328
  cleanupFork({ fork }) {
299
- if (typeof fork.sessionFile !== "string") return;
329
+ if (typeof fork.sessionFile !== 'string') return;
300
330
  cleanupForkSession({
301
331
  sessionFile: fork.sessionFile,
302
332
  sessionKey: fork.sessionKey,
@@ -310,7 +340,8 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
310
340
  startAccount: async (ctx) => {
311
341
  const account = resolveParallAccount({ cfg: ctx.cfg, accountId: ctx.accountId });
312
342
  if (!account.enabled) return;
313
- if (!account.configured) throw new Error("Parall account is not configured: parall_url/api_key/org_id required");
343
+ if (!account.configured)
344
+ throw new Error('Parall account is not configured: parall_url/api_key/org_id required');
314
345
 
315
346
  const { config } = account;
316
347
  const core = getParallRuntime();
@@ -331,113 +362,125 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
331
362
  });
332
363
  log?.info(`parall[${ctx.accountId}]: authenticated as ${me.display_name} (${agentUserId})`);
333
364
 
334
- const stateDir = process.env.OPENCLAW_STATE_DIR
335
- || path.join(process.env.HOME || "/data", ".openclaw");
336
- const openclawConfigPath = path.join(stateDir, "openclaw.json");
337
-
338
- const configManagerOpts = {
339
- client,
340
- stateDir,
341
- configPath: openclawConfigPath,
342
- credentials: { api_key: config.api_key, parall_url: config.parall_url },
343
- log,
344
- };
345
-
365
+ const telemetry = await initAgentTelemetry('parall-openclaw-agent', 'openclaw');
366
+ const otelLog = createOtelLogger('agent', 'openclaw-agent');
346
367
  try {
347
- await fetchAndApplyPlatformConfig(configManagerOpts);
348
- } catch (err) {
349
- log?.warn(`parall[${ctx.accountId}]: platform config fetch failed: ${String(err)}`);
350
- }
368
+ const stateDir =
369
+ process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || '/data', '.openclaw');
370
+ const openclawConfigPath = path.join(stateDir, 'openclaw.json');
351
371
 
352
- let stopWikiHelper: (() => void) | null = null;
353
- let wikiMountRoot: string | undefined;
354
- try {
355
- const wikiHelper = await startWikiHelper({
356
- accountId: ctx.accountId,
357
- parallUrl: config.parall_url,
358
- apiKey: config.api_key,
359
- orgId: config.org_id,
360
- agentId: agentUserId,
372
+ const configManagerOpts = {
373
+ client,
361
374
  stateDir,
362
- log,
363
- });
364
- wikiMountRoot = wikiHelper.mountRoot;
365
- stopWikiHelper = wikiHelper.stop;
366
- } catch (err) {
367
- log?.warn(`parall[${ctx.accountId}]: wiki helper startup failed: ${String(err)}`);
368
- }
375
+ configPath: openclawConfigPath,
376
+ credentials: { api_key: config.api_key, parall_url: config.parall_url },
377
+ log: otelLog,
378
+ };
369
379
 
370
- const wsUrl = resolveWsUrl(account);
371
- const ws = new ParallWs({
372
- getTicket: () => client.getWsTicket(),
373
- wsUrl,
374
- });
375
- const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
376
- const sessionsDir = path.join(stateDir, "agents", "main", "sessions");
377
- const workspaceDir = process.cwd();
378
- ensureLocalAttachmentGitExclude(workspaceDir);
379
- const dispatchAdapter = createOpenClawDispatchAdapter({
380
- core,
381
- cfg: ctx.cfg,
382
- accountId: ctx.accountId,
383
- sessionsDir,
384
- workspaceDir,
385
- });
386
-
387
- const gateway = new ParallAgentGateway({
388
- accountId: ctx.accountId,
389
- client,
390
- ws,
391
- connectionLabel: wsUrl,
392
- config: {
393
- parall_url: config.parall_url,
394
- api_key: config.api_key,
395
- org_id: config.org_id,
396
- },
397
- agentUserId,
398
- runtimeType: "openclaw",
399
- runtimeKey: orchestratorKey,
400
- runtimeRef: { hostname: os.hostname(), pid: process.pid },
401
- dispatchAdapter,
402
- log,
403
- shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
404
- onConfigUpdate: async () => {
380
+ try {
405
381
  await fetchAndApplyPlatformConfig(configManagerOpts);
406
- },
407
- onSessionReady: async ({ activeSessionId }) => {
408
- setParallAccountState(ctx.accountId, {
409
- client,
410
- apiUrl: config.parall_url,
382
+ } catch (err) {
383
+ otelLog.warn(`parall[${ctx.accountId}]: platform config fetch failed: ${String(err)}`);
384
+ }
385
+
386
+ let stopWikiHelper: (() => void) | null = null;
387
+ let wikiMountRoot: string | undefined;
388
+ try {
389
+ const wikiHelper = await startWikiHelper({
390
+ accountId: ctx.accountId,
391
+ parallUrl: config.parall_url,
411
392
  apiKey: config.api_key,
412
393
  orgId: config.org_id,
413
- agentUserId,
414
- activeSessionId,
415
- sessionBindings: new Map<string, string>(),
416
- sessionsDir,
417
- runtimeRef: { hostname: os.hostname(), pid: process.pid },
418
- wikiMountRoot,
419
- ws,
420
- orchestratorSessionKey: orchestratorKey,
394
+ agentId: agentUserId,
395
+ stateDir,
396
+ log: otelLog,
421
397
  });
422
- },
423
- onSessionBinding: async ({ sessionKey, agentSessionId }) => {
424
- setAgentSessionBinding(ctx.accountId, sessionKey, agentSessionId);
425
- },
426
- onBeforeDisconnect: async () => {
427
- stopWikiHelper?.();
428
- removeParallAccountState(ctx.accountId);
429
- },
430
- onNewSession: () => { clearSessionState(sessionsDir, orchestratorKey); },
431
- onSessionStale: () => { clearSessionState(sessionsDir, orchestratorKey); },
432
- });
398
+ wikiMountRoot = wikiHelper.mountRoot;
399
+ stopWikiHelper = wikiHelper.stop;
400
+ } catch (err) {
401
+ otelLog.warn(`parall[${ctx.accountId}]: wiki helper startup failed: ${String(err)}`);
402
+ }
433
403
 
434
- try {
435
- await gateway.run(ctx.abortSignal);
436
- } finally {
437
- if (!ctx.abortSignal.aborted) {
438
- stopWikiHelper?.();
439
- removeParallAccountState(ctx.accountId);
404
+ const wsUrl = resolveWsUrl(account);
405
+ const ws = new ParallWs({
406
+ getTicket: () => client.getWsTicket(),
407
+ wsUrl,
408
+ });
409
+ const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
410
+ const sessionsDir = path.join(stateDir, 'agents', 'main', 'sessions');
411
+ const workspaceDir = process.cwd();
412
+ ensureLocalAttachmentGitExclude(workspaceDir);
413
+ const dispatchAdapter = createOpenClawDispatchAdapter({
414
+ core,
415
+ cfg: ctx.cfg,
416
+ accountId: ctx.accountId,
417
+ sessionsDir,
418
+ workspaceDir,
419
+ });
420
+
421
+ const gateway = new ParallAgentGateway({
422
+ accountId: ctx.accountId,
423
+ client,
424
+ ws,
425
+ connectionLabel: wsUrl,
426
+ config: {
427
+ parall_url: config.parall_url,
428
+ api_key: config.api_key,
429
+ org_id: config.org_id,
430
+ },
431
+ agentUserId,
432
+ runtimeType: 'openclaw',
433
+ runtimeKey: orchestratorKey,
434
+ runtimeRef: { hostname: os.hostname(), pid: process.pid },
435
+ dispatchAdapter,
436
+ log: otelLog,
437
+ shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
438
+ forkDeadlineMs: parseForkDeadlineMs(process.env.PRLL_FORK_DEADLINE_MS),
439
+ dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
440
+ onConfigUpdate: async () => {
441
+ await fetchAndApplyPlatformConfig(configManagerOpts);
442
+ },
443
+ onSessionReady: async ({ activeSessionId }) => {
444
+ setParallAccountState(ctx.accountId, {
445
+ client,
446
+ apiUrl: config.parall_url,
447
+ apiKey: config.api_key,
448
+ orgId: config.org_id,
449
+ agentUserId,
450
+ activeSessionId,
451
+ sessionBindings: new Map<string, string>(),
452
+ sessionsDir,
453
+ runtimeRef: { hostname: os.hostname(), pid: process.pid },
454
+ wikiMountRoot,
455
+ ws,
456
+ orchestratorSessionKey: orchestratorKey,
457
+ });
458
+ },
459
+ onSessionBinding: async ({ sessionKey, agentSessionId }) => {
460
+ setAgentSessionBinding(ctx.accountId, sessionKey, agentSessionId);
461
+ },
462
+ onBeforeDisconnect: async () => {
463
+ stopWikiHelper?.();
464
+ removeParallAccountState(ctx.accountId);
465
+ },
466
+ onNewSession: () => {
467
+ clearSessionState(sessionsDir, orchestratorKey);
468
+ },
469
+ onSessionStale: () => {
470
+ clearSessionState(sessionsDir, orchestratorKey);
471
+ },
472
+ });
473
+
474
+ try {
475
+ await gateway.run(ctx.abortSignal);
476
+ } finally {
477
+ if (!ctx.abortSignal.aborted) {
478
+ stopWikiHelper?.();
479
+ removeParallAccountState(ctx.accountId);
480
+ }
440
481
  }
482
+ } finally {
483
+ await telemetry.shutdown();
441
484
  }
442
485
  },
443
486
  };