@parall/parall 1.42.1 → 1.44.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/config-manager.d.ts +2 -0
- package/dist/config-manager.d.ts.map +1 -1
- package/dist/config-manager.js +27 -0
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +42 -2
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +65 -0
- package/dist/index.bundle.mjs +1957 -1559
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.d.ts.map +1 -1
- package/package.json +3 -3
- package/skills/parall-platform/SKILL.md +24 -5
- package/src/config-manager.ts +39 -0
- package/src/gateway.ts +47 -1
- package/src/hooks.ts +67 -0
- package/src/runtime.ts +17 -0
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;
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "1.44.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.
|
|
20
|
-
"@parall/sdk": "1.
|
|
19
|
+
"@parall/agent-core": "1.44.0",
|
|
20
|
+
"@parall/sdk": "1.44.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^22.0.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: parall-platform
|
|
3
|
-
description: "Parall platform queries and lightweight agent provisioning: list org members, agents, chats, read message history, check identity,
|
|
3
|
+
description: "Parall platform queries and lightweight agent provisioning: list org members, agents, chats, read message history, check identity, create another agent, or walk the prll:// reference graph (resolve URIs, backlinks, multi-hop graph). Use when: user asks about org members, who's online, chat history, agent list, creating an agent, identity/auth questions, or you need to find what an entity is connected to / who references it."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Parall Platform
|
|
@@ -81,11 +81,11 @@ settled questions or repeat known mistakes. This searches live org data
|
|
|
81
81
|
can see.
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
# Semantic + keyword search across messages, tasks, and
|
|
84
|
+
# Semantic + keyword search across messages, tasks, wiki, and comments
|
|
85
85
|
parall search "auth v5 upgrade"
|
|
86
86
|
|
|
87
|
-
# Restrict entity types (m=message, t=task, w=wiki). --channel
|
|
88
|
-
# MESSAGE hits to one chat (tasks/wiki are unaffected
|
|
87
|
+
# Restrict entity types (m=message, t=task, w=wiki, c=comment). --channel
|
|
88
|
+
# narrows the MESSAGE hits to one chat (tasks/wiki/comments are unaffected).
|
|
89
89
|
parall search "auth v5 upgrade" --types m,w --channel prll://cht_eng
|
|
90
90
|
|
|
91
91
|
# Time-box to recent activity (RFC3339 or YYYY-MM-DD). Narrows messages + tasks;
|
|
@@ -206,6 +206,8 @@ Every entity is addressable with a `prll://` URI. Common prefixes you'll see in
|
|
|
206
206
|
| `prll://usr_` | User (human or agent) | parall-platform |
|
|
207
207
|
| `prll://cht_` | Chat | parall-platform |
|
|
208
208
|
| `prll://msg_` | Message | parall-platform |
|
|
209
|
+
| `prll://cmt_` | Comment (on tasks, wiki pages, changesets) | by target: task comment → parall-tasks, wiki/changeset comment → parall-wiki |
|
|
210
|
+
| `prll://ase_` | Agent session | parall-platform |
|
|
209
211
|
| `prll://tsk_` | Task | parall-tasks |
|
|
210
212
|
| `prll://prj_` | Project | parall-tasks |
|
|
211
213
|
| `prll://sch_` | Schedule (time trigger) | parall-schedules |
|
|
@@ -241,6 +243,23 @@ parall refs graph prll://tsk_xxx --depth 2
|
|
|
241
243
|
|
|
242
244
|
`refs graph` traverses both directions (inbound + outbound) and returns `nodes`
|
|
243
245
|
and `edges` with each node's hop `depth`. `truncated: true` means a size cap clipped
|
|
244
|
-
the result — narrow it with a smaller `--depth`.
|
|
246
|
+
the result — narrow it with a smaller `--depth`. Edges carry `context` — the
|
|
247
|
+
author's annotation from `[context](prll://...)` — telling you *why* two
|
|
248
|
+
entities are linked, not just that they are.
|
|
249
|
+
|
|
250
|
+
The graph returns bare node URIs (no titles). The usual two-step: `refs graph`
|
|
251
|
+
for topology, then batch-`refs resolve` the node URIs you care about for
|
|
252
|
+
titles/status. If graph rejects your URI with a path/anchor error, strip it to
|
|
253
|
+
the entity root (`prll://wik_xxx/docs/a.md` → `prll://wik_xxx`) and re-query —
|
|
254
|
+
but note this WIDENS the query to the whole entity, not that one file: the
|
|
255
|
+
graph seeds from the wiki id, so a specific file's outbound links may sit
|
|
256
|
+
deeper in the result (or past the size caps). For refs pointing AT one file
|
|
257
|
+
(inbound), `refs backlinks` on the full file URI is precise. There is no
|
|
258
|
+
precise query for one file's OUTBOUND edges today — the widened root graph is
|
|
259
|
+
best-effort for those, or read the file itself for its `prll://` links.
|
|
260
|
+
Wiki-file nodes inside a graph *result* do legitimately carry paths.
|
|
261
|
+
|
|
262
|
+
`refs backlinks` items include a `snippet` of the referencing content — often
|
|
263
|
+
enough to judge relevance without fetching the source entity.
|
|
245
264
|
|
|
246
265
|
CLI success output is JSON. Errors print a JSON line (`{"error","status","code",...}`) and, on a `PERMISSION_DENIED`, may add a plain-text `Request approval:` line — read both.
|
package/src/config-manager.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AgentCapability,
|
|
3
|
+
extractCapabilities,
|
|
4
|
+
materializeChannelCapabilities,
|
|
5
|
+
} from '@parall/agent-core';
|
|
1
6
|
import type { ParallClient } from '@parall/sdk';
|
|
2
7
|
import type { PlatformConfigResponse } from '@parall/sdk';
|
|
3
8
|
import * as fs from 'node:fs';
|
|
@@ -9,6 +14,36 @@ interface CachedPlatformConfig {
|
|
|
9
14
|
fetchedAt: string;
|
|
10
15
|
}
|
|
11
16
|
|
|
17
|
+
// Channel-capability snapshot for the OTHER consumers of capability state:
|
|
18
|
+
// the before_prompt_build hook (fragments into the system prompt — evaluated
|
|
19
|
+
// every prompt build, so a refresh reaches the very next turn without any
|
|
20
|
+
// restart) and the gateway's getCapabilityKeys (hint routing). Updated by
|
|
21
|
+
// applyChannelCapabilitySnapshot alongside every config apply, so the shim
|
|
22
|
+
// materialization and the declaration can never diverge within a refresh.
|
|
23
|
+
// Process-global BY THE SAME assumption as runtime.ts's agentIdentity:
|
|
24
|
+
// hosted agents run one-agent-per-pod, so a single snapshot is correct. A
|
|
25
|
+
// future multi-account host would need this keyed per account together with
|
|
26
|
+
// that identity global — do not fix one without the other.
|
|
27
|
+
let currentCapabilities: AgentCapability[] = [];
|
|
28
|
+
|
|
29
|
+
export function getChannelCapabilityFragments(): string[] {
|
|
30
|
+
return currentCapabilities.map((c) => c.fragment);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getChannelCapabilityKeys(): string[] {
|
|
34
|
+
return currentCapabilities.map((c) => c.key);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function applyChannelCapabilitySnapshot(
|
|
38
|
+
stateDir: string,
|
|
39
|
+
config: Record<string, unknown>,
|
|
40
|
+
log?: { info: (msg: string) => void; warn: (msg: string) => void; error: (msg: string) => void },
|
|
41
|
+
): void {
|
|
42
|
+
const caps = extractCapabilities(config);
|
|
43
|
+
materializeChannelCapabilities(stateDir, caps, log);
|
|
44
|
+
currentCapabilities = caps;
|
|
45
|
+
}
|
|
46
|
+
|
|
12
47
|
interface ConfigManagerOpts {
|
|
13
48
|
client: ParallClient;
|
|
14
49
|
stateDir: string;
|
|
@@ -204,6 +239,7 @@ export async function fetchAndApplyPlatformConfig(opts: ConfigManagerOpts): Prom
|
|
|
204
239
|
`platform config fetch failed, using cached version ${cached.version}: ${String(err)}`,
|
|
205
240
|
);
|
|
206
241
|
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
242
|
+
applyChannelCapabilitySnapshot(stateDir, cached.config, log);
|
|
207
243
|
return;
|
|
208
244
|
}
|
|
209
245
|
// No cache and fetch fails — degrade gracefully
|
|
@@ -216,6 +252,7 @@ export async function fetchAndApplyPlatformConfig(opts: ConfigManagerOpts): Prom
|
|
|
216
252
|
log?.info('platform config unchanged (304)');
|
|
217
253
|
if (cached) {
|
|
218
254
|
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
255
|
+
applyChannelCapabilitySnapshot(stateDir, cached.config, log);
|
|
219
256
|
}
|
|
220
257
|
return;
|
|
221
258
|
}
|
|
@@ -228,6 +265,7 @@ export async function fetchAndApplyPlatformConfig(opts: ConfigManagerOpts): Prom
|
|
|
228
265
|
);
|
|
229
266
|
if (cached) {
|
|
230
267
|
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
268
|
+
applyChannelCapabilitySnapshot(stateDir, cached.config, log);
|
|
231
269
|
}
|
|
232
270
|
return;
|
|
233
271
|
}
|
|
@@ -235,5 +273,6 @@ export async function fetchAndApplyPlatformConfig(opts: ConfigManagerOpts): Prom
|
|
|
235
273
|
// 5. Newer config received — save cache and apply
|
|
236
274
|
log?.info(`platform config updated to version ${fresh.version}`);
|
|
237
275
|
saveCachedConfig(stateDir, fresh);
|
|
276
|
+
applyChannelCapabilitySnapshot(stateDir, fresh.config, log);
|
|
238
277
|
applyToOpenClawConfig(configPath, fresh.config, credentials);
|
|
239
278
|
}
|
package/src/gateway.ts
CHANGED
|
@@ -5,6 +5,8 @@ import type { ChannelPlugin } from 'openclaw/plugin-sdk/core';
|
|
|
5
5
|
type ChannelGatewayAdapter<T = unknown> = NonNullable<ChannelPlugin<T>['gateway']>;
|
|
6
6
|
import {
|
|
7
7
|
ParallAgentGateway,
|
|
8
|
+
capabilityBinDir,
|
|
9
|
+
dispatchLaneContextDir,
|
|
8
10
|
parseShutdownDeadlineMs,
|
|
9
11
|
parseForkDeadlineMs,
|
|
10
12
|
parseDispatchDeadlineMs,
|
|
@@ -35,7 +37,7 @@ import {
|
|
|
35
37
|
} from './runtime.js';
|
|
36
38
|
import { buildOrchestratorSessionKey } from './session.js';
|
|
37
39
|
import type { ResolvedParallAccount } from './types.js';
|
|
38
|
-
import { fetchAndApplyPlatformConfig } from './config-manager.js';
|
|
40
|
+
import { fetchAndApplyPlatformConfig, getChannelCapabilityKeys } from './config-manager.js';
|
|
39
41
|
import { startWikiHelper } from './wiki-helper.js';
|
|
40
42
|
import { SessionManager } from './oc-session.js';
|
|
41
43
|
import {
|
|
@@ -46,6 +48,16 @@ import {
|
|
|
46
48
|
resolveTranscriptFile,
|
|
47
49
|
} from './fork.js';
|
|
48
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
|
+
|
|
49
61
|
function resolveWsUrl(account: ResolvedParallAccount): string {
|
|
50
62
|
if (account.config.ws_url) return account.config.ws_url;
|
|
51
63
|
const base = account.config.parall_url.replace(/\/$/, '');
|
|
@@ -406,6 +418,17 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
|
|
|
406
418
|
process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || '/data', '.openclaw');
|
|
407
419
|
const openclawConfigPath = path.join(stateDir, 'openclaw.json');
|
|
408
420
|
|
|
421
|
+
// Channel-capability shims resolve ahead of any globally-installed CLI
|
|
422
|
+
// of the same name. The plugin runs INSIDE the openclaw process, whose
|
|
423
|
+
// env every exec-tool child inherits — one idempotent prepend here
|
|
424
|
+
// covers the agent's shell commands for the process lifetime (the
|
|
425
|
+
// DIRECTORY is constant; its content tracks capability grants).
|
|
426
|
+
const shimDir = capabilityBinDir(stateDir);
|
|
427
|
+
const currentPath = process.env.PATH ?? '';
|
|
428
|
+
if (!currentPath.split(path.delimiter).includes(shimDir)) {
|
|
429
|
+
process.env.PATH = currentPath ? `${shimDir}${path.delimiter}${currentPath}` : shimDir;
|
|
430
|
+
}
|
|
431
|
+
|
|
409
432
|
const configManagerOpts = {
|
|
410
433
|
client,
|
|
411
434
|
stateDir,
|
|
@@ -470,7 +493,23 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
|
|
|
470
493
|
runtimeKey: orchestratorKey,
|
|
471
494
|
runtimeRef: { hostname: os.hostname(), pid: process.pid },
|
|
472
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),
|
|
473
509
|
log: otelLog,
|
|
510
|
+
// Live capability view for hint routing: the channel reply hint
|
|
511
|
+
// points at the vendor CLI only while the grant is active.
|
|
512
|
+
getCapabilityKeys: getChannelCapabilityKeys,
|
|
474
513
|
shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
|
|
475
514
|
forkDeadlineMs: parseForkDeadlineMs(process.env.PRLL_FORK_DEADLINE_MS),
|
|
476
515
|
dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
|
|
@@ -491,6 +530,13 @@ export const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount> = {
|
|
|
491
530
|
wikiMountRoot,
|
|
492
531
|
ws,
|
|
493
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),
|
|
494
540
|
});
|
|
495
541
|
},
|
|
496
542
|
onSessionBinding: async ({ sessionKey, agentSessionId }) => {
|
package/src/hooks.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
recordMessageSend,
|
|
10
10
|
recordNoReply,
|
|
11
11
|
} from '@parall/agent-core';
|
|
12
|
+
import { getChannelCapabilityFragments } from './config-manager.js';
|
|
12
13
|
import { extractAccountIdFromSessionKey } from './session.js';
|
|
13
14
|
import {
|
|
14
15
|
clearDispatchGroupKey,
|
|
@@ -127,12 +128,61 @@ export function registerParallHooks(api: OpenClawPluginApi) {
|
|
|
127
128
|
PRLL_CHANNEL_CONTEXT,
|
|
128
129
|
PRLL_BEHAVIOR,
|
|
129
130
|
PRLL_REFERENCE_GUIDE,
|
|
131
|
+
// Channel-capability declarations (platform-config
|
|
132
|
+
// agents.capabilities[]). Evaluated on EVERY prompt build, so a
|
|
133
|
+
// capability grant/revocation reaches the next turn without any
|
|
134
|
+
// restart — the openclaw analogue of the CLI bridges' prompt-file
|
|
135
|
+
// rewrite + respawn.
|
|
136
|
+
...getChannelCapabilityFragments(),
|
|
130
137
|
].join('\n\n'),
|
|
131
138
|
};
|
|
132
139
|
});
|
|
133
140
|
|
|
134
141
|
const pendingSendCalls = new Map<string, boolean>();
|
|
135
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
|
+
|
|
136
186
|
// before_tool_call -> (1) update dispatch metrics, (2) await step creation, (3) ENV injection
|
|
137
187
|
// OpenClaw tool calls bypass the RuntimeEvent stream (they go through hooks, not the
|
|
138
188
|
// gateway-base for-await loop), so dispatch metrics must be updated here as well.
|
|
@@ -140,6 +190,9 @@ export function registerParallHooks(api: OpenClawPluginApi) {
|
|
|
140
190
|
const sessionKey = ctx.sessionKey;
|
|
141
191
|
if (sessionKey) {
|
|
142
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);
|
|
143
196
|
if (event.toolName === 'exec' && event.toolCallId) {
|
|
144
197
|
const command = (event.params as Record<string, unknown>)?.command as string | undefined;
|
|
145
198
|
if (isParallSendCommand(command)) {
|
|
@@ -221,6 +274,19 @@ export function registerParallHooks(api: OpenClawPluginApi) {
|
|
|
221
274
|
}
|
|
222
275
|
if (triggerMsgId) injectedEnv.PRLL_TRIGGER_MESSAGE_ID = triggerMsgId;
|
|
223
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);
|
|
224
290
|
|
|
225
291
|
// OpenClaw context (upstream doesn't inject these into exec env yet)
|
|
226
292
|
injectedEnv.OPENCLAW_SESSION_KEY = sessionKey;
|
|
@@ -240,6 +306,7 @@ export function registerParallHooks(api: OpenClawPluginApi) {
|
|
|
240
306
|
|
|
241
307
|
// after_tool_call -> track CLI outcomes + send tool_result step to AgentSession
|
|
242
308
|
api.on('after_tool_call', async (event, ctx) => {
|
|
309
|
+
if (ctx.sessionKey) endToolRenewal(ctx.sessionKey);
|
|
243
310
|
if (ctx.sessionKey && event.toolCallId && pendingSendCalls.delete(event.toolCallId)) {
|
|
244
311
|
recordMessageSend(ctx.sessionKey, !event.error);
|
|
245
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
|
|