@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.
- package/dist/accounts.d.ts +1 -1
- package/dist/accounts.js +4 -4
- package/dist/channel.d.ts +2 -2
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +10 -10
- package/dist/config-manager.d.ts +1 -1
- package/dist/config-manager.d.ts.map +1 -1
- package/dist/config-manager.js +70 -30
- package/dist/fork.d.ts.map +1 -1
- package/dist/fork.js +17 -17
- package/dist/gateway.d.ts +3 -3
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +214 -171
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +52 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/oc-session.d.ts.map +1 -1
- package/dist/oc-session.js +35 -27
- package/dist/outbound.d.ts +2 -2
- package/dist/outbound.d.ts.map +1 -1
- package/dist/outbound.js +13 -14
- package/dist/routing.d.ts +2 -2
- package/dist/routing.js +1 -1
- package/dist/runtime.d.ts +5 -5
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -2
- package/dist/session.js +4 -4
- package/dist/wiki-helper.d.ts.map +1 -1
- package/dist/wiki-helper.js +27 -27
- package/openclaw.plugin.json +4 -1
- package/package.json +3 -3
- package/skills/parall-clips/SKILL.md +48 -0
- package/src/accounts.ts +5 -5
- package/src/channel.ts +15 -14
- package/src/config-manager.ts +79 -34
- package/src/fork.ts +26 -22
- package/src/gateway.ts +245 -175
- package/src/hooks.ts +109 -50
- package/src/index.ts +8 -8
- package/src/oc-session.ts +43 -35
- package/src/outbound.ts +18 -17
- package/src/routing.ts +2 -2
- package/src/runtime.ts +11 -7
- package/src/session.ts +4 -4
- package/src/wiki-helper.ts +47 -34
package/src/hooks.ts
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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(
|
|
14
|
-
if (targetId.startsWith(
|
|
15
|
-
if (targetId.startsWith(
|
|
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 =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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:
|
|
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(
|
|
123
|
+
api.on('before_prompt_build', () => {
|
|
105
124
|
return {
|
|
106
|
-
appendSystemContext: [
|
|
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
|
-
|
|
111
|
-
|
|
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(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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 !==
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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(
|
|
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
|
|
2
|
-
import { emptyPluginConfigSchema } from
|
|
3
|
-
import { parallPlugin } from
|
|
4
|
-
import { setParallRuntime } from
|
|
5
|
-
import { registerParallHooks } from
|
|
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:
|
|
9
|
-
name:
|
|
10
|
-
description:
|
|
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
|
|
10
|
-
import {
|
|
11
|
-
|
|
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:
|
|
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,
|
|
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 !==
|
|
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 ===
|
|
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 ===
|
|
88
|
+
if (e.type === 'compaction') {
|
|
94
89
|
const comp = e as Record<string, unknown>;
|
|
95
|
-
if (typeof comp.firstKeptEntryIndex ===
|
|
90
|
+
if (typeof comp.firstKeptEntryIndex === 'number') {
|
|
96
91
|
const target = entries[comp.firstKeptEntryIndex as number] as SessionEntry | undefined;
|
|
97
|
-
if (target && target.type !==
|
|
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 ===
|
|
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 (
|
|
114
|
-
|
|
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 ===
|
|
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 ===
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
{
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
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 !==
|
|
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:
|
|
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:
|
|
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 ===
|
|
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 ===
|
|
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
|
|
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[
|
|
5
|
-
import { resolveParallAccount } from
|
|
6
|
-
import { getParallAccountState } from
|
|
7
|
-
import { ParallClient } from
|
|
8
|
-
import type { SendMessageRequest } from
|
|
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:
|
|
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(
|
|
18
|
-
if (!account.configured)
|
|
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 =
|
|
22
|
-
|
|
23
|
-
|
|
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:
|
|
32
|
+
message_type: 'text',
|
|
32
33
|
content: { text },
|
|
33
34
|
};
|
|
34
35
|
const msg = await client.sendMessage(orgId, chatId, req);
|
|
35
|
-
return { channel:
|
|
36
|
+
return { channel: 'parall', messageId: msg.id, channelId: chatId };
|
|
36
37
|
},
|
|
37
38
|
};
|
package/src/routing.ts
CHANGED
package/src/runtime.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { PluginRuntime } from
|
|
2
|
-
import type { ParallClient, ParallWs } from
|
|
3
|
-
import type { AgentIdentity } from
|
|
4
|
-
export type { ForkResult, DispatchState, ParallEvent } from
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
30
|
+
return parts.slice(2).join(':') || undefined;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/** Build the single orchestrator session key for all Parall events. */
|