@parall/parall 1.43.0 → 1.45.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.
package/dist/runtime.d.ts CHANGED
@@ -16,6 +16,23 @@ export type ParallAccountState = {
16
16
  wikiMountRoot?: string;
17
17
  ws?: ParallWs;
18
18
  orchestratorSessionKey?: string;
19
+ /**
20
+ * Per-lane dispatch context dir (PRLL_CONTEXT_DIR contract) — set when the
21
+ * gateway runs the dispatch ledger; hooks inject it into exec env so the
22
+ * CLI binds replies to their dispatch lane.
23
+ */
24
+ dispatchContextDir?: string;
25
+ /**
26
+ * Per-session dispatch context file resolver (PRLL_CONTEXT_FILE contract)
27
+ * — the CLI's typed dispatch binding (task_update effects) reads from it.
28
+ */
29
+ contextFilePathForSession?: (sessionKey: string) => string;
30
+ /**
31
+ * Lease-renewal bridge into the gateway: openclaw tool activity flows
32
+ * through hooks (not the RuntimeEvent stream), so hooks must forward it or
33
+ * a long exec outlives the lane lease.
34
+ */
35
+ touchRuntimeActivity?: (sessionKey: string) => void;
19
36
  [key: string]: unknown;
20
37
  };
21
38
  export declare function setParallRuntime(next: PluginRuntime): void;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAIF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,QAEnD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAKhD;AAKD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,QAEjF;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,QAEzD;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEvF;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,QAOvB;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEhG;AAED,wBAAgB,yBAAyB,IAAI,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAEnF;AAID,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,QAEvD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAE5D"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3D;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAIF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,QAEnD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAKhD;AAKD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,QAEjF;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,QAEzD;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEvF;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,QAOvB;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEhG;AAED,wBAAgB,yBAAyB,IAAI,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAEnF;AAID,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,QAEvD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAE5D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/parall",
3
- "version": "1.43.0",
3
+ "version": "1.45.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.43.0",
20
- "@parall/sdk": "1.43.0"
19
+ "@parall/sdk": "1.45.0",
20
+ "@parall/agent-core": "1.45.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.0.0",
@@ -40,7 +40,7 @@ parall schedules create \
40
40
  --run-at <FUTURE_RFC3339_TIME>
41
41
  ```
42
42
 
43
- `--target-ids` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). `--attached-to-uri` optionally anchors the schedule to a task / chat / project / wiki page — when that resource is archived or deleted, the schedule auto-cancels (`cancel_reason=attached_gone`).
43
+ `--target-ids` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). `--attached-to-uri` optionally anchors the schedule to a task / chat / project / wiki page — when that resource is archived or deleted, the schedule auto-cancels (`status_reason=attached_gone`). A schedule whose agent targets all sit on a terminated machine is auto-paused by the platform (`status_reason=attendee_unreachable`) instead of firing into a void; resuming a recurring schedule while the machine is still terminated just pauses it again on the next slot (a one-shot resumed past its catch-up window instead follows the normal missed semantics and completes).
44
44
 
45
45
  ### Reminders for someone else
46
46
 
@@ -39,16 +39,32 @@ parall tasks list --assignee-id prll://usr_xxx # first page only (default 20)
39
39
  parall tasks subtasks prll://tsk_xxx # children of a single parent task
40
40
 
41
41
  # Create a task (add --parent-id to make it a SUBTASK of another task)
42
- parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]
42
+ parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx] [--due-date 2026-08-01]
43
43
 
44
- # Update task status
45
- parall tasks update prll://tsk_xxx --status in_progress
46
- parall tasks update prll://tsk_xxx --status done
44
+ # Update task status — add --placement end so the task lands at the end of
45
+ # its NEW status column (a bare --status keeps the old column's sort_order)
46
+ parall tasks update prll://tsk_xxx --status in_progress --placement end
47
+ parall tasks update prll://tsk_xxx --status done --placement end
48
+
49
+ # Due date — a plain YYYY-MM-DD date (no timestamps); "none" clears it
50
+ parall tasks update prll://tsk_xxx --due-date 2026-08-01
51
+ parall tasks update prll://tsk_xxx --due-date none
52
+
53
+ # Move a task to the end of its status column
54
+ parall tasks update prll://tsk_xxx --placement end
47
55
 
48
56
  # Add a comment
49
57
  parall tasks comments add prll://tsk_xxx --body "Progress update..."
50
58
  ```
