@parall/parall 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 (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 +214 -171
  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 +245 -175
  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/hooks.ts CHANGED
@@ -1,8 +1,26 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, buildIdentity } from "@parall/agent-core";
3
- import { extractAccountIdFromSessionKey } from "./session.js";
4
- import { clearDispatchGroupKey, getAgentIdentity, getAgentSessionBinding, getDispatchGroupKey, getDispatchMessageId, getParallAccountState, getSessionChatId, setAgentSessionBinding } from "./runtime.js";
5
- import { resolveSessionId } from "./fork.js";
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
+ import {
3
+ PRLL_BEHAVIOR,
4
+ PRLL_REFERENCE_GUIDE,
5
+ buildIdentity,
6
+ isParallSendCommand,
7
+ isParallNoReplyCommand,
8
+ recordToolCall,
9
+ recordMessageSend,
10
+ recordNoReply,
11
+ } from '@parall/agent-core';
12
+ import { extractAccountIdFromSessionKey } from './session.js';
13
+ import {
14
+ clearDispatchGroupKey,
15
+ getAgentIdentity,
16
+ getAgentSessionBinding,
17
+ getDispatchGroupKey,
18
+ getDispatchMessageId,
19
+ getParallAccountState,
20
+ getSessionChatId,
21
+ setAgentSessionBinding,
22
+ } from './runtime.js';
23
+ import { resolveSessionId } from './fork.js';
6
24
 
7
25
  /**
8
26
  * Map the session's stored target ID (whatever the current dispatch routed
@@ -10,10 +28,10 @@ import { resolveSessionId } from "./fork.js";
10
28
  * Keeps tool_call / tool_result attribution correct across event types.
11
29
  */
12
30
  function stepTargetType(targetId: string): string {
13
- if (targetId.startsWith("cht_")) return "chat";
14
- if (targetId.startsWith("tsk_")) return "task";
15
- if (targetId.startsWith("sch_")) return "schedule";
16
- return "";
31
+ if (targetId.startsWith('cht_')) return 'chat';
32
+ if (targetId.startsWith('tsk_')) return 'task';
33
+ if (targetId.startsWith('sch_')) return 'schedule';
34
+ return '';
17
35
  }
18
36
 
