@parall/parall 1.26.1 → 1.26.2

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.
@@ -0,0 +1,330 @@
1
+ import { ParallAgentGateway, parseShutdownDeadlineMs, } from "@parall/agent-core";
2
+ import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
3
+ import * as crypto from "node:crypto";
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ import { resolveParallAccount } from "./accounts.js";
7
+ import { getParallRuntime, removeParallAccountState, setAgentIdentity, setDispatchGroupKey, setParallAccountState, } from "./runtime.js";
8
+ import { buildOrchestratorSessionKey } from "./session.js";
9
+ import { fetchAndApplyPlatformConfig } from "./config-manager.js";
10
+ import { startWikiHelper } from "./wiki-helper.js";
11
+ import { SessionManager } from "@mariozechner/pi-coding-agent";
12
+ import { cleanupForkSession, forkOrchestratorSession, resolveTranscriptFile } from "./fork.js";
13
+ function resolveWsUrl(account) {
14
+ if (account.config.ws_url)
15
+ return account.config.ws_url;
16
+ const base = account.config.parall_url.replace(/\/$/, "");
17
+ const wsBase = base.replace(/^http/, "ws");
18
+ return `${wsBase}/ws`;
19
+ }
20
+ async function getAgentMeWithLegacyFallback(client, orgId) {
21
+ try {
22
+ return await client.getAgentMe(orgId);
23
+ }
24
+ catch (err) {
25
+ if (!(err instanceof ApiError && err.status === 404)) {
26
+ throw err;
27
+ }
28
+ const user = await client.getMe();
29
+ return { ...user, agent_profile: null };
30
+ }
31
+ }
32
+ function buildInboundCtx(core, accountId, event, sessionKey, bodyForAgent, earlierEvents = []) {
33
+ const inboundHistory = earlierEvents.length > 0 ? buildInboundHistory(earlierEvents) : undefined;
34
+ return core.channel.reply.finalizeInboundContext({
35
+ Body: event.body,
36
+ BodyForAgent: bodyForAgent,
37
+ RawBody: event.body,
38
+ CommandBody: event.body,
39
+ // Attachment metadata is now in event.attachments (structured), not mediaFields
40
+ ...(inboundHistory?.length ? { InboundHistory: inboundHistory } : {}),
41
+ From: `parall:${event.senderId}`,
42
+ To: `parall:orchestrator`,
43
+ SessionKey: sessionKey,
44
+ AccountId: accountId,
45
+ ChatType: event.targetType ?? "unknown",
46
+ SenderName: event.senderName,
47
+ SenderId: event.senderId,
48
+ Provider: "parall",
49
+ Surface: "parall",
50
+ MessageSid: event.messageId,
51
+ Timestamp: Date.now(),
52
+ CommandAuthorized: true,
53
+ OriginatingChannel: "parall",
54
+ OriginatingTo: `parall:orchestrator`,
55
+ });
56
+ }
57
+ function buildInboundHistory(events) {
58
+ return events.map((event) => {
59
+ const meta = [];
60
+ meta.push(`[Message ID: ${event.messageId}]`);
61
+ if (event.noReply)
62
+ meta.push(`[Hint: no_reply]`);
63
+ if (event.threadRootId)
64
+ meta.push(`[Thread: ${event.threadRootId}]`);
65
+ if (event.attachments?.length) {
66
+ for (const att of event.attachments) {
67
+ const safeMime = att.mimeType.replace(/[\r\n[\]|]/g, " ").trim();
68
+ meta.push(`[Attachment: prll://${att.id} | ${safeMime}]`);
69
+ }
70
+ }
71
+ return {
72
+ sender: event.senderName,
73
+ body: meta.length > 0 ? `${meta.join(" ")}\n${event.body}` : event.body,
74
+ };
75
+ });
76
+ }
77
+ function createRuntimeEventStream() {
78
+ const queue = [];
79
+ const waiters = [];
80
+ let done = false;
81
+ let failure;
82
+ function flush() {
83
+ while (waiters.length > 0) {
84
+ if (failure) {
85
+ waiters.shift().reject(failure);
86
+ continue;
87
+ }
88
+ if (queue.length > 0) {
89
+ waiters.shift().resolve(queue.shift());
90
+ continue;
91
+ }
92
+ if (done) {
93
+ waiters.shift().resolve(undefined);
94
+ continue;
95
+ }
96
+ break;
97
+ }
98
+ }
99
+ return {
100
+ push(event) {
101
+ queue.push(event);
102
+ flush();
103
+ },
104
+ end() {
105
+ done = true;
106
+ flush();
107
+ },
108
+ fail(err) {
109
+ failure = err;
110
+ flush();
111
+ },
112
+ async *stream() {
113
+ while (true) {
114
+ if (queue.length > 0) {
115
+ yield queue.shift();
116
+ continue;
117
+ }
118
+ if (failure)
119
+ throw failure;
120
+ if (done)
121
+ return;
122
+ const next = await new Promise((resolve, reject) => {
123
+ waiters.push({ resolve, reject });
124
+ });
125
+ if (!next)
126
+ return;
127
+ yield next;
128
+ }
129
+ },
130
+ };
131
+ }
132
+ function createOpenClawDispatchAdapter(opts) {
133
+ return {
134
+ async *dispatch({ event, earlierEvents = [], bodyForAgent, sessionKey }) {
135
+ const stream = createRuntimeEventStream();
136
+ let reasoningBuffer = "";
137
+ let turnGroupKey = "";
138
+ const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
139
+ ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, bodyForAgent, earlierEvents),
140
+ cfg: opts.cfg,
141
+ dispatcherOptions: {
142
+ deliver: async (payload) => {
143
+ const replyText = payload.text?.trim();
144
+ if (!replyText)
145
+ return;
146
+ stream.push({
147
+ type: "text",
148
+ text: replyText,
149
+ project: false,
150
+ groupKey: turnGroupKey || undefined,
151
+ });
152
+ },
153
+ onReplyStart: () => {
154
+ turnGroupKey = crypto.randomUUID();
155
+ setDispatchGroupKey(sessionKey, turnGroupKey);
156
+ },
157
+ },
158
+ replyOptions: {
159
+ onReasoningStream: (payload) => {
160
+ reasoningBuffer += payload.text ?? "";
161
+ },
162
+ onReasoningEnd: async () => {
163
+ if (!reasoningBuffer)
164
+ return;
165
+ stream.push({
166
+ type: "thinking",
167
+ text: reasoningBuffer,
168
+ groupKey: turnGroupKey || undefined,
169
+ });
170
+ reasoningBuffer = "";
171
+ },
172
+ },
173
+ });
174
+ run.then(() => stream.end()).catch((err) => stream.fail(err));
175
+ yield* stream.stream();
176
+ },
177
+ getBranchPoint(sessionKey) {
178
+ const transcriptFile = resolveTranscriptFile(opts.sessionsDir, sessionKey);
179
+ if (!transcriptFile)
180
+ return undefined;
181
+ try {
182
+ const manager = SessionManager.open(transcriptFile);
183
+ return manager.getLeafId() ?? undefined;
184
+ }
185
+ catch {
186
+ return undefined;
187
+ }
188
+ },
189
+ forkSession({ sessionKey, preDispatchBranchPoint }) {
190
+ const transcriptFile = resolveTranscriptFile(opts.sessionsDir, sessionKey);
191
+ if (!transcriptFile)
192
+ return null;
193
+ return forkOrchestratorSession({
194
+ orchestratorSessionKey: sessionKey,
195
+ accountId: opts.accountId,
196
+ transcriptFile,
197
+ sessionsDir: opts.sessionsDir,
198
+ branchPointId: preDispatchBranchPoint,
199
+ });
200
+ },
201
+ cleanupFork({ fork }) {
202
+ if (typeof fork.sessionFile !== "string")
203
+ return;
204
+ cleanupForkSession({
205
+ sessionFile: fork.sessionFile,
206
+ sessionKey: fork.sessionKey,
207
+ sessionsDir: opts.sessionsDir,
208
+ });
209
+ },
210
+ };
211
+ }
212
+ export const parallGateway = {
213
+ startAccount: async (ctx) => {
214
+ const account = resolveParallAccount({ cfg: ctx.cfg, accountId: ctx.accountId });
215
+ if (!account.enabled)
216
+ return;
217
+ if (!account.configured)
218
+ throw new Error("Parall account is not configured: parall_url/api_key/org_id required");
219
+ const { config } = account;
220
+ const core = getParallRuntime();
221
+ const log = ctx.log;
222
+ const client = new ParallClient({
223
+ baseUrl: config.parall_url,
224
+ token: config.api_key,
225
+ swimlaneName: process.env.PRLL_SWIMLANE_NAME,
226
+ });
227
+ const me = await getAgentMeWithLegacyFallback(client, config.org_id);
228
+ const agentUserId = me.id;
229
+ setAgentIdentity({
230
+ userId: agentUserId,
231
+ displayName: me.display_name,
232
+ description: me.agent_profile?.description ?? undefined,
233
+ });
234
+ log?.info(`parall[${ctx.accountId}]: authenticated as ${me.display_name} (${agentUserId})`);
235
+ const stateDir = process.env.OPENCLAW_STATE_DIR
236
+ || path.join(process.env.HOME || "/data", ".openclaw");
237
+ const openclawConfigPath = path.join(stateDir, "openclaw.json");
238
+ const configManagerOpts = {
239
+ client,
240
+ stateDir,
241
+ configPath: openclawConfigPath,
242
+ credentials: { api_key: config.api_key, parall_url: config.parall_url },
243
+ log,
244
+ };
245
+ try {
246
+ await fetchAndApplyPlatformConfig(configManagerOpts);
247
+ }
248
+ catch (err) {
249
+ log?.warn(`parall[${ctx.accountId}]: platform config fetch failed: ${String(err)}`);
250
+ }
251
+ let stopWikiHelper = null;
252
+ let wikiMountRoot;
253
+ try {
254
+ const wikiHelper = await startWikiHelper({
255
+ accountId: ctx.accountId,
256
+ parallUrl: config.parall_url,
257
+ apiKey: config.api_key,
258
+ orgId: config.org_id,
259
+ agentId: agentUserId,
260
+ stateDir,
261
+ log,
262
+ });
263
+ wikiMountRoot = wikiHelper.mountRoot;
264
+ stopWikiHelper = wikiHelper.stop;
265
+ }
266
+ catch (err) {
267
+ log?.warn(`parall[${ctx.accountId}]: wiki helper startup failed: ${String(err)}`);
268
+ }
269
+ const wsUrl = resolveWsUrl(account);
270
+ const ws = new ParallWs({
271
+ getTicket: () => client.getWsTicket(),
272
+ wsUrl,
273
+ });
274
+ const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
275
+ const sessionsDir = path.join(stateDir, "agents", "main", "sessions");
276
+ const dispatchAdapter = createOpenClawDispatchAdapter({
277
+ core,
278
+ cfg: ctx.cfg,
279
+ accountId: ctx.accountId,
280
+ sessionsDir,
281
+ });
282
+ const gateway = new ParallAgentGateway({
283
+ accountId: ctx.accountId,
284
+ client,
285
+ ws,
286
+ connectionLabel: wsUrl,
287
+ config: {
288
+ parall_url: config.parall_url,
289
+ api_key: config.api_key,
290
+ org_id: config.org_id,
291
+ },
292
+ agentUserId,
293
+ runtimeType: "openclaw",
294
+ runtimeKey: orchestratorKey,
295
+ runtimeRef: { hostname: os.hostname(), pid: process.pid },
296
+ dispatchAdapter,
297
+ log,
298
+ shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
299
+ onConfigUpdate: async () => {
300
+ await fetchAndApplyPlatformConfig(configManagerOpts);
301
+ },
302
+ onSessionReady: async ({ activeSessionId }) => {
303
+ setParallAccountState(ctx.accountId, {
304
+ client,
305
+ apiUrl: config.parall_url,
306
+ apiKey: config.api_key,
307
+ orgId: config.org_id,
308
+ agentUserId,
309
+ activeSessionId,
310
+ wikiMountRoot,
311
+ ws,
312
+ orchestratorSessionKey: orchestratorKey,
313
+ });
314
+ },
315
+ onBeforeDisconnect: async () => {
316
+ stopWikiHelper?.();
317
+ removeParallAccountState(ctx.accountId);
318
+ },
319
+ });
320
+ try {
321
+ await gateway.run(ctx.abortSignal);
322
+ }
323
+ finally {
324
+ if (!ctx.abortSignal.aborted) {
325
+ stopWikiHelper?.();
326
+ removeParallAccountState(ctx.accountId);
327
+ }
328
+ }
329
+ },
330
+ };
@@ -0,0 +1,3 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ export declare function registerParallHooks(api: OpenClawPluginApi): void;
3
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AA6D7D,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,iBAAiB,QAiIzD"}
package/dist/hooks.js ADDED
@@ -0,0 +1,194 @@
1
+ import { PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, buildIdentity } from "@parall/agent-core";
2
+ import { extractAccountIdFromSessionKey } from "./session.js";
3
+ import { clearDispatchGroupKey, getAgentIdentity, getDispatchGroupKey, getDispatchMessageId, getParallAccountState, getSessionChatId } from "./runtime.js";
4
+ /**
5
+ * Map the session's stored target ID (whatever the current dispatch routed
6
+ * to — chat, task, or schedule) to the `target_type` field on agent_step.
7
+ * Keeps tool_call / tool_result attribution correct across event types.
8
+ */
9
+ function stepTargetType(targetId) {
10
+ if (targetId.startsWith("cht_"))
11
+ return "chat";
12
+ if (targetId.startsWith("tsk_"))
13
+ return "task";
14
+ if (targetId.startsWith("sch_"))
15
+ return "schedule";
16
+ return "";
17
+ }
18
+ /**
19
+ * Resolve the ParallClient + orgId + sessionId for a hook context.
20
+ * Returns undefined if the account/session is not active.
21
+ */
22
+ function resolveClientForHook(sessionKey) {
23
+ if (!sessionKey)
24
+ return undefined;
25
+ const chatId = getSessionChatId(sessionKey);
26
+ if (!chatId)
27
+ return undefined;
28
+ const accountId = extractAccountIdFromSessionKey(sessionKey);
29
+ if (!accountId)
30
+ return undefined;
31
+ const state = getParallAccountState(accountId);
32
+ if (!state)
33
+ return undefined;
34
+ return { ...state, chatId, sessionId: state.activeSessionId };
35
+ }
36
+ const PRLL_CHANNEL_CONTEXT = `## Parall Channel — Message Delivery
37
+
38
+ Events from the Parall channel arrive as \`[Event: ...]\` blocks.
39
+ Your text output is **not delivered to the user** — it is discarded silently.
40
+ To respond, you **must** use the Parall CLI via the exec (Bash) tool. If \`parall\` is not on PATH, use \`npx --yes @parall/cli@latest\` instead.
41
+
42
+ ### Event types and where to reply
43
+
44
+ - **\`[Event: message.new]\`** — includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat:
45
+
46
+ parall messages send prll://cht_xxx --text "Your reply here"
47
+
48
+ - **\`[Event: task.assigned]\` / \`[Event: task.comment.created]\`** — includes \`[Task: ... (prll://tsk_xxx)]\`. Act on the task; use task CLI subcommands (\`tasks update\`, \`tasks comment\`). See the \`parall-tasks\` skill.
49
+
50
+ - **\`[Event: schedule.fired]\`** — includes \`[Schedule: prll://sch_xxx]\` + \`[Run: prll://srn_xxx]\` + optional \`[Attached: prll://...]\`. The body is the fire-time \`description\` verbatim. It is an opaque trigger — decide what to do (reply into an attached chat, create a task, update a wiki file, or do nothing) based on the description. The CLI does not have a "reply to schedule" primitive; any follow-up goes through the appropriate target (chat / task / wiki).
51
+
52
+ Credentials are pre-configured in every Bash command — no setup needed.
53
+ Load the \`parall-platform\` skill for full CLI reference.
54
+ When an event has \`[Hint: no_reply]\`, do not reply to that specific event.
55
+ A dispatch may contain multiple events — only skip replies to the hinted ones.
56
+
57
+ To silence a turn without running any \`messages send\` / \`dm\`, call:
58
+
59
+ parall no-reply [--reason "..."]
60
+
61
+ Do NOT emit polite acknowledgements like "No response needed" via \`messages send\` — they become real messages and start agent-to-agent loops.`;
62
+ export function registerParallHooks(api) {
63
+ const log = api.logger;
64
+ // Append Parall identity + channel context + behavior charter + reference guide
65
+ // AFTER workspace files so it takes precedence. Order matters: identity anchors
66
+ // who the agent is, channel context is a hard interface constraint, behavior
67
+ // sets working principles, reference guide is the URI lookup reference.
68
+ // Identity is resolved lazily from runtime state — not available at plugin
69
+ // registration time, only after the gateway authenticates via getMe().
70
+ // Uses process-global identity because hosted agents run one-agent-per-pod;
71
+ // before_prompt_build doesn't receive session context anyway.
72
+ api.on("before_prompt_build", () => {
73
+ return {
74
+ appendSystemContext: [buildIdentity(getAgentIdentity()), PRLL_CHANNEL_CONTEXT, PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE].join("\n\n"),
75
+ };
76
+ });
77
+ // before_tool_call -> (1) await step creation to get step ID, (2) ENV injection for exec tool
78
+ api.on("before_tool_call", async (event, ctx) => {
79
+ const sessionKey = ctx.sessionKey;
80
+ // (1) Create tool_call step and await to get step ID
81
+ let stepId;
82
+ const resolved = resolveClientForHook(sessionKey);
83
+ if (resolved?.sessionId && event.toolCallId) {
84
+ const groupKey = sessionKey ? getDispatchGroupKey(sessionKey) : undefined;
85
+ try {
86
+ const step = await resolved.client.createAgentStep(resolved.orgId, resolved.agentUserId, resolved.sessionId, {
87
+ step_type: "tool_call",
88
+ target_type: stepTargetType(resolved.chatId),
89
+ target_id: resolved.chatId || undefined,
90
+ content: {
91
+ call_id: event.toolCallId,
92
+ tool_name: event.toolName,
93
+ tool_input: event.params ?? {},
94
+ status: "running",
95
+ started_at: new Date().toISOString(),
96
+ },
97
+ group_key: groupKey,
98
+ runtime_key: event.toolCallId,
99
+ });
100
+ stepId = step.id;
101
+ }
102
+ catch (err) {
103
+ log?.warn(`parall hook before_tool_call failed: ${String(err)}`);
104
+ }
105
+ }
106
+ // (2) ENV injection — only for the exec (Bash) tool
107
+ if (event.toolName !== "exec")
108
+ return;
109
+ if (!sessionKey)
110
+ return;
111
+ const accountId = extractAccountIdFromSessionKey(sessionKey);
112
+ if (!accountId)
113
+ return;
114
+ const state = getParallAccountState(accountId);
115
+ if (!state)
116
+ return;
117
+ // Dynamic per-dispatch context (changes each dispatch)
118
+ const chatId = getSessionChatId(sessionKey);
119
+ const triggerMsgId = getDispatchMessageId(sessionKey);
120
+ const injectedEnv = {};
121
+ injectedEnv.PRLL_API_URL = state.apiUrl;
122
+ injectedEnv.PRLL_API_KEY = state.apiKey;
123
+ injectedEnv.PRLL_ORG_ID = state.orgId;
124
+ injectedEnv.PRLL_AGENT_ID = state.agentUserId;
125
+ if (state.activeSessionId)
126
+ injectedEnv.PRLL_SESSION_ID = state.activeSessionId;
127
+ if (stepId)
128
+ injectedEnv.PRLL_STEP_ID = stepId;
129
+ if (chatId) {
130
+ if (chatId.startsWith("sch_")) {
131
+ // Schedule-triggered dispatch: expose PRLL_SCHEDULE_ID so the agent
132
+ // can resolve the schedule; deliberately do NOT set PRLL_CHAT_ID,
133
+ // because schedule events have no chat to reply into, and leaving
134
+ // chatId=sch_* would mislead scripts into `messages send
135
+ // prll://sch_*` (a non-existent CLI path). The run snapshot is
136
+ // available via PRLL_TRIGGER_MESSAGE_ID (srn_*) for agents that
137
+ // want to re-fetch fire-time content.
138
+ injectedEnv.PRLL_SCHEDULE_ID = chatId;
139
+ }
140
+ else {
141
+ // Chat and task dispatches continue to populate PRLL_CHAT_ID for
142
+ // backward compatibility with existing agent scripts.
143
+ injectedEnv.PRLL_CHAT_ID = chatId;
144
+ }
145
+ }
146
+ if (triggerMsgId)
147
+ injectedEnv.PRLL_TRIGGER_MESSAGE_ID = triggerMsgId;
148
+ if (state.wikiMountRoot)
149
+ injectedEnv.PRLL_WIKI_MOUNT_ROOT = state.wikiMountRoot;
150
+ // OpenClaw context (upstream doesn't inject these into exec env yet)
151
+ injectedEnv.OPENCLAW_SESSION_KEY = sessionKey;
152
+ if (event.toolCallId)
153
+ injectedEnv.OPENCLAW_TOOL_CALL_ID = event.toolCallId;
154
+ // Shallow merge — preserve agent's original env params
155
+ const existingEnv = event.params?.env;
156
+ return {
157
+ params: {
158
+ env: { ...existingEnv, ...injectedEnv },
159
+ },
160
+ };
161
+ });
162
+ // after_tool_call -> send tool_result step to AgentSession
163
+ api.on("after_tool_call", async (event, ctx) => {
164
+ const resolved = resolveClientForHook(ctx.sessionKey);
165
+ if (!resolved?.sessionId || !event.toolCallId)
166
+ return;
167
+ try {
168
+ await resolved.client.createAgentStep(resolved.orgId, resolved.agentUserId, resolved.sessionId, {
169
+ step_type: "tool_result",
170
+ target_type: stepTargetType(resolved.chatId),
171
+ target_id: resolved.chatId || undefined,
172
+ content: {
173
+ call_id: event.toolCallId,
174
+ tool_name: event.toolName,
175
+ status: event.error ? "error" : "success",
176
+ output: event.error ?? (typeof event.result === "string" ? event.result : JSON.stringify(event.result ?? "")),
177
+ duration_ms: event.durationMs ?? 0,
178
+ collapsible: true,
179
+ },
180
+ });
181
+ }
182
+ catch (err) {
183
+ log?.warn(`parall hook after_tool_call failed: ${String(err)}`);
184
+ }
185
+ });
186
+ // agent_end -> end of a dispatch cycle, NOT end of session.
187
+ // Session lives across dispatches — don't update session status here.
188
+ // Dispatch-specific IDs are cleaned up in the generic gateway; hooks only own the group key.
189
+ api.on("agent_end", async (event, ctx) => {
190
+ if (ctx.sessionKey) {
191
+ clearDispatchGroupKey(ctx.sessionKey);
192
+ }
193
+ });
194
+ }
@@ -0,0 +1,10 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ declare const plugin: {
3
+ id: string;
4
+ name: string;
5
+ description: string;
6
+ configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
7
+ register(api: OpenClawPluginApi): void;
8
+ };
9
+ export default plugin;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAM7D,QAAA,MAAM,MAAM;;;;;kBAKI,iBAAiB;CAKhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
2
+ import { parallPlugin } from "./channel.js";
3
+ import { setParallRuntime } from "./runtime.js";
4
+ import { registerParallHooks } from "./hooks.js";
5
+ const plugin = {
6
+ id: "parall",
7
+ name: "Parall",
8
+ description: "Parall IM channel plugin — Agent-Native messaging with tool call visualization.",
9
+ configSchema: emptyPluginConfigSchema(),
10
+ register(api) {
11
+ setParallRuntime(api.runtime);
12
+ api.registerChannel({ plugin: parallPlugin });
13
+ registerParallHooks(api);
14
+ },
15
+ };
16
+ export default plugin;
@@ -0,0 +1,6 @@
1
+ import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
2
+ /** Extract ChannelOutboundAdapter from ChannelPlugin (removed from public SDK exports in 2026.3.24). */
3
+ type ChannelOutboundAdapter = NonNullable<ChannelPlugin["outbound"]>;
4
+ export declare const parallOutbound: ChannelOutboundAdapter;
5
+ export {};
6
+ //# sourceMappingURL=outbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,wGAAwG;AACxG,KAAK,sBAAsB,GAAG,WAAW,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;AAMrE,eAAO,MAAM,cAAc,EAAE,sBA2B5B,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { resolveParallAccount } from "./accounts.js";
2
+ import { getParallAccountState } from "./runtime.js";
3
+ import { ParallClient } from "@parall/sdk";
4
+ export const parallOutbound = {
5
+ deliveryMode: "direct",
6
+ textChunkLimit: 10000,
7
+ chunker: null,
8
+ sendText: async ({ cfg, to, text, accountId }) => {
9
+ const account = resolveParallAccount({ cfg, accountId: accountId ?? undefined });
10
+ if (!account.enabled)
11
+ throw new Error("Parall account is disabled");
12
+ if (!account.configured)
13
+ throw new Error("Parall account is not configured: parall_url/api_key/org_id required");
14
+ const state = getParallAccountState(account.accountId);
15
+ const client = state?.client ?? new ParallClient({
16
+ baseUrl: account.config.parall_url,
17
+ token: account.config.api_key,
18
+ });
19
+ const orgId = state?.orgId ?? account.config.org_id;
20
+ const chatId = to;
21
+ // Outbound path has no step context — send without agent_step_id.
22
+ // Step linkage happens via the CLI's ENV-injected PRLL_STEP_ID in the normal dispatch path.
23
+ const req = {
24
+ message_type: "text",
25
+ content: { text },
26
+ };
27
+ const msg = await client.sendMessage(orgId, chatId, req);
28
+ return { channel: "parall", messageId: msg.id, channelId: chatId };
29
+ },
30
+ };
@@ -0,0 +1,3 @@
1
+ export { defaultRoutingStrategy, routeTrigger, } from "@parall/agent-core";
2
+ export type { RoutingStrategy, TriggerDisposition, } from "@parall/agent-core";
3
+ //# sourceMappingURL=routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../src/routing.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,eAAe,EACf,kBAAkB,GACnB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1 @@
1
+ export { defaultRoutingStrategy, routeTrigger, } from "@parall/agent-core";
@@ -0,0 +1,26 @@
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
+ export { setSessionChatId, getSessionChatId, setSessionMessageId, getSessionMessageId, clearSessionMessageId, setDispatchMessageId, getDispatchMessageId, clearDispatchMessageId, setDispatchGroupKey, getDispatchGroupKey, clearDispatchGroupKey, setDispatchNoReply, getDispatchNoReply, clearDispatchNoReply, } from "@parall/agent-core";
6
+ export type ParallAccountState = {
7
+ client: ParallClient;
8
+ apiUrl: string;
9
+ apiKey: string;
10
+ orgId: string;
11
+ agentUserId: string;
12
+ activeSessionId?: string;
13
+ wikiMountRoot?: string;
14
+ ws?: ParallWs;
15
+ orchestratorSessionKey?: string;
16
+ [key: string]: unknown;
17
+ };
18
+ export declare function setParallRuntime(next: PluginRuntime): void;
19
+ export declare function getParallRuntime(): PluginRuntime;
20
+ export declare function setParallAccountState(accountId: string, state: ParallAccountState): void;
21
+ export declare function removeParallAccountState(accountId: string): void;
22
+ export declare function getParallAccountState(accountId: string): ParallAccountState | undefined;
23
+ export declare function getAllParallAccountStates(): ReadonlyMap<string, ParallAccountState>;
24
+ export declare function setAgentIdentity(identity: AgentIdentity): void;
25
+ export declare function getAgentIdentity(): AgentIdentity | undefined;
26
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +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,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,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"}