@parall/parall 1.12.0 → 1.13.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/src/hooks.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { extractAccountIdFromSessionKey } from "./session.js";
3
- import { clearDispatchGroupKey, clearDispatchMessageId, clearSessionMessageId, getDispatchGroupKey, getDispatchMessageId, getParallAccountState, getSessionChatId } from "./runtime.js";
3
+ import { clearDispatchGroupKey, getDispatchGroupKey, getDispatchMessageId, getParallAccountState, getSessionChatId } from "./runtime.js";
4
4
 
5
5
  /**
6
6
  * Resolve the ParallClient + orgId + sessionId for a hook context.
@@ -83,8 +83,11 @@ export function registerParallHooks(api: OpenClawPluginApi) {
83
83
  // Dynamic per-dispatch context (changes each dispatch)
84
84
  const chatId = getSessionChatId(sessionKey);
85
85
  const triggerMsgId = getDispatchMessageId(sessionKey);
86
-
87
86
  const injectedEnv: Record<string, string> = {};
87
+ injectedEnv.PRLL_API_URL = state.apiUrl;
88
+ injectedEnv.PRLL_API_KEY = state.apiKey;
89
+ injectedEnv.PRLL_ORG_ID = state.orgId;
90
+ injectedEnv.PRLL_AGENT_ID = state.agentUserId;
88
91
  if (state.activeSessionId) injectedEnv.PRLL_SESSION_ID = state.activeSessionId;
89
92
  if (stepId) injectedEnv.PRLL_STEP_ID = stepId;
90
93
  if (chatId) injectedEnv.PRLL_CHAT_ID = chatId;
@@ -130,32 +133,9 @@ export function registerParallHooks(api: OpenClawPluginApi) {
130
133
 
131
134
  // agent_end -> end of a dispatch cycle, NOT end of session.
132
135
  // Session lives across dispatches — don't update session status here.
133
- // On error: emit a visible error step so the chat user sees the failure.
136
+ // Dispatch-specific IDs are cleaned up in the generic gateway; hooks only own the group key.
134
137
  api.on("agent_end", async (event, ctx) => {
135
- // Emit error step if the dispatch failed
136
- if (event.error && ctx.sessionKey) {
137
- const accountId = extractAccountIdFromSessionKey(ctx.sessionKey);
138
- const state = accountId ? getParallAccountState(accountId) : undefined;
139
- const chatId = getSessionChatId(ctx.sessionKey);
140
- if (state?.activeSessionId && chatId) {
141
- try {
142
- await state.client.createAgentStep(state.orgId, state.agentUserId, state.activeSessionId, {
143
- step_type: "text",
144
- target_type: "chat",
145
- target_id: chatId,
146
- content: { text: `Dispatch failed: ${String(event.error)}`, suppressed: false },
147
- projection: true,
148
- });
149
- } catch (err) {
150
- log?.warn(`parall hook agent_end: failed to emit error step: ${String(err)}`);
151
- }
152
- }
153
- }
154
-
155
- // Clean up dispatch-specific state
156
138
  if (ctx.sessionKey) {
157
- clearSessionMessageId(ctx.sessionKey);
158
- clearDispatchMessageId(ctx.sessionKey);
159
139
  clearDispatchGroupKey(ctx.sessionKey);
160
140
  }
161
141
  });
package/src/routing.ts CHANGED
@@ -1,48 +1,8 @@
1
- import type { ParallEvent, DispatchState } from "./runtime.js";
2
-
3
- /** Where an inbound event should be routed. */
4
- export type TriggerDisposition =
5
- | { action: "main" }
6
- | { action: "buffer-main" }
7
- | { action: "buffer-fork"; forkKey: string }
8
- | { action: "new-fork" };
9
-
10
- /** Pluggable strategy for routing triggers when main session is busy. */
11
- export type RoutingStrategy = (event: ParallEvent, state: DispatchState) => TriggerDisposition;
12
-
13
- const MAX_CONCURRENT_FORKS = 20;
14
-
15
- /**
16
- * Default routing strategy:
17
- * - Same target as main → buffer-main (strict per-target serial ordering)
18
- * - Active fork for same target → buffer into that fork
19
- * - Too many forks → buffer-main (backpressure)
20
- * - Otherwise → create a new fork
21
- */
22
- export const defaultRoutingStrategy: RoutingStrategy = (event, state) => {
23
- // P0: same target as main must stay serial — fork would see stale transcript
24
- if (state.mainCurrentTargetId === event.targetId) {
25
- return { action: "buffer-main" };
26
- }
27
-
28
- // Same target already has a fork → buffer into it
29
- const existingForkKey = state.activeForks.get(event.targetId);
30
- if (existingForkKey) return { action: "buffer-fork", forkKey: existingForkKey };
31
-
32
- // Backpressure: cap concurrent forks to avoid unbounded fan-out
33
- if (state.activeForks.size >= MAX_CONCURRENT_FORKS) {
34
- return { action: "buffer-main" };
35
- }
36
-
37
- return { action: "new-fork" };
38
- };
39
-
40
- /** Route an inbound event based on current dispatch state. */
41
- export function routeTrigger(
42
- event: ParallEvent,
43
- state: DispatchState,
44
- strategy: RoutingStrategy = defaultRoutingStrategy,
45
- ): TriggerDisposition {
46
- if (!state.mainDispatching) return { action: "main" };
47
- return strategy(event, state);
48
- }
1
+ export {
2
+ defaultRoutingStrategy,
3
+ routeTrigger,
4
+ } from "@parall/agent-core";
5
+ export type {
6
+ RoutingStrategy,
7
+ TriggerDisposition,
8
+ } from "@parall/agent-core";
package/src/runtime.ts CHANGED
@@ -1,5 +1,35 @@
1
1
  import type { PluginRuntime } from "openclaw/plugin-sdk";