19
37
  /**
@@ -37,14 +55,15 @@ async function resolveClientForHook(sessionKey: string | undefined) {
37
55
  if (!sessionId && state.sessionsDir) {
38
56
  const runtimeSessionId = resolveSessionId(state.sessionsDir, sessionKey);
39
57
  if (runtimeSessionId) {
40
- const parentSessionId = sessionKey === state.orchestratorSessionKey
41
- ? undefined
42
- : state.orchestratorSessionKey
43
- ? getAgentSessionBinding(accountId, state.orchestratorSessionKey)
44
- : undefined;
58
+ const parentSessionId =
59
+ sessionKey === state.orchestratorSessionKey
60
+ ? undefined
61
+ : state.orchestratorSessionKey
62
+ ? getAgentSessionBinding(accountId, state.orchestratorSessionKey)
63
+ : undefined;
45
64
  try {
46
65
  const session = await state.client.createAgentSession(state.orgId, state.agentUserId, {
47
- runtime_type: "openclaw",
66
+ runtime_type: 'openclaw',
48
67
  runtime_key: sessionKey,
49
68
  runtime_lane_key: sessionKey,
50
69
  runtime_session_id: runtimeSessionId,
@@ -101,15 +120,35 @@ export function registerParallHooks(api: OpenClawPluginApi) {
101
120
  // registration time, only after the gateway authenticates via getMe().
102
121
  // Uses process-global identity because hosted agents run one-agent-per-pod;
103
122
  // before_prompt_build doesn't receive session context anyway.
104
- api.on("before_prompt_build", () => {
123
+ api.on('before_prompt_build', () => {
105
124
  return {
106
- appendSystemContext: [buildIdentity(getAgentIdentity()), PRLL_CHANNEL_CONTEXT, PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE].join("\n\n"),
125
+ appendSystemContext: [
126
+ buildIdentity(getAgentIdentity()),
127
+ PRLL_CHANNEL_CONTEXT,
128
+ PRLL_BEHAVIOR,
129
+ PRLL_REFERENCE_GUIDE,
130
+ ].join('\n\n'),
107
131
  };
108
132
  });
109
133
 
110
- // before_tool_call -> (1) await step creation to get step ID, (2) ENV injection for exec tool
111
- api.on("before_tool_call", async (event, ctx) => {
134
+ const pendingSendCalls = new Map<string, boolean>();
135
+
136
+ // before_tool_call -> (1) update dispatch metrics, (2) await step creation, (3) ENV injection
137
+ // OpenClaw tool calls bypass the RuntimeEvent stream (they go through hooks, not the
138
+ // gateway-base for-await loop), so dispatch metrics must be updated here as well.
139
+ api.on('before_tool_call', async (event, ctx) => {
112
140
  const sessionKey = ctx.sessionKey;
141
+ if (sessionKey) {
142
+ recordToolCall(sessionKey);
143
+ if (event.toolName === 'exec' && event.toolCallId) {
144
+ const command = (event.params as Record<string, unknown>)?.command as string | undefined;
145
+ if (isParallSendCommand(command)) {
146
+ pendingSendCalls.set(event.toolCallId, true);
147
+ } else if (isParallNoReplyCommand(command)) {
148
+ recordNoReply(sessionKey);
149
+ }
150
+ }
151
+ }
113
152
 
114
153
  // (1) Create tool_call step and await to get step ID
115
154
  let stepId: string | undefined;
@@ -117,20 +156,25 @@ export function registerParallHooks(api: OpenClawPluginApi) {
117
156
  if (resolved?.sessionId && event.toolCallId) {
118
157
  const groupKey = sessionKey ? getDispatchGroupKey(sessionKey) : undefined;
119
158
  try {
120
- const step = await resolved.client.createAgentStep(resolved.orgId, resolved.agentUserId, resolved.sessionId, {
121
- step_type: "tool_call",
122
- target_type: stepTargetType(resolved.chatId),
123
- target_id: resolved.chatId || undefined,
124
- content: {
125
- call_id: event.toolCallId,
126
- tool_name: event.toolName,
127
- tool_input: event.params ?? {},
128
- status: "running",
129
- started_at: new Date().toISOString(),
159
+ const step = await resolved.client.createAgentStep(
160
+ resolved.orgId,
161
+ resolved.agentUserId,
162
+ resolved.sessionId,
163
+ {
164
+ step_type: 'tool_call',
165
+ target_type: stepTargetType(resolved.chatId),
166
+ target_id: resolved.chatId || undefined,
167
+ content: {
168
+ call_id: event.toolCallId,
169
+ tool_name: event.toolName,
170
+ tool_input: event.params ?? {},
171
+ status: 'running',
172
+ started_at: new Date().toISOString(),
173
+ },
174
+ group_key: groupKey,
175
+ runtime_key: event.toolCallId,
130
176
  },
131
- group_key: groupKey,
132
- runtime_key: event.toolCallId,
133
- });
177
+ );
134
178
  stepId = step.id;
135
179
  } catch (err) {
136
180
  log?.warn(`parall hook before_tool_call failed: ${String(err)}`);
@@ -138,7 +182,7 @@ export function registerParallHooks(api: OpenClawPluginApi) {
138
182
  }
139
183
 
140
184
  // (2) ENV injection — only for the exec (Bash) tool
141
- if (event.toolName !== "exec") return;
185
+ if (event.toolName !== 'exec') return;
142
186
  if (!sessionKey) return;
143
187
  const accountId = extractAccountIdFromSessionKey(sessionKey);
144
188
  if (!accountId) return;
@@ -160,7 +204,7 @@ export function registerParallHooks(api: OpenClawPluginApi) {
160
204
  }
161
205
  if (stepId) injectedEnv.PRLL_STEP_ID = stepId;
162
206
  if (chatId) {
163
- if (chatId.startsWith("sch_")) {
207
+ if (chatId.startsWith('sch_')) {
164
208
  // Schedule-triggered dispatch: expose PRLL_SCHEDULE_ID so the agent
165
209
  // can resolve the schedule; deliberately do NOT set PRLL_CHAT_ID,
166
210
  // because schedule events have no chat to reply into, and leaving
@@ -183,7 +227,9 @@ export function registerParallHooks(api: OpenClawPluginApi) {
183
227
  if (event.toolCallId) injectedEnv.OPENCLAW_TOOL_CALL_ID = event.toolCallId;
184
228
 
185
229
  // Shallow merge — preserve agent's original env params
186
- const existingEnv = (event.params as Record<string, unknown>)?.env as Record<string, string> | undefined;
230
+ const existingEnv = (event.params as Record<string, unknown>)?.env as
231
+ | Record<string, string>
232
+ | undefined;
187
233
 
188
234
  return {
189
235
  params: {
@@ -192,24 +238,37 @@ export function registerParallHooks(api: OpenClawPluginApi) {
192
238
  };
193
239
  });
194
240
 
195
- // after_tool_call -> send tool_result step to AgentSession
196
- api.on("after_tool_call", async (event, ctx) => {
241
+ // after_tool_call -> track CLI outcomes + send tool_result step to AgentSession
242
+ api.on('after_tool_call', async (event, ctx) => {
243
+ if (ctx.sessionKey && event.toolCallId && pendingSendCalls.delete(event.toolCallId)) {
244
+ recordMessageSend(ctx.sessionKey, !event.error);
245
+ }
246
+
197
247
  const resolved = await resolveClientForHook(ctx.sessionKey);
198
248
  if (!resolved?.sessionId || !event.toolCallId) return;
199
249
  try {
200
- await resolved.client.createAgentStep(resolved.orgId, resolved.agentUserId, resolved.sessionId, {
201
- step_type: "tool_result",
202
- target_type: stepTargetType(resolved.chatId),
203
- target_id: resolved.chatId || undefined,
204
- content: {
205
- call_id: event.toolCallId,
206
- tool_name: event.toolName,
207
- status: event.error ? "error" : "success",
208
- output: event.error ?? (typeof event.result === "string" ? event.result : JSON.stringify(event.result ?? "")),
209
- duration_ms: event.durationMs ?? 0,
210
- collapsible: true,
250
+ await resolved.client.createAgentStep(
251
+ resolved.orgId,
252
+ resolved.agentUserId,
253
+ resolved.sessionId,
254
+ {
255
+ step_type: 'tool_result',
256
+ target_type: stepTargetType(resolved.chatId),
257
+ target_id: resolved.chatId || undefined,
258
+ content: {
259
+ call_id: event.toolCallId,
260
+ tool_name: event.toolName,
261
+ status: event.error ? 'error' : 'success',
262
+ output:
263
+ event.error ??
264
+ (typeof event.result === 'string'
265
+ ? event.result
266
+ : JSON.stringify(event.result ?? '')),
267
+ duration_ms: event.durationMs ?? 0,
268
+ collapsible: true,
269
+ },
211
270
  },
212
- });
271
+ );
213
272
  } catch (err) {
214
273
  log?.warn(`parall hook after_tool_call failed: ${String(err)}`);
215
274
  }
@@ -218,7 +277,7 @@ export function registerParallHooks(api: OpenClawPluginApi) {
218
277
  // agent_end -> end of a dispatch cycle, NOT end of session.
219
278
  // Session lives across dispatches — don't update session status here.
220
279
  // Dispatch-specific IDs are cleaned up in the generic gateway; hooks only own the group key.
221
- api.on("agent_end", async (event, ctx) => {
280
+ api.on('agent_end', async (event, ctx) => {
222
281
  if (ctx.sessionKey) {
223
282
  clearDispatchGroupKey(ctx.sessionKey);
224
283
  }
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
3
- import { parallPlugin } from "./channel.js";
4
- import { setParallRuntime } from "./runtime.js";
5
- import { registerParallHooks } from "./hooks.js";
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
+ import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk/core';
3
+ import { parallPlugin } from './channel.js';
4
+ import { setParallRuntime } from './runtime.js';
5
+ import { registerParallHooks } from './hooks.js';
6
6
 
7
7
  const plugin = {
8
- id: "parall",
9
- name: "Parall",
10
- description: "Parall IM channel plugin — Agent-Native messaging with tool call visualization.",
8
+ id: 'parall',
9
+ name: 'Parall',
10
+ description: 'Parall IM channel plugin — Agent-Native messaging with tool call visualization.',
11
11
  configSchema: emptyPluginConfigSchema(),
12
12
  register(api: OpenClawPluginApi) {
13
13
  setParallRuntime(api.runtime);
package/src/oc-session.ts CHANGED
@@ -6,14 +6,9 @@
6
6
  // pi-coding-agent/dist/core/session-manager.js for new migration steps
7
7
  // or structural changes to createBranchedSession / _buildIndex.
8
8
 
9
- import { randomUUID } from "node:crypto";
10
- import {
11
- existsSync,
12
- mkdirSync,
13
- readFileSync,
14
- writeFileSync,
15
- } from "node:fs";
16
- import { join, resolve } from "node:path";
9
+ import { randomUUID } from 'node:crypto';
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
11
+ import { join, resolve } from 'node:path';
17
12
 
18
13
  export const CURRENT_SESSION_VERSION = 3;
19
14
 
@@ -22,7 +17,7 @@ export const CURRENT_SESSION_VERSION = 3;
22
17
  // ---------------------------------------------------------------------------
23
18
 
24
19
  interface SessionHeader {
25
- type: "session";
20
+ type: 'session';
26
21
  version: number;
27
22
  id: string;
28
23
  timestamp: string;
@@ -59,7 +54,7 @@ function generateId(existing: Map<string, unknown> | Set<string>): string {
59
54
 
60
55
  function loadEntries(filePath: string): AnyEntry[] {
61
56
  if (!existsSync(filePath)) return [];
62
- const lines = readFileSync(filePath, "utf-8").trim().split("\n");
57
+ const lines = readFileSync(filePath, 'utf-8').trim().split('\n');
63
58
  const entries: AnyEntry[] = [];
64
59
  for (const line of lines) {
65
60
  if (!line.trim()) continue;
@@ -71,7 +66,7 @@ function loadEntries(filePath: string): AnyEntry[] {
71
66
  }
72
67
  if (entries.length === 0) return entries;
73
68
  const h = entries[0] as Record<string, unknown>;
74
- if (h.type !== "session" || typeof h.id !== "string") return [];
69
+ if (h.type !== 'session' || typeof h.id !== 'string') return [];
75
70
  return entries;
76
71
  }
77
72
 
@@ -81,7 +76,7 @@ function migrateV1ToV2(entries: AnyEntry[]): void {
81
76
  const ids = new Set<string>();
82
77
  let prevId: string | null = null;
83
78
  for (const entry of entries) {
84
- if ((entry as SessionHeader).type === "session") {
79
+ if ((entry as SessionHeader).type === 'session') {
85
80
  (entry as SessionHeader).version = 2;
86
81
  continue;
87
82
  }
@@ -90,11 +85,11 @@ function migrateV1ToV2(entries: AnyEntry[]): void {
90
85
  ids.add(e.id);
91
86
  e.parentId = prevId;
92
87
  prevId = e.id;
93
- if (e.type === "compaction") {
88
+ if (e.type === 'compaction') {
94
89
  const comp = e as Record<string, unknown>;
95
- if (typeof comp.firstKeptEntryIndex === "number") {
90
+ if (typeof comp.firstKeptEntryIndex === 'number') {
96
91
  const target = entries[comp.firstKeptEntryIndex as number] as SessionEntry | undefined;
97
- if (target && target.type !== "session") {
92
+ if (target && target.type !== 'session') {
98
93
  comp.firstKeptEntryId = target.id;
99
94
  }
100
95
  delete comp.firstKeptEntryIndex;
@@ -105,19 +100,23 @@ function migrateV1ToV2(entries: AnyEntry[]): void {
105
100
 
106
101
  function migrateV2ToV3(entries: AnyEntry[]): void {
107
102
  for (const entry of entries) {
108
- if ((entry as SessionHeader).type === "session") {
103
+ if ((entry as SessionHeader).type === 'session') {
109
104
  (entry as SessionHeader).version = 3;
110
105
  continue;
111
106
  }
112
107
  const e = entry as SessionEntry;
113
- if (e.type === "message" && e.message && (e.message as Record<string, unknown>).role === "hookMessage") {
114
- (e.message as Record<string, unknown>).role = "custom";
108
+ if (
109
+ e.type === 'message' &&
110
+ e.message &&
111
+ (e.message as Record<string, unknown>).role === 'hookMessage'
112
+ ) {
113
+ (e.message as Record<string, unknown>).role = 'custom';
115
114
  }
116
115
  }
117
116
  }
118
117
 
119
118
  function migrate(entries: AnyEntry[]): boolean {
120
- const header = entries.find((e) => e.type === "session") as SessionHeader | undefined;
119
+ const header = entries.find((e) => e.type === 'session') as SessionHeader | undefined;
121
120
  const version = header?.version ?? 1;
122
121
  if (version >= CURRENT_SESSION_VERSION) return false;
123
122
  if (version < 2) migrateV1ToV2(entries);
@@ -130,7 +129,7 @@ function migrate(entries: AnyEntry[]): boolean {
130
129
  // ---------------------------------------------------------------------------
131
130
 
132
131
  export class SessionManager {
133
- private sessionId = "";
132
+ private sessionId = '';
134
133
  private sessionFile: string | undefined;
135
134
  private readonly sessionDir: string;
136
135
  private readonly cwd: string;
@@ -174,12 +173,12 @@ export class SessionManager {
174
173
  this.flushed = true;
175
174
  return;
176
175
  }
177
- const header = this.fileEntries.find((e) => e.type === "session") as SessionHeader | undefined;
176
+ const header = this.fileEntries.find((e) => e.type === 'session') as SessionHeader | undefined;
178
177
  if (header && header.version > CURRENT_SESSION_VERSION) {
179
178
  throw new Error(
180
179
  `Session file ${this.sessionFile} uses version ${header.version}, ` +
181
- `but oc-session.ts only supports up to ${CURRENT_SESSION_VERSION}. ` +
182
- `Sync oc-session.ts with upstream pi-coding-agent (see AGENTS.md step 4).`,
180
+ `but oc-session.ts only supports up to ${CURRENT_SESSION_VERSION}. ` +
181
+ `Sync oc-session.ts with upstream pi-coding-agent (see AGENTS.md step 4).`,
183
182
  );
184
183
  }
185
184
  this.sessionId = header?.id ?? randomUUID();
@@ -192,13 +191,19 @@ export class SessionManager {
192
191
  this.sessionId = randomUUID();
193
192
  const timestamp = new Date().toISOString();
194
193
  this.fileEntries = [
195
- { type: "session", version: CURRENT_SESSION_VERSION, id: this.sessionId, timestamp, cwd: this.cwd },
194
+ {
195
+ type: 'session',
196
+ version: CURRENT_SESSION_VERSION,
197
+ id: this.sessionId,
198
+ timestamp,
199
+ cwd: this.cwd,
200
+ },
196
201
  ];
197
202
  this.byId.clear();
198
203
  this.labelsById.clear();
199
204
  this.leafId = null;
200
205
  this.flushed = false;
201
- const ts = timestamp.replace(/[:.]/g, "-");
206
+ const ts = timestamp.replace(/[:.]/g, '-');
202
207
  this.sessionFile = join(this.sessionDir, `${ts}_${this.sessionId}.jsonl`);
203
208
  }
204
209
 
@@ -208,11 +213,11 @@ export class SessionManager {
208
213
  this.labelTimestampsById.clear();
209
214
  this.leafId = null;
210
215
  for (const entry of this.fileEntries) {
211
- if (entry.type === "session") continue;
216
+ if (entry.type === 'session') continue;
212
217
  const e = entry as SessionEntry;
213
218
  this.byId.set(e.id, e);
214
219
  this.leafId = e.id;
215
- if (e.type === "label") {
220
+ if (e.type === 'label') {
216
221
  if (e.label) {
217
222
  this.labelsById.set(e.targetId!, e.label);
218
223
  this.labelTimestampsById.set(e.targetId!, e.timestamp);
@@ -226,7 +231,10 @@ export class SessionManager {
226
231
 
227
232
  private rewrite(): void {
228
233
  if (!this.sessionFile) return;
229
- writeFileSync(this.sessionFile, this.fileEntries.map((e) => JSON.stringify(e)).join("\n") + "\n");
234
+ writeFileSync(
235
+ this.sessionFile,
236
+ this.fileEntries.map((e) => JSON.stringify(e)).join('\n') + '\n',
237
+ );
230
238
  }
231
239
 
232
240
  // -- Public accessors ------------------------------------------------------
@@ -268,14 +276,14 @@ export class SessionManager {
268
276
  const branch = this.getBranch(leafId);
269
277
  if (branch.length === 0) throw new Error(`Entry ${leafId} not found`);
270
278
 
271
- const pathWithoutLabels = branch.filter((e) => e.type !== "label");
279
+ const pathWithoutLabels = branch.filter((e) => e.type !== 'label');
272
280
  const newId = randomUUID();
273
281
  const timestamp = new Date().toISOString();
274
- const ts = timestamp.replace(/[:.]/g, "-");
282
+ const ts = timestamp.replace(/[:.]/g, '-');
275
283
  const newFile = join(this.sessionDir, `${ts}_${newId}.jsonl`);
276
284
 
277
285
  const header: SessionHeader = {
278
- type: "session",
286
+ type: 'session',
279
287
  version: CURRENT_SESSION_VERSION,
280
288
  id: newId,
281
289
  timestamp,
@@ -296,7 +304,7 @@ export class SessionManager {
296
304
  const labelEntries: SessionEntry[] = [];
297
305
  for (const { targetId, label, timestamp: labelTs } of labelsToWrite) {
298
306
  const le: SessionEntry = {
299
- type: "label",
307
+ type: 'label',
300
308
  id: generateId(pathEntryIds),
301
309
  parentId,
302
310
  timestamp: labelTs ?? timestamp,
@@ -314,7 +322,7 @@ export class SessionManager {
314
322
  this.buildIndex();
315
323
 
316
324
  const hasAssistant = this.fileEntries.some(
317
- (e) => e.type === "message" && (e as SessionEntry).message?.role === "assistant",
325
+ (e) => e.type === 'message' && (e as SessionEntry).message?.role === 'assistant',
318
326
  );
319
327
  if (hasAssistant) {
320
328
  this.rewrite();
@@ -329,9 +337,9 @@ export class SessionManager {
329
337
 
330
338
  static open(path: string): SessionManager {
331
339
  const entries = loadEntries(path);
332
- const header = entries.find((e) => e.type === "session") as SessionHeader | undefined;
340
+ const header = entries.find((e) => e.type === 'session') as SessionHeader | undefined;
333
341
  const cwd = header?.cwd ?? process.cwd();
334
- const dir = resolve(path, "..");
342
+ const dir = resolve(path, '..');
335
343
  return new SessionManager(cwd, dir, path);
336
344
  }
337
345
  }
package/src/outbound.ts CHANGED
@@ -1,37 +1,38 @@
1
- import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
1
+ import type { ChannelPlugin } from 'openclaw/plugin-sdk/core';
2
2
 
3
3
  /** Extract ChannelOutboundAdapter from ChannelPlugin (removed from public SDK exports in 2026.3.24). */
