@clawhive/openclaw-plugin 0.2.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.
Files changed (80) hide show
  1. package/README.md +217 -0
  2. package/dist/api.d.ts +6 -0
  3. package/dist/api.js +6 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +324 -0
  6. package/dist/runtime-api.d.ts +1 -0
  7. package/dist/runtime-api.js +1 -0
  8. package/dist/setup-entry.d.ts +4 -0
  9. package/dist/setup-entry.js +3 -0
  10. package/dist/src/agency-install/claimLoop.d.ts +41 -0
  11. package/dist/src/agency-install/claimLoop.js +188 -0
  12. package/dist/src/agent-panel/claimLoop.d.ts +32 -0
  13. package/dist/src/agent-panel/claimLoop.js +118 -0
  14. package/dist/src/agent-panel/executor.d.ts +8 -0
  15. package/dist/src/agent-panel/executor.js +368 -0
  16. package/dist/src/agent-panel/plan.d.ts +20 -0
  17. package/dist/src/agent-panel/plan.js +111 -0
  18. package/dist/src/agent-panel/prompts.d.ts +38 -0
  19. package/dist/src/agent-panel/prompts.js +87 -0
  20. package/dist/src/agent-panel/types.d.ts +45 -0
  21. package/dist/src/agent-panel/types.js +1 -0
  22. package/dist/src/agents.d.ts +44 -0
  23. package/dist/src/agents.js +195 -0
  24. package/dist/src/authorization.d.ts +34 -0
  25. package/dist/src/authorization.js +183 -0
  26. package/dist/src/channel.d.ts +5 -0
  27. package/dist/src/channel.js +93 -0
  28. package/dist/src/claimPolling.d.ts +9 -0
  29. package/dist/src/claimPolling.js +13 -0
  30. package/dist/src/client.d.ts +386 -0
  31. package/dist/src/client.js +595 -0
  32. package/dist/src/config.d.ts +28 -0
  33. package/dist/src/config.js +94 -0
  34. package/dist/src/defaults.d.ts +40 -0
  35. package/dist/src/defaults.js +52 -0
  36. package/dist/src/deviceCode.d.ts +16 -0
  37. package/dist/src/deviceCode.js +65 -0
  38. package/dist/src/heartbeat.d.ts +25 -0
  39. package/dist/src/heartbeat.js +65 -0
  40. package/dist/src/imageAttachments.d.ts +14 -0
  41. package/dist/src/imageAttachments.js +81 -0
  42. package/dist/src/inbound.d.ts +10 -0
  43. package/dist/src/inbound.js +140 -0
  44. package/dist/src/installMutex.d.ts +4 -0
  45. package/dist/src/installMutex.js +18 -0
  46. package/dist/src/market-install/claimLoop.d.ts +41 -0
  47. package/dist/src/market-install/claimLoop.js +183 -0
  48. package/dist/src/market-install/downloader.d.ts +36 -0
  49. package/dist/src/market-install/downloader.js +133 -0
  50. package/dist/src/market-install/executor.d.ts +28 -0
  51. package/dist/src/market-install/executor.js +352 -0
  52. package/dist/src/market-install/types.d.ts +62 -0
  53. package/dist/src/market-install/types.js +1 -0
  54. package/dist/src/market-install/verifier.d.ts +32 -0
  55. package/dist/src/market-install/verifier.js +60 -0
  56. package/dist/src/market-publish/packager.d.ts +34 -0
  57. package/dist/src/market-publish/packager.js +168 -0
  58. package/dist/src/market-publish/publishFlow.d.ts +70 -0
  59. package/dist/src/market-publish/publishFlow.js +107 -0
  60. package/dist/src/market-publish/uploader.d.ts +73 -0
  61. package/dist/src/market-publish/uploader.js +132 -0
  62. package/dist/src/openclawVersion.d.ts +4 -0
  63. package/dist/src/openclawVersion.js +13 -0
  64. package/dist/src/outbound.d.ts +13 -0
  65. package/dist/src/outbound.js +41 -0
  66. package/dist/src/pairing.d.ts +32 -0
  67. package/dist/src/pairing.js +64 -0
  68. package/dist/src/runtime.d.ts +52 -0
  69. package/dist/src/runtime.js +26 -0
  70. package/dist/src/telemetry.d.ts +34 -0
  71. package/dist/src/telemetry.js +89 -0
  72. package/dist/src/transport/realtime.d.ts +49 -0
  73. package/dist/src/transport/realtime.js +273 -0
  74. package/dist/src/transport.d.ts +38 -0
  75. package/dist/src/transport.js +15 -0
  76. package/dist/src/wake.d.ts +31 -0
  77. package/dist/src/wake.js +101 -0
  78. package/openclaw.config.example.yaml +10 -0
  79. package/openclaw.plugin.json +122 -0
  80. package/package.json +84 -0
