@parall/parall 1.30.0 → 1.31.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.
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,uGAAuG;AACvG,KAAK,qBAAqB,CAAC,CAAC,GAAG,OAAO,IAAI,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAuBnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAkQxD,eAAO,MAAM,aAAa,EAAE,qBAAqB,CAAC,qBAAqB,CAkItE,CAAC"}
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,uGAAuG;AACvG,KAAK,qBAAqB,CAAC,CAAC,GAAG,OAAO,IAAI,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AA4BnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAoRxD,eAAO,MAAM,aAAa,EAAE,qBAAqB,CAAC,qBAAqB,CAsItE,CAAC"}
package/dist/gateway.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ParallAgentGateway, parseShutdownDeadlineMs, } from "@parall/agent-core";
2
+ import { appendPreparedLocalAttachmentRefs, ensureLocalAttachmentGitExclude, pinLocalAttachmentPaths, } from "@parall/agent-core/internal/attachment-input";
2
3
  import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
3
4
  import * as crypto from "node:crypto";
4
5
  import * as os from "node:os";
@@ -146,68 +147,86 @@ async function waitForOpenClawSessionId(sessionsDir, sessionKey, timeoutMs = 10_
146
147
  }
147
148
  function createOpenClawDispatchAdapter(opts) {
148
149
  return {
149
- async *dispatch({ event, earlierEvents = [], bodyForAgent, sessionKey }) {
150
- const stream = createRuntimeEventStream();
151
- let reasoningBuffer = "";
152
- let turnGroupKey = "";
153
- const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
154
- ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, bodyForAgent, earlierEvents),
155
- cfg: opts.cfg,
156
- dispatcherOptions: {
157
- deliver: async (payload) => {
158
- const replyText = payload.text?.trim();
159
- if (!replyText)
160
- return;
161
- stream.push({
162
- type: "text",
163
- text: replyText,
164
- project: false,
165
- groupKey: turnGroupKey || undefined,
166
- });
167
- },
168
- onReplyStart: () => {
169
- turnGroupKey = crypto.randomUUID();
170
- setDispatchGroupKey(sessionKey, turnGroupKey);
171
- },
172
- },
173
- replyOptions: {
174
- onReasoningStream: (payload) => {
175
- reasoningBuffer += payload.text ?? "";
176
- },
177
- onReasoningEnd: async () => {
178
- if (!reasoningBuffer)
179
- return;
180
- stream.push({
181
- type: "thinking",
182
- text: reasoningBuffer,
183
- groupKey: turnGroupKey || undefined,
184
- });
185
- reasoningBuffer = "";
186
- },
187
- },
188
- });
189
- run.then(() => stream.end()).catch((err) => stream.fail(err));
190
- let sessionId;
150
+ async *dispatch({ event, earlierEvents = [], bodyForAgent, sessionKey, context }) {
151
+ let promptBody = bodyForAgent;
152
+ let releasePreparedAttachments = () => { };
191
153
  try {
192
- sessionId = await Promise.race([
193
- waitForOpenClawSessionId(opts.sessionsDir, sessionKey),
194
- run.then(() => null, (err) => { throw err; }),
195
- ]);
154
+ const prepared = await appendPreparedLocalAttachmentRefs(bodyForAgent, event, context, {
155
+ workspaceDir: opts.workspaceDir,
156
+ log: context.log,
157
+ });
158
+ promptBody = prepared.body;
159
+ releasePreparedAttachments = pinLocalAttachmentPaths(prepared.attachments.images);
196
160
  }
197
- catch {
198
- sessionId = null;
161
+ catch (err) {
162
+ context.log?.warn?.(`failed to prepare local attachments: ${String(err)}`);
199
163
  }
200
- if (!sessionId) {
201
- stream.fail(new Error(`OpenClaw did not expose a sessionId for ${sessionKey}`));
164
+ try {
165
+ const stream = createRuntimeEventStream();
166
+ let reasoningBuffer = "";
167
+ let turnGroupKey = "";
168
+ const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
169
+ ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, promptBody, earlierEvents),
170
+ cfg: opts.cfg,
171
+ dispatcherOptions: {
172
+ deliver: async (payload) => {
173
+ const replyText = payload.text?.trim();
174
+ if (!replyText)
175
+ return;
176
+ stream.push({
177
+ type: "text",
178
+ text: replyText,
179
+ project: false,
180
+ groupKey: turnGroupKey || undefined,
181
+ });
182
+ },
183
+ onReplyStart: () => {
184
+ turnGroupKey = crypto.randomUUID();
185
+ setDispatchGroupKey(sessionKey, turnGroupKey);
186
+ },
187
+ },
188
+ replyOptions: {
189
+ onReasoningStream: (payload) => {
190
+ reasoningBuffer += payload.text ?? "";
191
+ },
192
+ onReasoningEnd: async () => {
193
+ if (!reasoningBuffer)
194
+ return;
195
+ stream.push({
196
+ type: "thinking",
197
+ text: reasoningBuffer,
198
+ groupKey: turnGroupKey || undefined,
199
+ });
200
+ reasoningBuffer = "";
201
+ },
202
+ },
203
+ });
204
+ run.then(() => stream.end()).catch((err) => stream.fail(err));
205
+ let sessionId;
206
+ try {
207
+ sessionId = await Promise.race([
208
+ waitForOpenClawSessionId(opts.sessionsDir, sessionKey),
209
+ run.then(() => null, (err) => { throw err; }),
210
+ ]);
211
+ }
212
+ catch {
213
+ sessionId = null;
214
+ }
215
+ if (!sessionId) {
216
+ stream.fail(new Error(`OpenClaw did not expose a sessionId for ${sessionKey}`));
217
+ }
218
+ else {
219
+ yield {
220
+ type: "runtime_session",
221
+ runtimeSessionId: sessionId,
222
+ runtimeLaneKey: sessionKey,
223
+ };
224
+ }
225
+ yield* stream.stream();
202
226
  }