4
- type ChannelOutboundAdapter = NonNullable<ChannelPlugin["outbound"]>;
5
- import { resolveParallAccount } from "./accounts.js";
6
- import { getParallAccountState } from "./runtime.js";
7
- import { ParallClient } from "@parall/sdk";
8
- import type { SendMessageRequest } from "@parall/sdk";
4
+ type ChannelOutboundAdapter = NonNullable<ChannelPlugin['outbound']>;
5
+ import { resolveParallAccount } from './accounts.js';
6
+ import { getParallAccountState } from './runtime.js';
7
+ import { ParallClient } from '@parall/sdk';
8
+ import type { SendMessageRequest } from '@parall/sdk';
9
9
 
10
10
  export const parallOutbound: ChannelOutboundAdapter = {
11
- deliveryMode: "direct",
11
+ deliveryMode: 'direct',
12
12
  textChunkLimit: 10000,
13
13
  chunker: null,
14
14
 
15
15
  sendText: async ({ cfg, to, text, accountId }) => {
16
16
  const account = resolveParallAccount({ cfg, accountId: accountId ?? undefined });
17
- if (!account.enabled) throw new Error("Parall account is disabled");
18
- if (!account.configured) throw new Error("Parall account is not configured: parall_url/api_key/org_id required");
17
+ if (!account.enabled) throw new Error('Parall account is disabled');
18
+ if (!account.configured)
19
+ throw new Error('Parall account is not configured: parall_url/api_key/org_id required');
19
20
 
20
21
  const state = getParallAccountState(account.accountId);
21
- const client = state?.client ?? new ParallClient({
22
- baseUrl: account.config.parall_url,
23
- token: account.config.api_key,
24
- });
22
+ const client =
23
+ state?.client ??
24
+ new ParallClient({
25
+ baseUrl: account.config.parall_url,
26
+ token: account.config.api_key,
27
+ });
25
28
  const orgId = state?.orgId ?? account.config.org_id;
26
29
  const chatId = to;
27
30
 
28
- // Outbound path has no step context — send without agent_step_id.
29
- // Step linkage happens via the CLI's ENV-injected PRLL_STEP_ID in the normal dispatch path.
30
31
  const req: SendMessageRequest = {
31
- message_type: "text",
32
+ message_type: 'text',
32
33
  content: { text },
33
34
  };
34
35
  const msg = await client.sendMessage(orgId, chatId, req);
35
- return { channel: "parall", messageId: msg.id, channelId: chatId };
36
+ return { channel: 'parall', messageId: msg.id, channelId: chatId };
36
37
  },
37
38
  };