2
2
  import type { ParallClient, ParallWs } from "@parall/sdk";
3
+ export type { ForkResult, DispatchState, ParallEvent } from "@parall/agent-core";
4
+ export {
5
+ setSessionChatId,
6
+ getSessionChatId,
7
+ setSessionMessageId,
8
+ getSessionMessageId,
9
+ clearSessionMessageId,
10
+ setDispatchMessageId,
11
+ getDispatchMessageId,
12
+ clearDispatchMessageId,
13
+ setDispatchGroupKey,
14
+ getDispatchGroupKey,
15
+ clearDispatchGroupKey,
16
+ setDispatchNoReply,
17
+ getDispatchNoReply,
18
+ clearDispatchNoReply,
19
+ } from "@parall/agent-core";
20
+
21
+ export type ParallAccountState = {
22
+ client: ParallClient;
23
+ apiUrl: string;
24
+ apiKey: string;
25
+ orgId: string;
26
+ agentUserId: string;
27
+ activeSessionId?: string;
28
+ wikiMountRoot?: string;
29
+ ws?: ParallWs;
30
+ orchestratorSessionKey?: string;
31
+ [key: string]: unknown;
32
+ };
3
33
 
4
34
  let runtime: PluginRuntime | null = null;
5
35
 
@@ -15,16 +45,6 @@ export function getParallRuntime(): PluginRuntime {
15
45
  }
16
46
 
17
47
  // Cached client state per account, populated by gateway.ts startAccount
18
- export type ParallAccountState = {
19
- client: ParallClient;
20
- orgId: string;
21
- agentUserId: string;
22
- activeSessionId?: string;
23
- wikiMountRoot?: string;
24
- ws?: ParallWs;
25
- orchestratorSessionKey?: string;
26
- };
27
-
28
48
  const accountStates = new Map<string, ParallAccountState>();
29
49
 