203
- else {
204
- yield {
205
- type: "runtime_session",
206
- runtimeSessionId: sessionId,
207
- runtimeLaneKey: sessionKey,
208
- };
227
+ finally {
228
+ releasePreparedAttachments();
209
229
  }
210
- yield* stream.stream();
211
230
  },
212
231
  getBranchPoint(sessionKey) {
213
232
  const transcriptFile = resolveTranscriptFile(opts.sessionsDir, sessionKey);
@@ -308,11 +327,14 @@ export const parallGateway = {
308
327
  });
309
328
  const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
310
329
  const sessionsDir = path.join(stateDir, "agents", "main", "sessions");
330
+ const workspaceDir = process.cwd();
331
+ ensureLocalAttachmentGitExclude(workspaceDir);
311
332
  const dispatchAdapter = createOpenClawDispatchAdapter({
312
333
  core,
313
334
  cfg: ctx.cfg,
314
335
  accountId: ctx.accountId,
315
336
  sessionsDir,
337
+ workspaceDir,
316
338
  });
317
339
  const gateway = new ParallAgentGateway({
318
340
  accountId: ctx.accountId,
@@ -358,6 +380,7 @@ export const parallGateway = {
358
380
  removeParallAccountState(ctx.accountId);
359
381
  },
360
382
  onNewSession: () => { clearSessionState(sessionsDir, orchestratorKey); },
383
+ onSessionStale: () => { clearSessionState(sessionsDir, orchestratorKey); },
361
384
  });
362
385
  try {
363
386
  await gateway.run(ctx.abortSignal);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/parall",
3
- "version": "1.30.0",
3
+ "version": "1.31.0",
4
4
  "description": "OpenClaw channel plugin for Parall IM",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,8 +16,8 @@
16
16
  "openclaw.plugin.json"
17
17
  ],