51
59
 
60
+ Ordering: to append a task to the end of a status column, always use
61
+ `--placement end` — the server resolves the position atomically. This
62
+ includes status changes: a bare `--status` keeps the task's old
63
+ `sort_order`, which may collide inside the new column. Do NOT compute a
64
+ `sort_order` value yourself from listed tasks (your view may be stale or
65
+ partial). `--sort-order` is only for pinpoint insertion between two cards
66
+ you just listed, and it cannot be combined with `--placement`.
67
+
52
68
  Subtasks are just tasks with a parent: create one with `tasks create --parent-id`,
53
69
  re-parent with `tasks update --parent-id`, list a parent's children with
54
70
  `tasks subtasks`. `tasks list` without `--parent-id` already returns both
@@ -83,7 +99,7 @@ watcher.
83
99
  When you receive `[Event: task.assigned]`:
84
100
 
85
101
  1. Acknowledge with a comment: `tasks comments add prll://tsk_xxx --body "On it"`
86
- 2. Update status: `tasks update prll://tsk_xxx --status in_progress`
102
+ 2. Update status: `tasks update prll://tsk_xxx --status in_progress --placement end`
87
103
  3. Do the work
88
104
  4. Report results in a comment. If a gate remains — review, merge, deploy,
89
105
  requester acceptance — set `in_review` and name the gate; set `done`
package/src/gateway.ts CHANGED
@@ -6,6 +6,7 @@ type ChannelGatewayAdapter<T = unknown> = NonNullable<ChannelPlugin<T>['gateway'
6
6
  import {
7
7
  ParallAgentGateway,
8
8
  capabilityBinDir,
9
+ dispatchLaneContextDir,
9
10
  parseShutdownDeadlineMs,
10
11
  parseForkDeadlineMs,
11
12
  parseDispatchDeadlineMs,
@@ -47,6 +48,16 @@ import {
47
48
  resolveTranscriptFile,
48
49
  } from './fork.js';
49
50
 
51
+ /**
52
+ * Per-session dispatch context file (PRLL_CONTEXT_FILE contract) — same
53
+ * encoding as the claude/codex bridges: base64url(sessionKey) under the
54
+ * state dir's dispatch-context/.
55
+ */
56
+ function sessionContextFilePath(stateDir: string, sessionKey: string): string {
57
+ const fileName = Buffer.from(sessionKey).toString('base64url');
58
+ return path.join(stateDir, 'dispatch-context', `${fileName}.json`);
59
+ }
60
+
50
61
  function resolveWsUrl(account: ResolvedParallAccount): string {
51
62
  if (account.config.ws_url) return account.config.ws_url;
52
63
  const base = account.config.parall_url.replace(/\/$/, '');
@@ -482,6 +493,19 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
482
493
  runtimeKey: orchestratorKey,
483
494
  runtimeRef: { hostname: os.hostname(), pid: process.pid },
484
495
  dispatchAdapter,
496
+ // Opts openclaw into the dispatch ledger (claim/fold/complete +
497
+ // idempotent reply effects) — the same shared-gateway machinery
498
+ // claude/codex ride; openclaw stays buffer-only (no mid-turn steer),
499
+ // which the ledger does not require. Replies already flow through
500
+ // @parall/cli, which reads PRLL_CONTEXT_DIR (injected in hooks.ts)
501
+ // to bind dispatch_lane + reply effect keys.
502
+ // Design: docs/engineering-design/dispatch-convergence-design.md §7 (S2).
503
+ dispatchContextDir: dispatchLaneContextDir(stateDir),
504
+ // Per-session context file (PRLL_CONTEXT_FILE contract) — the CLI's
505
+ // TYPED dispatch binding (parall tasks update → task_update effect)
506
+ // reads lane/event/task fields from this file, not the lane dir.
507
+ contextFilePathForSession: (sessionKey: string) =>
508
+ sessionContextFilePath(stateDir, sessionKey),
485
509
  log: otelLog,
486
510
  // Live capability view for hint routing: the channel reply hint
487
511
  // points at the vendor CLI only while the grant is active.
@@ -506,6 +530,13 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
506
530
  wikiMountRoot,
507
531
  ws,
508
532
  orchestratorSessionKey: orchestratorKey,
533
+ dispatchContextDir: dispatchLaneContextDir(stateDir),
534
+ contextFilePathForSession: (sessionKey: string) =>
535
+ sessionContextFilePath(stateDir, sessionKey),
536
+ // Lease-renewal bridge: openclaw tool calls flow through hooks,
537
+ // not the RuntimeEvent stream, so hook activity must renew the
538
+ // ledger lanes or a long exec outlives the lease (STALE_LANE).
539
+ touchRuntimeActivity: (sessionKey: string) => gateway.touchRuntimeActivity(sessionKey),
509
540
  });
510
541
  },