30
50
  export function setParallAccountState(accountId: string, state: ParallAccountState) {
@@ -42,94 +62,3 @@ export function getParallAccountState(accountId: string): ParallAccountState | u
42
62
  export function getAllParallAccountStates(): ReadonlyMap<string, ParallAccountState> {
43
63
  return new Map(accountStates);
44
64
  }
45
-
46
- // Session key → original chat ID mapping.
47
- // OpenClaw normalizes session keys to lowercase, but Parall IDs are case-sensitive.
48
- const sessionChatIdMap = new Map<string, string>();
49
- const sessionMessageIdMap = new Map<string, string>();
50
- // Per-dispatch snapshot: message ID captured when a dispatch starts, immune to later overwrites.
51
- // Keyed by lowercased session key, cleared when the agent run ends.
52
- const dispatchMessageIdMap = new Map<string, string>();
53
-
54
- export function setSessionChatId(sessionKey: string, chatId: string) {
55
- sessionChatIdMap.set(sessionKey.toLowerCase(), chatId);
56
- }
57
-
58
- export function getSessionChatId(sessionKey: string): string | undefined {
59
- return sessionChatIdMap.get(sessionKey.toLowerCase());
60
- }
61
-
62
- export function setSessionMessageId(sessionKey: string, messageId: string) {
63
- sessionMessageIdMap.set(sessionKey.toLowerCase(), messageId);
64
- }
65
-
66
- export function getSessionMessageId(sessionKey: string): string | undefined {
67
- return sessionMessageIdMap.get(sessionKey.toLowerCase());
68
- }
69
-
70
- export function clearSessionMessageId(sessionKey: string) {
71
- sessionMessageIdMap.delete(sessionKey.toLowerCase());
72
- }
73
-
74
- /** Snapshot the message ID at dispatch time — immune to later session-level overwrites. */
75
- export function setDispatchMessageId(sessionKey: string, messageId: string) {
76
- dispatchMessageIdMap.set(sessionKey.toLowerCase(), messageId);
77
- }
78
-
79
- /** Read the dispatch-time snapshot. Prefer this over getSessionMessageId for provenance. */
80
- export function getDispatchMessageId(sessionKey: string): string | undefined {
81
- return dispatchMessageIdMap.get(sessionKey.toLowerCase());
82
- }
83
-
84
- export function clearDispatchMessageId(sessionKey: string) {
85
- dispatchMessageIdMap.delete(sessionKey.toLowerCase());
86
- }
87
-
88
- // Per-dispatch group key — shared between thinking + tool_call steps for Session Panel grouping.
89
- // Set per LLM response turn in gateway.ts, read in hooks.ts.
90
- const dispatchGroupKeyMap = new Map<string, string>();
91
-
92
- export function setDispatchGroupKey(sessionKey: string, groupKey: string) {
93
- dispatchGroupKeyMap.set(sessionKey.toLowerCase(), groupKey);
94
- }
95
-
96
- export function getDispatchGroupKey(sessionKey: string): string | undefined {
97
- return dispatchGroupKeyMap.get(sessionKey.toLowerCase());
98
- }
99
-
100
- export function clearDispatchGroupKey(sessionKey: string) {
101
- dispatchGroupKeyMap.delete(sessionKey.toLowerCase());
102
- }
103
-
104
- /** Result from a completed fork dispatch, to be injected into main session's next turn. */
105
- export type ForkResult = {
106
- forkSessionKey: string;
107
- sourceEvent: { type: string; targetId: string; summary: string };
108
- actions: string[]; // human-readable list of actions taken (e.g. "replied to cht_xxx")
109
- };
110
-
111
- /** Mutable dispatch state for the orchestrator gateway. Per-account, managed by gateway.ts. */
112
- export type DispatchState = {
113
- mainDispatching: boolean;
114
- /** The targetId currently being processed by main. Used to enforce per-target serial ordering. */
115
- mainCurrentTargetId?: string;
116
- /** targetId → fork session key. One active fork per target at most. */
117
- activeForks: Map<string, string>;
118
- pendingForkResults: ForkResult[];
119
- mainBuffer: ParallEvent[];
120
- };
121
-
122
- /** Normalized inbound event from Parall. */
123
- export type ParallEvent = {
124
- type: "message" | "task";
125
- targetId: string; // chatId or taskId
126
- targetName?: string;
127
- targetType?: string; // "direct" | "group" | "task"
128
- senderId: string;
129
- senderName: string;
130
- messageId: string;
131
- body: string; // raw message text or task description
132
- threadRootId?: string;
133
- noReply?: boolean;
134
- mediaFields?: Record<string, string | undefined>;
135
- };