18
18
  "dependencies": {
19
- "@parall/agent-core": "1.30.0",
20
- "@parall/sdk": "1.30.0"
19
+ "@parall/sdk": "1.31.0",
20
+ "@parall/agent-core": "1.31.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.0.0",
package/src/gateway.ts CHANGED
@@ -10,6 +10,11 @@ import {
10
10
  type ParallEvent,
11
11
  type RuntimeEvent,
12
12
  } from "@parall/agent-core";
13
+ import {
14
+ appendPreparedLocalAttachmentRefs,
15
+ ensureLocalAttachmentGitExclude,
16
+ pinLocalAttachmentPaths,
17
+ } from "@parall/agent-core/internal/attachment-input";
13
18
  import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
14
19
  import type { ParallChannelConfig } from "./types.js";
15
20
  import * as crypto from "node:crypto";
@@ -185,68 +190,86 @@ function createOpenClawDispatchAdapter(opts: {
185
190
  cfg: Record<string, unknown>;
186
191
  accountId: string;
187
192
  sessionsDir: string;
193
+ workspaceDir: string;
188
194
  }): DispatchAdapter {
189
195
  return {
190
- async *dispatch({ event, earlierEvents = [], bodyForAgent, sessionKey }) {
191
- const stream = createRuntimeEventStream();
192
- let reasoningBuffer = "";
193
- let turnGroupKey = "";
196
+ async *dispatch({ event, earlierEvents = [], bodyForAgent, sessionKey, context }) {
197
+ let promptBody = bodyForAgent;
198
+ let releasePreparedAttachments = () => {};
199
+ try {
200
+ const prepared = await appendPreparedLocalAttachmentRefs(bodyForAgent, event, context, {
201
+ workspaceDir: opts.workspaceDir,
202
+ log: context.log,
203
+ });
204
+ promptBody = prepared.body;
205
+ releasePreparedAttachments = pinLocalAttachmentPaths(prepared.attachments.images);
206
+ } catch (err) {
207
+ context.log?.warn?.(`failed to prepare local attachments: ${String(err)}`);
208
+ }
194
209
 
195
- const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
196
- ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, bodyForAgent, earlierEvents),
197
- cfg: opts.cfg,
198
- dispatcherOptions: {
199
- deliver: async (payload: { text?: string }) => {
200
- const replyText = payload.text?.trim();
201
- if (!replyText) return;
202
- stream.push({
203
- type: "text",
204
- text: replyText,
205
- project: false,
206
- groupKey: turnGroupKey || undefined,
207
- });
208
- },
209
- onReplyStart: () => {
210
- turnGroupKey = crypto.randomUUID();
211
- setDispatchGroupKey(sessionKey, turnGroupKey);
212
- },
213
- },
214
- replyOptions: {
215
- onReasoningStream: (payload: { text?: string }) => {
216
- reasoningBuffer += payload.text ?? "";
210
+ try {
211
+ const stream = createRuntimeEventStream();
212
+ let reasoningBuffer = "";
213
+ let turnGroupKey = "";
214
+
215
+ const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
216
+ ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, promptBody, earlierEvents),
217
+ cfg: opts.cfg,
218
+ dispatcherOptions: {
219
+ deliver: async (payload: { text?: string }) => {
220
+ const replyText = payload.text?.trim();
221
+ if (!replyText) return;
222
+ stream.push({
223
+ type: "text",
224
+ text: replyText,
225
+ project: false,
226
+ groupKey: turnGroupKey || undefined,
227
+ });
228
+ },
229
+ onReplyStart: () => {
230
+ turnGroupKey = crypto.randomUUID();
231
+ setDispatchGroupKey(sessionKey, turnGroupKey);
232
+ },
217
233
  },
218
- onReasoningEnd: async () => {
219
- if (!reasoningBuffer) return;
220
- stream.push({
221
- type: "thinking",
222
- text: reasoningBuffer,
223
- groupKey: turnGroupKey || undefined,
224
- });
225
- reasoningBuffer = "";
234
+ replyOptions: {
235
+ onReasoningStream: (payload: { text?: string }) => {
236
+ reasoningBuffer += payload.text ?? "";
237
+ },
238
+ onReasoningEnd: async () => {
239
+ if (!reasoningBuffer) return;
240
+ stream.push({
241
+ type: "thinking",
242
+ text: reasoningBuffer,
243
+ groupKey: turnGroupKey || undefined,
244
+ });
245
+ reasoningBuffer = "";
246
+ },
226
247
  },
227
- },
228
- });
248
+ });
229
249
 
230
- run.then(() => stream.end()).catch((err) => stream.fail(err));
231
- let sessionId: string | null;
232
- try {
233
- sessionId = await Promise.race([
234
- waitForOpenClawSessionId(opts.sessionsDir, sessionKey),
235
- run.then(() => null as string | null, (err) => { throw err; }),
236
- ]);
237
- } catch {
238
- sessionId = null;
239
- }
240
- if (!sessionId) {
241
- stream.fail(new Error(`OpenClaw did not expose a sessionId for ${sessionKey}`));
242
- } else {
243
- yield {
244
- type: "runtime_session",
245
- runtimeSessionId: sessionId,
246
- runtimeLaneKey: sessionKey,
247
- };
250
+ run.then(() => stream.end()).catch((err) => stream.fail(err));
251
+ let sessionId: string | null;
252
+ try {
253
+ sessionId = await Promise.race([
254
+ waitForOpenClawSessionId(opts.sessionsDir, sessionKey),
255
+ run.then(() => null as string | null, (err) => { throw err; }),
256
+ ]);
257
+ } catch {
258
+ sessionId = null;
259
+ }
260
+ if (!sessionId) {
261
+ stream.fail(new Error(`OpenClaw did not expose a sessionId for ${sessionKey}`));
262
+ } else {
263
+ yield {
264
+ type: "runtime_session",
265
+ runtimeSessionId: sessionId,
266
+ runtimeLaneKey: sessionKey,
267
+ };
268
+ }
269
+ yield* stream.stream();
270
+ } finally {
271
+ releasePreparedAttachments();
248
272
  }
249
- yield* stream.stream();
250
273
  },
251
274
 
252
275
  getBranchPoint(sessionKey: string): string | undefined {
@@ -351,11 +374,14 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
351
374
  });
352
375
  const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
353
376
  const sessionsDir = path.join(stateDir, "agents", "main", "sessions");
377
+ const workspaceDir = process.cwd();
378
+ ensureLocalAttachmentGitExclude(workspaceDir);
354
379
  const dispatchAdapter = createOpenClawDispatchAdapter({
355
380
  core,
356
381
  cfg: ctx.cfg,
357
382
  accountId: ctx.accountId,
358
383
  sessionsDir,
384
+ workspaceDir,
359
385
  });
360
386
 
361
387
  const gateway = new ParallAgentGateway({
@@ -402,6 +428,7 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
402
428
  removeParallAccountState(ctx.accountId);
403
429
  },
404
430
  onNewSession: () => { clearSessionState(sessionsDir, orchestratorKey); },
431
+ onSessionStale: () => { clearSessionState(sessionsDir, orchestratorKey); },
405
432
  });
406
433
 
407
434
  try {