511
542
  onSessionBinding: async ({ sessionKey, agentSessionId }) => {
package/src/hooks.ts CHANGED
@@ -140,6 +140,49 @@ export function registerParallHooks(api: OpenClawPluginApi) {
140
140
 
141
141
  const pendingSendCalls = new Map<string, boolean>();
142
142
 
143
+ // Lane lease keepalive across the tool-call lifecycle. OpenClaw tool calls
144
+ // bypass the RuntimeEvent stream, so lane renewal must ride these hooks —
145
+ // and a ONE-SHOT touch at tool start is not enough: a single exec running
146
+ // past the lane TTL (10min server-side) would still be dethroned mid-call
147
+ // and the eventual CLI reply would die with STALE_LANE. While any tool call
148
+ // is in flight for a session, an unref'd interval keeps touching; the
149
+ // half-TTL throttle in the ledger decides when a real heartbeat is sent.
150
+ const laneRenewals = new Map<string, { count: number; timer?: ReturnType<typeof setInterval> }>();
151
+ const LANE_RENEW_INTERVAL_MS = 180_000;
152
+
153
+ const beginToolRenewal = (sessionKey: string) => {
154
+ const accountId = extractAccountIdFromSessionKey(sessionKey);
155
+ const state = accountId ? getParallAccountState(accountId) : undefined;
156
+ if (!state?.touchRuntimeActivity) return;
157
+ state.touchRuntimeActivity(sessionKey);
158
+ const entry = laneRenewals.get(sessionKey) ?? { count: 0 };
159
+ entry.count++;
160
+ if (!entry.timer) {
161
+ entry.timer = setInterval(
162
+ () => state.touchRuntimeActivity?.(sessionKey),
163
+ LANE_RENEW_INTERVAL_MS,
164
+ );
165
+ entry.timer.unref?.();
166
+ }
167
+ laneRenewals.set(sessionKey, entry);
168
+ };
169
+
170
+ const endToolRenewal = (sessionKey: string) => {
171
+ const entry = laneRenewals.get(sessionKey);
172
+ if (entry) {
173
+ entry.count--;
174
+ if (entry.count <= 0) {
175
+ if (entry.timer) clearInterval(entry.timer);
176
+ laneRenewals.delete(sessionKey);
177
+ }
178
+ }
179
+ const accountId = extractAccountIdFromSessionKey(sessionKey);
180
+ const state = accountId ? getParallAccountState(accountId) : undefined;
181
+ // Final touch on completion — covers the reply window right after a
182
+ // long tool call ends.
183
+ state?.touchRuntimeActivity?.(sessionKey);
184
+ };
185
+
143
186
  // before_tool_call -> (1) update dispatch metrics, (2) await step creation, (3) ENV injection
144
187
  // OpenClaw tool calls bypass the RuntimeEvent stream (they go through hooks, not the
145
188
  // gateway-base for-await loop), so dispatch metrics must be updated here as well.
@@ -147,6 +190,9 @@ export function registerParallHooks(api: OpenClawPluginApi) {
147
190
  const sessionKey = ctx.sessionKey;
148
191
  if (sessionKey) {
149
192
  recordToolCall(sessionKey);
193
+ // Every tool (not just exec) keeps the ledger lane alive for its
194
+ // whole lifetime — see laneRenewals above.
195
+ beginToolRenewal(sessionKey);
150
196
  if (event.toolName === 'exec' && event.toolCallId) {
151
197
  const command = (event.params as Record<string, unknown>)?.command as string | undefined;
152
198
  if (isParallSendCommand(command)) {
@@ -228,6 +274,19 @@ export function registerParallHooks(api: OpenClawPluginApi) {
228
274
  }
229
275
  if (triggerMsgId) injectedEnv.PRLL_TRIGGER_MESSAGE_ID = triggerMsgId;
230
276
  if (state.wikiMountRoot) injectedEnv.PRLL_WIKI_MOUNT_ROOT = state.wikiMountRoot;
277
+ // Dispatch-ledger contract: the CLI keys into this directory by send
278
+ // target to bind dispatch_lane + the reply:<dsp> effect key (idempotent
279
+ // replies). Set only when the gateway runs the ledger.
280
+ if (state.dispatchContextDir) injectedEnv.PRLL_CONTEXT_DIR = state.dispatchContextDir;
281
+ // Per-session context file — the CLI's TYPED binding (parall tasks
282
+ // update → task_update:<dsp> effect) reads lane/event/task from here.
283
+ if (state.contextFilePathForSession) {
284
+ injectedEnv.PRLL_CONTEXT_FILE = state.contextFilePathForSession(sessionKey);
285
+ }
286
+ // Long tool calls bypass the RuntimeEvent stream — forward activity so
287
+ // the ledger lane lease renews (a >TTL exec would otherwise be dethroned
288
+ // mid-turn and the eventual CLI reply would die with STALE_LANE).
289
+ state.touchRuntimeActivity?.(sessionKey);
231
290
 
232
291
  // OpenClaw context (upstream doesn't inject these into exec env yet)
233
292
  injectedEnv.OPENCLAW_SESSION_KEY = sessionKey;
@@ -247,6 +306,7 @@ export function registerParallHooks(api: OpenClawPluginApi) {
247
306
 
248
307
  // after_tool_call -> track CLI outcomes + send tool_result step to AgentSession
249
308
  api.on('after_tool_call', async (event, ctx) => {
309
+ if (ctx.sessionKey) endToolRenewal(ctx.sessionKey);
250
310
  if (ctx.sessionKey && event.toolCallId && pendingSendCalls.delete(event.toolCallId)) {
251
311
  recordMessageSend(ctx.sessionKey, !event.error);
252
312
  }
package/src/runtime.ts CHANGED
@@ -32,6 +32,23 @@ export type ParallAccountState = {
32
32
  wikiMountRoot?: string;
33
33
  ws?: ParallWs;
34
34
  orchestratorSessionKey?: string;
35
+ /**
36
+ * Per-lane dispatch context dir (PRLL_CONTEXT_DIR contract) — set when the
37
+ * gateway runs the dispatch ledger; hooks inject it into exec env so the
38
+ * CLI binds replies to their dispatch lane.
39
+ */
40
+ dispatchContextDir?: string;
41
+ /**
42
+ * Per-session dispatch context file resolver (PRLL_CONTEXT_FILE contract)
43
+ * — the CLI's typed dispatch binding (task_update effects) reads from it.
44
+ */
45
+ contextFilePathForSession?: (sessionKey: string) => string;
46
+ /**
47
+ * Lease-renewal bridge into the gateway: openclaw tool activity flows
48
+ * through hooks (not the RuntimeEvent stream), so hooks must forward it or
49
+ * a long exec outlives the lane lease.
50
+ */
51
+ touchRuntimeActivity?: (sessionKey: string) => void;
35
52
  [key: string]: unknown;
36
53
  };
37
54