@@ -0,0 +1,368 @@
1
+ import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
2
+ import { CLAWHIVE_CHANNEL_ID } from "../config.js";
3
+ import { buildClawHiveSessionKey, indexSession, resolveLocalAgentId, } from "../runtime.js";
4
+ import { buildAssignedWorkPrompt, buildPlanningPrompt, buildRoundOnePrompt, buildRoundTwoPrompt, buildSynthesisPrompt, } from "./prompts.js";
5
+ import { buildFallbackAssignments, parsePanelPlan } from "./plan.js";
6
+ function extractVisibleReplyText(result) {
7
+ const finalText = result.meta?.finalAssistantVisibleText?.trim();
8
+ if (finalText)
9
+ return finalText;
10
+ const payloadText = (result.payloads ?? [])
11
+ .filter((payload) => !payload?.isReasoning && !payload?.isError)
12
+ .map((payload) => payload?.text?.trim() ?? "")
13
+ .filter((text) => text.length > 0)
14
+ .join("\n\n")
15
+ .trim();
16
+ return payloadText || null;
17
+ }
18
+ function findContribution(contributions, agentId, round) {
19
+ return contributions.find((item) => item.agentId === agentId && item.round === round) ?? null;
20
+ }
21
+ function findAssignment(assignments, agentId) {
22
+ return assignments.find((assignment) => assignment.agentId === agentId) ?? null;
23
+ }
24
+ function resolveReadyPanelists(input) {
25
+ const ready = input.panelists.filter((agent) => {
26
+ if (input.scheduledAgentIds.has(agent.agentId))
27
+ return false;
28
+ const assignment = findAssignment(input.assignments, agent.agentId);
29
+ const dependsOnAgentIds = assignment?.dependsOnAgentIds ?? [];
30
+ return dependsOnAgentIds.every((agentId) => input.completedAgentIds.has(agentId));
31
+ });
32
+ if (ready.length > 0)
33
+ return ready;
34
+ // Break invalid dependency cycles by running the remaining panelists together.
35
+ return input.panelists.filter((agent) => !input.scheduledAgentIds.has(agent.agentId));
36
+ }
37
+ function replaceContribution(contributions, contribution, patch) {
38
+ const updated = { ...contribution, ...patch };
39
+ const found = contributions.some((item) => item.id === contribution.id);
40
+ if (!found)
41
+ return [...contributions, updated];
42
+ return contributions.map((item) => item.id === contribution.id ? updated : item);
43
+ }
44
+ async function withTimeout(promise, timeoutMs) {
45
+ let timer = null;
46
+ try {
47
+ return await Promise.race([
48
+ promise,
49
+ new Promise((_, reject) => {
50
+ timer = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs);
51
+ }),
52
+ ]);
53
+ }
54
+ finally {
55
+ if (timer)
56
+ clearTimeout(timer);
57
+ }
58
+ }
59
+ function resolveAgentModelSelection(runtime, localAgentId) {
60
+ const cfg = runtime.pluginApi.runtime.config.loadConfig();
61
+ return resolveDefaultModelForAgent({
62
+ cfg,
63
+ agentId: localAgentId,
64
+ });
65
+ }
66
+ async function runLocalAgent(input) {
67
+ const cfg = input.runtime.pluginApi.runtime.config.loadConfig();
68
+ const modelSelection = resolveAgentModelSelection(input.runtime, input.localAgentId);
69
+ const workspaceDir = input.runtime.pluginApi.runtime.agent.resolveAgentWorkspaceDir(cfg, input.localAgentId);
70
+ const sessionFile = input.runtime.pluginApi.runtime.agent.session.resolveSessionFilePath(input.turn.sessionId, undefined, { agentId: input.localAgentId });
71
+ const sessionKey = buildClawHiveSessionKey(input.turn.sessionId);
72
+ const result = await withTimeout(input.runtime.pluginApi.runtime.agent.runEmbeddedPiAgent({
73
+ runId: input.runId,
74
+ trigger: "user",
75
+ sessionId: input.turn.sessionId,
76
+ sessionKey,
77
+ agentId: input.localAgentId,
78
+ messageChannel: CLAWHIVE_CHANNEL_ID,
79
+ messageTo: sessionKey,
80
+ senderId: input.turn.userId,
81
+ currentMessageId: input.turn.userMessageId,
82
+ sessionFile,
83
+ workspaceDir,
84
+ config: cfg,
85
+ provider: modelSelection.provider,
86
+ model: modelSelection.model,
87
+ prompt: input.prompt,
88
+ timeoutMs: input.timeoutMs,
89
+ }), input.timeoutMs);
90
+ return extractVisibleReplyText(result) ?? "";
91
+ }
92
+ async function runPanelistRound(input) {
93
+ const localAgentId = resolveLocalAgentId(input.runtime, input.agent.agentId) ?? input.agent.openclawAgentId;
94
+ await input.runtime.api.updatePanelContribution({
95
+ contributionId: input.contribution.id,
96
+ status: "running",
97
+ assignment: input.assignment,
98
+ });
99
+ try {
100
+ const content = await runLocalAgent({
101
+ runtime: input.runtime,
102
+ localAgentId,
103
+ turn: input.turn,
104
+ runId: `clawhive-panel-${input.turn.id}-r${input.contribution.round}-${input.agent.agentId}`,
105
+ prompt: input.prompt,
106
+ timeoutMs: input.timeoutMs,
107
+ });
108
+ await input.runtime.api.updatePanelContribution({
109
+ contributionId: input.contribution.id,
110
+ status: "completed",
111
+ content,
112
+ assignment: input.assignment,
113
+ });
114
+ return {
115
+ ...input.contribution,
116
+ status: "completed",
117
+ assignment: input.assignment ?? input.contribution.assignment,
118
+ content,
119
+ errorMessage: null,
120
+ };
121
+ }
122
+ catch (error) {
123
+ const reason = error instanceof Error ? error.message : String(error);
124
+ const status = reason.includes("Timed out") ? "timed_out" : "failed";
125
+ await input.runtime.api.updatePanelContribution({
126
+ contributionId: input.contribution.id,
127
+ status,
128
+ errorMessage: reason,
129
+ assignment: input.assignment,
130
+ });
131
+ return {
132
+ ...input.contribution,
133
+ status,
134
+ assignment: input.assignment ?? input.contribution.assignment,
135
+ errorMessage: reason,
136
+ };
137
+ }
138
+ }
139
+ export async function executePanelTurn(options) {
140
+ const { runtime, turn } = options;
141
+ const sessionKey = buildClawHiveSessionKey(turn.sessionId);
142
+ const moderator = turn.members.find((member) => member.agentId === turn.moderatorAgentId);
143
+ if (!moderator) {
144
+ throw new Error(`Panel turn ${turn.id} has no moderator member`);
145
+ }
146
+ const moderatorLocalAgentId = resolveLocalAgentId(runtime, moderator.agentId) ?? moderator.openclawAgentId;
147
+ const binding = {
148
+ sessionId: turn.sessionId,
149
+ cloudAgentId: moderator.agentId,
150
+ localAgentId: moderatorLocalAgentId,
151
+ userId: turn.userId,
152
+ sessionKey,
153
+ lastSeenSequence: 0,
154
+ };
155
+ indexSession(runtime, binding);
156
+ let contributions = [...turn.contributions];
157
+ const panelists = turn.members.filter((member) => member.role === "panelist");
158
+ const panelistTimeoutMs = options.panelistTimeoutMs ?? 10 * 60_000;
159
+ await runtime.api.updatePanelTurn({ turnId: turn.id, status: "planning" });
160
+ let plan = null;
161
+ let planFallback = false;
162
+ let planStatus = "completed";
163
+ let assignments = [];
164
+ try {
165
+ const planningText = await runLocalAgent({
166
+ runtime,
167
+ localAgentId: moderatorLocalAgentId,
168
+ turn,
169
+ runId: `clawhive-panel-${turn.id}-planning`,
170
+ prompt: buildPlanningPrompt({
171
+ userQuestion: turn.userMessageContent,
172
+ moderator,
173
+ members: turn.members,
174
+ recentContext: "",
175
+ }),
176
+ timeoutMs: options.moderatorTimeoutMs ?? 10 * 60_000,
177
+ });
178
+ const parsed = parsePanelPlan({
179
+ rawText: planningText,
180
+ members: turn.members,
181
+ userQuestion: turn.userMessageContent,
182
+ });
183
+ if (parsed.ok) {
184
+ plan = parsed.plan;
185
+ assignments = parsed.plan.assignments;
186
+ await runtime.api.updatePanelTurn({
187
+ turnId: turn.id,
188
+ status: "planning",
189
+ plan,
190
+ planStatus: "completed",
191
+ planErrorMessage: parsed.warnings.length ? parsed.warnings.join("; ") : null,
192
+ });
193
+ }
194
+ else {
195
+ planFallback = true;
196
+ planStatus = "fallback";
197
+ assignments = parsed.fallbackAssignments;
198
+ await runtime.api.updatePanelTurn({
199
+ turnId: turn.id,
200
+ status: "planning",
201
+ plan: null,
202
+ planStatus: "fallback",
203
+ planErrorMessage: parsed.errorMessage,
204
+ });
205
+ }
206
+ }
207
+ catch (error) {
208
+ planFallback = true;
209
+ planStatus = "failed";
210
+ assignments = buildFallbackAssignments({
211
+ members: turn.members,
212
+ userQuestion: turn.userMessageContent,
213
+ });
214
+ await runtime.api.updatePanelTurn({
215
+ turnId: turn.id,
216
+ status: "planning",
217
+ plan: null,
218
+ planStatus: "failed",
219
+ planErrorMessage: error instanceof Error ? error.message : String(error),
220
+ });
221
+ }
222
+ const assignedPanelists = planFallback
223
+ ? panelists
224
+ : panelists.filter((agent) => Boolean(findAssignment(assignments, agent.agentId)));
225
+ async function runRoundOnePanelist(agent) {
226
+ const contribution = findContribution(contributions, agent.agentId, 1);
227
+ if (!contribution)
228
+ return null;
229
+ const assignment = findAssignment(assignments, agent.agentId);
230
+ const updated = await runPanelistRound({
231
+ runtime,
232
+ turn,
233
+ agent,
234
+ contribution,
235
+ assignment,
236
+ prompt: assignment && !planFallback
237
+ ? buildAssignedWorkPrompt({
238
+ userQuestion: turn.userMessageContent,
239
+ agent,
240
+ assignment,
241
+ recentContext: "",
242
+ })
243
+ : buildRoundOnePrompt({
244
+ userQuestion: turn.userMessageContent,
245
+ agent,
246
+ recentContext: "",
247
+ }),
248
+ timeoutMs: panelistTimeoutMs,
249
+ });
250
+ contributions = replaceContribution(contributions, contribution, updated);
251
+ return updated;
252
+ }
253
+ const roundOneResults = [];
254
+ if (assignedPanelists.length > 0) {
255
+ await runtime.api.updatePanelTurn({ turnId: turn.id, status: "round_1_running" });
256
+ const scheduledRoundOneAgentIds = new Set();
257
+ const completedRoundOneAgentIds = new Set();
258
+ while (scheduledRoundOneAgentIds.size < assignedPanelists.length) {
259
+ const readyPanelists = resolveReadyPanelists({
260
+ panelists: assignedPanelists,
261
+ assignments,
262
+ scheduledAgentIds: scheduledRoundOneAgentIds,
263
+ completedAgentIds: completedRoundOneAgentIds,
264
+ });
265
+ for (const agent of readyPanelists)
266
+ scheduledRoundOneAgentIds.add(agent.agentId);
267
+ const batchResults = await Promise.all(readyPanelists.map(runRoundOnePanelist));
268
+ for (const result of batchResults) {
269
+ roundOneResults.push(result);
270
+ if (result?.status === "completed")
271
+ completedRoundOneAgentIds.add(result.agentId);
272
+ }
273
+ }
274
+ }
275
+ const completedRoundOne = roundOneResults
276
+ .filter((item) => Boolean(item))
277
+ .filter((item) => item.status === "completed");
278
+ const revisionRound = Boolean(plan?.requiresRevisionRound ?? planFallback);
279
+ if (revisionRound) {
280
+ await runtime.api.updatePanelTurn({ turnId: turn.id, status: "round_2_running" });
281
+ await Promise.all(assignedPanelists.map(async (agent) => {
282
+ let roundTwo = findContribution(contributions, agent.agentId, 2);
283
+ const ownRoundOne = completedRoundOne.find((item) => item.agentId === agent.agentId);
284
+ if (!ownRoundOne?.content)
285
+ return null;
286
+ const assignment = findAssignment(assignments, agent.agentId);
287
+ if (!roundTwo) {
288
+ const created = await runtime.api.createPanelContribution({
289
+ turnId: turn.id,
290
+ agentId: agent.agentId,
291
+ round: 2,
292
+ assignment,
293
+ });
294
+ roundTwo = created.contribution;
295
+ contributions = replaceContribution(contributions, roundTwo, roundTwo);
296
+ }
297
+ const updated = await runPanelistRound({
298
+ runtime,
299
+ turn,
300
+ agent,
301
+ contribution: roundTwo,
302
+ assignment: assignment ?? roundTwo.assignment,
303
+ prompt: buildRoundTwoPrompt({
304
+ userQuestion: turn.userMessageContent,
305
+ agent,
306
+ ownRoundOne: ownRoundOne.content,
307
+ otherRoundOne: completedRoundOne.filter((item) => item.agentId !== agent.agentId),
308
+ }),
309
+ timeoutMs: panelistTimeoutMs,
310
+ });
311
+ contributions = replaceContribution(contributions, roundTwo, updated);
312
+ return updated;
313
+ }));
314
+ }
315
+ await runtime.api.updatePanelTurn({ turnId: turn.id, status: "moderating" });
316
+ try {
317
+ const failedAgentIds = contributions
318
+ .filter((item) => item.status === "failed" || item.status === "timed_out")
319
+ .map((item) => item.agentId);
320
+ const finalText = await runLocalAgent({
321
+ runtime,
322
+ localAgentId: moderatorLocalAgentId,
323
+ turn,
324
+ runId: `clawhive-panel-${turn.id}-moderator`,
325
+ prompt: buildSynthesisPrompt({
326
+ userQuestion: turn.userMessageContent,
327
+ recentContext: "",
328
+ plan,
329
+ contributions,
330
+ failedAgentIds,
331
+ planFallback,
332
+ }),
333
+ timeoutMs: options.moderatorTimeoutMs ?? 10 * 60_000,
334
+ });
335
+ await runtime.api.writeAgentMessage({
336
+ pluginNodeId: runtime.pluginNodeId,
337
+ sessionId: turn.sessionId,
338
+ agentId: turn.moderatorAgentId,
339
+ content: finalText,
340
+ metadata: {
341
+ panel_turn_id: turn.id,
342
+ panel_summary: true,
343
+ plan_status: planStatus,
344
+ revision_round: revisionRound,
345
+ participant_count: turn.members.length,
346
+ completed_contribution_count: contributions.filter((item) => item.status === "completed").length,
347
+ timed_out_agent_ids: contributions
348
+ .filter((item) => item.status === "timed_out")
349
+ .map((item) => item.agentId),
350
+ },
351
+ });
352
+ await runtime.api.updatePanelTurn({
353
+ turnId: turn.id,
354
+ status: "completed",
355
+ completedAt: new Date().toISOString(),
356
+ });
357
+ }
358
+ catch (error) {
359
+ const reason = error instanceof Error ? error.message : String(error);
360
+ await runtime.api.updatePanelTurn({
361
+ turnId: turn.id,
362
+ status: "failed",
363
+ errorMessage: reason,
364
+ completedAt: new Date().toISOString(),
365
+ });
366
+ throw error;
367
+ }
368
+ }
@@ -0,0 +1,20 @@
1
+ import type { PanelAgentMember, PanelPlan, PanelPlanAssignment } from "./types.js";
2
+ export type ParsePanelPlanInput = {
3
+ rawText: string;
4
+ members: PanelAgentMember[];
5
+ userQuestion: string;
6
+ };
7
+ export type ParsePanelPlanResult = {
8
+ ok: true;
9
+ plan: PanelPlan;
10
+ warnings: string[];
11
+ } | {
12
+ ok: false;
13
+ errorMessage: string;
14
+ fallbackAssignments: PanelPlanAssignment[];
15
+ };
16
+ export declare function buildFallbackAssignments(input: {
17
+ members: PanelAgentMember[];
18
+ userQuestion: string;
19
+ }): PanelPlanAssignment[];
20
+ export declare function parsePanelPlan(input: ParsePanelPlanInput): ParsePanelPlanResult;
@@ -0,0 +1,111 @@
1
+ function stripMarkdownFence(rawText) {
2
+ const trimmed = rawText.trim();
3
+ const match = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
4
+ return (match?.[1] ?? trimmed).trim();
5
+ }
6
+ function normalizeText(value, fallback) {
7
+ return typeof value === "string" && value.trim() ? value.trim() : fallback;
8
+ }
9
+ function normalizePriority(value) {
10
+ return value === "supporting" ? "supporting" : "primary";
11
+ }
12
+ function normalizeDependsOnAgentIds(input) {
13
+ if (!Array.isArray(input.value))
14
+ return [];
15
+ const seen = new Set();
16
+ const dependsOn = [];
17
+ for (const item of input.value) {
18
+ if (typeof item !== "string" || !item.trim())
19
+ continue;
20
+ const dependencyId = item.trim();
21
+ if (dependencyId === input.agentId) {
22
+ input.warnings.push(`Ignored self dependency for agent ${input.agentId}`);
23
+ continue;
24
+ }
25
+ if (!input.validPanelistIds.has(dependencyId)) {
26
+ input.warnings.push(`Ignored dependency on unknown agent ${dependencyId} for agent ${input.agentId}`);
27
+ continue;
28
+ }
29
+ if (seen.has(dependencyId))
30
+ continue;
31
+ seen.add(dependencyId);
32
+ dependsOn.push(dependencyId);
33
+ }
34
+ return dependsOn;
35
+ }
36
+ function panelists(members) {
37
+ return members.filter((member) => member.role === "panelist");
38
+ }
39
+ export function buildFallbackAssignments(input) {
40
+ return panelists(input.members).map((member) => ({
41
+ agentId: member.agentId,
42
+ task: `Provide expert input for this user request: ${input.userQuestion}`,
43
+ reason: `${member.name} was selected as a panelist for this session.`,
44
+ priority: "primary",
45
+ expectedOutput: "Viewpoint, reasoning, risks or uncertainty, and recommendation.",
46
+ dependsOnAgentIds: [],
47
+ }));
48
+ }
49
+ export function parsePanelPlan(input) {
50
+ let parsed;
51
+ try {
52
+ parsed = JSON.parse(stripMarkdownFence(input.rawText));
53
+ }
54
+ catch (error) {
55
+ return {
56
+ ok: false,
57
+ errorMessage: `Invalid panel plan JSON: ${error instanceof Error ? error.message : String(error)}`,
58
+ fallbackAssignments: buildFallbackAssignments(input),
59
+ };
60
+ }
61
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
62
+ return {
63
+ ok: false,
64
+ errorMessage: "Invalid panel plan JSON: root must be an object",
65
+ fallbackAssignments: buildFallbackAssignments(input),
66
+ };
67
+ }
68
+ const raw = parsed;
69
+ const validPanelistIds = new Set(panelists(input.members).map((member) => member.agentId));
70
+ const seen = new Set();
71
+ const warnings = [];
72
+ const assignments = [];
73
+ for (const item of Array.isArray(raw.assignments) ? raw.assignments : []) {
74
+ if (!item || typeof item !== "object" || Array.isArray(item))
75
+ continue;
76
+ const entry = item;
77
+ const agentId = normalizeText(entry.agentId, "");
78
+ if (!validPanelistIds.has(agentId)) {
79
+ warnings.push(`Ignored assignment for unknown agent ${agentId || "(missing)"}`);
80
+ continue;
81
+ }
82
+ if (seen.has(agentId)) {
83
+ warnings.push(`Ignored duplicate assignment for agent ${agentId}`);
84
+ continue;
85
+ }
86
+ seen.add(agentId);
87
+ assignments.push({
88
+ agentId,
89
+ task: normalizeText(entry.task, `Provide expert input for: ${input.userQuestion}`),
90
+ reason: normalizeText(entry.reason, "Selected panelist expertise."),
91
+ priority: normalizePriority(entry.priority),
92
+ expectedOutput: normalizeText(entry.expectedOutput, "Concise expert findings for the moderator."),
93
+ dependsOnAgentIds: normalizeDependsOnAgentIds({
94
+ value: entry.dependsOnAgentIds ?? entry.depends_on_agent_ids,
95
+ agentId,
96
+ validPanelistIds,
97
+ warnings,
98
+ }),
99
+ });
100
+ }
101
+ return {
102
+ ok: true,
103
+ warnings,
104
+ plan: {
105
+ summary: normalizeText(raw.summary, input.userQuestion),
106
+ requiresRevisionRound: raw.requiresRevisionRound === true,
107
+ synthesisGuidance: normalizeText(raw.synthesisGuidance, "Synthesize the panel outputs into one useful answer."),
108
+ assignments,
109
+ },
110
+ };
111
+ }
@@ -0,0 +1,38 @@
1
+ import type { PanelAgentMember, PanelContribution, PanelPlan, PanelPlanAssignment } from "./types.js";
2
+ export declare function buildPlanningPrompt(input: {
3
+ userQuestion: string;
4
+ moderator: PanelAgentMember;
5
+ members: PanelAgentMember[];
6
+ recentContext: string;
7
+ }): string;
8
+ export declare function buildAssignedWorkPrompt(input: {
9
+ userQuestion: string;
10
+ agent: PanelAgentMember;
11
+ assignment: PanelPlanAssignment;
12
+ recentContext: string;
13
+ }): string;
14
+ export declare function buildSynthesisPrompt(input: {
15
+ userQuestion: string;
16
+ recentContext: string;
17
+ plan: PanelPlan | null;
18
+ contributions: PanelContribution[];
19
+ failedAgentIds: string[];
20
+ planFallback: boolean;
21
+ }): string;
22
+ export declare function buildRoundOnePrompt(input: {
23
+ userQuestion: string;
24
+ agent: PanelAgentMember;
25
+ recentContext: string;
26
+ }): string;
27
+ export declare function buildRoundTwoPrompt(input: {
28
+ userQuestion: string;
29
+ agent: PanelAgentMember;
30
+ ownRoundOne: string;
31
+ otherRoundOne: PanelContribution[];
32
+ }): string;
33
+ export declare function buildModeratorPrompt(input: {
34
+ userQuestion: string;
35
+ recentContext: string;
36
+ contributions: PanelContribution[];
37
+ failedAgentIds: string[];
38
+ }): string;
@@ -0,0 +1,87 @@
1
+ export function buildPlanningPrompt(input) {
2
+ const panelists = input.members
3
+ .filter((member) => member.role === "panelist")
4
+ .map((member) => `- agentId=${member.agentId}; openclawAgentId=${member.openclawAgentId}; name=${member.name}; position=${member.position}`)
5
+ .join("\n");
6
+ return [
7
+ `You are ${input.moderator.name}, the moderator for an expert panel.`,
8
+ "Analyze the user's request complexity before assigning work.",
9
+ "If the task is simple and one panelist is clearly best suited, assign only that panelist.",
10
+ "If no panelist is a good fit or the task is simple enough for you, return assignments: [] and answer during synthesis.",
11
+ "For complex tasks, assign only the panelists that are genuinely needed.",
12
+ "Decide which assignments can run in parallel and which must wait for another panelist's result.",
13
+ "For each assignment, set dependsOnAgentIds to the panelist agent IDs that must complete before this agent starts. Use [] when it can run immediately.",
14
+ "Return strict JSON only. Do not wrap it in markdown.",
15
+ "Schema: {\"summary\":\"string\",\"requiresRevisionRound\":boolean,\"synthesisGuidance\":\"string\",\"assignments\":[{\"agentId\":\"string\",\"task\":\"string\",\"reason\":\"string\",\"priority\":\"primary|supporting\",\"expectedOutput\":\"string\",\"dependsOnAgentIds\":[\"agentId\"]}]}",
16
+ input.recentContext ? `Recent context:\n${input.recentContext}` : "",
17
+ `Panelists:\n${panelists}`,
18
+ `User question:\n${input.userQuestion}`,
19
+ ].filter(Boolean).join("\n\n");
20
+ }
21
+ export function buildAssignedWorkPrompt(input) {
22
+ return [
23
+ `You are ${input.agent.name}, participating in an expert panel.`,
24
+ "Complete your assigned task for the moderator. Do not answer the user directly.",
25
+ `Task:\n${input.assignment.task}`,
26
+ `Why you were assigned:\n${input.assignment.reason}`,
27
+ `Priority: ${input.assignment.priority}`,
28
+ `Expected output:\n${input.assignment.expectedOutput}`,
29
+ input.recentContext ? `Recent context:\n${input.recentContext}` : "",
30
+ `User question:\n${input.userQuestion}`,
31
+ ].filter(Boolean).join("\n\n");
32
+ }
33
+ export function buildSynthesisPrompt(input) {
34
+ const completed = input.contributions
35
+ .filter((item) => item.status === "completed" && item.content?.trim())
36
+ .map((item) => {
37
+ const task = item.assignment?.task ? `Assignment: ${item.assignment.task}\n` : "";
38
+ return `Round ${item.round} agent ${item.agentId}:\n${task}${item.content}`;
39
+ })
40
+ .join("\n\n");
41
+ return [
42
+ "You are the moderator. Write one clear final answer to the user.",
43
+ "Synthesize the work; do not dump every contribution.",
44
+ input.planFallback ? "Planning used fallback generic assignments because structured planning failed." : "",
45
+ input.plan ? `Plan summary:\n${input.plan.summary}\n\nSynthesis guidance:\n${input.plan.synthesisGuidance}` : "",
46
+ input.failedAgentIds.length ? `Some panelists did not complete: ${input.failedAgentIds.join(", ")}` : "",
47
+ input.recentContext ? `Recent context:\n${input.recentContext}` : "",
48
+ `User question:\n${input.userQuestion}`,
49
+ completed ? `Panel work:\n${completed}` : "No panelist completed. Answer directly from your own expertise.",
50
+ ].filter(Boolean).join("\n\n");
51
+ }
52
+ export function buildRoundOnePrompt(input) {
53
+ return [
54
+ `You are ${input.agent.name}, participating in an expert panel.`,
55
+ "Give expert input for the moderator. Do not answer the user directly.",
56
+ "Use this structure: Viewpoint, Reasoning, Risks or uncertainty, Recommendation.",
57
+ input.recentContext ? `Recent context:\n${input.recentContext}` : "",
58
+ `User question:\n${input.userQuestion}`,
59
+ ].filter(Boolean).join("\n\n");
60
+ }
61
+ export function buildRoundTwoPrompt(input) {
62
+ const others = input.otherRoundOne
63
+ .filter((item) => item.content?.trim())
64
+ .map((item) => `Agent ${item.agentId}:\n${item.content}`)
65
+ .join("\n\n");
66
+ return [
67
+ `You are ${input.agent.name}, revising your expert input after seeing other panelists.`,
68
+ "Do not repeat your first answer. Add corrections, disagreements, risks, or final recommendation.",
69
+ `User question:\n${input.userQuestion}`,
70
+ `Your Round 1:\n${input.ownRoundOne}`,
71
+ others ? `Other Round 1 contributions:\n${others}` : "No other Round 1 contributions completed.",
72
+ ].join("\n\n");
73
+ }
74
+ export function buildModeratorPrompt(input) {
75
+ const completed = input.contributions
76
+ .filter((item) => item.status === "completed" && item.content?.trim())
77
+ .map((item) => `Round ${item.round} agent ${item.agentId}:\n${item.content}`)
78
+ .join("\n\n");
79
+ return [
80
+ "You are the moderator. Write one clear final answer to the user.",
81
+ "Do not dump every contribution. Mention disagreement only when useful.",
82
+ input.failedAgentIds.length ? `Some panelists did not complete: ${input.failedAgentIds.join(", ")}` : "",
83
+ input.recentContext ? `Recent context:\n${input.recentContext}` : "",
84
+ `User question:\n${input.userQuestion}`,
85
+ completed ? `Panel discussion:\n${completed}` : "No panelist completed. Answer directly from your own expertise.",
86
+ ].filter(Boolean).join("\n\n");
87
+ }
@@ -0,0 +1,45 @@
1
+ export type PanelTurnStatus = "queued" | "planning" | "round_1_running" | "round_2_running" | "moderating" | "completed" | "failed";
2
+ export type PanelContributionStatus = "queued" | "running" | "completed" | "timed_out" | "failed";
3
+ export type PanelAgentMember = {
4
+ agentId: string;
5
+ role: "moderator" | "panelist";
6
+ position: number;
7
+ openclawAgentId: string;
8
+ name: string;
9
+ };
10
+ export type PanelPlanAssignment = {
11
+ agentId: string;
12
+ task: string;
13
+ reason: string;
14
+ priority: "primary" | "supporting";
15
+ expectedOutput: string;
16
+ dependsOnAgentIds: string[];
17
+ };
18
+ export type PanelPlan = {
19
+ summary: string;
20
+ requiresRevisionRound: boolean;
21
+ synthesisGuidance: string;
22
+ assignments: PanelPlanAssignment[];
23
+ };
24
+ export type PanelContribution = {
25
+ id: string;
26
+ agentId: string;
27
+ round: 1 | 2;
28
+ status: PanelContributionStatus;
29
+ assignment: PanelPlanAssignment | null;
30
+ content: string | null;
31
+ errorMessage: string | null;
32
+ };
33
+ export type ClaimedPanelTurn = {
34
+ id: string;
35
+ sessionId: string;
36
+ userMessageId: string;
37
+ moderatorAgentId: string;
38
+ userId: string;
39
+ userMessageContent: string;
40
+ members: PanelAgentMember[];
41
+ contributions: PanelContribution[];
42
+ plan: PanelPlan | null;
43
+ planStatus: "pending" | "completed" | "fallback" | "failed";
44
+ planErrorMessage: string | null;
45
+ };
@@ -0,0 +1 @@
1
+ export {};