package/src/routing.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export {
2
2
  defaultRoutingStrategy,
3
3
  routeTrigger,
4
- } from "@parall/agent-core";
4
+ } from '@parall/agent-core';
5
5
  export type {
6
6
  RoutingStrategy,
7
7
  TriggerDisposition,
8
- } from "@parall/agent-core";
8
+ } from '@parall/agent-core';
package/src/runtime.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
2
- import type { ParallClient, ParallWs } from "@parall/sdk";
3
- import type { AgentIdentity } from "@parall/agent-core";
4
- export type { ForkResult, DispatchState, ParallEvent } from "@parall/agent-core";
1
+ import type { PluginRuntime } from 'openclaw/plugin-sdk';
2
+ import type { ParallClient, ParallWs } from '@parall/sdk';
3
+ import type { AgentIdentity } from '@parall/agent-core';
4
+ export type { ForkResult, DispatchState, ParallEvent } from '@parall/agent-core';
5
5
  export {
6
6
  setSessionChatId,
7
7
  getSessionChatId,
@@ -17,7 +17,7 @@ export {
17
17
  setDispatchNoReply,
18
18
  getDispatchNoReply,
19
19
  clearDispatchNoReply,
20
- } from "@parall/agent-core";
20
+ } from '@parall/agent-core';
21
21
 
