@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/dist/gateway.js CHANGED
@@ -1,21 +1,21 @@
1
- import { ParallAgentGateway, parseShutdownDeadlineMs, } from "@parall/agent-core";
2
- import { appendPreparedLocalAttachmentRefs, ensureLocalAttachmentGitExclude, pinLocalAttachmentPaths, } from "@parall/agent-core/internal/attachment-input";
3
- import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
4
- import * as crypto from "node:crypto";
5
- import * as os from "node:os";
6
- import * as path from "node:path";
7
- import { resolveParallAccount } from "./accounts.js";
8
- import { getParallRuntime, removeParallAccountState, setAgentIdentity, setAgentSessionBinding, setDispatchGroupKey, setParallAccountState, } from "./runtime.js";
9
- import { buildOrchestratorSessionKey } from "./session.js";
10
- import { fetchAndApplyPlatformConfig } from "./config-manager.js";
11
- import { startWikiHelper } from "./wiki-helper.js";
12
- import { SessionManager } from "./oc-session.js";
13
- import { cleanupForkSession, clearSessionState, forkOrchestratorSession, resolveSessionId, resolveTranscriptFile } from "./fork.js";
1
+ import { ParallAgentGateway, parseShutdownDeadlineMs, parseForkDeadlineMs, parseDispatchDeadlineMs, initAgentTelemetry, createOtelLogger, } from '@parall/agent-core';
2
+ import { appendPreparedLocalAttachmentRefs, ensureLocalAttachmentGitExclude, pinLocalAttachmentPaths, } from '@parall/agent-core/internal/attachment-input';
3
+ import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
4
+ import * as crypto from 'node:crypto';
5
+ import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import { resolveParallAccount } from './accounts.js';
8
+ import { getParallRuntime, removeParallAccountState, setAgentIdentity, setAgentSessionBinding, setDispatchGroupKey, setParallAccountState, } from './runtime.js';
9
+ import { buildOrchestratorSessionKey } from './session.js';
10
+ import { fetchAndApplyPlatformConfig } from './config-manager.js';
11
+ import { startWikiHelper } from './wiki-helper.js';
12
+ import { SessionManager } from './oc-session.js';
13
+ import { cleanupForkSession, clearSessionState, forkOrchestratorSession, resolveSessionId, resolveTranscriptFile, } from './fork.js';
14
14
  function resolveWsUrl(account) {
15
15
  if (account.config.ws_url)
16
16
  return account.config.ws_url;
17
- const base = account.config.parall_url.replace(/\/$/, "");
18
- const wsBase = base.replace(/^http/, "ws");
17
+ const base = account.config.parall_url.replace(/\/$/, '');
18
+ const wsBase = base.replace(/^http/, 'ws');
19
19
  return `${wsBase}/ws`;
20
20
  }
21
21
  async function getAgentMeWithLegacyFallback(client, orgId) {
@@ -43,15 +43,15 @@ function buildInboundCtx(core, accountId, event, sessionKey, bodyForAgent, earli
43
43
  To: `parall:orchestrator`,
44
44
  SessionKey: sessionKey,
45
45
  AccountId: accountId,
46
- ChatType: event.targetType ?? "unknown",
46
+ ChatType: event.targetType ?? 'unknown',
47
47
  SenderName: event.senderName,
48
48
  SenderId: event.senderId,
49
- Provider: "parall",
50
- Surface: "parall",
49
+ Provider: 'parall',
50
+ Surface: 'parall',
51
51
  MessageSid: event.messageId,
52
52
  Timestamp: Date.now(),
53
53
  CommandAuthorized: true,
54
- OriginatingChannel: "parall",
54
+ OriginatingChannel: 'parall',
55
55
  OriginatingTo: `parall:orchestrator`,
56
56
  });
57
57
  }