22
22
  export type ParallAccountState = {
23
23
  client: ParallClient;
@@ -43,7 +43,7 @@ export function setParallRuntime(next: PluginRuntime) {
43
43
 
44
44
  export function getParallRuntime(): PluginRuntime {
45
45
  if (!runtime) {
46
- throw new Error("Parall runtime not initialized");
46
+ throw new Error('Parall runtime not initialized');
47
47
  }
48
48
  return runtime;
49
49
  }
@@ -63,7 +63,11 @@ export function getParallAccountState(accountId: string): ParallAccountState | u
63
63
  return accountStates.get(accountId);
64
64
  }
65
65
 
66
- export function setAgentSessionBinding(accountId: string, sessionKey: string, agentSessionId: string) {
66
+ export function setAgentSessionBinding(
67
+ accountId: string,
68
+ sessionKey: string,
69
+ agentSessionId: string,
70
+ ) {
67
71
  const state = accountStates.get(accountId);
68
72
  if (!state) return;
69
73
  const sessionBindings = state.sessionBindings ?? new Map<string, string>();
package/src/session.ts CHANGED
@@ -1,4 +1,4 @@
1
- const PRLL_SESSION_PREFIX = "agent:main:parall:";
1
+ const PRLL_SESSION_PREFIX = 'agent:main:parall:';
2
2
 
3
3
  /** Build OpenClaw session key from Parall account, chat type, and chat ID. */
4
4
  export function buildSessionKey(accountId: string, chatType: string, chatId: string): string {
@@ -12,7 +12,7 @@ export function buildSessionKey(accountId: string, chatType: string, chatId: str
12
12
  export function extractAccountIdFromSessionKey(sessionKey: string | undefined): string | undefined {
13
13
  if (!sessionKey?.startsWith(PRLL_SESSION_PREFIX)) return undefined;
14
14
  const rest = sessionKey.slice(PRLL_SESSION_PREFIX.length);
15
- const firstColon = rest.indexOf(":");
15
+ const firstColon = rest.indexOf(':');
16
16
  if (firstColon < 0) return undefined;
17
17
  return rest.slice(0, firstColon) || undefined;
18
18
  }
@@ -25,9 +25,9 @@ export function extractChatIdFromSessionKey(sessionKey: string | undefined): str
25
25
  if (!sessionKey?.startsWith(PRLL_SESSION_PREFIX)) return undefined;
26
26
  const rest = sessionKey.slice(PRLL_SESSION_PREFIX.length);
27
27
  // Skip accountId and chatType segments
28
- const parts = rest.split(":");
28
+ const parts = rest.split(':');
29
29
  if (parts.length < 3) return undefined;
30
- return parts.slice(2).join(":") || undefined;
30
+ return parts.slice(2).join(':') || undefined;
31
31
  }
32
32
 
33
33
  /** Build the single orchestrator session key for all Parall events. */