@@ -65,13 +65,13 @@ function buildInboundHistory(events) {
65
65
  meta.push(`[Thread: ${event.threadRootId}]`);
66
66
  if (event.attachments?.length) {
67
67
  for (const att of event.attachments) {
68
- const safeMime = att.mimeType.replace(/[\r\n[\]|]/g, " ").trim();
68
+ const safeMime = att.mimeType.replace(/[\r\n[\]|]/g, ' ').trim();
69
69
  meta.push(`[Attachment: prll://${att.id} | ${safeMime}]`);
70
70
  }
71
71
  }
72
72
  return {
73
73
  sender: event.senderName,
74
- body: meta.length > 0 ? `${meta.join(" ")}\n${event.body}` : event.body,
74
+ body: meta.length > 0 ? `${meta.join(' ')}\n${event.body}` : event.body,
75
75
  };
76
76
  });
77
77
  }
@@ -146,7 +146,11 @@ async function waitForOpenClawSessionId(sessionsDir, sessionKey, timeoutMs = 10_
146
146
  return null;
147
147
  }
148
148
  function createOpenClawDispatchAdapter(opts) {
149
+ const activeStreams = new Map();
149
150
  return {
151
+ abortDispatch(sessionKey) {
152
+ activeStreams.get(sessionKey)?.fail(new Error('dispatch deadline exceeded'));
153
+ },
150
154
  async *dispatch({ event, earlierEvents = [], bodyForAgent, sessionKey, context }) {
151
155
  let promptBody = bodyForAgent;
152
156
  let releasePreparedAttachments = () => { };
@@ -163,8 +167,9 @@ function createOpenClawDispatchAdapter(opts) {
163
167
  }
164
168
  try {
165
169
  const stream = createRuntimeEventStream();
166
- let reasoningBuffer = "";
167
- let turnGroupKey = "";
170
+ activeStreams.set(sessionKey, stream);
171
+ let reasoningBuffer = '';
172
+ let turnGroupKey = '';
168
173
  const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
169
174
  ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, promptBody, earlierEvents),
170
175
  cfg: opts.cfg,
@@ -174,7 +179,7 @@ function createOpenClawDispatchAdapter(opts) {
174
179
  if (!replyText)
175
180
  return;
176
181
  stream.push({
177
- type: "text",
182
+ type: 'text',
178
183
  text: replyText,
179
184
  project: false,
180
185
  groupKey: turnGroupKey || undefined,
@@ -187,17 +192,17 @@ function createOpenClawDispatchAdapter(opts) {
187
192
  },
188
193
  replyOptions: {
189
194
  onReasoningStream: (payload) => {
190
- reasoningBuffer += payload.text ?? "";
195
+ reasoningBuffer += payload.text ?? '';
191
196
  },
192
197
  onReasoningEnd: async () => {
193
198
  if (!reasoningBuffer)
194
199
  return;
195
200
  stream.push({
196
- type: "thinking",
201
+ type: 'thinking',
197
202
  text: reasoningBuffer,
198
203
  groupKey: turnGroupKey || undefined,
199
204
  });
200
- reasoningBuffer = "";
205
+ reasoningBuffer = '';
201
206
  },
202
207
  },
203
208
  });
@@ -206,7 +211,9 @@ function createOpenClawDispatchAdapter(opts) {
206
211
  try {
207
212
  sessionId = await Promise.race([
208
213
  waitForOpenClawSessionId(opts.sessionsDir, sessionKey),
209
- run.then(() => null, (err) => { throw err; }),
214
+ run.then(() => null, (err) => {
215
+ throw err;
216
+ }),
210
217
  ]);
211
218
  }
212
219
  catch {
@@ -217,7 +224,7 @@ function createOpenClawDispatchAdapter(opts) {
217
224
  }
218
225
  else {
219
226
  yield {
220
- type: "runtime_session",
227
+ type: 'runtime_session',
221
228
  runtimeSessionId: sessionId,
222
229
  runtimeLaneKey: sessionKey,
223
230
  };
@@ -225,6 +232,7 @@ function createOpenClawDispatchAdapter(opts) {
225
232
  yield* stream.stream();
226
233
  }
227
234
  finally {
235
+ activeStreams.delete(sessionKey);
228
236
  releasePreparedAttachments();
229
237
  }
230
238
  },
@@ -253,7 +261,7 @@ function createOpenClawDispatchAdapter(opts) {
253
261
  });
254
262
  },
255
263
  cleanupFork({ fork }) {
256
- if (typeof fork.sessionFile !== "string")
264
+ if (typeof fork.sessionFile !== 'string')
257
265
  return;
258
266
  cleanupForkSession({
259
267
  sessionFile: fork.sessionFile,
@@ -269,7 +277,7 @@ export const parallGateway = {
269
277
  if (!account.enabled)
270
278
  return;
271
279
  if (!account.configured)
272
- throw new Error("Parall account is not configured: parall_url/api_key/org_id required");
280
+ throw new Error('Parall account is not configured: parall_url/api_key/org_id required');
273
281
  const { config } = account;
274
282
  const core = getParallRuntime();
275
283
  const log = ctx.log;
@@ -286,110 +294,122 @@ export const parallGateway = {
286
294
  description: me.agent_profile?.description ?? undefined,
287
295
  });
288
296
  log?.info(`parall[${ctx.accountId}]: authenticated as ${me.display_name} (${agentUserId})`);
289
- const stateDir = process.env.OPENCLAW_STATE_DIR
290
- || path.join(process.env.HOME || "/data", ".openclaw");
291
- const openclawConfigPath = path.join(stateDir, "openclaw.json");
292
- const configManagerOpts = {
293
- client,
294
- stateDir,
295
- configPath: openclawConfigPath,
296
- credentials: { api_key: config.api_key, parall_url: config.parall_url },
297
- log,
298
- };
299
- try {
300
- await fetchAndApplyPlatformConfig(configManagerOpts);
301
- }
302
- catch (err) {
303
- log?.warn(`parall[${ctx.accountId}]: platform config fetch failed: ${String(err)}`);
304
- }
305
- let stopWikiHelper = null;
306
- let wikiMountRoot;
297
+ const telemetry = await initAgentTelemetry('parall-openclaw-agent', 'openclaw');
298
+ const otelLog = createOtelLogger('agent', 'openclaw-agent');
307
299
  try {
308
- const wikiHelper = await startWikiHelper({
309
- accountId: ctx.accountId,
310
- parallUrl: config.parall_url,
311
- apiKey: config.api_key,
312
- orgId: config.org_id,
313
- agentId: agentUserId,
300
+ const stateDir = process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || '/data', '.openclaw');
301
+ const openclawConfigPath = path.join(stateDir, 'openclaw.json');
302
+ const configManagerOpts = {
303
+ client,
314
304
  stateDir,
315
- log,
316
- });
317
- wikiMountRoot = wikiHelper.mountRoot;
318
- stopWikiHelper = wikiHelper.stop;
319
- }
320
- catch (err) {
321
- log?.warn(`parall[${ctx.accountId}]: wiki helper startup failed: ${String(err)}`);
322
- }
323
- const wsUrl = resolveWsUrl(account);
324
- const ws = new ParallWs({
325
- getTicket: () => client.getWsTicket(),
326
- wsUrl,
327
- });
328
- const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
329
- const sessionsDir = path.join(stateDir, "agents", "main", "sessions");
330
- const workspaceDir = process.cwd();
331
- ensureLocalAttachmentGitExclude(workspaceDir);
332
- const dispatchAdapter = createOpenClawDispatchAdapter({
333
- core,
334
- cfg: ctx.cfg,
335
- accountId: ctx.accountId,
336
- sessionsDir,
337
- workspaceDir,
338
- });
339
- const gateway = new ParallAgentGateway({
340
- accountId: ctx.accountId,
341
- client,
342
- ws,
343
- connectionLabel: wsUrl,
344
- config: {
345
- parall_url: config.parall_url,
346
- api_key: config.api_key,
347
- org_id: config.org_id,
348
- },
349
- agentUserId,
350
- runtimeType: "openclaw",
351
- runtimeKey: orchestratorKey,
352
- runtimeRef: { hostname: os.hostname(), pid: process.pid },
353
- dispatchAdapter,
354
- log,
355
- shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
356
- onConfigUpdate: async () => {
305
+ configPath: openclawConfigPath,
306
+ credentials: { api_key: config.api_key, parall_url: config.parall_url },
307
+ log: otelLog,
308
+ };
309
+ try {
357
310
  await fetchAndApplyPlatformConfig(configManagerOpts);
358
- },
359
- onSessionReady: async ({ activeSessionId }) => {
360
- setParallAccountState(ctx.accountId, {
361
- client,
362
- apiUrl: config.parall_url,
311
+ }
312
+ catch (err) {
313
+ otelLog.warn(`parall[${ctx.accountId}]: platform config fetch failed: ${String(err)}`);
314
+ }
315
+ let stopWikiHelper = null;
316
+ let wikiMountRoot;
317
+ try {
318
+ const wikiHelper = await startWikiHelper({
319
+ accountId: ctx.accountId,
320
+ parallUrl: config.parall_url,
363
321
  apiKey: config.api_key,
364
322
  orgId: config.org_id,
365
- agentUserId,
366
- activeSessionId,
367
- sessionBindings: new Map(),
368
- sessionsDir,
369
- runtimeRef: { hostname: os.hostname(), pid: process.pid },
370
- wikiMountRoot,
371
- ws,
372
- orchestratorSessionKey: orchestratorKey,
323
+ agentId: agentUserId,
324
+ stateDir,
325
+ log: otelLog,
373
326
  });
374
- },
375
- onSessionBinding: async ({ sessionKey, agentSessionId }) => {
376
- setAgentSessionBinding(ctx.accountId, sessionKey, agentSessionId);
377
- },
378
- onBeforeDisconnect: async () => {
379
- stopWikiHelper?.();
380
- removeParallAccountState(ctx.accountId);
381
- },
382
- onNewSession: () => { clearSessionState(sessionsDir, orchestratorKey); },
383
- onSessionStale: () => { clearSessionState(sessionsDir, orchestratorKey); },
384
- });
385
- try {
386
- await gateway.run(ctx.abortSignal);
327
+ wikiMountRoot = wikiHelper.mountRoot;
328
+ stopWikiHelper = wikiHelper.stop;
329
+ }
330
+ catch (err) {
331
+ otelLog.warn(`parall[${ctx.accountId}]: wiki helper startup failed: ${String(err)}`);
332
+ }
333
+ const wsUrl = resolveWsUrl(account);
334
+ const ws = new ParallWs({
335
+ getTicket: () => client.getWsTicket(),
336
+ wsUrl,
337
+ });
338
+ const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
339
+ const sessionsDir = path.join(stateDir, 'agents', 'main', 'sessions');
340
+ const workspaceDir = process.cwd();
341
+ ensureLocalAttachmentGitExclude(workspaceDir);
342
+ const dispatchAdapter = createOpenClawDispatchAdapter({
343
+ core,
344
+ cfg: ctx.cfg,
345
+ accountId: ctx.accountId,
346
+ sessionsDir,
347
+ workspaceDir,
348
+ });
349
+ const gateway = new ParallAgentGateway({
350
+ accountId: ctx.accountId,
351
+ client,
352
+ ws,
353
+ connectionLabel: wsUrl,
354
+ config: {
355
+ parall_url: config.parall_url,
356
+ api_key: config.api_key,
357
+ org_id: config.org_id,
358
+ },
359
+ agentUserId,
360
+ runtimeType: 'openclaw',
361
+ runtimeKey: orchestratorKey,
362
+ runtimeRef: { hostname: os.hostname(), pid: process.pid },
363
+ dispatchAdapter,
364
+ log: otelLog,
365
+ shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
366
+ forkDeadlineMs: parseForkDeadlineMs(process.env.PRLL_FORK_DEADLINE_MS),
367
+ dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
368
+ onConfigUpdate: async () => {
369
+ await fetchAndApplyPlatformConfig(configManagerOpts);
370
+ },
371
+ onSessionReady: async ({ activeSessionId }) => {
372
+ setParallAccountState(ctx.accountId, {
373
+ client,
374
+ apiUrl: config.parall_url,
375
+ apiKey: config.api_key,
376
+ orgId: config.org_id,
377
+ agentUserId,
378
+ activeSessionId,
379
+ sessionBindings: new Map(),
380
+ sessionsDir,
381
+ runtimeRef: { hostname: os.hostname(), pid: process.pid },
382
+ wikiMountRoot,
383
+ ws,
384
+ orchestratorSessionKey: orchestratorKey,
385
+ });
386
+ },
387
+ onSessionBinding: async ({ sessionKey, agentSessionId }) => {
388
+ setAgentSessionBinding(ctx.accountId, sessionKey, agentSessionId);
389
+ },
390
+ onBeforeDisconnect: async () => {
391
+ stopWikiHelper?.();
392
+ removeParallAccountState(ctx.accountId);
393
+ },
394
+ onNewSession: () => {
395
+ clearSessionState(sessionsDir, orchestratorKey);
396
+ },
397
+ onSessionStale: () => {
398
+ clearSessionState(sessionsDir, orchestratorKey);
399
+ },
400
+ });
401
+ try {
402
+ await gateway.run(ctx.abortSignal);
403
+ }
404
+ finally {
405
+ if (!ctx.abortSignal.aborted) {
406
+ stopWikiHelper?.();
407
+ removeParallAccountState(ctx.accountId);
408
+ }
409
+ }
387
410
  }
388
411
  finally {
389
- if (!ctx.abortSignal.aborted) {
390
- stopWikiHelper?.();
391
- removeParallAccountState(ctx.accountId);
392
- }
412
+ await telemetry.shutdown();
393
413
  }
394
414
  },
395
415
  };
package/dist/hooks.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
2
  export declare function registerParallHooks(api: OpenClawPluginApi): void;
3
3
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AA4F7D,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,iBAAiB,QAqIzD"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AA+G7D,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,iBAAiB,QA6KzD"}
package/dist/hooks.js CHANGED
@@ -1,20 +1,20 @@
1
- import { PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, buildIdentity } from "@parall/agent-core";
2
- import { extractAccountIdFromSessionKey } from "./session.js";
3
- import { clearDispatchGroupKey, getAgentIdentity, getAgentSessionBinding, getDispatchGroupKey, getDispatchMessageId, getParallAccountState, getSessionChatId, setAgentSessionBinding } from "./runtime.js";
4
- import { resolveSessionId } from "./fork.js";
1
+ import { PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, buildIdentity, isParallSendCommand, isParallNoReplyCommand, recordToolCall, recordMessageSend, recordNoReply, } from '@parall/agent-core';
2
+ import { extractAccountIdFromSessionKey } from './session.js';
3
+ import { clearDispatchGroupKey, getAgentIdentity, getAgentSessionBinding, getDispatchGroupKey, getDispatchMessageId, getParallAccountState, getSessionChatId, setAgentSessionBinding, } from './runtime.js';
4
+ import { resolveSessionId } from './fork.js';
5
5
  /**
6
6
  * Map the session's stored target ID (whatever the current dispatch routed
7
7
  * to — chat, task, or schedule) to the `target_type` field on agent_step.
8
8
  * Keeps tool_call / tool_result attribution correct across event types.
9
9
  */
10
10
  function stepTargetType(targetId) {
11
- if (targetId.startsWith("cht_"))
12
- return "chat";
13
- if (targetId.startsWith("tsk_"))
14
- return "task";
15
- if (targetId.startsWith("sch_"))
16
- return "schedule";
17
- return "";
11
+ if (targetId.startsWith('cht_'))
12
+ return 'chat';
13
+ if (targetId.startsWith('tsk_'))
14
+ return 'task';
15
+ if (targetId.startsWith('sch_'))
16
+ return 'schedule';
17
+ return '';
18
18
  }
19
19
  /**
20
20
  * Resolve the ParallClient + orgId + sessionId for a hook context.
@@ -46,7 +46,7 @@ async function resolveClientForHook(sessionKey) {
46
46
  : undefined;
47
47
  try {
48
48
  const session = await state.client.createAgentSession(state.orgId, state.agentUserId, {
49
- runtime_type: "openclaw",
49
+ runtime_type: 'openclaw',
50
50
  runtime_key: sessionKey,
51
51
  runtime_lane_key: sessionKey,
52
52
  runtime_session_id: runtimeSessionId,
@@ -100,14 +100,34 @@ export function registerParallHooks(api) {
100
100
  // registration time, only after the gateway authenticates via getMe().
101
101
  // Uses process-global identity because hosted agents run one-agent-per-pod;
102
102
  // before_prompt_build doesn't receive session context anyway.
103
- api.on("before_prompt_build", () => {
103
+ api.on('before_prompt_build', () => {
104
104
  return {
105
- appendSystemContext: [buildIdentity(getAgentIdentity()), PRLL_CHANNEL_CONTEXT, PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE].join("\n\n"),
105
+ appendSystemContext: [
106
+ buildIdentity(getAgentIdentity()),
107
+ PRLL_CHANNEL_CONTEXT,
108
+ PRLL_BEHAVIOR,
109
+ PRLL_REFERENCE_GUIDE,
110
+ ].join('\n\n'),
106
111
  };
107
112
  });
108
- // before_tool_call -> (1) await step creation to get step ID, (2) ENV injection for exec tool
109
- api.on("before_tool_call", async (event, ctx) => {
113
+ const pendingSendCalls = new Map();
114
+ // before_tool_call -> (1) update dispatch metrics, (2) await step creation, (3) ENV injection
115
+ // OpenClaw tool calls bypass the RuntimeEvent stream (they go through hooks, not the
116
+ // gateway-base for-await loop), so dispatch metrics must be updated here as well.
117
+ api.on('before_tool_call', async (event, ctx) => {
110
118
  const sessionKey = ctx.sessionKey;
119
+ if (sessionKey) {
120
+ recordToolCall(sessionKey);
121
+ if (event.toolName === 'exec' && event.toolCallId) {
122
+ const command = event.params?.command;
123
+ if (isParallSendCommand(command)) {
124
+ pendingSendCalls.set(event.toolCallId, true);
125
+ }
126
+ else if (isParallNoReplyCommand(command)) {
127
+ recordNoReply(sessionKey);
128
+ }
129
+ }
130
+ }
111
131
  // (1) Create tool_call step and await to get step ID
112
132
  let stepId;
113
133
  const resolved = await resolveClientForHook(sessionKey);
@@ -115,14 +135,14 @@ export function registerParallHooks(api) {
115
135
  const groupKey = sessionKey ? getDispatchGroupKey(sessionKey) : undefined;
116
136
  try {
117
137
  const step = await resolved.client.createAgentStep(resolved.orgId, resolved.agentUserId, resolved.sessionId, {
118
- step_type: "tool_call",
138
+ step_type: 'tool_call',
119
139
  target_type: stepTargetType(resolved.chatId),
120
140
  target_id: resolved.chatId || undefined,
121
141
  content: {
122
142
  call_id: event.toolCallId,
123
143
  tool_name: event.toolName,
124
144
  tool_input: event.params ?? {},
125
- status: "running",
145
+ status: 'running',
126
146
  started_at: new Date().toISOString(),
127
147
  },
128
148
  group_key: groupKey,
@@ -135,7 +155,7 @@ export function registerParallHooks(api) {
135
155
  }
136
156
  }
137
157
  // (2) ENV injection — only for the exec (Bash) tool
138
- if (event.toolName !== "exec")
158
+ if (event.toolName !== 'exec')
139
159
  return;
140
160
  if (!sessionKey)
141
161
  return;
@@ -162,7 +182,7 @@ export function registerParallHooks(api) {
162
182
  if (stepId)
163
183
  injectedEnv.PRLL_STEP_ID = stepId;
164
184
  if (chatId) {
165
- if (chatId.startsWith("sch_")) {
185
+ if (chatId.startsWith('sch_')) {
166
186
  // Schedule-triggered dispatch: expose PRLL_SCHEDULE_ID so the agent
167
187
  // can resolve the schedule; deliberately do NOT set PRLL_CHAT_ID,
168
188
  // because schedule events have no chat to reply into, and leaving
@@ -194,21 +214,27 @@ export function registerParallHooks(api) {
194
214
  },
195
215
  };
196
216
  });
197
- // after_tool_call -> send tool_result step to AgentSession
198
- api.on("after_tool_call", async (event, ctx) => {
217
+ // after_tool_call -> track CLI outcomes + send tool_result step to AgentSession
218
+ api.on('after_tool_call', async (event, ctx) => {
219
+ if (ctx.sessionKey && event.toolCallId && pendingSendCalls.delete(event.toolCallId)) {
220
+ recordMessageSend(ctx.sessionKey, !event.error);
221
+ }
199
222
  const resolved = await resolveClientForHook(ctx.sessionKey);
200
223
  if (!resolved?.sessionId || !event.toolCallId)
201
224
  return;
202
225
  try {
203
226
  await resolved.client.createAgentStep(resolved.orgId, resolved.agentUserId, resolved.sessionId, {
204
- step_type: "tool_result",
227
+ step_type: 'tool_result',
205
228
  target_type: stepTargetType(resolved.chatId),
206
229
  target_id: resolved.chatId || undefined,
207
230
  content: {
208
231
  call_id: event.toolCallId,
209
232
  tool_name: event.toolName,
210
- status: event.error ? "error" : "success",
211
- output: event.error ?? (typeof event.result === "string" ? event.result : JSON.stringify(event.result ?? "")),
233
+ status: event.error ? 'error' : 'success',
234
+ output: event.error ??
235
+ (typeof event.result === 'string'
236
+ ? event.result
237
+ : JSON.stringify(event.result ?? '')),
212
238
  duration_ms: event.durationMs ?? 0,
213
239
  collapsible: true,
214
240
  },
@@ -221,7 +247,7 @@ export function registerParallHooks(api) {
221
247
  // agent_end -> end of a dispatch cycle, NOT end of session.
222
248
  // Session lives across dispatches — don't update session status here.
223
249
  // Dispatch-specific IDs are cleaned up in the generic gateway; hooks only own the group key.
224
- api.on("agent_end", async (event, ctx) => {
250
+ api.on('agent_end', async (event, ctx) => {
225
251
  if (ctx.sessionKey) {
226
252
  clearDispatchGroupKey(ctx.sessionKey);
227
253
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
2
  declare const plugin: {
3
3
  id: string;
4
4
  name: string;
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
2
- import { parallPlugin } from "./channel.js";
3
- import { setParallRuntime } from "./runtime.js";
4
- import { registerParallHooks } from "./hooks.js";
1
+ import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk/core';
2
+ import { parallPlugin } from './channel.js';
3
+ import { setParallRuntime } from './runtime.js';
4
+ import { registerParallHooks } from './hooks.js';
5
5
  const plugin = {
6
- id: "parall",
7
- name: "Parall",
8
- description: "Parall IM channel plugin — Agent-Native messaging with tool call visualization.",
6
+ id: 'parall',
7
+ name: 'Parall',
8
+ description: 'Parall IM channel plugin — Agent-Native messaging with tool call visualization.',
9
9
  configSchema: emptyPluginConfigSchema(),
10
10
  register(api) {
11
11
  setParallRuntime(api.runtime);
@@ -1 +1 @@
1
- {"version":3,"file":"oc-session.d.ts","sourceRoot":"","sources":["../src/oc-session.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAkHzC,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,IAAI,CAAmC;IAC/C,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,mBAAmB,CAA6B;IACxD,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO;IAeP,OAAO,CAAC,IAAI;IA+BZ,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,UAAU;IAsBlB,OAAO,CAAC,OAAO;IAOf,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B,MAAM,IAAI,MAAM;IAIhB,aAAa,IAAI,MAAM;IAIvB,YAAY,IAAI,MAAM;IAItB,cAAc,IAAI,MAAM,GAAG,SAAS;IAMpC,OAAO,CAAC,SAAS;IAWjB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA+DzD,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc;CAO1C"}
1
+ {"version":3,"file":"oc-session.d.ts","sourceRoot":"","sources":["../src/oc-session.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAsHzC,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,IAAI,CAAmC;IAC/C,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,mBAAmB,CAA6B;IACxD,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO;IAeP,OAAO,CAAC,IAAI;IA+BZ,OAAO,CAAC,SAAS;IAoBjB,OAAO,CAAC,UAAU;IAsBlB,OAAO,CAAC,OAAO;IAUf,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B,MAAM,IAAI,MAAM;IAIhB,aAAa,IAAI,MAAM;IAIvB,YAAY,IAAI,MAAM;IAItB,cAAc,IAAI,MAAM,GAAG,SAAS;IAMpC,OAAO,CAAC,SAAS;IAWjB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA+DzD,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc;CAO1C"}