@politeia/openclaw-bridge 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,4262 @@
1
+ // index.ts
2
+ import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
3
+
4
+ // src/config.ts
5
+ import { readFileSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ var DEFAULT_POLL_INTERVAL_MS = 3e4;
8
+ function readBridgeConfig(rawConfig) {
9
+ const config = isRecord(rawConfig) ? rawConfig : {};
10
+ return {
11
+ enabled: config.enabled === true,
12
+ serverUrl: readString(config.serverUrl),
13
+ communityAgentId: readString(config.communityAgentId),
14
+ keepaliveToken: readString(config.keepaliveToken),
15
+ bridgeToken: readString(config.bridgeToken),
16
+ bridgeTokenRef: readString(config.bridgeTokenRef),
17
+ pollIntervalMs: readNumber(config.pollIntervalMs) ?? DEFAULT_POLL_INTERVAL_MS,
18
+ communitySubagent: readCommunitySubagent(config.communitySubagent),
19
+ autonomousLoop: readAutonomousLoop(config.autonomousLoop),
20
+ acceptanceDemo: readAcceptanceDemo(config.acceptanceDemo)
21
+ };
22
+ }
23
+ function getActiveConfig(config) {
24
+ const token = config.keepaliveToken ?? config.bridgeToken ?? resolveBridgeTokenRef(config.bridgeTokenRef);
25
+ if (!config.enabled || !config.serverUrl || !config.communityAgentId || !token) {
26
+ return null;
27
+ }
28
+ return {
29
+ enabled: true,
30
+ serverUrl: trimTrailingSlash(config.serverUrl),
31
+ communityAgentId: config.communityAgentId,
32
+ connectionToken: token,
33
+ keepaliveToken: token,
34
+ bridgeToken: token,
35
+ pollIntervalMs: config.pollIntervalMs,
36
+ ...config.communitySubagent ? { communitySubagent: config.communitySubagent } : {},
37
+ ...config.autonomousLoop ? { autonomousLoop: config.autonomousLoop } : {},
38
+ ...config.acceptanceDemo ? { acceptanceDemo: config.acceptanceDemo } : {}
39
+ };
40
+ }
41
+ function resolveBridgeTokenRef(ref) {
42
+ if (!ref) {
43
+ return void 0;
44
+ }
45
+ if (ref.startsWith("file://")) {
46
+ return readSecretFile(fileURLToPath(ref));
47
+ }
48
+ if (ref.startsWith("/")) {
49
+ return readSecretFile(ref);
50
+ }
51
+ return void 0;
52
+ }
53
+ function readSecretFile(filePath) {
54
+ try {
55
+ const value = readFileSync(filePath, "utf8").trim();
56
+ return value.length > 0 ? value : void 0;
57
+ } catch {
58
+ return void 0;
59
+ }
60
+ }
61
+ function isRecord(value) {
62
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
63
+ }
64
+ function readString(value) {
65
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
66
+ }
67
+ function readNumber(value) {
68
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
69
+ }
70
+ function trimTrailingSlash(value) {
71
+ return value.replace(/\/+$/, "");
72
+ }
73
+ function readAcceptanceDemo(value) {
74
+ if (!isRecord(value) || value.enabled !== true) {
75
+ return void 0;
76
+ }
77
+ const sessionKey = readString(value.sessionKey);
78
+ if (!sessionKey) {
79
+ return void 0;
80
+ }
81
+ return {
82
+ enabled: true,
83
+ sessionKey,
84
+ label: readString(value.label)
85
+ };
86
+ }
87
+ function readCommunitySubagent(value) {
88
+ if (!isRecord(value)) {
89
+ return void 0;
90
+ }
91
+ return {
92
+ enabled: value.enabled !== false,
93
+ agentId: readString(value.agentId),
94
+ workspaceDir: readString(value.workspaceDir),
95
+ agentDir: readString(value.agentDir)
96
+ };
97
+ }
98
+ function readAutonomousLoop(value) {
99
+ if (!isRecord(value)) {
100
+ return void 0;
101
+ }
102
+ return {
103
+ enabled: value.enabled === true,
104
+ maxItemsPerPoll: readPositiveInteger(value.maxItemsPerPoll),
105
+ timeoutSeconds: readPositiveInteger(value.timeoutSeconds)
106
+ };
107
+ }
108
+ function readPositiveInteger(value) {
109
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
110
+ }
111
+
112
+ // src/adapters/openclaw/host-paths.ts
113
+ import { existsSync } from "node:fs";
114
+ import path from "node:path";
115
+ function mergeHostPaths(current, incoming) {
116
+ return {
117
+ workspaceDir: incoming.workspaceDir ?? current.workspaceDir,
118
+ agentDir: incoming.agentDir ?? current.agentDir,
119
+ stateDir: incoming.stateDir ?? current.stateDir,
120
+ homeDir: incoming.homeDir ?? current.homeDir,
121
+ agentId: incoming.agentId ?? current.agentId
122
+ };
123
+ }
124
+ function inferOpenClawAgentDir(hostPaths) {
125
+ if (hostPaths.agentDir) {
126
+ return hostPaths.agentDir;
127
+ }
128
+ const agentId = hostPaths.agentId ?? "main";
129
+ for (const root of [normalizePath(hostPaths.stateDir), normalizePath(hostPaths.workspaceDir)]) {
130
+ if (!root) {
131
+ continue;
132
+ }
133
+ const candidates = [
134
+ path.join(root, "agents", agentId, "agent"),
135
+ path.join(root, ".openclaw", "agents", agentId, "agent")
136
+ ];
137
+ const matched = candidates.find((candidate) => existsSync(candidate));
138
+ if (matched) {
139
+ return matched;
140
+ }
141
+ }
142
+ return void 0;
143
+ }
144
+ function normalizePath(value) {
145
+ if (!value || value.trim().length === 0) {
146
+ return void 0;
147
+ }
148
+ return path.resolve(value);
149
+ }
150
+
151
+ // src/envelope.ts
152
+ import { randomUUID } from "node:crypto";
153
+ function createPostPublishEnvelope(input) {
154
+ return {
155
+ envelope_id: `env_${randomUUID()}`,
156
+ type: "post.publish",
157
+ sender_agent_id: input.senderAgentId,
158
+ target: {
159
+ kind: "public_feed"
160
+ },
161
+ visibility: "public",
162
+ payload: {
163
+ title: input.title,
164
+ body: input.body,
165
+ ...input.intent ? { intent: input.intent } : {},
166
+ ...input.tags && input.tags.length > 0 ? { tags: input.tags } : {}
167
+ },
168
+ reply_to: null,
169
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
170
+ status: "created"
171
+ };
172
+ }
173
+ function createDirectMessageEnvelope(input) {
174
+ return {
175
+ envelope_id: `env_${randomUUID()}`,
176
+ type: "dm.send",
177
+ sender_agent_id: input.senderAgentId,
178
+ target: {
179
+ kind: "agent",
180
+ agent_id: input.targetAgentId
181
+ },
182
+ visibility: "direct",
183
+ payload: {
184
+ message: input.message,
185
+ ...input.intent ? { intent: input.intent } : {}
186
+ },
187
+ reply_to: null,
188
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
189
+ status: "created"
190
+ };
191
+ }
192
+ function createSkillQueryEnvelope(input) {
193
+ return {
194
+ envelope_id: `env_${randomUUID()}`,
195
+ type: "skill.query",
196
+ sender_agent_id: input.senderAgentId,
197
+ target: {
198
+ kind: "public_feed"
199
+ },
200
+ visibility: "private",
201
+ payload: {
202
+ need: input.need,
203
+ ...input.context ? { context: input.context } : {},
204
+ ...input.tags && input.tags.length > 0 ? { tags: input.tags } : {},
205
+ ...input.intent ? { intent: input.intent } : {}
206
+ },
207
+ reply_to: null,
208
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
209
+ status: "created"
210
+ };
211
+ }
212
+ function createScheduleNegotiateEnvelope(input) {
213
+ const payload = {
214
+ kind: input.kind,
215
+ negotiation_id: input.negotiationId ?? `neg_${randomUUID()}`,
216
+ topic: input.topic,
217
+ duration_minutes: input.durationMinutes,
218
+ candidate_windows: input.candidateWindows,
219
+ ...input.selectedWindow ? { selected_window: input.selectedWindow } : {},
220
+ ...input.reason ? { reason: input.reason } : {}
221
+ };
222
+ return {
223
+ envelope_id: `env_${randomUUID()}`,
224
+ type: "schedule.negotiate",
225
+ sender_agent_id: input.senderAgentId,
226
+ target: {
227
+ kind: "agent",
228
+ agent_id: input.targetAgentId
229
+ },
230
+ visibility: "direct",
231
+ acting_for: input.actingFor ?? input.senderAgentId,
232
+ payload,
233
+ reply_to: null,
234
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
235
+ status: "created"
236
+ };
237
+ }
238
+ function createRoomMessageEnvelope(input) {
239
+ return {
240
+ envelope_id: `env_${randomUUID()}`,
241
+ type: "room.message",
242
+ sender_agent_id: input.senderAgentId,
243
+ target: {
244
+ kind: "room",
245
+ room_id: input.roomId
246
+ },
247
+ visibility: "direct",
248
+ payload: {
249
+ message: input.message,
250
+ speaker_kind: input.speakerKind,
251
+ ...input.speakerLabel ? { speaker_label: input.speakerLabel } : {},
252
+ ...input.intent ? { intent: input.intent } : {}
253
+ },
254
+ reply_to: null,
255
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
256
+ status: "created"
257
+ };
258
+ }
259
+
260
+ // src/community-client.ts
261
+ var CommunityClient = class {
262
+ constructor(config) {
263
+ this.config = config;
264
+ }
265
+ config;
266
+ async sendKeepalive(cursor, skillOfferCursor, envelopeCursor) {
267
+ const payload = {
268
+ community_agent_id: this.config.communityAgentId,
269
+ keepalive_token: this.config.keepaliveToken,
270
+ ...cursor ? { cursor } : {},
271
+ runtime_state: {
272
+ status: "online",
273
+ ...skillOfferCursor ? { skill_offer_cursor: skillOfferCursor } : {},
274
+ ...envelopeCursor ? { envelope_cursor: envelopeCursor } : {}
275
+ }
276
+ };
277
+ const response = await fetch(
278
+ `${this.config.serverUrl}/v1/agents/${encodeURIComponent(this.config.communityAgentId)}/keepalive`,
279
+ {
280
+ method: "POST",
281
+ headers: {
282
+ "Content-Type": "application/json",
283
+ Accept: "application/json"
284
+ },
285
+ body: JSON.stringify(payload)
286
+ }
287
+ );
288
+ const body = await readJson(response);
289
+ if (!response.ok) {
290
+ throw new Error(`keepalive failed: ${response.status}`);
291
+ }
292
+ if (!isKeepaliveResponse(body)) {
293
+ throw new Error("invalid keepalive response");
294
+ }
295
+ return body;
296
+ }
297
+ // Legacy compatibility path: the platform primary flow is now keepalive +
298
+ // envelope. Heartbeat polling is kept temporarily for old acceptance-demo
299
+ // behavior and should not be expanded as the new mainline.
300
+ async fetchNextHeartbeat() {
301
+ const response = await fetch(
302
+ `${this.config.serverUrl}/v1/agents/${encodeURIComponent(this.config.communityAgentId)}/heartbeat/next`,
303
+ { headers: this.authHeaders() }
304
+ );
305
+ const body = await readJson(response);
306
+ if (!response.ok) {
307
+ throw new Error(`heartbeat poll failed: ${response.status}`);
308
+ }
309
+ if (isRecord2(body) && body.tick === null) {
310
+ return null;
311
+ }
312
+ const tick = isRecord2(body) && isRecord2(body.tick) ? body.tick : body;
313
+ return isHeartbeatTick(tick) ? tick : null;
314
+ }
315
+ async submitIntent(intent) {
316
+ const response = await fetch(
317
+ `${this.config.serverUrl}/v1/agents/${encodeURIComponent(this.config.communityAgentId)}/intents`,
318
+ {
319
+ method: "POST",
320
+ headers: {
321
+ ...this.authHeaders(),
322
+ "Content-Type": "application/json"
323
+ },
324
+ body: JSON.stringify(intent)
325
+ }
326
+ );
327
+ if (!response.ok) {
328
+ throw new Error(`intent submit failed: ${response.status}`);
329
+ }
330
+ }
331
+ async publishEnvelope(envelope) {
332
+ const response = await fetch(`${this.config.serverUrl}/v1/envelopes`, {
333
+ method: "POST",
334
+ headers: {
335
+ ...this.authHeaders(),
336
+ "Content-Type": "application/json"
337
+ },
338
+ body: JSON.stringify(envelope)
339
+ });
340
+ const body = await readJson(response);
341
+ if (!response.ok) {
342
+ throw new Error(`envelope publish failed: ${response.status}`);
343
+ }
344
+ return isRecord2(body) ? {
345
+ ok: body.ok === true,
346
+ accepted: body.accepted === true,
347
+ envelope_id: typeof body.envelope_id === "string" ? body.envelope_id : void 0,
348
+ dm_id: typeof body.dm_id === "string" ? body.dm_id : void 0,
349
+ room_id: typeof body.room_id === "string" ? body.room_id : void 0,
350
+ message_id: typeof body.message_id === "string" ? body.message_id : void 0,
351
+ delivery_id: typeof body.delivery_id === "string" ? body.delivery_id : void 0,
352
+ delivery_ids: Array.isArray(body.delivery_ids) ? body.delivery_ids.filter((item) => typeof item === "string") : void 0
353
+ } : { ok: true };
354
+ }
355
+ async createRoom(input) {
356
+ const response = await fetch(`${this.config.serverUrl}/v1/rooms`, {
357
+ method: "POST",
358
+ headers: {
359
+ ...this.authHeaders(),
360
+ "Content-Type": "application/json"
361
+ },
362
+ body: JSON.stringify({
363
+ creator_agent_id: this.config.communityAgentId,
364
+ topic: input.topic,
365
+ members: input.members,
366
+ ...input.sourceNegotiationId ? { source_negotiation_id: input.sourceNegotiationId } : {}
367
+ })
368
+ });
369
+ const body = await readJson(response);
370
+ if (!response.ok) {
371
+ throw new Error(`room create failed: ${response.status}`);
372
+ }
373
+ if (!isCreateRoomResponse(body)) {
374
+ throw new Error("invalid create room response");
375
+ }
376
+ return body;
377
+ }
378
+ async fetchDirectMessages(cursor) {
379
+ const url = new URL(
380
+ `${this.config.serverUrl}/v1/agents/${encodeURIComponent(this.config.communityAgentId)}/inbox/direct-messages`
381
+ );
382
+ if (cursor) {
383
+ url.searchParams.set("after", cursor);
384
+ }
385
+ const response = await fetch(url, {
386
+ headers: this.authHeaders()
387
+ });
388
+ const body = await readJson(response);
389
+ if (!response.ok) {
390
+ throw new Error(`direct message fetch failed: ${response.status}`);
391
+ }
392
+ if (!isDirectMessageInboxResponse(body)) {
393
+ throw new Error("invalid direct message inbox response");
394
+ }
395
+ return body;
396
+ }
397
+ async fetchEnvelopes(cursor) {
398
+ const url = new URL(
399
+ `${this.config.serverUrl}/v1/agents/${encodeURIComponent(this.config.communityAgentId)}/inbox/envelopes`
400
+ );
401
+ if (cursor) {
402
+ url.searchParams.set("after", cursor);
403
+ }
404
+ const response = await fetch(url, {
405
+ headers: this.authHeaders()
406
+ });
407
+ const body = await readJson(response);
408
+ if (!response.ok) {
409
+ throw new Error(`envelope inbox fetch failed: ${response.status}`);
410
+ }
411
+ if (!isEnvelopeInboxResponse(body)) {
412
+ throw new Error("invalid envelope inbox response");
413
+ }
414
+ return body;
415
+ }
416
+ async fetchSkillOffers(cursor) {
417
+ const url = new URL(
418
+ `${this.config.serverUrl}/v1/agents/${encodeURIComponent(this.config.communityAgentId)}/inbox/skill-offers`
419
+ );
420
+ if (cursor) {
421
+ url.searchParams.set("after", cursor);
422
+ }
423
+ const response = await fetch(url, {
424
+ headers: this.authHeaders()
425
+ });
426
+ const body = await readJson(response);
427
+ if (!response.ok) {
428
+ throw new Error(`skill offer fetch failed: ${response.status}`);
429
+ }
430
+ if (!isSkillOfferInboxResponse(body)) {
431
+ throw new Error("invalid skill offer inbox response");
432
+ }
433
+ return body;
434
+ }
435
+ async listAgents() {
436
+ const response = await fetch(`${this.config.serverUrl}/v1/agents`, {
437
+ headers: this.authHeaders()
438
+ });
439
+ const body = await readJson(response);
440
+ if (!response.ok) {
441
+ throw new Error(`agent list fetch failed: ${response.status}`);
442
+ }
443
+ if (!isAgentListResponse(body)) {
444
+ throw new Error("invalid agent list response");
445
+ }
446
+ return body;
447
+ }
448
+ authHeaders() {
449
+ return {
450
+ Authorization: `Bearer ${this.config.connectionToken}`,
451
+ Accept: "application/json"
452
+ };
453
+ }
454
+ };
455
+ async function readJson(response) {
456
+ const text = await response.text();
457
+ return text.trim().length > 0 ? JSON.parse(text) : null;
458
+ }
459
+ function isRecord2(value) {
460
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
461
+ }
462
+ function isHeartbeatTick(value) {
463
+ return isRecord2(value) && typeof value.tick_id === "string" && typeof value.community_agent_id === "string" && typeof value.reason === "string";
464
+ }
465
+ function isKeepaliveResponse(value) {
466
+ return isRecord2(value) && value.ok === true && typeof value.server_time === "string";
467
+ }
468
+ function isDirectMessageRecord(value) {
469
+ return isRecord2(value) && typeof value.dm_id === "string" && typeof value.envelope_id === "string" && typeof value.sender_agent_id === "string" && typeof value.recipient_agent_id === "string" && typeof value.message === "string" && typeof value.created_at === "string";
470
+ }
471
+ function isDirectMessageInboxResponse(value) {
472
+ return isRecord2(value) && value.ok === true && Array.isArray(value.items) && value.items.every((item) => isDirectMessageRecord(item)) && (value.next_cursor === null || typeof value.next_cursor === "string");
473
+ }
474
+ function isEnvelopeInboxResponse(value) {
475
+ return isRecord2(value) && value.ok === true && Array.isArray(value.items) && value.items.every(
476
+ (item) => isRecord2(item) && typeof item.delivery_id === "string" && typeof item.envelope_id === "string" && typeof item.sender_agent_id === "string" && typeof item.recipient_agent_id === "string" && typeof item.type === "string" && isRecord2(item.envelope)
477
+ ) && (value.next_cursor === null || typeof value.next_cursor === "string");
478
+ }
479
+ function isCreateRoomResponse(value) {
480
+ return isRecord2(value) && value.ok === true && value.accepted === true && isRecord2(value.room) && typeof value.room.room_id === "string" && typeof value.room.topic === "string" && Array.isArray(value.room.members);
481
+ }
482
+ function isSkillOfferRecord(value) {
483
+ return isRecord2(value) && typeof value.offer_id === "string" && typeof value.envelope_id === "string" && typeof value.sender_agent_id === "string" && typeof value.recipient_agent_id === "string" && typeof value.query_id === "string" && typeof value.skill_slug === "string" && typeof value.name === "string" && typeof value.description === "string" && typeof value.source === "string" && typeof value.risk_level === "string" && typeof value.recommendation_reason === "string" && typeof value.status === "string" && typeof value.created_at === "string";
484
+ }
485
+ function isSkillOfferInboxResponse(value) {
486
+ return isRecord2(value) && value.ok === true && Array.isArray(value.items) && value.items.every((item) => isSkillOfferRecord(item)) && (value.next_cursor === null || typeof value.next_cursor === "string");
487
+ }
488
+ function isAgentListItem(value) {
489
+ return isRecord2(value) && typeof value.agent_id === "string" && typeof value.display_name === "string" && typeof value.online === "boolean" && (value.last_keepalive_at === null || typeof value.last_keepalive_at === "undefined" || typeof value.last_keepalive_at === "string");
490
+ }
491
+ function isAgentListResponse(value) {
492
+ return isRecord2(value) && value.ok === true && Array.isArray(value.items) && value.items.every(isAgentListItem);
493
+ }
494
+
495
+ // src/intent.ts
496
+ import { randomUUID as randomUUID2 } from "node:crypto";
497
+ function createIntentFromTick(tick) {
498
+ const acceptanceGreeting = tick.opportunities?.find((item) => item.type === "acceptance_demo_greeting");
499
+ if (acceptanceGreeting && typeof acceptanceGreeting.message === "string") {
500
+ return baseIntent(tick, "propose_user_conversation", {
501
+ topic: "Acceptance demo greeting",
502
+ reason: "Community heartbeat requested a visible greeting for local acceptance testing.",
503
+ message: acceptanceGreeting.message,
504
+ cadence_seconds: typeof acceptanceGreeting.cadence_seconds === "number" ? acceptanceGreeting.cadence_seconds : void 0
505
+ });
506
+ }
507
+ const staleTask = tick.opportunities?.find((item) => item.type === "stale_local_task");
508
+ if (!staleTask) {
509
+ return baseIntent(tick, "no_op", {});
510
+ }
511
+ return baseIntent(tick, "propose_user_conversation", {
512
+ topic: "Stale local task",
513
+ reason: "Heartbeat reported a stale local task.",
514
+ local_ref: staleTask.local_ref
515
+ });
516
+ }
517
+ function baseIntent(tick, intentType, payload) {
518
+ return {
519
+ intent_id: `intent_${randomUUID2()}`,
520
+ tick_id: tick.tick_id,
521
+ community_agent_id: tick.community_agent_id,
522
+ intent_type: intentType,
523
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
524
+ payload
525
+ };
526
+ }
527
+
528
+ // src/workspace.ts
529
+ import { randomUUID as randomUUID3 } from "node:crypto";
530
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
531
+ import path2 from "node:path";
532
+
533
+ // src/protocol.ts
534
+ var AGENTCOMM_PROTOCOL_VERSION = "dap-mvp-0.1";
535
+ var AGENTCOMM_BRIDGE_PACKAGE_VERSION = "0.1.2";
536
+
537
+ // src/workspace.ts
538
+ var WORKSPACE_FOLDER = ".community";
539
+ var STATE_VERSION = 1;
540
+ async function resolvePlatformWorkspacePaths(input) {
541
+ const rootDir = await resolvePlatformWorkspaceRootDir(input);
542
+ const strategy = await resolvePlatformWorkspaceStrategy(input);
543
+ return buildWorkspacePaths(rootDir, strategy);
544
+ }
545
+ async function resolvePlatformWorkspaceRootDir(input) {
546
+ if (input.agentDir) {
547
+ return path2.join(input.agentDir, "community");
548
+ }
549
+ const scannedAgentDir = await detectSingleAgentDir(input.workspaceDir, input.stateDir, input.homeDir);
550
+ if (scannedAgentDir) {
551
+ return path2.join(scannedAgentDir, "community");
552
+ }
553
+ const workspaceDir = input.workspaceDir ?? resolveWorkspaceDirFromState(input.stateDir) ?? resolveWorkspaceDirFromStateRoot(process.cwd()) ?? process.cwd();
554
+ const safeAgentId = sanitizeSegment(input.communityAgentId);
555
+ return path2.join(workspaceDir, WORKSPACE_FOLDER, safeAgentId);
556
+ }
557
+ async function resolvePlatformWorkspaceStrategy(input) {
558
+ if (input.agentDir) {
559
+ return "agentDir";
560
+ }
561
+ const scannedAgentDir = await detectSingleAgentDir(input.workspaceDir, input.stateDir, input.homeDir);
562
+ if (scannedAgentDir) {
563
+ return "openclaw-state-scan";
564
+ }
565
+ return "workspace-fallback";
566
+ }
567
+ function buildWorkspacePaths(rootDir, strategy) {
568
+ return {
569
+ strategy,
570
+ rootDir,
571
+ inboxDir: path2.join(rootDir, "inbox"),
572
+ feedCacheDir: path2.join(rootDir, "feed-cache"),
573
+ outboxDir: path2.join(rootDir, "outbox"),
574
+ skillsDir: path2.join(rootDir, "skills"),
575
+ configPatchesDir: path2.join(rootDir, "config-patches"),
576
+ confirmationsDir: path2.join(rootDir, "confirmations"),
577
+ personaDir: path2.join(rootDir, "persona"),
578
+ roomsDir: path2.join(rootDir, "rooms"),
579
+ workspaceMetaPath: path2.join(rootDir, "workspace.json"),
580
+ statePath: path2.join(rootDir, "state.json"),
581
+ personaProfilePath: path2.join(rootDir, "persona", "profile.json"),
582
+ auditLogPath: path2.join(rootDir, "persona", "audit-log.jsonl"),
583
+ delegationPolicyPath: path2.join(rootDir, "persona", "delegation-policy.json"),
584
+ activityLogPath: path2.join(rootDir, "persona", "activity-log.jsonl"),
585
+ subagentLoopStatePath: path2.join(rootDir, "persona", "subagent-loop-state.json"),
586
+ receivedDirectMessagesPath: path2.join(rootDir, "inbox", "received-direct-messages.jsonl"),
587
+ receivedEnvelopesPath: path2.join(rootDir, "inbox", "received-envelopes.jsonl"),
588
+ sentDirectMessagesPath: path2.join(rootDir, "outbox", "sent-direct-messages.jsonl"),
589
+ scheduleStatePath: path2.join(rootDir, "persona", "schedule-state.json"),
590
+ roomsIndexPath: path2.join(rootDir, "rooms", "index.json"),
591
+ meetingSummariesPath: path2.join(rootDir, "rooms", "meeting-summaries.jsonl"),
592
+ postsPath: path2.join(rootDir, "feed-cache", "posts.jsonl"),
593
+ envelopesPath: path2.join(rootDir, "outbox", "envelopes.jsonl"),
594
+ skillsIndexPath: path2.join(rootDir, "skills", "index.json"),
595
+ skillOffersPath: path2.join(rootDir, "skills", "offers.jsonl"),
596
+ skillOffersStagingDir: path2.join(rootDir, "skills", "staging"),
597
+ pendingActionsPath: path2.join(rootDir, "confirmations", "pending-actions.jsonl"),
598
+ actionDecisionsPath: path2.join(rootDir, "confirmations", "action-decisions.jsonl"),
599
+ actionResultsPath: path2.join(rootDir, "confirmations", "action-results.jsonl"),
600
+ configPatchesIndexPath: path2.join(rootDir, "config-patches", "index.json")
601
+ };
602
+ }
603
+ function resolveWorkspaceDirFromState(stateDir) {
604
+ if (!stateDir || !path2.isAbsolute(stateDir)) {
605
+ return void 0;
606
+ }
607
+ return path2.join(stateDir, "workspace");
608
+ }
609
+ function resolveWorkspaceDirFromStateRoot(candidate) {
610
+ if (!candidate || !path2.isAbsolute(candidate) || path2.basename(candidate) !== ".openclaw") {
611
+ return void 0;
612
+ }
613
+ return path2.join(candidate, "workspace");
614
+ }
615
+ async function detectSingleAgentDir(workspaceDir, stateDir, homeDir) {
616
+ const roots = [
617
+ workspaceDir ? path2.join(workspaceDir, ".openclaw", "agents") : void 0,
618
+ homeDir ? path2.join(homeDir, ".openclaw", "agents") : void 0,
619
+ stateDir ? path2.join(stateDir, "agents") : void 0,
620
+ homeDir ? path2.join(homeDir, ".openclaw", "state", "agents") : void 0
621
+ ].filter((value) => typeof value === "string" && value.length > 0);
622
+ for (const agentsRoot of roots) {
623
+ try {
624
+ const entries = await readdir(agentsRoot, { withFileTypes: true });
625
+ const agentDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path2.join(agentsRoot, entry.name, "agent"));
626
+ if (agentDirs.length === 1) {
627
+ return agentDirs[0];
628
+ }
629
+ } catch {
630
+ }
631
+ }
632
+ return void 0;
633
+ }
634
+ async function ensurePlatformWorkspace(paths, communityAgentId) {
635
+ await mkdir(paths.inboxDir, { recursive: true });
636
+ await mkdir(paths.feedCacheDir, { recursive: true });
637
+ await mkdir(paths.outboxDir, { recursive: true });
638
+ await mkdir(paths.skillsDir, { recursive: true });
639
+ await mkdir(paths.skillOffersStagingDir, { recursive: true });
640
+ await mkdir(paths.configPatchesDir, { recursive: true });
641
+ await mkdir(paths.confirmationsDir, { recursive: true });
642
+ await mkdir(paths.personaDir, { recursive: true });
643
+ await mkdir(paths.roomsDir, { recursive: true });
644
+ const now = (/* @__PURE__ */ new Date()).toISOString();
645
+ const current = await readPlatformWorkspaceState(paths);
646
+ const state = current ?? {
647
+ version: STATE_VERSION,
648
+ communityAgentId,
649
+ createdAt: now,
650
+ updatedAt: now
651
+ };
652
+ if (!current) {
653
+ await writePlatformWorkspaceState(paths, state);
654
+ }
655
+ await ensureWorkspaceScaffold(paths, communityAgentId);
656
+ return state;
657
+ }
658
+ async function readPlatformWorkspaceState(paths) {
659
+ try {
660
+ const raw = await readFile(paths.statePath, "utf8");
661
+ const parsed = JSON.parse(raw);
662
+ if (!isPlatformWorkspaceState(parsed)) {
663
+ return null;
664
+ }
665
+ return parsed;
666
+ } catch {
667
+ return null;
668
+ }
669
+ }
670
+ async function writePlatformWorkspaceState(paths, state) {
671
+ await writeFile(paths.statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
672
+ }
673
+ async function writePersonaSubagentInfo(paths, communityAgentId, subagent) {
674
+ let profile;
675
+ try {
676
+ const raw = await readFile(paths.personaProfilePath, "utf8");
677
+ const parsed = JSON.parse(raw);
678
+ profile = isRecord3(parsed) ? parsed : {};
679
+ } catch {
680
+ profile = {};
681
+ }
682
+ profile = {
683
+ ...createInitialPersonaProfile(communityAgentId),
684
+ ...profile,
685
+ communityAgentId,
686
+ personaId: readString2(profile.personaId) ?? `persona_${sanitizeSegment(communityAgentId)}`,
687
+ subagent
688
+ };
689
+ profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
690
+ await writeFile(paths.personaProfilePath, JSON.stringify(profile, null, 2) + "\n", "utf8");
691
+ }
692
+ async function readPersonaProfile(paths) {
693
+ try {
694
+ const raw = await readFile(paths.personaProfilePath, "utf8");
695
+ const parsed = JSON.parse(raw);
696
+ return isRecord3(parsed) ? parsed : null;
697
+ } catch (error) {
698
+ return {
699
+ readError: error instanceof Error ? error.message : "unknown persona profile read error",
700
+ path: paths.personaProfilePath
701
+ };
702
+ }
703
+ }
704
+ async function readDelegationPolicy(paths, communityAgentId) {
705
+ try {
706
+ const raw = await readFile(paths.delegationPolicyPath, "utf8");
707
+ const parsed = JSON.parse(raw);
708
+ if (isDelegationPolicyLike(parsed)) {
709
+ const policy2 = mergeDelegationPolicyDefaults(parsed, createDefaultDelegationPolicy(communityAgentId));
710
+ if (JSON.stringify(policy2) !== JSON.stringify(parsed)) {
711
+ await writeFile(paths.delegationPolicyPath, JSON.stringify(policy2, null, 2) + "\n", "utf8");
712
+ }
713
+ return policy2;
714
+ }
715
+ } catch {
716
+ }
717
+ const policy = createDefaultDelegationPolicy(communityAgentId);
718
+ await writeFile(paths.delegationPolicyPath, JSON.stringify(policy, null, 2) + "\n", "utf8");
719
+ return policy;
720
+ }
721
+ function evaluateDelegationPolicy(policy, input) {
722
+ const matchedRule = policy.rules.find((rule) => rule.action === input.action && rule.risk === input.risk);
723
+ return {
724
+ decision: matchedRule?.decision ?? policy.defaultDecision,
725
+ matchedRuleId: matchedRule?.id ?? null
726
+ };
727
+ }
728
+ async function appendAuditLog(paths, record) {
729
+ const audit = {
730
+ audit_id: `audit_${randomUUID3()}`,
731
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
732
+ ...record
733
+ };
734
+ await writeFile(paths.auditLogPath, JSON.stringify(audit) + "\n", { encoding: "utf8", flag: "a" });
735
+ return audit;
736
+ }
737
+ async function appendAgentActivity(paths, record) {
738
+ const activity = {
739
+ activity_id: `act_${randomUUID3()}`,
740
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
741
+ visibility: "user_audit_only",
742
+ ...record
743
+ };
744
+ await writeFile(paths.activityLogPath, JSON.stringify(activity) + "\n", { encoding: "utf8", flag: "a" });
745
+ return activity;
746
+ }
747
+ async function markWorkspaceKeepalive(paths, communityAgentId, at) {
748
+ const existing = await readPlatformWorkspaceState(paths);
749
+ const state = existing ?? {
750
+ version: STATE_VERSION,
751
+ communityAgentId,
752
+ createdAt: at,
753
+ updatedAt: at
754
+ };
755
+ state.lastKeepaliveAt = at;
756
+ state.updatedAt = at;
757
+ await writePlatformWorkspaceState(paths, state);
758
+ }
759
+ async function appendWorkspaceEnvelope(paths, communityAgentId, envelope) {
760
+ const line = JSON.stringify(envelope) + "\n";
761
+ await writeFile(paths.envelopesPath, line, { encoding: "utf8", flag: "a" });
762
+ if (envelope.type === "dm.send") {
763
+ await writeFile(paths.sentDirectMessagesPath, line, { encoding: "utf8", flag: "a" });
764
+ }
765
+ if (envelope.type === "post.publish") {
766
+ await writeFile(paths.postsPath, line, { encoding: "utf8", flag: "a" });
767
+ }
768
+ const at = (/* @__PURE__ */ new Date()).toISOString();
769
+ const existing = await readPlatformWorkspaceState(paths);
770
+ const state = existing ?? {
771
+ version: STATE_VERSION,
772
+ communityAgentId,
773
+ createdAt: at,
774
+ updatedAt: at
775
+ };
776
+ state.lastEnvelopeAt = at;
777
+ state.lastEnvelopeId = envelope.envelope_id;
778
+ state.updatedAt = at;
779
+ await writePlatformWorkspaceState(paths, state);
780
+ }
781
+ async function appendStagedSkillOffers(paths, communityAgentId, offers, nextCursor) {
782
+ if (offers.length > 0) {
783
+ const lines = offers.map((offer) => JSON.stringify(offer)).join("\n") + "\n";
784
+ await writeFile(paths.skillOffersPath, lines, { encoding: "utf8", flag: "a" });
785
+ for (const offer of offers) {
786
+ const manifestPath = path2.join(paths.skillOffersStagingDir, `${sanitizeSegment(offer.offer_id)}.json`);
787
+ await writeFile(manifestPath, JSON.stringify(offer, null, 2) + "\n", "utf8");
788
+ }
789
+ const index = await readSkillOfferIndex(paths);
790
+ const merged = mergeSkillOffers(index, offers);
791
+ await writeFile(paths.skillsIndexPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
792
+ }
793
+ const at = (/* @__PURE__ */ new Date()).toISOString();
794
+ const existing = await readPlatformWorkspaceState(paths);
795
+ const state = existing ?? {
796
+ version: STATE_VERSION,
797
+ communityAgentId,
798
+ createdAt: at,
799
+ updatedAt: at
800
+ };
801
+ state.lastSkillOfferSyncAt = at;
802
+ if (typeof nextCursor === "string" && nextCursor.length > 0) {
803
+ state.lastSkillOfferCursor = nextCursor;
804
+ }
805
+ state.updatedAt = at;
806
+ await writePlatformWorkspaceState(paths, state);
807
+ }
808
+ async function appendPendingUserActions(paths, actions) {
809
+ if (actions.length === 0) {
810
+ return { ok: true, written: 0, pendingActionsPath: paths.pendingActionsPath };
811
+ }
812
+ const existing = await readPendingUserActions(paths);
813
+ const existingIds = new Set(existing.map((action) => action.action_id));
814
+ const nextActions = actions.filter((action) => !existingIds.has(action.action_id));
815
+ if (nextActions.length === 0) {
816
+ return { ok: true, written: 0, pendingActionsPath: paths.pendingActionsPath };
817
+ }
818
+ const lines = nextActions.map((action) => JSON.stringify(action)).join("\n") + "\n";
819
+ await writeFile(paths.pendingActionsPath, lines, { encoding: "utf8", flag: "a" });
820
+ return {
821
+ ok: true,
822
+ written: nextActions.length,
823
+ pendingActionsPath: paths.pendingActionsPath
824
+ };
825
+ }
826
+ async function readPendingUserActions(paths) {
827
+ try {
828
+ const raw = await readFile(paths.pendingActionsPath, "utf8");
829
+ return raw.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isPendingUserActionLike);
830
+ } catch {
831
+ return [];
832
+ }
833
+ }
834
+ async function readReceivedDirectMessages(paths) {
835
+ try {
836
+ const raw = await readFile(paths.receivedDirectMessagesPath, "utf8");
837
+ return raw.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isDirectMessageRecordLike);
838
+ } catch {
839
+ return [];
840
+ }
841
+ }
842
+ async function readSentDirectMessages(paths) {
843
+ try {
844
+ const raw = await readFile(paths.sentDirectMessagesPath, "utf8");
845
+ return raw.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isCommunityEnvelopeLike);
846
+ } catch {
847
+ return [];
848
+ }
849
+ }
850
+ async function readUserActionDecisions(paths) {
851
+ try {
852
+ const raw = await readFile(paths.actionDecisionsPath, "utf8");
853
+ return raw.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isUserActionDecisionLike);
854
+ } catch {
855
+ return [];
856
+ }
857
+ }
858
+ async function decidePendingUserAction(paths, input) {
859
+ const actionId = input.actionId.trim();
860
+ if (actionId.length === 0) {
861
+ throw new Error("actionId is required");
862
+ }
863
+ const actions = await readPendingUserActions(paths);
864
+ const action = actions.find((item) => item.action_id === actionId);
865
+ if (!action) {
866
+ throw new Error(`pending action not found: ${actionId}`);
867
+ }
868
+ const decisions = await readUserActionDecisions(paths);
869
+ const existing = decisions.find((item) => item.action_id === actionId);
870
+ if (existing) {
871
+ return {
872
+ ok: true,
873
+ actionId,
874
+ alreadyDecided: true,
875
+ decision: existing,
876
+ pendingActionsPath: paths.pendingActionsPath,
877
+ actionDecisionsPath: paths.actionDecisionsPath
878
+ };
879
+ }
880
+ const decision = {
881
+ decision_id: `decision_${randomUUID3()}`,
882
+ action_id: actionId,
883
+ decision: input.decision,
884
+ decided_at: (/* @__PURE__ */ new Date()).toISOString(),
885
+ decided_by: input.decidedBy ?? "bridge",
886
+ ...input.reason && input.reason.trim().length > 0 ? { reason: input.reason.trim() } : {}
887
+ };
888
+ await writeFile(paths.actionDecisionsPath, JSON.stringify(decision) + "\n", { encoding: "utf8", flag: "a" });
889
+ return {
890
+ ok: true,
891
+ actionId,
892
+ alreadyDecided: false,
893
+ action,
894
+ decision,
895
+ pendingActionsPath: paths.pendingActionsPath,
896
+ actionDecisionsPath: paths.actionDecisionsPath
897
+ };
898
+ }
899
+ async function readUserActionResults(paths) {
900
+ try {
901
+ const raw = await readFile(paths.actionResultsPath, "utf8");
902
+ return raw.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isUserActionResultLike);
903
+ } catch {
904
+ return [];
905
+ }
906
+ }
907
+ async function appendUserActionResult(paths, result) {
908
+ await writeFile(paths.actionResultsPath, JSON.stringify(result) + "\n", { encoding: "utf8", flag: "a" });
909
+ return {
910
+ ok: true,
911
+ actionId: result.action_id,
912
+ status: result.status,
913
+ actionResultsPath: paths.actionResultsPath
914
+ };
915
+ }
916
+ function createInstallSkillActionResult(actionId, status, payload) {
917
+ const now = (/* @__PURE__ */ new Date()).toISOString();
918
+ return {
919
+ result_id: `result_${randomUUID3()}`,
920
+ action_id: actionId,
921
+ kind: "install_skill",
922
+ status,
923
+ created_at: now,
924
+ updated_at: now,
925
+ payload
926
+ };
927
+ }
928
+ function createSkillOfferPendingActions(communityAgentId, offers) {
929
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
930
+ return offers.map((offer) => ({
931
+ action_id: `action_install_skill_${sanitizeSegment(offer.offer_id)}`,
932
+ kind: "install_skill",
933
+ source: "openclaw-bridge",
934
+ community_agent_id: communityAgentId,
935
+ status: "pending_user_confirmation",
936
+ user_confirmation_required: true,
937
+ created_at: createdAt,
938
+ title: `\u5B89\u88C5 Skill\uFF1A${offer.name || offer.skill_slug}`,
939
+ summary: offer.recommendation_reason,
940
+ payload: {
941
+ offer_id: offer.offer_id,
942
+ skill_slug: offer.skill_slug,
943
+ name: offer.name,
944
+ description: offer.description,
945
+ risk_level: offer.risk_level,
946
+ install_hint: offer.install_hint,
947
+ source: offer.source,
948
+ created_at: offer.created_at
949
+ }
950
+ }));
951
+ }
952
+ async function appendSubagentHandoffItems(subagentWorkspaceDir, communityAgentId, items) {
953
+ if (!subagentWorkspaceDir || items.length === 0) {
954
+ return { ok: false, written: 0, reason: "subagent workspace unavailable" };
955
+ }
956
+ const paths = resolveSubagentHandoffPaths(subagentWorkspaceDir);
957
+ await mkdir(paths.inboxDir, { recursive: true });
958
+ const lines = items.map((item) => JSON.stringify(item)).join("\n") + "\n";
959
+ await writeFile(paths.queuePath, lines, { encoding: "utf8", flag: "a" });
960
+ return {
961
+ ok: true,
962
+ written: items.length,
963
+ communityAgentId,
964
+ queuePath: paths.queuePath
965
+ };
966
+ }
967
+ async function readSubagentHandoffItems(subagentWorkspaceDir) {
968
+ if (!subagentWorkspaceDir) {
969
+ return [];
970
+ }
971
+ try {
972
+ const paths = resolveSubagentHandoffPaths(subagentWorkspaceDir);
973
+ const raw = await readFile(paths.queuePath, "utf8");
974
+ return raw.split(/\n+/).filter(Boolean).map((line) => JSON.parse(line)).filter(isSubagentHandoffItemLike);
975
+ } catch {
976
+ return [];
977
+ }
978
+ }
979
+ async function readSubagentLoopState(paths) {
980
+ try {
981
+ const raw = await readFile(paths.subagentLoopStatePath, "utf8");
982
+ const parsed = JSON.parse(raw);
983
+ if (isSubagentLoopStateLike(parsed)) {
984
+ return parsed;
985
+ }
986
+ } catch {
987
+ }
988
+ return {
989
+ version: 1,
990
+ processedHandoffIds: []
991
+ };
992
+ }
993
+ async function writeSubagentLoopState(paths, state) {
994
+ const nextState = {
995
+ version: 1,
996
+ processedHandoffIds: [...new Set(state.processedHandoffIds)].slice(-500),
997
+ ...state.lastRunAt ? { lastRunAt: state.lastRunAt } : {},
998
+ ...state.lastProcessedHandoffId ? { lastProcessedHandoffId: state.lastProcessedHandoffId } : {},
999
+ ...state.lastError ? { lastError: state.lastError } : {}
1000
+ };
1001
+ await writeFile(paths.subagentLoopStatePath, JSON.stringify(nextState, null, 2) + "\n", "utf8");
1002
+ return nextState;
1003
+ }
1004
+ function createDirectMessageHandoffItems(communityAgentId, messages) {
1005
+ const receivedAt = (/* @__PURE__ */ new Date()).toISOString();
1006
+ return messages.map((message) => ({
1007
+ handoff_id: `handoff_${randomUUID3()}`,
1008
+ kind: "direct_message",
1009
+ source: "openclaw-bridge",
1010
+ community_agent_id: communityAgentId,
1011
+ envelope_id: message.envelope_id,
1012
+ status: "pending_subagent_review",
1013
+ user_confirmation_required: false,
1014
+ received_at: receivedAt,
1015
+ payload: {
1016
+ dm_id: message.dm_id,
1017
+ sender_agent_id: message.sender_agent_id,
1018
+ recipient_agent_id: message.recipient_agent_id,
1019
+ message: message.message,
1020
+ intent: message.intent ?? null,
1021
+ visibility: message.visibility ?? "direct",
1022
+ created_at: message.created_at
1023
+ }
1024
+ }));
1025
+ }
1026
+ function createSkillOfferHandoffItems(communityAgentId, offers) {
1027
+ const receivedAt = (/* @__PURE__ */ new Date()).toISOString();
1028
+ return offers.map((offer) => ({
1029
+ handoff_id: `handoff_${randomUUID3()}`,
1030
+ kind: "skill_offer",
1031
+ source: "openclaw-bridge",
1032
+ community_agent_id: communityAgentId,
1033
+ envelope_id: offer.envelope_id,
1034
+ status: "pending_subagent_review",
1035
+ user_confirmation_required: true,
1036
+ received_at: receivedAt,
1037
+ payload: {
1038
+ offer_id: offer.offer_id,
1039
+ skill_slug: offer.skill_slug,
1040
+ name: offer.name,
1041
+ description: offer.description,
1042
+ recommendation_reason: offer.recommendation_reason,
1043
+ risk_level: offer.risk_level,
1044
+ status: offer.status,
1045
+ install_hint: offer.install_hint,
1046
+ created_at: offer.created_at
1047
+ }
1048
+ }));
1049
+ }
1050
+ function createMeetingSummaryHandoffItem(communityAgentId, input) {
1051
+ const receivedAt = (/* @__PURE__ */ new Date()).toISOString();
1052
+ return {
1053
+ handoff_id: `handoff_meeting_summary_${sanitizeSegment(input.room.room_id)}_${randomUUID3()}`,
1054
+ kind: "meeting_summary",
1055
+ source: "openclaw-bridge",
1056
+ community_agent_id: communityAgentId,
1057
+ envelope_id: `room_transcript_${sanitizeSegment(input.room.room_id)}`,
1058
+ status: "pending_subagent_review",
1059
+ user_confirmation_required: true,
1060
+ received_at: receivedAt,
1061
+ payload: {
1062
+ room_id: input.room.room_id,
1063
+ topic: input.room.topic,
1064
+ members: input.room.members,
1065
+ timezone: input.timezone,
1066
+ current_date: input.currentDate,
1067
+ current_time: input.currentTime,
1068
+ transcript: input.messages.map((message) => ({
1069
+ message_id: message.message_id ?? null,
1070
+ envelope_id: message.envelope_id,
1071
+ sender_agent_id: message.sender_agent_id,
1072
+ speaker_kind: message.speaker_kind,
1073
+ speaker_label: message.speaker_label ?? null,
1074
+ message: message.message,
1075
+ created_at: message.created_at
1076
+ }))
1077
+ }
1078
+ };
1079
+ }
1080
+ function createDirectMessageReplyPendingActions(communityAgentId, messages) {
1081
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1082
+ return messages.map((message) => ({
1083
+ action_id: `action_reply_dm_${sanitizeSegment(message.dm_id)}`,
1084
+ kind: "reply_to_dm",
1085
+ source: "openclaw-bridge",
1086
+ community_agent_id: communityAgentId,
1087
+ status: "pending_user_confirmation",
1088
+ user_confirmation_required: true,
1089
+ created_at: createdAt,
1090
+ title: `\u56DE\u590D ${message.sender_agent_id}`,
1091
+ summary: `\u6536\u5230\u4E00\u6761\u9700\u8981\u7528\u6237\u51B3\u5B9A\u5982\u4F55\u56DE\u590D\u7684\u79C1\u4FE1\uFF1A${message.message}`,
1092
+ payload: {
1093
+ dm_id: message.dm_id,
1094
+ envelope_id: message.envelope_id,
1095
+ sender_agent_id: message.sender_agent_id,
1096
+ recipient_agent_id: message.recipient_agent_id,
1097
+ message: message.message,
1098
+ intent: message.intent ?? null,
1099
+ risk_level: "medium",
1100
+ suggested_action: "ask_user_to_reply",
1101
+ created_at: message.created_at
1102
+ }
1103
+ }));
1104
+ }
1105
+ function resolveSubagentHandoffPaths(subagentWorkspaceDir) {
1106
+ const rootDir = path2.join(subagentWorkspaceDir, ".agentcomm");
1107
+ return {
1108
+ rootDir,
1109
+ inboxDir: path2.join(rootDir, "inbox"),
1110
+ queuePath: path2.join(rootDir, "inbox", "handoff.jsonl")
1111
+ };
1112
+ }
1113
+ async function readStagedSkillOffers(paths) {
1114
+ try {
1115
+ const entries = await readdir(paths.skillOffersStagingDir, { withFileTypes: true });
1116
+ const offers = [];
1117
+ for (const entry of entries) {
1118
+ if (!entry.isFile() || !entry.name.endsWith(".json")) {
1119
+ continue;
1120
+ }
1121
+ const raw = await readFile(path2.join(paths.skillOffersStagingDir, entry.name), "utf8");
1122
+ const parsed = JSON.parse(raw);
1123
+ if (isSkillOfferRecordLike(parsed)) {
1124
+ offers.push(parsed);
1125
+ }
1126
+ }
1127
+ return offers.sort((a, b) => String(b.created_at).localeCompare(String(a.created_at)));
1128
+ } catch {
1129
+ return [];
1130
+ }
1131
+ }
1132
+ async function writeStagedSkillOffer(paths, offer) {
1133
+ const manifestPath = path2.join(paths.skillOffersStagingDir, `${sanitizeSegment(offer.offer_id)}.json`);
1134
+ await writeFile(manifestPath, JSON.stringify(offer, null, 2) + "\n", "utf8");
1135
+ const index = await readSkillOfferIndex(paths);
1136
+ const merged = mergeSkillOffers(index, [offer]);
1137
+ await writeFile(paths.skillsIndexPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
1138
+ }
1139
+ async function appendReceivedDirectMessages(paths, communityAgentId, messages, nextCursor) {
1140
+ if (messages.length > 0) {
1141
+ const lines = messages.map((message) => JSON.stringify(message)).join("\n") + "\n";
1142
+ await writeFile(paths.receivedDirectMessagesPath, lines, { encoding: "utf8", flag: "a" });
1143
+ }
1144
+ const at = (/* @__PURE__ */ new Date()).toISOString();
1145
+ const existing = await readPlatformWorkspaceState(paths);
1146
+ const state = existing ?? {
1147
+ version: STATE_VERSION,
1148
+ communityAgentId,
1149
+ createdAt: at,
1150
+ updatedAt: at
1151
+ };
1152
+ state.lastInboxSyncAt = at;
1153
+ if (typeof nextCursor === "string" && nextCursor.length > 0) {
1154
+ state.lastInboxCursor = nextCursor;
1155
+ }
1156
+ state.updatedAt = at;
1157
+ await writePlatformWorkspaceState(paths, state);
1158
+ }
1159
+ async function appendReceivedEnvelopes(paths, communityAgentId, items, nextCursor) {
1160
+ if (items.length > 0) {
1161
+ const lines = items.map((item) => JSON.stringify(item)).join("\n") + "\n";
1162
+ await writeFile(paths.receivedEnvelopesPath, lines, { encoding: "utf8", flag: "a" });
1163
+ }
1164
+ const at = (/* @__PURE__ */ new Date()).toISOString();
1165
+ const existing = await readPlatformWorkspaceState(paths);
1166
+ const state = existing ?? {
1167
+ version: STATE_VERSION,
1168
+ communityAgentId,
1169
+ createdAt: at,
1170
+ updatedAt: at
1171
+ };
1172
+ state.lastEnvelopeSyncAt = at;
1173
+ if (typeof nextCursor === "string" && nextCursor.length > 0) {
1174
+ state.lastEnvelopeCursor = nextCursor;
1175
+ }
1176
+ state.updatedAt = at;
1177
+ await writePlatformWorkspaceState(paths, state);
1178
+ }
1179
+ async function readScheduleState(paths) {
1180
+ try {
1181
+ const raw = await readFile(paths.scheduleStatePath, "utf8");
1182
+ const parsed = JSON.parse(raw);
1183
+ return isRecord3(parsed) ? parsed : {};
1184
+ } catch {
1185
+ return {};
1186
+ }
1187
+ }
1188
+ async function writeScheduleState(paths, state) {
1189
+ await writeFile(paths.scheduleStatePath, JSON.stringify(state, null, 2) + "\n", "utf8");
1190
+ }
1191
+ async function readRooms(paths) {
1192
+ try {
1193
+ const raw = await readFile(paths.roomsIndexPath, "utf8");
1194
+ const parsed = JSON.parse(raw);
1195
+ return Array.isArray(parsed) ? parsed.filter(isRoomRecordLike) : [];
1196
+ } catch {
1197
+ return [];
1198
+ }
1199
+ }
1200
+ async function upsertRoom(paths, room) {
1201
+ const rooms = await readRooms(paths);
1202
+ const byId = new Map(rooms.map((item) => [item.room_id, item]));
1203
+ byId.set(room.room_id, room);
1204
+ const next = [...byId.values()].sort((a, b) => timeValue(b.updated_at) - timeValue(a.updated_at));
1205
+ await writeFile(paths.roomsIndexPath, JSON.stringify(next, null, 2) + "\n", "utf8");
1206
+ return room;
1207
+ }
1208
+ async function readRoomMessages(paths, roomId) {
1209
+ try {
1210
+ const raw = await readFile(roomMessagesPath(paths, roomId), "utf8");
1211
+ return raw.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRoomMessageRecordLike);
1212
+ } catch {
1213
+ return [];
1214
+ }
1215
+ }
1216
+ async function writeMeetingSummary(paths, summary) {
1217
+ await mkdir(paths.roomsDir, { recursive: true });
1218
+ await writeFile(paths.meetingSummariesPath, JSON.stringify(summary) + "\n", { encoding: "utf8", flag: "a" });
1219
+ await writeFile(
1220
+ path2.join(paths.roomsDir, `${sanitizeSegment(summary.room_id)}.summary.json`),
1221
+ JSON.stringify(summary, null, 2) + "\n",
1222
+ "utf8"
1223
+ );
1224
+ return summary;
1225
+ }
1226
+ async function appendRoomMessages(paths, messages) {
1227
+ const byRoom = /* @__PURE__ */ new Map();
1228
+ for (const message of messages) {
1229
+ if (!message.room_id) continue;
1230
+ const items = byRoom.get(message.room_id) ?? [];
1231
+ items.push(message);
1232
+ byRoom.set(message.room_id, items);
1233
+ }
1234
+ for (const [roomId, items] of byRoom.entries()) {
1235
+ await mkdir(paths.roomsDir, { recursive: true });
1236
+ const existing = await readRoomMessages(paths, roomId);
1237
+ const existingEnvelopeIds = new Set(existing.map((message) => message.envelope_id));
1238
+ const next = items.filter((message) => !existingEnvelopeIds.has(message.envelope_id));
1239
+ if (next.length > 0) {
1240
+ await writeFile(roomMessagesPath(paths, roomId), next.map((message) => JSON.stringify(message)).join("\n") + "\n", {
1241
+ encoding: "utf8",
1242
+ flag: "a"
1243
+ });
1244
+ }
1245
+ }
1246
+ }
1247
+ async function ensureWorkspaceScaffold(paths, communityAgentId) {
1248
+ const meta = {
1249
+ version: STATE_VERSION,
1250
+ communityAgentId,
1251
+ strategy: paths.strategy,
1252
+ rootDir: paths.rootDir,
1253
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1254
+ layout: {
1255
+ persona: paths.personaDir,
1256
+ inbox: paths.inboxDir,
1257
+ feedCache: paths.feedCacheDir,
1258
+ outbox: paths.outboxDir,
1259
+ skills: paths.skillsDir,
1260
+ confirmations: paths.confirmationsDir,
1261
+ configPatches: paths.configPatchesDir,
1262
+ rooms: paths.roomsDir
1263
+ }
1264
+ };
1265
+ await ensureFile(paths.workspaceMetaPath, JSON.stringify(meta, null, 2) + "\n");
1266
+ await ensureFile(
1267
+ paths.personaProfilePath,
1268
+ JSON.stringify(createInitialPersonaProfile(communityAgentId), null, 2) + "\n"
1269
+ );
1270
+ await ensureFile(paths.auditLogPath, "");
1271
+ await ensureFile(paths.activityLogPath, "");
1272
+ await ensureFile(paths.subagentLoopStatePath, JSON.stringify({ version: 1, processedHandoffIds: [] }, null, 2) + "\n");
1273
+ await ensureFile(paths.delegationPolicyPath, JSON.stringify(createDefaultDelegationPolicy(communityAgentId), null, 2) + "\n");
1274
+ await ensureFile(paths.receivedDirectMessagesPath, "");
1275
+ await ensureFile(paths.receivedEnvelopesPath, "");
1276
+ await ensureFile(paths.sentDirectMessagesPath, "");
1277
+ await ensureFile(paths.scheduleStatePath, "{}\n");
1278
+ await ensureFile(paths.roomsIndexPath, "[]\n");
1279
+ await ensureFile(paths.meetingSummariesPath, "");
1280
+ await ensureFile(paths.postsPath, "");
1281
+ await ensureFile(paths.envelopesPath, "");
1282
+ await ensureFile(paths.skillsIndexPath, "[]\n");
1283
+ await ensureFile(paths.skillOffersPath, "");
1284
+ await ensureFile(paths.pendingActionsPath, "");
1285
+ await ensureFile(paths.actionDecisionsPath, "");
1286
+ await ensureFile(paths.actionResultsPath, "");
1287
+ await ensureFile(paths.configPatchesIndexPath, "[]\n");
1288
+ }
1289
+ function createInitialPersonaProfile(communityAgentId) {
1290
+ return {
1291
+ version: 1,
1292
+ personaId: `persona_${sanitizeSegment(communityAgentId)}`,
1293
+ communityAgentId,
1294
+ displayName: communityAgentId,
1295
+ mode: "bridge-managed-workspace",
1296
+ trustBoundary: "community-inputs-remain-in-persona-workspace-until-user-confirmation",
1297
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1298
+ profile: {
1299
+ bio: "Local Community Persona scaffold for AgentComm MVP.",
1300
+ allowedCommunityActions: ["post.publish", "dm.send", "skill.query", "skill.offer.stage"]
1301
+ }
1302
+ };
1303
+ }
1304
+ function createDefaultDelegationPolicy(communityAgentId) {
1305
+ return {
1306
+ protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
1307
+ principal: {
1308
+ userId: "local-user",
1309
+ mainAgentId: "openclaw-main",
1310
+ communityAgentId
1311
+ },
1312
+ defaultDecision: "ask",
1313
+ rules: [
1314
+ {
1315
+ id: "allow-low-risk-dm-receipt",
1316
+ action: "dm.receipt",
1317
+ risk: "low",
1318
+ decision: "allow"
1319
+ },
1320
+ {
1321
+ id: "allow-low-risk-dm-send",
1322
+ action: "dm.send",
1323
+ risk: "low",
1324
+ decision: "allow"
1325
+ },
1326
+ {
1327
+ id: "ask-medium-risk-dm-reply",
1328
+ action: "dm.reply",
1329
+ risk: "medium",
1330
+ decision: "ask"
1331
+ },
1332
+ {
1333
+ id: "allow-schedule-propose",
1334
+ action: "schedule.negotiate.propose",
1335
+ risk: "low",
1336
+ decision: "allow"
1337
+ },
1338
+ {
1339
+ id: "allow-schedule-counter",
1340
+ action: "schedule.negotiate.counter",
1341
+ risk: "low",
1342
+ decision: "allow"
1343
+ },
1344
+ {
1345
+ id: "allow-schedule-agree-pending",
1346
+ action: "schedule.negotiate.agree_pending",
1347
+ risk: "low",
1348
+ decision: "allow"
1349
+ },
1350
+ {
1351
+ id: "allow-schedule-decline",
1352
+ action: "schedule.negotiate.decline",
1353
+ risk: "low",
1354
+ decision: "allow"
1355
+ },
1356
+ {
1357
+ id: "allow-room-message-send",
1358
+ action: "room.message.send",
1359
+ risk: "low",
1360
+ decision: "allow"
1361
+ },
1362
+ {
1363
+ id: "allow-room-message-receive",
1364
+ action: "room.message.receive",
1365
+ risk: "low",
1366
+ decision: "allow"
1367
+ },
1368
+ {
1369
+ id: "ask-meeting-summary",
1370
+ action: "meeting.summary",
1371
+ risk: "medium",
1372
+ decision: "ask"
1373
+ },
1374
+ {
1375
+ id: "ask-schedule-commit",
1376
+ action: "schedule.negotiate.commit",
1377
+ risk: "medium",
1378
+ decision: "ask"
1379
+ }
1380
+ ],
1381
+ audit: {
1382
+ required: true
1383
+ },
1384
+ autoReceipt: {
1385
+ enabled: true,
1386
+ message: "\u5DF2\u6536\u5230\uFF0C\u4F1A\u8F6C\u544A\u6211\u7684\u7528\u6237\u3002"
1387
+ },
1388
+ scheduling: {
1389
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1390
+ available_windows: [
1391
+ {
1392
+ start: "2026-06-18T09:00:00+08:00",
1393
+ end: "2026-06-18T11:00:00+08:00"
1394
+ },
1395
+ {
1396
+ start: "2026-06-18T14:00:00+08:00",
1397
+ end: "2026-06-18T15:00:00+08:00"
1398
+ }
1399
+ ]
1400
+ }
1401
+ };
1402
+ }
1403
+ function mergeDelegationPolicyDefaults(policy, defaults) {
1404
+ const existingRuleIds = new Set(policy.rules.map((rule) => rule.id));
1405
+ const missingDefaultRules = defaults.rules.filter((rule) => !existingRuleIds.has(rule.id));
1406
+ return {
1407
+ ...defaults,
1408
+ ...policy,
1409
+ principal: {
1410
+ ...defaults.principal,
1411
+ ...policy.principal,
1412
+ communityAgentId: policy.principal?.communityAgentId ?? defaults.principal.communityAgentId
1413
+ },
1414
+ rules: [...policy.rules, ...missingDefaultRules],
1415
+ audit: policy.audit ?? defaults.audit,
1416
+ autoReceipt: policy.autoReceipt ?? defaults.autoReceipt,
1417
+ scheduling: policy.scheduling ?? defaults.scheduling
1418
+ };
1419
+ }
1420
+ async function readSkillOfferIndex(paths) {
1421
+ try {
1422
+ const raw = await readFile(paths.skillsIndexPath, "utf8");
1423
+ const parsed = JSON.parse(raw);
1424
+ return Array.isArray(parsed) ? parsed.filter(isSkillOfferRecordLike) : [];
1425
+ } catch {
1426
+ return [];
1427
+ }
1428
+ }
1429
+ function mergeSkillOffers(existing, incoming) {
1430
+ const byOfferId = new Map(existing.map((offer) => [offer.offer_id, offer]));
1431
+ for (const offer of incoming) {
1432
+ byOfferId.set(offer.offer_id, offer);
1433
+ }
1434
+ return [...byOfferId.values()].sort((a, b) => String(b.created_at).localeCompare(String(a.created_at)));
1435
+ }
1436
+ async function ensureFile(filePath, initialContent) {
1437
+ try {
1438
+ await readFile(filePath, "utf8");
1439
+ } catch {
1440
+ await writeFile(filePath, initialContent, "utf8");
1441
+ }
1442
+ }
1443
+ function sanitizeSegment(value) {
1444
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
1445
+ }
1446
+ function readString2(value) {
1447
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1448
+ }
1449
+ function isRecord3(value) {
1450
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1451
+ }
1452
+ function isPlatformWorkspaceState(value) {
1453
+ return Boolean(
1454
+ value && typeof value === "object" && !Array.isArray(value) && value.version === STATE_VERSION && typeof value.communityAgentId === "string" && typeof value.createdAt === "string" && typeof value.updatedAt === "string"
1455
+ );
1456
+ }
1457
+ function isSkillOfferRecordLike(value) {
1458
+ return Boolean(
1459
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.offer_id === "string" && typeof value.skill_slug === "string"
1460
+ );
1461
+ }
1462
+ function isDirectMessageRecordLike(value) {
1463
+ return Boolean(
1464
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.dm_id === "string" && typeof value.envelope_id === "string" && typeof value.sender_agent_id === "string" && typeof value.recipient_agent_id === "string" && typeof value.message === "string" && typeof value.created_at === "string"
1465
+ );
1466
+ }
1467
+ function isRoomRecordLike(value) {
1468
+ return Boolean(
1469
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.room_id === "string" && typeof value.topic === "string" && Array.isArray(value.members) && typeof value.created_by_agent_id === "string" && (value.status === "open" || value.status === "closed") && typeof value.created_at === "string" && typeof value.updated_at === "string"
1470
+ );
1471
+ }
1472
+ function isRoomMessageRecordLike(value) {
1473
+ return Boolean(
1474
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.room_id === "string" && typeof value.envelope_id === "string" && typeof value.sender_agent_id === "string" && (value.speaker_kind === "human" || value.speaker_kind === "agent") && typeof value.message === "string" && typeof value.created_at === "string"
1475
+ );
1476
+ }
1477
+ function isSubagentHandoffItemLike(value) {
1478
+ return Boolean(
1479
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.handoff_id === "string" && (value.kind === "direct_message" || value.kind === "skill_offer" || value.kind === "meeting_summary") && value.source === "openclaw-bridge" && typeof value.community_agent_id === "string" && typeof value.envelope_id === "string" && typeof value.received_at === "string" && isRecord3(value.payload)
1480
+ );
1481
+ }
1482
+ function isSubagentLoopStateLike(value) {
1483
+ return Boolean(
1484
+ value && typeof value === "object" && !Array.isArray(value) && value.version === 1 && Array.isArray(value.processedHandoffIds) && value.processedHandoffIds.every((item) => typeof item === "string")
1485
+ );
1486
+ }
1487
+ function isCommunityEnvelopeLike(value) {
1488
+ return Boolean(
1489
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.envelope_id === "string" && typeof value.type === "string" && typeof value.sender_agent_id === "string"
1490
+ );
1491
+ }
1492
+ function isPendingUserActionLike(value) {
1493
+ return Boolean(
1494
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.action_id === "string" && (value.kind === "install_skill" || value.kind === "reply_to_dm" || value.kind === "commit_schedule" || value.kind === "meeting_summary") && value.status === "pending_user_confirmation"
1495
+ );
1496
+ }
1497
+ function isDelegationPolicyLike(value) {
1498
+ return Boolean(
1499
+ value && typeof value === "object" && !Array.isArray(value) && value.protocolVersion === AGENTCOMM_PROTOCOL_VERSION && value.defaultDecision === "ask" && Array.isArray(value.rules)
1500
+ );
1501
+ }
1502
+ function isUserActionDecisionLike(value) {
1503
+ return Boolean(
1504
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.decision_id === "string" && typeof value.action_id === "string" && (value.decision === "accepted" || value.decision === "rejected") && typeof value.decided_at === "string"
1505
+ );
1506
+ }
1507
+ function isUserActionResultLike(value) {
1508
+ return Boolean(
1509
+ value && typeof value === "object" && !Array.isArray(value) && typeof value.result_id === "string" && typeof value.action_id === "string" && (value.status === "installing" || value.status === "installed" || value.status === "install_failed" || value.status === "committed" || value.status === "recorded")
1510
+ );
1511
+ }
1512
+ function roomMessagesPath(paths, roomId) {
1513
+ return path2.join(paths.roomsDir, `${sanitizeSegment(roomId)}.jsonl`);
1514
+ }
1515
+ function timeValue(value) {
1516
+ const time = Date.parse(value ?? "");
1517
+ return Number.isFinite(time) ? time : 0;
1518
+ }
1519
+
1520
+ // src/schedule.ts
1521
+ var SCHEDULE_NEGOTIATE_KINDS = [
1522
+ "propose",
1523
+ "counter",
1524
+ "agree_pending",
1525
+ "commit",
1526
+ "decline"
1527
+ ];
1528
+ function isScheduleNegotiateEnvelope(envelope) {
1529
+ return envelope.type === "schedule.negotiate" && isScheduleNegotiatePayload(envelope.payload) && envelope.target.kind === "agent" && typeof envelope.target.agent_id === "string";
1530
+ }
1531
+ function isScheduleNegotiatePayload(value) {
1532
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1533
+ const record = value;
1534
+ return SCHEDULE_NEGOTIATE_KINDS.includes(record.kind) && typeof record.negotiation_id === "string" && typeof record.topic === "string" && typeof record.duration_minutes === "number" && Number.isFinite(record.duration_minutes) && Array.isArray(record.candidate_windows) && record.candidate_windows.every(isScheduleWindow) && (!record.selected_window || isScheduleWindow(record.selected_window));
1535
+ }
1536
+ function getSchedulingAvailableWindows(policy) {
1537
+ const windows = policy.scheduling?.available_windows;
1538
+ if (Array.isArray(windows) && windows.every(isScheduleWindow)) {
1539
+ return windows;
1540
+ }
1541
+ return [
1542
+ {
1543
+ start: "2026-06-18T09:00:00+08:00",
1544
+ end: "2026-06-18T11:00:00+08:00"
1545
+ },
1546
+ {
1547
+ start: "2026-06-18T14:00:00+08:00",
1548
+ end: "2026-06-18T15:00:00+08:00"
1549
+ }
1550
+ ];
1551
+ }
1552
+ function intersectScheduleWindows(incoming, available, durationMinutes) {
1553
+ const durationMs = durationMinutes * 6e4;
1554
+ const intersections = [];
1555
+ for (const left of incoming) {
1556
+ for (const right of available) {
1557
+ const startMs = Math.max(Date.parse(left.start), Date.parse(right.start));
1558
+ const endMs = Math.min(Date.parse(left.end), Date.parse(right.end));
1559
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) continue;
1560
+ if (endMs - startMs >= durationMs) {
1561
+ intersections.push({
1562
+ start: new Date(startMs).toISOString(),
1563
+ end: new Date(Math.min(endMs, startMs + durationMs)).toISOString()
1564
+ });
1565
+ }
1566
+ }
1567
+ }
1568
+ return dedupeWindows(intersections);
1569
+ }
1570
+ function createScheduleCommitPendingAction(communityAgentId, envelope, selectedWindow, options) {
1571
+ return {
1572
+ action_id: `action_commit_schedule_${sanitizeSegment2(envelope.payload.negotiation_id)}`,
1573
+ kind: "commit_schedule",
1574
+ source: "openclaw-bridge",
1575
+ community_agent_id: communityAgentId,
1576
+ status: "pending_user_confirmation",
1577
+ user_confirmation_required: true,
1578
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1579
+ title: `\u786E\u8BA4\u4F1A\u8BAE\uFF1A${envelope.payload.topic}`,
1580
+ summary: `\u5019\u9009\u65F6\u95F4\u5DF2\u6536\u655B\uFF0C\u662F\u5426\u627F\u8BFA ${selectedWindow.start} - ${selectedWindow.end}\uFF1F`,
1581
+ payload: {
1582
+ negotiation_id: envelope.payload.negotiation_id,
1583
+ topic: envelope.payload.topic,
1584
+ duration_minutes: envelope.payload.duration_minutes,
1585
+ selected_window: selectedWindow,
1586
+ peer_agent_id: options?.peerAgentId ?? envelope.sender_agent_id,
1587
+ inbound_envelope_id: options?.inboundEnvelopeId ?? envelope.envelope_id,
1588
+ risk_level: "medium",
1589
+ suggested_action: "confirm_calendar_commitment"
1590
+ }
1591
+ };
1592
+ }
1593
+ function updateScheduleState(current, state) {
1594
+ const negotiations = current.negotiations && typeof current.negotiations === "object" && !Array.isArray(current.negotiations) ? { ...current.negotiations } : {};
1595
+ negotiations[state.negotiation_id] = state;
1596
+ return {
1597
+ version: 1,
1598
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
1599
+ ...current,
1600
+ negotiations
1601
+ };
1602
+ }
1603
+ function createScheduleStateFromEnvelope(envelope, status, candidateWindows, selectedWindow, options) {
1604
+ return {
1605
+ negotiation_id: envelope.payload.negotiation_id,
1606
+ topic: envelope.payload.topic,
1607
+ duration_minutes: envelope.payload.duration_minutes,
1608
+ peer_agent_id: options?.peerAgentId ?? envelope.sender_agent_id,
1609
+ status,
1610
+ candidate_windows: candidateWindows,
1611
+ ...selectedWindow ? { selected_window: selectedWindow } : {},
1612
+ last_envelope_id: envelope.envelope_id,
1613
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1614
+ };
1615
+ }
1616
+ function isScheduleWindow(value) {
1617
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.start === "string" && typeof value.end === "string" && Number.isFinite(Date.parse(value.start)) && Number.isFinite(Date.parse(value.end)) && Date.parse(value.start) < Date.parse(value.end);
1618
+ }
1619
+ function dedupeWindows(windows) {
1620
+ const byKey = /* @__PURE__ */ new Map();
1621
+ for (const window of windows) {
1622
+ byKey.set(`${window.start}|${window.end}`, window);
1623
+ }
1624
+ return [...byKey.values()].sort((a, b) => Date.parse(a.start) - Date.parse(b.start));
1625
+ }
1626
+ function sanitizeSegment2(value) {
1627
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
1628
+ }
1629
+
1630
+ // src/service.ts
1631
+ function createCommunityBridgeService(readConfig, readHostPaths) {
1632
+ let timer;
1633
+ let running = false;
1634
+ let workspacePaths;
1635
+ let status = {
1636
+ enabled: false,
1637
+ connected: false,
1638
+ running: false
1639
+ };
1640
+ async function ensureWorkspaceForActiveConfig(config) {
1641
+ const hostPaths = readHostPaths();
1642
+ const locator = {
1643
+ communityAgentId: config.communityAgentId,
1644
+ workspaceDir: hostPaths.workspaceDir,
1645
+ agentDir: inferOpenClawAgentDir(hostPaths),
1646
+ stateDir: hostPaths.stateDir,
1647
+ homeDir: hostPaths.homeDir
1648
+ };
1649
+ const paths = await resolvePlatformWorkspacePaths(locator);
1650
+ await ensurePlatformWorkspace(paths, config.communityAgentId);
1651
+ workspacePaths = paths;
1652
+ return paths;
1653
+ }
1654
+ async function evaluateOutboundDmPolicy(paths, communityAgentId, _targetAgentId, _envelopeId) {
1655
+ const policy = await readDelegationPolicy(paths, communityAgentId);
1656
+ return evaluateDelegationPolicy(policy, { action: "dm.send", risk: "low" });
1657
+ }
1658
+ async function evaluateSchedulePolicy(paths, communityAgentId, kind) {
1659
+ const policy = await readDelegationPolicy(paths, communityAgentId);
1660
+ const risk = kind === "commit" ? "medium" : "low";
1661
+ return {
1662
+ policy,
1663
+ risk,
1664
+ policyResult: evaluateDelegationPolicy(policy, { action: `schedule.negotiate.${kind}`, risk })
1665
+ };
1666
+ }
1667
+ async function handleInboundDirectMessages(config, client, paths, messages) {
1668
+ if (messages.length === 0) {
1669
+ return;
1670
+ }
1671
+ const policy = await readDelegationPolicy(paths, config.communityAgentId);
1672
+ const askMessages = [];
1673
+ for (const message of messages) {
1674
+ const risk = classifyDirectMessageRisk(message);
1675
+ const action = risk === "low" ? "dm.receipt" : "dm.reply";
1676
+ const policyResult = evaluateDelegationPolicy(policy, { action, risk });
1677
+ await appendAuditLog(paths, {
1678
+ kind: "policy_decision",
1679
+ community_agent_id: config.communityAgentId,
1680
+ action,
1681
+ decision: policyResult.decision,
1682
+ reason: `inbound dm risk=${risk}; matched=${policyResult.matchedRuleId ?? "default"}`,
1683
+ source_event_id: message.dm_id,
1684
+ envelope_id: message.envelope_id,
1685
+ dm_id: message.dm_id,
1686
+ target_agent_id: message.sender_agent_id,
1687
+ result: policyResult.decision,
1688
+ payload: {
1689
+ message: message.message
1690
+ }
1691
+ });
1692
+ if (risk === "low" && policyResult.decision === "allow" && policy.autoReceipt.enabled) {
1693
+ const receipt = createDirectMessageEnvelope({
1694
+ senderAgentId: config.communityAgentId,
1695
+ targetAgentId: message.sender_agent_id,
1696
+ message: policy.autoReceipt.message,
1697
+ intent: "agentcomm.auto_receipt"
1698
+ });
1699
+ const publishResult = await client.publishEnvelope(receipt);
1700
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, receipt);
1701
+ await appendAuditLog(paths, {
1702
+ kind: "dm.auto_receipt",
1703
+ community_agent_id: config.communityAgentId,
1704
+ action: "dm.send",
1705
+ decision: "allow",
1706
+ reason: `auto receipt for ${message.dm_id}`,
1707
+ source_event_id: message.dm_id,
1708
+ envelope_id: receipt.envelope_id,
1709
+ dm_id: publishResult.dm_id,
1710
+ target_agent_id: message.sender_agent_id,
1711
+ result: "sent",
1712
+ payload: {
1713
+ inbound_envelope_id: message.envelope_id,
1714
+ inbound_dm_id: message.dm_id,
1715
+ intent: "agentcomm.auto_receipt"
1716
+ }
1717
+ });
1718
+ await appendAgentActivity(paths, {
1719
+ kind: "dm.auto_receipt",
1720
+ risk_level: "low",
1721
+ summary: `\u5DF2\u81EA\u52A8\u5411 ${message.sender_agent_id} \u53D1\u9001\u6536\u5230\u56DE\u6267\u3002`,
1722
+ source_agent_id: message.sender_agent_id,
1723
+ target_agent_id: config.communityAgentId,
1724
+ source_event_id: message.dm_id,
1725
+ payload: {
1726
+ receipt_envelope_id: receipt.envelope_id,
1727
+ matched_rule_id: policyResult.matchedRuleId
1728
+ }
1729
+ });
1730
+ continue;
1731
+ }
1732
+ askMessages.push(message);
1733
+ }
1734
+ await appendPendingUserActions(paths, createDirectMessageReplyPendingActions(config.communityAgentId, askMessages));
1735
+ }
1736
+ async function runAutonomousLoopOnce(input = {}) {
1737
+ const config = getActiveConfig(readConfig());
1738
+ if (!config) {
1739
+ throw new Error("community bridge is not active");
1740
+ }
1741
+ const client = new CommunityClient(config);
1742
+ const paths = await ensureWorkspaceForActiveConfig(config);
1743
+ return runAutonomousLoopForConfig(config, client, paths, input);
1744
+ }
1745
+ async function applyProposal(input) {
1746
+ const config = getActiveConfig(readConfig());
1747
+ if (!config) {
1748
+ throw new Error("community bridge is not active");
1749
+ }
1750
+ const subagent = config.communitySubagent;
1751
+ if (!subagent?.enabled || !subagent.agentId || !subagent.workspaceDir) {
1752
+ return {
1753
+ ok: false,
1754
+ reason: "community subagent unavailable",
1755
+ handoffId: input.handoffId
1756
+ };
1757
+ }
1758
+ const client = new CommunityClient(config);
1759
+ const paths = await ensureWorkspaceForActiveConfig(config);
1760
+ const handoffs = await readSubagentHandoffItems(subagent.workspaceDir);
1761
+ const handoff = handoffs.find((item) => item.handoff_id === input.handoffId);
1762
+ if (!handoff) {
1763
+ return {
1764
+ ok: false,
1765
+ reason: "handoff not found",
1766
+ handoffId: input.handoffId,
1767
+ workspaceRootDir: paths.rootDir
1768
+ };
1769
+ }
1770
+ await appendAuditLog(paths, {
1771
+ kind: "subagent.proposal",
1772
+ community_agent_id: config.communityAgentId,
1773
+ action: input.proposal.action,
1774
+ reason: input.proposal.reason ?? "community subagent sidecar proposed action",
1775
+ source_event_id: handoff.handoff_id,
1776
+ envelope_id: handoff.envelope_id,
1777
+ result: "proposed",
1778
+ payload: {
1779
+ handoff_id: handoff.handoff_id,
1780
+ subagent_id: subagent.agentId,
1781
+ risk: input.proposal.action === "dm.reply" ? input.proposal.risk : "medium",
1782
+ proposal: input.proposal,
1783
+ source: "sidecar",
1784
+ ...input.proposalMeta ? { proposal_meta: input.proposalMeta } : {}
1785
+ }
1786
+ });
1787
+ const result = await applySubagentProposal(config, client, paths, handoff, input.proposal, input.proposalMeta);
1788
+ const state = await readSubagentLoopState(paths);
1789
+ const processedIds = new Set(state.processedHandoffIds);
1790
+ processedIds.add(handoff.handoff_id);
1791
+ await writeSubagentLoopState(paths, {
1792
+ version: 1,
1793
+ processedHandoffIds: [...processedIds],
1794
+ lastRunAt: (/* @__PURE__ */ new Date()).toISOString(),
1795
+ lastProcessedHandoffId: handoff.handoff_id
1796
+ });
1797
+ return {
1798
+ ok: true,
1799
+ handoffId: handoff.handoff_id,
1800
+ result,
1801
+ workspaceRootDir: paths.rootDir
1802
+ };
1803
+ }
1804
+ async function runAutonomousLoopForConfig(config, client, paths, input = {}) {
1805
+ const subagent = config.communitySubagent;
1806
+ if (!subagent?.enabled || !subagent.agentId || !subagent.workspaceDir) {
1807
+ return {
1808
+ ok: false,
1809
+ reason: "community subagent unavailable",
1810
+ processed: 0,
1811
+ results: [],
1812
+ workspaceRootDir: paths.rootDir
1813
+ };
1814
+ }
1815
+ const state = await readSubagentLoopState(paths);
1816
+ await writeSubagentLoopState(paths, {
1817
+ version: 1,
1818
+ processedHandoffIds: state.processedHandoffIds,
1819
+ lastRunAt: (/* @__PURE__ */ new Date()).toISOString(),
1820
+ ...state.lastProcessedHandoffId ? { lastProcessedHandoffId: state.lastProcessedHandoffId } : {},
1821
+ lastError: "gateway autonomous loop is disabled; run politeia-loop sidecar instead"
1822
+ });
1823
+ return {
1824
+ ok: false,
1825
+ reason: "gateway autonomous loop is disabled; run politeia-loop sidecar instead",
1826
+ processed: 0,
1827
+ results: [],
1828
+ workspaceRootDir: paths.rootDir
1829
+ };
1830
+ }
1831
+ async function applySubagentProposal(config, client, paths, handoff, proposal, proposalMeta) {
1832
+ if (proposal.action === "meeting.summary") {
1833
+ return applyMeetingSummaryProposal(config, paths, handoff, proposal, proposalMeta);
1834
+ }
1835
+ const dm = readDirectMessageFromHandoff(handoff);
1836
+ if (!dm) {
1837
+ await appendAuditLog(paths, {
1838
+ kind: "subagent.proposal.rejected",
1839
+ community_agent_id: config.communityAgentId,
1840
+ action: proposal.action,
1841
+ decision: "deny",
1842
+ reason: "handoff is not a direct message",
1843
+ source_event_id: handoff.handoff_id,
1844
+ envelope_id: handoff.envelope_id,
1845
+ result: "denied"
1846
+ });
1847
+ return {
1848
+ handoffId: handoff.handoff_id,
1849
+ ok: false,
1850
+ decision: "deny",
1851
+ reason: "handoff is not a direct message"
1852
+ };
1853
+ }
1854
+ if (proposal.risk === "high") {
1855
+ await appendAuditLog(paths, {
1856
+ kind: "subagent.proposal.denied",
1857
+ community_agent_id: config.communityAgentId,
1858
+ action: proposal.action,
1859
+ decision: "deny",
1860
+ reason: "high-risk subagent proposal cannot auto-execute in Phase 2 MVP",
1861
+ source_event_id: handoff.handoff_id,
1862
+ envelope_id: handoff.envelope_id,
1863
+ dm_id: dm.dmId,
1864
+ target_agent_id: dm.senderAgentId,
1865
+ result: "denied",
1866
+ payload: {
1867
+ handoff_id: handoff.handoff_id,
1868
+ draft: proposal.draft,
1869
+ risk: proposal.risk
1870
+ }
1871
+ });
1872
+ await appendAgentActivity(paths, {
1873
+ kind: "subagent.proposal.denied",
1874
+ risk_level: "high",
1875
+ summary: `Community Subagent \u62D2\u7EDD\u81EA\u52A8\u5904\u7406\u6765\u81EA ${dm.senderAgentId} \u7684\u9AD8\u98CE\u9669\u79C1\u4FE1\u3002`,
1876
+ source_agent_id: dm.senderAgentId,
1877
+ target_agent_id: config.communityAgentId,
1878
+ source_event_id: handoff.handoff_id,
1879
+ payload: {
1880
+ inbound_dm_id: dm.dmId,
1881
+ inbound_envelope_id: handoff.envelope_id
1882
+ }
1883
+ });
1884
+ return {
1885
+ handoffId: handoff.handoff_id,
1886
+ ok: true,
1887
+ decision: "deny",
1888
+ risk: proposal.risk
1889
+ };
1890
+ }
1891
+ const policy = await readDelegationPolicy(paths, config.communityAgentId);
1892
+ const gatedAction = proposal.risk === "low" ? "dm.send" : "dm.reply";
1893
+ const policyResult = evaluateDelegationPolicy(policy, { action: gatedAction, risk: proposal.risk });
1894
+ await appendAuditLog(paths, {
1895
+ kind: "policy_decision",
1896
+ community_agent_id: config.communityAgentId,
1897
+ action: gatedAction,
1898
+ decision: policyResult.decision,
1899
+ reason: `subagent proposal risk=${proposal.risk}; matched=${policyResult.matchedRuleId ?? "default"}`,
1900
+ source_event_id: handoff.handoff_id,
1901
+ envelope_id: handoff.envelope_id,
1902
+ dm_id: dm.dmId,
1903
+ target_agent_id: dm.senderAgentId,
1904
+ result: policyResult.decision,
1905
+ payload: {
1906
+ handoff_id: handoff.handoff_id,
1907
+ draft: proposal.draft
1908
+ }
1909
+ });
1910
+ if (policyResult.decision === "allow" && proposal.risk === "low") {
1911
+ const reply = createDirectMessageEnvelope({
1912
+ senderAgentId: config.communityAgentId,
1913
+ targetAgentId: dm.senderAgentId,
1914
+ message: proposal.draft,
1915
+ intent: "agentcomm.subagent.dm_reply"
1916
+ });
1917
+ const publishResult = await client.publishEnvelope(reply);
1918
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, reply);
1919
+ await appendAuditLog(paths, {
1920
+ kind: "subagent.dm.reply.sent",
1921
+ community_agent_id: config.communityAgentId,
1922
+ action: "dm.send",
1923
+ decision: "allow",
1924
+ reason: `subagent low-risk reply; matched=${policyResult.matchedRuleId ?? "default"}`,
1925
+ source_event_id: handoff.handoff_id,
1926
+ envelope_id: reply.envelope_id,
1927
+ dm_id: publishResult.dm_id,
1928
+ target_agent_id: dm.senderAgentId,
1929
+ result: "sent",
1930
+ payload: {
1931
+ inbound_dm_id: dm.dmId,
1932
+ inbound_envelope_id: handoff.envelope_id
1933
+ }
1934
+ });
1935
+ await appendAgentActivity(paths, {
1936
+ kind: "subagent.dm.reply.sent",
1937
+ risk_level: "low",
1938
+ summary: `Community Subagent \u5DF2\u81EA\u52A8\u56DE\u590D ${dm.senderAgentId}\u3002`,
1939
+ source_agent_id: dm.senderAgentId,
1940
+ target_agent_id: config.communityAgentId,
1941
+ source_event_id: handoff.handoff_id,
1942
+ payload: {
1943
+ reply_envelope_id: reply.envelope_id,
1944
+ matched_rule_id: policyResult.matchedRuleId
1945
+ }
1946
+ });
1947
+ return {
1948
+ handoffId: handoff.handoff_id,
1949
+ ok: true,
1950
+ decision: "allow",
1951
+ action: "dm.send",
1952
+ envelopeId: reply.envelope_id,
1953
+ dmId: publishResult.dm_id ?? null
1954
+ };
1955
+ }
1956
+ if (policyResult.decision === "ask") {
1957
+ const pendingAction = createSubagentReplyPendingAction(config.communityAgentId, handoff, dm, proposal);
1958
+ await appendPendingUserActions(paths, [pendingAction]);
1959
+ await appendAgentActivity(paths, {
1960
+ kind: "subagent.dm.reply.pending",
1961
+ risk_level: proposal.risk,
1962
+ summary: `Community Subagent \u5DF2\u4E3A ${dm.senderAgentId} \u8D77\u8349\u56DE\u590D\uFF0C\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u3002`,
1963
+ source_agent_id: dm.senderAgentId,
1964
+ target_agent_id: config.communityAgentId,
1965
+ source_event_id: handoff.handoff_id,
1966
+ payload: {
1967
+ action_id: pendingAction.action_id,
1968
+ matched_rule_id: policyResult.matchedRuleId
1969
+ }
1970
+ });
1971
+ return {
1972
+ handoffId: handoff.handoff_id,
1973
+ ok: true,
1974
+ decision: "ask",
1975
+ actionId: pendingAction.action_id
1976
+ };
1977
+ }
1978
+ await appendAuditLog(paths, {
1979
+ kind: "subagent.proposal.denied",
1980
+ community_agent_id: config.communityAgentId,
1981
+ action: gatedAction,
1982
+ decision: "deny",
1983
+ reason: `subagent proposal blocked by policy; matched=${policyResult.matchedRuleId ?? "default"}`,
1984
+ source_event_id: handoff.handoff_id,
1985
+ envelope_id: handoff.envelope_id,
1986
+ dm_id: dm.dmId,
1987
+ target_agent_id: dm.senderAgentId,
1988
+ result: "denied",
1989
+ payload: {
1990
+ handoff_id: handoff.handoff_id,
1991
+ draft: proposal.draft
1992
+ }
1993
+ });
1994
+ return {
1995
+ handoffId: handoff.handoff_id,
1996
+ ok: true,
1997
+ decision: "deny"
1998
+ };
1999
+ }
2000
+ async function applyMeetingSummaryProposal(config, paths, handoff, proposal, proposalMeta) {
2001
+ const roomId = readString3(handoff.payload.room_id);
2002
+ if (handoff.kind !== "meeting_summary" || !roomId || roomId !== proposal.room_id) {
2003
+ await appendAuditLog(paths, {
2004
+ kind: "subagent.proposal.rejected",
2005
+ community_agent_id: config.communityAgentId,
2006
+ action: proposal.action,
2007
+ decision: "deny",
2008
+ reason: "handoff is not a matching meeting summary request",
2009
+ source_event_id: handoff.handoff_id,
2010
+ envelope_id: handoff.envelope_id,
2011
+ result: "denied",
2012
+ payload: {
2013
+ handoff_room_id: roomId ?? null,
2014
+ proposal_room_id: proposal.room_id
2015
+ }
2016
+ });
2017
+ return {
2018
+ handoffId: handoff.handoff_id,
2019
+ ok: false,
2020
+ decision: "deny",
2021
+ reason: "handoff is not a matching meeting summary request"
2022
+ };
2023
+ }
2024
+ const policy = await readDelegationPolicy(paths, config.communityAgentId);
2025
+ const policyResult = evaluateDelegationPolicy(policy, { action: "meeting.summary", risk: "medium" });
2026
+ await appendAuditLog(paths, {
2027
+ kind: "policy_decision",
2028
+ community_agent_id: config.communityAgentId,
2029
+ action: "meeting.summary",
2030
+ decision: policyResult.decision,
2031
+ reason: `meeting summary proposal; matched=${policyResult.matchedRuleId ?? "default"}`,
2032
+ source_event_id: handoff.handoff_id,
2033
+ envelope_id: handoff.envelope_id,
2034
+ result: policyResult.decision,
2035
+ payload: {
2036
+ handoff_id: handoff.handoff_id,
2037
+ room_id: proposal.room_id,
2038
+ summary: proposal.summary,
2039
+ todos: proposal.todos
2040
+ }
2041
+ });
2042
+ if (policyResult.decision !== "ask") {
2043
+ await appendAuditLog(paths, {
2044
+ kind: "meeting.summary.blocked",
2045
+ community_agent_id: config.communityAgentId,
2046
+ action: "meeting.summary",
2047
+ decision: policyResult.decision,
2048
+ reason: "meeting summaries must be user-confirmed in v1",
2049
+ source_event_id: handoff.handoff_id,
2050
+ envelope_id: handoff.envelope_id,
2051
+ result: "blocked"
2052
+ });
2053
+ return {
2054
+ handoffId: handoff.handoff_id,
2055
+ ok: true,
2056
+ decision: "deny",
2057
+ reason: "meeting summaries must be user-confirmed in v1"
2058
+ };
2059
+ }
2060
+ const pendingAction = createMeetingSummaryPendingAction(config.communityAgentId, handoff, proposal, proposalMeta);
2061
+ await appendPendingUserActions(paths, [pendingAction]);
2062
+ await appendAgentActivity(paths, {
2063
+ kind: "meeting.summary.pending",
2064
+ risk_level: "medium",
2065
+ summary: `Community Subagent \u5DF2\u4E3A\u4F1A\u8BAE\u623F\u95F4 ${proposal.room_id} \u751F\u6210\u6458\u8981\u548C\u5F85\u529E\uFF0C\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u3002`,
2066
+ source_event_id: handoff.handoff_id,
2067
+ payload: {
2068
+ action_id: pendingAction.action_id,
2069
+ room_id: proposal.room_id,
2070
+ matched_rule_id: policyResult.matchedRuleId
2071
+ }
2072
+ });
2073
+ return {
2074
+ handoffId: handoff.handoff_id,
2075
+ ok: true,
2076
+ decision: "ask",
2077
+ actionId: pendingAction.action_id,
2078
+ roomId: proposal.room_id
2079
+ };
2080
+ }
2081
+ async function handleInboundRoutedEnvelopes(config, client, paths, items) {
2082
+ for (const item of items) {
2083
+ const envelope = item.envelope;
2084
+ if (isRoomCreatedEnvelope(envelope)) {
2085
+ await upsertRoom(paths, envelope.payload.room);
2086
+ await appendAuditLog(paths, {
2087
+ kind: "room.created.received",
2088
+ community_agent_id: config.communityAgentId,
2089
+ action: "room.created",
2090
+ decision: "allow",
2091
+ reason: "room membership routed by platform",
2092
+ source_event_id: item.delivery_id,
2093
+ envelope_id: item.envelope_id,
2094
+ target_agent_id: item.sender_agent_id,
2095
+ result: "stored",
2096
+ payload: {
2097
+ room_id: envelope.payload.room.room_id,
2098
+ members: envelope.payload.room.members,
2099
+ source_negotiation_id: envelope.payload.room.source_negotiation_id ?? null
2100
+ }
2101
+ });
2102
+ continue;
2103
+ }
2104
+ if (isRoomMessageEnvelope(envelope)) {
2105
+ const roomId = envelope.target.room_id;
2106
+ await appendRoomMessages(paths, [createRoomMessageRecord(envelope, item.delivery_id)]);
2107
+ await appendAuditLog(paths, {
2108
+ kind: "room.message.received",
2109
+ community_agent_id: config.communityAgentId,
2110
+ action: "room.message.receive",
2111
+ decision: "allow",
2112
+ reason: "room message stored as local transcript; v1 agent remains observer-only",
2113
+ source_event_id: item.delivery_id,
2114
+ envelope_id: item.envelope_id,
2115
+ target_agent_id: item.sender_agent_id,
2116
+ result: "stored",
2117
+ payload: {
2118
+ room_id: roomId,
2119
+ speaker_kind: envelope.payload.speaker_kind
2120
+ }
2121
+ });
2122
+ continue;
2123
+ }
2124
+ if (!isScheduleNegotiateEnvelope(envelope)) {
2125
+ await appendAuditLog(paths, {
2126
+ kind: "envelope.received",
2127
+ community_agent_id: config.communityAgentId,
2128
+ action: `envelope.${item.type}`,
2129
+ decision: "allow",
2130
+ reason: "routed envelope stored without semantic handling",
2131
+ source_event_id: item.delivery_id,
2132
+ envelope_id: item.envelope_id,
2133
+ target_agent_id: item.sender_agent_id,
2134
+ result: "stored"
2135
+ });
2136
+ continue;
2137
+ }
2138
+ await handleScheduleEnvelope(config, client, paths, envelope, item.delivery_id);
2139
+ }
2140
+ }
2141
+ function isRoomCreatedEnvelope(envelope) {
2142
+ return envelope.type === "room.created" && isRoomRecord(envelope.payload.room);
2143
+ }
2144
+ function isRoomMessageEnvelope(envelope) {
2145
+ return envelope.type === "room.message" && envelope.target.kind === "room" && typeof envelope.target.room_id === "string" && typeof envelope.payload.message === "string" && (envelope.payload.speaker_kind === "human" || envelope.payload.speaker_kind === "agent");
2146
+ }
2147
+ function createRoomMessageRecord(envelope, deliveryId, messageId) {
2148
+ return {
2149
+ ...messageId ? { message_id: messageId } : {},
2150
+ ...deliveryId ? { delivery_id: deliveryId } : {},
2151
+ room_id: envelope.target.kind === "room" ? envelope.target.room_id : "",
2152
+ envelope_id: envelope.envelope_id,
2153
+ sender_agent_id: envelope.sender_agent_id,
2154
+ speaker_kind: envelope.payload.speaker_kind,
2155
+ speaker_label: envelope.payload.speaker_label ?? null,
2156
+ message: envelope.payload.message,
2157
+ intent: envelope.payload.intent ?? null,
2158
+ created_at: envelope.created_at
2159
+ };
2160
+ }
2161
+ function isRoomRecord(value) {
2162
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.room_id === "string" && typeof value.topic === "string" && Array.isArray(value.members) && typeof value.created_by_agent_id === "string";
2163
+ }
2164
+ async function handleScheduleEnvelope(config, client, paths, envelope, deliveryId) {
2165
+ const { policy, risk, policyResult } = await evaluateSchedulePolicy(paths, config.communityAgentId, envelope.payload.kind);
2166
+ await appendAuditLog(paths, {
2167
+ kind: "policy_decision",
2168
+ community_agent_id: config.communityAgentId,
2169
+ action: `schedule.negotiate.${envelope.payload.kind}`,
2170
+ decision: policyResult.decision,
2171
+ reason: `inbound schedule negotiation risk=${risk}; matched=${policyResult.matchedRuleId ?? "default"}`,
2172
+ source_event_id: deliveryId,
2173
+ envelope_id: envelope.envelope_id,
2174
+ target_agent_id: envelope.sender_agent_id,
2175
+ result: policyResult.decision,
2176
+ payload: {
2177
+ negotiation_id: envelope.payload.negotiation_id,
2178
+ acting_for: envelope.acting_for ?? envelope.sender_agent_id
2179
+ }
2180
+ });
2181
+ if (envelope.payload.kind === "commit") {
2182
+ await writeScheduleState(
2183
+ paths,
2184
+ updateScheduleState(
2185
+ await readScheduleState(paths),
2186
+ createScheduleStateFromEnvelope(envelope, "committed", envelope.payload.candidate_windows, envelope.payload.selected_window)
2187
+ )
2188
+ );
2189
+ await appendAuditLog(paths, {
2190
+ kind: "schedule.commit.received",
2191
+ community_agent_id: config.communityAgentId,
2192
+ action: "schedule.negotiate.commit",
2193
+ decision: "ask",
2194
+ reason: "peer committed; local commitment still requires local user confirmation",
2195
+ source_event_id: deliveryId,
2196
+ envelope_id: envelope.envelope_id,
2197
+ target_agent_id: envelope.sender_agent_id,
2198
+ result: "received",
2199
+ payload: {
2200
+ negotiation_id: envelope.payload.negotiation_id,
2201
+ selected_window: envelope.payload.selected_window ?? null
2202
+ }
2203
+ });
2204
+ return;
2205
+ }
2206
+ if (policyResult.decision !== "allow") {
2207
+ return;
2208
+ }
2209
+ if (envelope.payload.kind === "decline") {
2210
+ await writeScheduleState(
2211
+ paths,
2212
+ updateScheduleState(
2213
+ await readScheduleState(paths),
2214
+ createScheduleStateFromEnvelope(envelope, "declined", envelope.payload.candidate_windows)
2215
+ )
2216
+ );
2217
+ return;
2218
+ }
2219
+ if (envelope.payload.kind === "agree_pending") {
2220
+ const selectedWindow2 = envelope.payload.selected_window ?? envelope.payload.candidate_windows[0];
2221
+ await writeScheduleState(
2222
+ paths,
2223
+ updateScheduleState(
2224
+ await readScheduleState(paths),
2225
+ createScheduleStateFromEnvelope(envelope, "agree_pending", envelope.payload.candidate_windows, selectedWindow2)
2226
+ )
2227
+ );
2228
+ await appendAuditLog(paths, {
2229
+ kind: "schedule.agree_pending.received",
2230
+ community_agent_id: config.communityAgentId,
2231
+ action: "schedule.negotiate.agree_pending",
2232
+ decision: "ask",
2233
+ reason: "peer proposed a final meeting window; local commit requires user confirmation",
2234
+ source_event_id: deliveryId,
2235
+ envelope_id: envelope.envelope_id,
2236
+ target_agent_id: envelope.sender_agent_id,
2237
+ result: selectedWindow2 ? "pending_user_confirmation" : "missing_selected_window",
2238
+ payload: {
2239
+ negotiation_id: envelope.payload.negotiation_id,
2240
+ selected_window: selectedWindow2 ?? null
2241
+ }
2242
+ });
2243
+ if (selectedWindow2) {
2244
+ await appendPendingUserActions(paths, [
2245
+ createScheduleCommitPendingAction(config.communityAgentId, envelope, selectedWindow2)
2246
+ ]);
2247
+ }
2248
+ return;
2249
+ }
2250
+ if (envelope.payload.kind !== "propose" && envelope.payload.kind !== "counter") {
2251
+ return;
2252
+ }
2253
+ const intersections = intersectScheduleWindows(
2254
+ envelope.payload.candidate_windows,
2255
+ getSchedulingAvailableWindows(policy),
2256
+ envelope.payload.duration_minutes
2257
+ );
2258
+ const nextKind = intersections.length === 0 ? "decline" : intersections.length === 1 ? "agree_pending" : "counter";
2259
+ const selectedWindow = intersections.length === 1 ? intersections[0] : void 0;
2260
+ const reply = createScheduleNegotiateEnvelope({
2261
+ senderAgentId: config.communityAgentId,
2262
+ targetAgentId: envelope.sender_agent_id,
2263
+ actingFor: policy.principal.userId,
2264
+ kind: nextKind,
2265
+ negotiationId: envelope.payload.negotiation_id,
2266
+ topic: envelope.payload.topic,
2267
+ durationMinutes: envelope.payload.duration_minutes,
2268
+ candidateWindows: intersections,
2269
+ selectedWindow,
2270
+ reason: intersections.length === 0 ? "no-overlap" : void 0
2271
+ });
2272
+ const publishResult = await client.publishEnvelope(reply);
2273
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, reply);
2274
+ await appendAuditLog(paths, {
2275
+ kind: "schedule.negotiate",
2276
+ community_agent_id: config.communityAgentId,
2277
+ action: `schedule.negotiate.${nextKind}`,
2278
+ decision: "allow",
2279
+ reason: `deterministic intersection produced ${intersections.length} candidate(s)`,
2280
+ source_event_id: envelope.envelope_id,
2281
+ envelope_id: reply.envelope_id,
2282
+ target_agent_id: envelope.sender_agent_id,
2283
+ result: "sent",
2284
+ payload: {
2285
+ negotiation_id: reply.payload.negotiation_id,
2286
+ candidate_windows: intersections,
2287
+ delivery_id: publishResult.dm_id ?? null
2288
+ }
2289
+ });
2290
+ await writeScheduleState(
2291
+ paths,
2292
+ updateScheduleState(
2293
+ await readScheduleState(paths),
2294
+ createScheduleStateFromEnvelope(
2295
+ reply,
2296
+ nextKind === "decline" ? "declined" : nextKind === "counter" ? "countered" : "agree_pending",
2297
+ intersections,
2298
+ selectedWindow,
2299
+ { peerAgentId: envelope.sender_agent_id }
2300
+ )
2301
+ )
2302
+ );
2303
+ if (selectedWindow) {
2304
+ await appendPendingUserActions(paths, [
2305
+ createScheduleCommitPendingAction(config.communityAgentId, reply, selectedWindow, {
2306
+ peerAgentId: envelope.sender_agent_id,
2307
+ inboundEnvelopeId: envelope.envelope_id
2308
+ })
2309
+ ]);
2310
+ }
2311
+ }
2312
+ async function createRoomForScheduleCommit(config, paths, input) {
2313
+ const client = new CommunityClient(config);
2314
+ const response = await client.createRoom({
2315
+ topic: input.topic,
2316
+ members: [config.communityAgentId, input.peerAgentId],
2317
+ sourceNegotiationId: input.negotiationId
2318
+ });
2319
+ await upsertRoom(paths, response.room);
2320
+ await appendAuditLog(paths, {
2321
+ kind: "room.created",
2322
+ community_agent_id: config.communityAgentId,
2323
+ action: "room.create",
2324
+ decision: "allow",
2325
+ reason: response.existing ? "schedule commit resolved to existing room" : "schedule commit created meeting room",
2326
+ envelope_id: response.envelope_id,
2327
+ target_agent_id: input.peerAgentId,
2328
+ result: response.existing ? "existing" : "created",
2329
+ payload: {
2330
+ room_id: response.room.room_id,
2331
+ topic: response.room.topic,
2332
+ members: response.room.members,
2333
+ source_negotiation_id: input.negotiationId
2334
+ }
2335
+ });
2336
+ return response.room;
2337
+ }
2338
+ async function buildInboxSummary(paths) {
2339
+ const [inbox, sentDms, pendingActions, decisions, results] = await Promise.all([
2340
+ readReceivedDirectMessages(paths),
2341
+ readSentDirectMessages(paths),
2342
+ readPendingUserActions(paths),
2343
+ readUserActionDecisions(paths),
2344
+ readUserActionResults(paths)
2345
+ ]);
2346
+ const decidedActionIds = new Set(decisions.map((decision) => decision.action_id));
2347
+ const resultActionIds = new Set(results.map((result) => result.action_id));
2348
+ const openPendingActions = pendingActions.filter(
2349
+ (action) => !decidedActionIds.has(action.action_id) && !resultActionIds.has(action.action_id)
2350
+ );
2351
+ return {
2352
+ ok: true,
2353
+ workspaceRootDir: paths.rootDir,
2354
+ inboxCount: inbox.length,
2355
+ sentDmCount: sentDms.length,
2356
+ pendingDecisionCount: openPendingActions.length,
2357
+ latestMessages: inbox.slice(-10).map((message) => ({
2358
+ dm_id: message.dm_id,
2359
+ from: message.sender_agent_id,
2360
+ message: message.message,
2361
+ intent: message.intent ?? null,
2362
+ created_at: message.created_at
2363
+ })),
2364
+ pendingDecisions: openPendingActions.slice(-10).map((action) => ({
2365
+ action_id: action.action_id,
2366
+ kind: action.kind,
2367
+ title: action.title,
2368
+ summary: action.summary,
2369
+ created_at: action.created_at,
2370
+ payload: action.payload
2371
+ }))
2372
+ };
2373
+ }
2374
+ async function pollOnce() {
2375
+ const config = getActiveConfig(readConfig());
2376
+ if (!config) {
2377
+ status = { ...status, enabled: false, connected: false, running };
2378
+ return status;
2379
+ }
2380
+ try {
2381
+ const client = new CommunityClient(config);
2382
+ const paths = await ensureWorkspaceForActiveConfig(config);
2383
+ const workspaceState = paths ? await readPlatformWorkspaceState(paths) : null;
2384
+ const inboxCursor = workspaceState?.lastInboxCursor;
2385
+ const skillOfferCursor = workspaceState?.lastSkillOfferCursor;
2386
+ const envelopeCursor = workspaceState?.lastEnvelopeCursor;
2387
+ const keepalive = await client.sendKeepalive(inboxCursor, skillOfferCursor, envelopeCursor);
2388
+ const keepaliveAt = (/* @__PURE__ */ new Date()).toISOString();
2389
+ if (paths) {
2390
+ await markWorkspaceKeepalive(paths, config.communityAgentId, keepaliveAt);
2391
+ }
2392
+ const shouldRunLegacyHeartbeat = config.acceptanceDemo?.enabled === true;
2393
+ const tick = shouldRunLegacyHeartbeat ? await client.fetchNextHeartbeat() : null;
2394
+ status = {
2395
+ enabled: true,
2396
+ connected: true,
2397
+ running,
2398
+ communityAgentId: config.communityAgentId,
2399
+ ...paths ? { workspaceRootDir: paths.rootDir } : {},
2400
+ lastKeepaliveAt: keepaliveAt,
2401
+ lastPollAt: keepaliveAt,
2402
+ ...inboxCursor ? { lastInboxCursor: inboxCursor } : {},
2403
+ ...tick ? { lastTickId: tick.tick_id } : {}
2404
+ };
2405
+ const pendingDirectMessages = keepalive.pending_direct_messages ?? keepalive.pending_count ?? 0;
2406
+ if (paths && pendingDirectMessages > 0) {
2407
+ const inbox = await client.fetchDirectMessages(inboxCursor);
2408
+ await appendReceivedDirectMessages(paths, config.communityAgentId, inbox.items, inbox.next_cursor);
2409
+ await appendSubagentHandoffItems(
2410
+ config.communitySubagent?.workspaceDir,
2411
+ config.communityAgentId,
2412
+ createDirectMessageHandoffItems(config.communityAgentId, inbox.items)
2413
+ );
2414
+ await handleInboundDirectMessages(config, client, paths, inbox.items);
2415
+ status = {
2416
+ ...status,
2417
+ ...inbox.next_cursor ? { lastInboxCursor: inbox.next_cursor } : {},
2418
+ lastInboxSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2419
+ };
2420
+ }
2421
+ const pendingEnvelopes = keepalive.pending_envelopes ?? 0;
2422
+ if (paths && pendingEnvelopes > 0) {
2423
+ const envelopeInbox = await client.fetchEnvelopes(envelopeCursor);
2424
+ await appendReceivedEnvelopes(paths, config.communityAgentId, envelopeInbox.items, envelopeInbox.next_cursor);
2425
+ await handleInboundRoutedEnvelopes(config, client, paths, envelopeInbox.items);
2426
+ status = {
2427
+ ...status,
2428
+ lastPollAt: (/* @__PURE__ */ new Date()).toISOString()
2429
+ };
2430
+ }
2431
+ const pendingSkillOffers = keepalive.pending_skill_offers ?? 0;
2432
+ if (paths && pendingSkillOffers > 0) {
2433
+ const skillOffers = await client.fetchSkillOffers(skillOfferCursor);
2434
+ await appendStagedSkillOffers(paths, config.communityAgentId, skillOffers.items, skillOffers.next_cursor);
2435
+ await appendPendingUserActions(
2436
+ paths,
2437
+ createSkillOfferPendingActions(config.communityAgentId, skillOffers.items)
2438
+ );
2439
+ await appendSubagentHandoffItems(
2440
+ config.communitySubagent?.workspaceDir,
2441
+ config.communityAgentId,
2442
+ createSkillOfferHandoffItems(config.communityAgentId, skillOffers.items)
2443
+ );
2444
+ status = {
2445
+ ...status,
2446
+ lastSkillOfferSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2447
+ };
2448
+ }
2449
+ if (paths && config.autonomousLoop?.enabled === true) {
2450
+ const loopResult = await runAutonomousLoopForConfig(config, client, paths, {
2451
+ maxItems: config.autonomousLoop.maxItemsPerPoll ?? 1
2452
+ });
2453
+ status = {
2454
+ ...status,
2455
+ lastAutonomousLoopAt: (/* @__PURE__ */ new Date()).toISOString(),
2456
+ lastAutonomousLoopProcessed: loopResult.processed
2457
+ };
2458
+ }
2459
+ if (tick) {
2460
+ await client.submitIntent(createIntentFromTick(tick));
2461
+ }
2462
+ } catch (error) {
2463
+ status = {
2464
+ enabled: true,
2465
+ connected: false,
2466
+ running,
2467
+ communityAgentId: config.communityAgentId,
2468
+ lastPollAt: (/* @__PURE__ */ new Date()).toISOString(),
2469
+ lastError: error instanceof Error ? error.message : "unknown error"
2470
+ };
2471
+ }
2472
+ return status;
2473
+ }
2474
+ async function start() {
2475
+ if (running) {
2476
+ return;
2477
+ }
2478
+ running = true;
2479
+ status = { ...status, running: true };
2480
+ await pollOnce();
2481
+ const config = getActiveConfig(readConfig());
2482
+ if (config) {
2483
+ timer = setInterval(() => {
2484
+ void pollOnce();
2485
+ }, config.pollIntervalMs);
2486
+ }
2487
+ }
2488
+ async function stop() {
2489
+ if (timer) {
2490
+ clearInterval(timer);
2491
+ timer = void 0;
2492
+ }
2493
+ running = false;
2494
+ status = { ...status, running: false };
2495
+ }
2496
+ return {
2497
+ start,
2498
+ stop,
2499
+ pollOnce,
2500
+ runAutonomousLoopOnce,
2501
+ applyProposal,
2502
+ listAgents: async () => {
2503
+ const config = getActiveConfig(readConfig());
2504
+ if (!config) {
2505
+ throw new Error("community bridge is not active");
2506
+ }
2507
+ const client = new CommunityClient(config);
2508
+ return client.listAgents();
2509
+ },
2510
+ checkInbox: async () => {
2511
+ const config = getActiveConfig(readConfig());
2512
+ if (!config) {
2513
+ throw new Error("community bridge is not active");
2514
+ }
2515
+ const paths = await ensureWorkspaceForActiveConfig(config);
2516
+ return buildInboxSummary(paths);
2517
+ },
2518
+ postPublish: async (input) => {
2519
+ const config = getActiveConfig(readConfig());
2520
+ if (!config) {
2521
+ throw new Error("community bridge is not active");
2522
+ }
2523
+ const client = new CommunityClient(config);
2524
+ const envelope = createPostPublishEnvelope({
2525
+ senderAgentId: config.communityAgentId,
2526
+ title: input.title,
2527
+ body: input.body,
2528
+ intent: input.intent,
2529
+ tags: input.tags
2530
+ });
2531
+ await client.publishEnvelope(envelope);
2532
+ const paths = await ensureWorkspaceForActiveConfig(config);
2533
+ if (paths) {
2534
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, envelope);
2535
+ }
2536
+ return {
2537
+ ok: true,
2538
+ envelopeId: envelope.envelope_id,
2539
+ ...paths ? { workspaceRootDir: paths.rootDir } : {}
2540
+ };
2541
+ },
2542
+ dmSend: async (input) => {
2543
+ const config = getActiveConfig(readConfig());
2544
+ if (!config) {
2545
+ throw new Error("community bridge is not active");
2546
+ }
2547
+ const client = new CommunityClient(config);
2548
+ const paths = await ensureWorkspaceForActiveConfig(config);
2549
+ const envelope = createDirectMessageEnvelope({
2550
+ senderAgentId: config.communityAgentId,
2551
+ targetAgentId: input.targetAgentId,
2552
+ message: input.message,
2553
+ intent: input.intent
2554
+ });
2555
+ const policyResult = await evaluateOutboundDmPolicy(
2556
+ paths,
2557
+ config.communityAgentId,
2558
+ input.targetAgentId,
2559
+ envelope.envelope_id
2560
+ );
2561
+ if (policyResult.decision !== "allow") {
2562
+ throw new Error(`dm.send blocked by delegation policy: ${policyResult.decision}`);
2563
+ }
2564
+ const publishResult = await client.publishEnvelope(envelope);
2565
+ if (paths) {
2566
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, envelope);
2567
+ await appendAuditLog(paths, {
2568
+ kind: "policy_decision",
2569
+ community_agent_id: config.communityAgentId,
2570
+ action: "dm.send",
2571
+ decision: policyResult.decision,
2572
+ reason: `target=${input.targetAgentId}; matched=${policyResult.matchedRuleId ?? "default"}`,
2573
+ envelope_id: envelope.envelope_id,
2574
+ dm_id: publishResult.dm_id,
2575
+ source_event_id: envelope.envelope_id,
2576
+ target_agent_id: input.targetAgentId,
2577
+ result: "allowed"
2578
+ });
2579
+ await appendAuditLog(paths, {
2580
+ kind: "dm.send",
2581
+ community_agent_id: config.communityAgentId,
2582
+ action: "dm.send",
2583
+ decision: "allow",
2584
+ reason: `matched ${policyResult.matchedRuleId ?? "default"}`,
2585
+ envelope_id: envelope.envelope_id,
2586
+ dm_id: publishResult.dm_id,
2587
+ source_event_id: envelope.envelope_id,
2588
+ target_agent_id: input.targetAgentId,
2589
+ result: "sent",
2590
+ payload: {
2591
+ intent: input.intent ?? null
2592
+ }
2593
+ });
2594
+ }
2595
+ return {
2596
+ ok: true,
2597
+ envelopeId: envelope.envelope_id,
2598
+ ...paths ? { workspaceRootDir: paths.rootDir } : {}
2599
+ };
2600
+ },
2601
+ schedulePropose: async (input) => {
2602
+ const config = getActiveConfig(readConfig());
2603
+ if (!config) {
2604
+ throw new Error("community bridge is not active");
2605
+ }
2606
+ const client = new CommunityClient(config);
2607
+ const paths = await ensureWorkspaceForActiveConfig(config);
2608
+ const { policy, policyResult } = await evaluateSchedulePolicy(paths, config.communityAgentId, "propose");
2609
+ if (policyResult.decision !== "allow") {
2610
+ throw new Error(`schedule.negotiate.propose blocked by delegation policy: ${policyResult.decision}`);
2611
+ }
2612
+ const envelope = createScheduleNegotiateEnvelope({
2613
+ senderAgentId: config.communityAgentId,
2614
+ targetAgentId: input.targetAgentId,
2615
+ actingFor: input.actingFor ?? policy.principal.userId,
2616
+ kind: "propose",
2617
+ topic: input.topic,
2618
+ durationMinutes: input.durationMinutes,
2619
+ candidateWindows: input.candidateWindows
2620
+ });
2621
+ const publishResult = await client.publishEnvelope(envelope);
2622
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, envelope);
2623
+ await appendAuditLog(paths, {
2624
+ kind: "policy_decision",
2625
+ community_agent_id: config.communityAgentId,
2626
+ action: "schedule.negotiate.propose",
2627
+ decision: policyResult.decision,
2628
+ reason: `target=${input.targetAgentId}; matched=${policyResult.matchedRuleId ?? "default"}`,
2629
+ envelope_id: envelope.envelope_id,
2630
+ source_event_id: envelope.envelope_id,
2631
+ target_agent_id: input.targetAgentId,
2632
+ result: "allowed",
2633
+ payload: {
2634
+ negotiation_id: envelope.payload.negotiation_id,
2635
+ acting_for: envelope.acting_for
2636
+ }
2637
+ });
2638
+ await appendAuditLog(paths, {
2639
+ kind: "schedule.negotiate",
2640
+ community_agent_id: config.communityAgentId,
2641
+ action: "schedule.negotiate.propose",
2642
+ decision: "allow",
2643
+ reason: "outbound schedule proposal",
2644
+ envelope_id: envelope.envelope_id,
2645
+ source_event_id: envelope.envelope_id,
2646
+ target_agent_id: input.targetAgentId,
2647
+ result: "sent",
2648
+ payload: {
2649
+ negotiation_id: envelope.payload.negotiation_id,
2650
+ candidate_windows: envelope.payload.candidate_windows,
2651
+ delivery_id: publishResult.dm_id ?? null
2652
+ }
2653
+ });
2654
+ await writeScheduleState(
2655
+ paths,
2656
+ updateScheduleState(
2657
+ await readScheduleState(paths),
2658
+ createScheduleStateFromEnvelope(envelope, "proposed", envelope.payload.candidate_windows, void 0, {
2659
+ peerAgentId: input.targetAgentId
2660
+ })
2661
+ )
2662
+ );
2663
+ return {
2664
+ ok: true,
2665
+ envelopeId: envelope.envelope_id,
2666
+ negotiationId: envelope.payload.negotiation_id,
2667
+ workspaceRootDir: paths.rootDir
2668
+ };
2669
+ },
2670
+ prepareMeetingSummary: async (input) => {
2671
+ const config = getActiveConfig(readConfig());
2672
+ if (!config) {
2673
+ throw new Error("community bridge is not active");
2674
+ }
2675
+ const subagent = config.communitySubagent;
2676
+ if (!subagent?.enabled || !subagent.workspaceDir) {
2677
+ throw new Error("community subagent workspace is unavailable");
2678
+ }
2679
+ const paths = await ensureWorkspaceForActiveConfig(config);
2680
+ const room = (await readRooms(paths)).find((item) => item.room_id === input.roomId);
2681
+ if (!room) {
2682
+ throw new Error(`room not found in local workspace: ${input.roomId}`);
2683
+ }
2684
+ const messages = await readRoomMessages(paths, input.roomId);
2685
+ if (messages.length === 0) {
2686
+ throw new Error(`room transcript is empty: ${input.roomId}`);
2687
+ }
2688
+ const now = /* @__PURE__ */ new Date();
2689
+ const policy = await readDelegationPolicy(paths, config.communityAgentId);
2690
+ const timezone = policy.scheduling?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
2691
+ const handoff = createMeetingSummaryHandoffItem(config.communityAgentId, {
2692
+ room,
2693
+ messages,
2694
+ timezone,
2695
+ currentDate: formatDateForTimeZone(now, timezone),
2696
+ currentTime: now.toISOString()
2697
+ });
2698
+ const writeResult = await appendSubagentHandoffItems(subagent.workspaceDir, config.communityAgentId, [handoff]);
2699
+ await appendAuditLog(paths, {
2700
+ kind: "meeting.summary.requested",
2701
+ community_agent_id: config.communityAgentId,
2702
+ action: "meeting.summary",
2703
+ decision: "ask",
2704
+ reason: "manual meeting summary requested; sidecar will call community subagent",
2705
+ source_event_id: handoff.handoff_id,
2706
+ envelope_id: handoff.envelope_id,
2707
+ result: "handoff_created",
2708
+ payload: {
2709
+ room_id: input.roomId,
2710
+ message_count: messages.length,
2711
+ timezone,
2712
+ current_date: handoff.payload.current_date,
2713
+ current_time: handoff.payload.current_time,
2714
+ subagent_handoff: writeResult
2715
+ }
2716
+ });
2717
+ return {
2718
+ ok: true,
2719
+ roomId: input.roomId,
2720
+ handoffId: handoff.handoff_id,
2721
+ workspaceRootDir: paths.rootDir,
2722
+ subagentWorkspaceDir: subagent.workspaceDir
2723
+ };
2724
+ },
2725
+ roomSend: async (input) => {
2726
+ const config = getActiveConfig(readConfig());
2727
+ if (!config) {
2728
+ throw new Error("community bridge is not active");
2729
+ }
2730
+ const paths = await ensureWorkspaceForActiveConfig(config);
2731
+ const policy = await readDelegationPolicy(paths, config.communityAgentId);
2732
+ const policyResult = evaluateDelegationPolicy(policy, { action: "room.message.send", risk: "low" });
2733
+ if (policyResult.decision !== "allow") {
2734
+ throw new Error(`room.message.send blocked by delegation policy: ${policyResult.decision}`);
2735
+ }
2736
+ const room = (await readRooms(paths)).find((item) => item.room_id === input.roomId);
2737
+ if (!room) {
2738
+ throw new Error(`room not found in local workspace: ${input.roomId}`);
2739
+ }
2740
+ if (!room.members.includes(config.communityAgentId)) {
2741
+ throw new Error(`local agent is not a room member: ${input.roomId}`);
2742
+ }
2743
+ const envelope = createRoomMessageEnvelope({
2744
+ senderAgentId: config.communityAgentId,
2745
+ roomId: input.roomId,
2746
+ message: input.message,
2747
+ speakerKind: input.speakerKind ?? "human",
2748
+ speakerLabel: input.speakerLabel ?? policy.principal.userId,
2749
+ intent: input.intent
2750
+ });
2751
+ const publishResult = await new CommunityClient(config).publishEnvelope(envelope);
2752
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, envelope);
2753
+ await appendRoomMessages(paths, [createRoomMessageRecord(envelope, void 0, publishResult.message_id)]);
2754
+ await appendAuditLog(paths, {
2755
+ kind: "room.message.sent",
2756
+ community_agent_id: config.communityAgentId,
2757
+ action: "room.message.send",
2758
+ decision: "allow",
2759
+ reason: `matched ${policyResult.matchedRuleId ?? "default"}`,
2760
+ envelope_id: envelope.envelope_id,
2761
+ source_event_id: envelope.envelope_id,
2762
+ result: "sent",
2763
+ payload: {
2764
+ room_id: input.roomId,
2765
+ message_id: publishResult.message_id ?? null,
2766
+ speaker_kind: envelope.payload.speaker_kind,
2767
+ delivery_ids: publishResult.delivery_ids ?? []
2768
+ }
2769
+ });
2770
+ return {
2771
+ ok: true,
2772
+ envelopeId: envelope.envelope_id,
2773
+ roomId: input.roomId,
2774
+ messageId: publishResult.message_id,
2775
+ workspaceRootDir: paths.rootDir
2776
+ };
2777
+ },
2778
+ skillQuery: async (input) => {
2779
+ const config = getActiveConfig(readConfig());
2780
+ if (!config) {
2781
+ throw new Error("community bridge is not active");
2782
+ }
2783
+ const client = new CommunityClient(config);
2784
+ const envelope = createSkillQueryEnvelope({
2785
+ senderAgentId: config.communityAgentId,
2786
+ need: input.need,
2787
+ context: input.context,
2788
+ intent: input.intent,
2789
+ tags: input.tags
2790
+ });
2791
+ await client.publishEnvelope(envelope);
2792
+ const paths = await ensureWorkspaceForActiveConfig(config);
2793
+ let stagedOffers = 0;
2794
+ if (paths) {
2795
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, envelope);
2796
+ const workspaceState = await readPlatformWorkspaceState(paths);
2797
+ const skillOffers = await client.fetchSkillOffers(workspaceState?.lastSkillOfferCursor);
2798
+ await appendStagedSkillOffers(paths, config.communityAgentId, skillOffers.items, skillOffers.next_cursor);
2799
+ await appendPendingUserActions(
2800
+ paths,
2801
+ createSkillOfferPendingActions(config.communityAgentId, skillOffers.items)
2802
+ );
2803
+ await appendSubagentHandoffItems(
2804
+ config.communitySubagent?.workspaceDir,
2805
+ config.communityAgentId,
2806
+ createSkillOfferHandoffItems(config.communityAgentId, skillOffers.items)
2807
+ );
2808
+ stagedOffers = skillOffers.items.length;
2809
+ }
2810
+ return {
2811
+ ok: true,
2812
+ envelopeId: envelope.envelope_id,
2813
+ stagedOffers,
2814
+ ...paths ? { workspaceRootDir: paths.rootDir } : {}
2815
+ };
2816
+ },
2817
+ skillInstall: async (input) => {
2818
+ const config = getActiveConfig(readConfig());
2819
+ if (!config) {
2820
+ throw new Error("community bridge is not active");
2821
+ }
2822
+ const target = input.offerId ?? input.skillSlug;
2823
+ if (!target || target.trim().length === 0) {
2824
+ throw new Error("offerId or skillSlug is required");
2825
+ }
2826
+ const paths = await ensureWorkspaceForActiveConfig(config);
2827
+ const offers = await readStagedSkillOffers(paths);
2828
+ const offer = offers.find((item) => item.offer_id === target || item.skill_slug === target);
2829
+ if (!offer) {
2830
+ throw new Error(`no staged skill offer matched ${target}`);
2831
+ }
2832
+ if (offer.status !== "pending_user_confirmation" && offer.status !== "accepted") {
2833
+ throw new Error(`skill offer is not installable yet: status=${offer.status}`);
2834
+ }
2835
+ const installingOffer = attachSkillInstallMetadata(offer, {
2836
+ status: "installing",
2837
+ install_method: "pending_user_side_installer",
2838
+ install_agent: input.agentId ?? "main",
2839
+ install_started_at: (/* @__PURE__ */ new Date()).toISOString()
2840
+ });
2841
+ await writeStagedSkillOffer(paths, installingOffer);
2842
+ const result = installOpenClawSkill(offer.skill_slug);
2843
+ const nextOffer = attachSkillInstallMetadata(offer, {
2844
+ status: result.ok ? "installed" : "install_failed",
2845
+ install_status: result.ok ? "passed" : "failed",
2846
+ install_method: "pending_user_side_installer",
2847
+ install_agent: input.agentId ?? "main",
2848
+ install_checked_at: (/* @__PURE__ */ new Date()).toISOString(),
2849
+ install_result: result
2850
+ });
2851
+ await writeStagedSkillOffer(paths, nextOffer);
2852
+ return {
2853
+ ok: result.ok,
2854
+ offerId: nextOffer.offer_id,
2855
+ skillSlug: nextOffer.skill_slug,
2856
+ status: nextOffer.status,
2857
+ installAgent: commandContext.agentId,
2858
+ installHome: commandContext.homeDir,
2859
+ installCwd: commandContext.cwd,
2860
+ stdout: result.stdout,
2861
+ stderr: result.stderr,
2862
+ workspaceRootDir: paths.rootDir
2863
+ };
2864
+ },
2865
+ confirmAction: async (input) => {
2866
+ const config = getActiveConfig(readConfig());
2867
+ if (!config) {
2868
+ throw new Error("community bridge is not active");
2869
+ }
2870
+ const paths = await ensureWorkspaceForActiveConfig(config);
2871
+ return decidePendingUserAction(paths, {
2872
+ actionId: input.actionId,
2873
+ decision: "accepted",
2874
+ decidedBy: input.decidedBy ?? "bridge",
2875
+ reason: input.reason
2876
+ });
2877
+ },
2878
+ rejectAction: async (input) => {
2879
+ const config = getActiveConfig(readConfig());
2880
+ if (!config) {
2881
+ throw new Error("community bridge is not active");
2882
+ }
2883
+ const paths = await ensureWorkspaceForActiveConfig(config);
2884
+ return decidePendingUserAction(paths, {
2885
+ actionId: input.actionId,
2886
+ decision: "rejected",
2887
+ decidedBy: input.decidedBy ?? "bridge",
2888
+ reason: input.reason
2889
+ });
2890
+ },
2891
+ applyAcceptedAction: async (input) => {
2892
+ const config = getActiveConfig(readConfig());
2893
+ if (!config) {
2894
+ throw new Error("community bridge is not active");
2895
+ }
2896
+ const paths = await ensureWorkspaceForActiveConfig(config);
2897
+ const actionId = input.actionId.trim();
2898
+ if (!actionId) {
2899
+ throw new Error("actionId is required");
2900
+ }
2901
+ const actions = await readPendingUserActions(paths);
2902
+ const action = actions.find((item) => item.action_id === actionId);
2903
+ if (!action) {
2904
+ throw new Error(`pending action not found: ${actionId}`);
2905
+ }
2906
+ if (action.kind !== "install_skill" && action.kind !== "commit_schedule" && action.kind !== "meeting_summary") {
2907
+ throw new Error(`unsupported action kind: ${action.kind}`);
2908
+ }
2909
+ const decisions = await readUserActionDecisions(paths);
2910
+ const decision = decisions.find((item) => item.action_id === actionId);
2911
+ if (!decision || decision.decision !== "accepted") {
2912
+ throw new Error(`action is not accepted: ${actionId}`);
2913
+ }
2914
+ if (action.kind === "meeting_summary") {
2915
+ const existingSummary = (await readUserActionResults(paths)).find(
2916
+ (item) => item.action_id === actionId && item.kind === "meeting_summary" && item.status === "recorded"
2917
+ );
2918
+ if (existingSummary) {
2919
+ return {
2920
+ ok: true,
2921
+ actionId,
2922
+ status: "recorded",
2923
+ alreadyApplied: true,
2924
+ result: existingSummary,
2925
+ workspaceRootDir: paths.rootDir
2926
+ };
2927
+ }
2928
+ const record = createMeetingSummaryRecord(config.communityAgentId, action);
2929
+ await writeMeetingSummary(paths, record);
2930
+ await appendAuditLog(paths, {
2931
+ kind: "meeting.summary.confirmed",
2932
+ community_agent_id: config.communityAgentId,
2933
+ action: "meeting.summary",
2934
+ decision: "ask",
2935
+ reason: `user accepted ${actionId}; summary recorded locally`,
2936
+ source_event_id: actionId,
2937
+ envelope_id: typeof action.payload.handoff_id === "string" ? action.payload.handoff_id : void 0,
2938
+ result: "recorded",
2939
+ payload: {
2940
+ summary_id: record.summary_id,
2941
+ room_id: record.room_id,
2942
+ summary_path: `${paths.roomsDir}/${sanitizeSegment3(record.room_id)}.summary.json`,
2943
+ egress: "local_only"
2944
+ }
2945
+ });
2946
+ await appendUserActionResult(paths, {
2947
+ result_id: `result_${Date.now()}_${actionId}`,
2948
+ action_id: actionId,
2949
+ kind: "meeting_summary",
2950
+ status: "recorded",
2951
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
2952
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
2953
+ payload: {
2954
+ summary_id: record.summary_id,
2955
+ room_id: record.room_id,
2956
+ local_summary_file: `${paths.roomsDir}/${sanitizeSegment3(record.room_id)}.summary.json`
2957
+ }
2958
+ });
2959
+ return {
2960
+ ok: true,
2961
+ actionId,
2962
+ status: "recorded",
2963
+ roomId: record.room_id,
2964
+ summaryId: record.summary_id,
2965
+ workspaceRootDir: paths.rootDir
2966
+ };
2967
+ }
2968
+ if (action.kind === "commit_schedule") {
2969
+ const topic = typeof action.payload.topic === "string" ? action.payload.topic : "";
2970
+ const peerAgentId = typeof action.payload.peer_agent_id === "string" ? action.payload.peer_agent_id : "";
2971
+ const negotiationId = typeof action.payload.negotiation_id === "string" ? action.payload.negotiation_id : "";
2972
+ const durationMinutes = typeof action.payload.duration_minutes === "number" && Number.isFinite(action.payload.duration_minutes) ? action.payload.duration_minutes : 30;
2973
+ const selectedWindow = action.payload.selected_window && typeof action.payload.selected_window === "object" && !Array.isArray(action.payload.selected_window) && typeof action.payload.selected_window.start === "string" && typeof action.payload.selected_window.end === "string" ? action.payload.selected_window : null;
2974
+ if (!topic || !peerAgentId || !negotiationId || !selectedWindow) {
2975
+ throw new Error(`commit_schedule action missing required payload: ${actionId}`);
2976
+ }
2977
+ const policy = await readDelegationPolicy(paths, config.communityAgentId);
2978
+ const envelope = createScheduleNegotiateEnvelope({
2979
+ senderAgentId: config.communityAgentId,
2980
+ targetAgentId: peerAgentId,
2981
+ actingFor: policy.principal.userId,
2982
+ kind: "commit",
2983
+ negotiationId,
2984
+ topic,
2985
+ durationMinutes,
2986
+ candidateWindows: [selectedWindow],
2987
+ selectedWindow
2988
+ });
2989
+ await new CommunityClient(config).publishEnvelope(envelope);
2990
+ await appendWorkspaceEnvelope(paths, config.communityAgentId, envelope);
2991
+ await appendAuditLog(paths, {
2992
+ kind: "schedule.commit.sent",
2993
+ community_agent_id: config.communityAgentId,
2994
+ action: "schedule.negotiate.commit",
2995
+ decision: "ask",
2996
+ reason: `user accepted ${actionId}`,
2997
+ envelope_id: envelope.envelope_id,
2998
+ source_event_id: actionId,
2999
+ target_agent_id: peerAgentId,
3000
+ result: "sent",
3001
+ payload: {
3002
+ negotiation_id: negotiationId,
3003
+ selected_window: selectedWindow,
3004
+ acting_for: envelope.acting_for
3005
+ }
3006
+ });
3007
+ await appendUserActionResult(paths, {
3008
+ result_id: `result_${Date.now()}_${actionId}`,
3009
+ action_id: actionId,
3010
+ kind: "schedule_commitment",
3011
+ status: "committed",
3012
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
3013
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
3014
+ payload: {
3015
+ negotiation_id: negotiationId,
3016
+ envelope_id: envelope.envelope_id,
3017
+ selected_window: selectedWindow
3018
+ }
3019
+ });
3020
+ await writeScheduleState(
3021
+ paths,
3022
+ updateScheduleState(
3023
+ await readScheduleState(paths),
3024
+ createScheduleStateFromEnvelope(envelope, "committed", [selectedWindow], selectedWindow, {
3025
+ peerAgentId
3026
+ })
3027
+ )
3028
+ );
3029
+ const room = await createRoomForScheduleCommit(config, paths, {
3030
+ topic,
3031
+ peerAgentId,
3032
+ negotiationId
3033
+ });
3034
+ return {
3035
+ ok: true,
3036
+ actionId,
3037
+ status: "committed",
3038
+ envelopeId: envelope.envelope_id,
3039
+ negotiationId,
3040
+ roomId: room.room_id,
3041
+ workspaceRootDir: paths.rootDir
3042
+ };
3043
+ }
3044
+ const results = await readUserActionResults(paths);
3045
+ const existingFinal = results.filter((item) => item.action_id === actionId).find((item) => item.status === "installed" || item.status === "install_failed");
3046
+ if (existingFinal) {
3047
+ return {
3048
+ ok: existingFinal.status === "installed",
3049
+ actionId,
3050
+ status: existingFinal.status,
3051
+ alreadyApplied: true,
3052
+ result: existingFinal,
3053
+ workspaceRootDir: paths.rootDir
3054
+ };
3055
+ }
3056
+ const skillSlug = typeof action.payload.skill_slug === "string" ? action.payload.skill_slug : "";
3057
+ if (!skillSlug) {
3058
+ throw new Error(`action missing payload.skill_slug: ${actionId}`);
3059
+ }
3060
+ await appendUserActionResult(
3061
+ paths,
3062
+ createInstallSkillActionResult(actionId, "installing", {
3063
+ skill_slug: skillSlug,
3064
+ install_agent: input.agentId ?? "main",
3065
+ install_method: "pending_user_side_installer"
3066
+ })
3067
+ );
3068
+ const result = installOpenClawSkill(skillSlug);
3069
+ const finalStatus = result.ok ? "installed" : "install_failed";
3070
+ const actionResult = createInstallSkillActionResult(actionId, finalStatus, {
3071
+ skill_slug: skillSlug,
3072
+ install_agent: input.agentId ?? "main",
3073
+ install_method: "pending_user_side_installer",
3074
+ install_result: result
3075
+ });
3076
+ await appendUserActionResult(paths, actionResult);
3077
+ const offerId = typeof action.payload.offer_id === "string" ? action.payload.offer_id : void 0;
3078
+ if (offerId) {
3079
+ const offers = await readStagedSkillOffers(paths);
3080
+ const offer = offers.find((item) => item.offer_id === offerId);
3081
+ if (offer) {
3082
+ await writeStagedSkillOffer(
3083
+ paths,
3084
+ attachSkillInstallMetadata(offer, {
3085
+ status: result.ok ? "installed" : "install_failed",
3086
+ install_status: result.ok ? "passed" : "failed",
3087
+ install_method: "openclaw skills install --agent",
3088
+ install_agent: commandContext.agentId,
3089
+ install_home: commandContext.homeDir,
3090
+ install_cwd: commandContext.cwd,
3091
+ install_checked_at: (/* @__PURE__ */ new Date()).toISOString(),
3092
+ install_result: result
3093
+ })
3094
+ );
3095
+ }
3096
+ }
3097
+ return {
3098
+ ok: result.ok,
3099
+ actionId,
3100
+ status: finalStatus,
3101
+ skillSlug,
3102
+ result,
3103
+ actionResult,
3104
+ workspaceRootDir: paths.rootDir
3105
+ };
3106
+ },
3107
+ getStatus: () => status
3108
+ };
3109
+ }
3110
+ function attachSkillInstallMetadata(offer, metadata) {
3111
+ return {
3112
+ ...offer,
3113
+ ...metadata
3114
+ };
3115
+ }
3116
+ function readDirectMessageFromHandoff(handoff) {
3117
+ const payload = handoff.payload;
3118
+ const dmId = typeof payload.dm_id === "string" ? payload.dm_id : "";
3119
+ const senderAgentId = typeof payload.sender_agent_id === "string" ? payload.sender_agent_id : "";
3120
+ const recipientAgentId = typeof payload.recipient_agent_id === "string" ? payload.recipient_agent_id : "";
3121
+ const message = typeof payload.message === "string" ? payload.message : "";
3122
+ if (!dmId || !senderAgentId || !recipientAgentId || !message) {
3123
+ return null;
3124
+ }
3125
+ return {
3126
+ dmId,
3127
+ senderAgentId,
3128
+ recipientAgentId,
3129
+ message,
3130
+ intent: typeof payload.intent === "string" ? payload.intent : null,
3131
+ createdAt: typeof payload.created_at === "string" ? payload.created_at : null
3132
+ };
3133
+ }
3134
+ function createSubagentReplyPendingAction(communityAgentId, handoff, dm, proposal) {
3135
+ return {
3136
+ action_id: `action_subagent_reply_${sanitizeSegment3(dm.dmId)}`,
3137
+ kind: "reply_to_dm",
3138
+ source: "openclaw-bridge",
3139
+ community_agent_id: communityAgentId,
3140
+ status: "pending_user_confirmation",
3141
+ user_confirmation_required: true,
3142
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
3143
+ title: `\u786E\u8BA4\u56DE\u590D ${dm.senderAgentId}`,
3144
+ summary: `Community Subagent \u5DF2\u8D77\u8349\u4E00\u6761${proposal.risk}\u98CE\u9669\u56DE\u590D\uFF1A${proposal.draft}`,
3145
+ payload: {
3146
+ dm_id: dm.dmId,
3147
+ envelope_id: handoff.envelope_id,
3148
+ handoff_id: handoff.handoff_id,
3149
+ sender_agent_id: dm.senderAgentId,
3150
+ recipient_agent_id: dm.recipientAgentId,
3151
+ message: dm.message,
3152
+ draft_message: proposal.draft,
3153
+ intent: dm.intent,
3154
+ risk_level: proposal.risk,
3155
+ suggested_action: "confirm_subagent_draft_reply",
3156
+ proposal_reason: proposal.reason ?? null
3157
+ }
3158
+ };
3159
+ }
3160
+ function createMeetingSummaryPendingAction(communityAgentId, handoff, proposal, proposalMeta) {
3161
+ const topic = readString3(handoff.payload.topic) ?? proposal.room_id;
3162
+ return {
3163
+ action_id: `action_meeting_summary_${sanitizeSegment3(proposal.room_id)}_${sanitizeSegment3(handoff.handoff_id)}`,
3164
+ kind: "meeting_summary",
3165
+ source: "openclaw-bridge",
3166
+ community_agent_id: communityAgentId,
3167
+ status: "pending_user_confirmation",
3168
+ user_confirmation_required: true,
3169
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
3170
+ title: `\u786E\u8BA4\u4F1A\u8BAE\u6458\u8981\uFF1A${topic}`,
3171
+ summary: proposal.summary,
3172
+ payload: {
3173
+ room_id: proposal.room_id,
3174
+ topic,
3175
+ handoff_id: handoff.handoff_id,
3176
+ summary: proposal.summary,
3177
+ todos: proposal.todos,
3178
+ risk_level: "medium",
3179
+ suggested_action: "confirm_meeting_summary",
3180
+ proposal_reason: proposal.reason ?? null,
3181
+ proposal_model: readString3(proposalMeta?.model) ?? null,
3182
+ proposal_run_id: readString3(proposalMeta?.run_id) ?? null,
3183
+ proposal_session_id: readString3(proposalMeta?.session_id) ?? null,
3184
+ egress: "local_only"
3185
+ }
3186
+ };
3187
+ }
3188
+ function createMeetingSummaryRecord(communityAgentId, action) {
3189
+ const roomId = readString3(action.payload.room_id);
3190
+ const summary = readString3(action.payload.summary);
3191
+ if (!roomId || !summary) {
3192
+ throw new Error(`meeting_summary action missing room_id or summary: ${action.action_id}`);
3193
+ }
3194
+ const todos = Array.isArray(action.payload.todos) ? action.payload.todos.map((todo) => {
3195
+ if (typeof todo === "string") {
3196
+ const text2 = todo.trim();
3197
+ return text2 ? { text: text2, due_at: null, owner: null } : null;
3198
+ }
3199
+ if (!todo || typeof todo !== "object" || Array.isArray(todo)) {
3200
+ return null;
3201
+ }
3202
+ const record = todo;
3203
+ const text = readString3(record.text);
3204
+ return text ? {
3205
+ text,
3206
+ due_at: readString3(record.due_at) ?? null,
3207
+ owner: readString3(record.owner) ?? null
3208
+ } : null;
3209
+ }).filter((todo) => Boolean(todo)) : [];
3210
+ return {
3211
+ summary_id: `summary_${Date.now()}_${sanitizeSegment3(roomId)}`,
3212
+ action_id: action.action_id,
3213
+ room_id: roomId,
3214
+ community_agent_id: communityAgentId,
3215
+ summary,
3216
+ todos,
3217
+ source_handoff_id: readString3(action.payload.handoff_id) ?? null,
3218
+ source_model: readString3(action.payload.proposal_model) ?? null,
3219
+ confirmed_at: (/* @__PURE__ */ new Date()).toISOString(),
3220
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
3221
+ };
3222
+ }
3223
+ function classifyDirectMessageRisk(message) {
3224
+ const text = `${message.message} ${message.intent ?? ""}`.toLowerCase();
3225
+ if (text.includes("\u5F00\u4F1A") || text.includes("\u4F1A\u8BAE") || text.includes("meeting") || text.includes("\u660E\u5929") || text.includes("\u4E0B\u5348") || text.includes("\u4E0A\u5348") || text.includes("\u53EF\u4EE5\u5417")) {
3226
+ return "medium";
3227
+ }
3228
+ if (text.includes("\u5728\u4E0D\u5728\u7EBF") || text.includes("\u5728\u7EBF") || text.includes("ping") || text.includes("hello") || text.includes("hi") || text.includes("\u6536\u5230") || text.includes("ack")) {
3229
+ return "low";
3230
+ }
3231
+ if (text.includes("?") || text.includes("\uFF1F")) {
3232
+ return "medium";
3233
+ }
3234
+ return "medium";
3235
+ }
3236
+ function sanitizeSegment3(value) {
3237
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
3238
+ }
3239
+ function readString3(value) {
3240
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3241
+ }
3242
+ function formatDateForTimeZone(value, timeZone) {
3243
+ const parts = new Intl.DateTimeFormat("en-CA", {
3244
+ timeZone,
3245
+ year: "numeric",
3246
+ month: "2-digit",
3247
+ day: "2-digit"
3248
+ }).formatToParts(value);
3249
+ const byType = new Map(parts.map((part) => [part.type, part.value]));
3250
+ return `${byType.get("year")}-${byType.get("month")}-${byType.get("day")}`;
3251
+ }
3252
+ function installOpenClawSkill(slug) {
3253
+ return {
3254
+ ok: false,
3255
+ message: `Skill install for ${slug} is pending user-side installer integration.`,
3256
+ stdout: "",
3257
+ stderr: "openclaw-bridge beta package does not execute local shell installers",
3258
+ code: null
3259
+ };
3260
+ }
3261
+
3262
+ // src/setup.ts
3263
+ import { randomUUID as randomUUID4 } from "node:crypto";
3264
+ import { chmod, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
3265
+ import path3 from "node:path";
3266
+ import { pathToFileURL } from "node:url";
3267
+ function describeCommunityBridgeSetup() {
3268
+ return {
3269
+ title: "Connect AgentComm Community",
3270
+ protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
3271
+ requiredInputs: [
3272
+ "serverUrl",
3273
+ "auth.method",
3274
+ "agent.openclawWorkspaceId",
3275
+ "agent.openclawAgentId",
3276
+ "agent.displayName",
3277
+ "agent.capabilities"
3278
+ ],
3279
+ optionalInputs: [
3280
+ "communitySubagent.enabled",
3281
+ "communitySubagent.autoApprove",
3282
+ "communitySubagent.agentId",
3283
+ "communitySubagent.displayName",
3284
+ "communitySubagent.model"
3285
+ ],
3286
+ recommendedFlow: [
3287
+ "Register Bridge with AgentComm Platform",
3288
+ "Create Community Persona workspace",
3289
+ "Create OpenClaw Community Subagent after user confirmation",
3290
+ "Write Bridge config patch",
3291
+ "Run validation"
3292
+ ],
3293
+ authMethods: [
3294
+ {
3295
+ method: "browser",
3296
+ label: "Browser login",
3297
+ description: "Complete account login in the Community web app, then continue setup."
3298
+ },
3299
+ {
3300
+ method: "paste_token",
3301
+ label: "Paste registration token",
3302
+ description: "Paste a short-lived token generated by the Community web app."
3303
+ }
3304
+ ]
3305
+ };
3306
+ }
3307
+ function parseSetupInput(value) {
3308
+ if (!isRecord4(value)) {
3309
+ throw new Error("setup input must be an object");
3310
+ }
3311
+ const auth = readAuthInput(value.auth);
3312
+ const agent = readAgentInput(value.agent);
3313
+ return {
3314
+ serverUrl: requiredString(value.serverUrl, "serverUrl"),
3315
+ auth,
3316
+ agent,
3317
+ pollIntervalMs: readNumber2(value.pollIntervalMs) ?? 3e4,
3318
+ sharingPolicy: {
3319
+ allowProactiveConversation: readBoolean(readRecord(value.sharingPolicy)?.allowProactiveConversation) ?? true,
3320
+ maxContextLevel: "sanitized_summary"
3321
+ },
3322
+ communitySubagent: readCommunitySubagentInput(value.communitySubagent, agent.openclawAgentId)
3323
+ };
3324
+ }
3325
+ async function runCommunityBridgeSetup(input, context = {}) {
3326
+ const serverUrl = trimTrailingSlash2(input.serverUrl);
3327
+ const accessToken = input.auth.method === "browser" ? input.auth.userAccessToken : input.auth.token;
3328
+ const registration = await registerAgent(serverUrl, accessToken, input);
3329
+ const personaSetup = await setupCommunityPersona(input, registration, context);
3330
+ const bridgeTokenRef = await persistBridgeTokenSecret(personaSetup.workspaceRootDir, registration.bridge_token);
3331
+ return {
3332
+ ok: true,
3333
+ communityAgentId: registration.community_agent_id,
3334
+ personaSetup,
3335
+ policyRevision: registration.policy_snapshot?.policy_revision,
3336
+ configPatch: {
3337
+ plugins: {
3338
+ entries: {
3339
+ "openclaw-bridge": {
3340
+ enabled: true,
3341
+ config: {
3342
+ enabled: true,
3343
+ serverUrl,
3344
+ communityAgentId: registration.community_agent_id,
3345
+ bridgeTokenRef,
3346
+ pollIntervalMs: secondsToMs(registration.heartbeat?.interval_seconds) ?? input.pollIntervalMs,
3347
+ communitySubagent: personaSetup?.subagent ? {
3348
+ enabled: true,
3349
+ agentId: personaSetup.subagent.agentId,
3350
+ workspaceDir: personaSetup.subagent.workspaceDir,
3351
+ agentDir: personaSetup.subagent.agentDir
3352
+ } : void 0
3353
+ }
3354
+ }
3355
+ }
3356
+ }
3357
+ },
3358
+ notes: [
3359
+ "bridgeToken was written to a local 0600 secret file.",
3360
+ "configPatch stores bridgeTokenRef only; no bridge token plaintext is written to OpenClaw config.",
3361
+ "Community Subagent creation belongs to connect/setup, not plugin install."
3362
+ ]
3363
+ };
3364
+ }
3365
+ async function persistBridgeTokenSecret(communityWorkspaceRoot, bridgeToken) {
3366
+ const secretsDir = path3.join(communityWorkspaceRoot, "secrets");
3367
+ const tokenPath = path3.join(secretsDir, "bridge-token");
3368
+ await mkdir2(secretsDir, { recursive: true, mode: 448 });
3369
+ await writeFile2(tokenPath, `${bridgeToken}
3370
+ `, { encoding: "utf8", mode: 384 });
3371
+ await chmod(secretsDir, 448);
3372
+ await chmod(tokenPath, 384);
3373
+ return pathToFileURL(tokenPath).href;
3374
+ }
3375
+ async function setupCommunityPersona(input, registration, context) {
3376
+ const hostPaths = context.hostPaths ?? {};
3377
+ const paths = await resolvePlatformWorkspacePaths({
3378
+ communityAgentId: registration.community_agent_id,
3379
+ workspaceDir: hostPaths.workspaceDir,
3380
+ agentDir: inferOpenClawAgentDir(hostPaths),
3381
+ stateDir: hostPaths.stateDir,
3382
+ homeDir: hostPaths.homeDir
3383
+ });
3384
+ await ensurePlatformWorkspace(paths, registration.community_agent_id);
3385
+ if (!input.communitySubagent.enabled) {
3386
+ await writePersonaSubagentInfo(
3387
+ paths,
3388
+ registration.community_agent_id,
3389
+ {
3390
+ species: "openclaw",
3391
+ agentId: input.communitySubagent.agentId ?? defaultCommunitySubagentId(input.agent.openclawAgentId),
3392
+ workspaceDir: path3.join(paths.rootDir, "subagent-workspace"),
3393
+ agentDir: path3.join(paths.rootDir, "subagent-agent"),
3394
+ status: "skipped",
3395
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3396
+ setupMode: input.communitySubagent.autoApprove ? "auto_approved" : "interactive_onboarding"
3397
+ }
3398
+ );
3399
+ return {
3400
+ workspaceRootDir: paths.rootDir,
3401
+ subagent: null,
3402
+ status: "skipped",
3403
+ reason: "community subagent disabled by setup input"
3404
+ };
3405
+ }
3406
+ const subagent = await ensureOpenClawCommunitySubagent(input, paths.rootDir, context);
3407
+ await writePersonaSubagentInfo(
3408
+ paths,
3409
+ registration.community_agent_id,
3410
+ {
3411
+ species: "openclaw",
3412
+ agentId: subagent.agentId,
3413
+ workspaceDir: subagent.workspaceDir,
3414
+ agentDir: subagent.agentDir,
3415
+ status: subagent.status,
3416
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3417
+ setupMode: input.communitySubagent.autoApprove ? "auto_approved" : "interactive_onboarding"
3418
+ }
3419
+ );
3420
+ return {
3421
+ workspaceRootDir: paths.rootDir,
3422
+ subagent,
3423
+ status: subagent.status
3424
+ };
3425
+ }
3426
+ async function ensureOpenClawCommunitySubagent(input, communityWorkspaceRoot, _context) {
3427
+ const agentId = input.communitySubagent.agentId ?? defaultCommunitySubagentId(input.agent.openclawAgentId);
3428
+ const workspaceDir = path3.join(communityWorkspaceRoot, "subagent-workspace");
3429
+ const agentDir = path3.join(communityWorkspaceRoot, "subagent-agent");
3430
+ await mkdir2(workspaceDir, { recursive: true });
3431
+ await mkdir2(agentDir, { recursive: true });
3432
+ return {
3433
+ agentId,
3434
+ workspaceDir,
3435
+ agentDir,
3436
+ status: "pending_openclaw_setup"
3437
+ };
3438
+ }
3439
+ async function registerAgent(serverUrl, accessToken, input) {
3440
+ const response = await fetch(`${serverUrl}/v1/agents/register`, {
3441
+ method: "POST",
3442
+ headers: {
3443
+ Authorization: `Bearer ${accessToken}`,
3444
+ "Content-Type": "application/json",
3445
+ Accept: "application/json"
3446
+ },
3447
+ body: JSON.stringify({
3448
+ adapter_instance_id: `community_bridge_${randomUUID4()}`,
3449
+ openclaw_workspace_id: input.agent.openclawWorkspaceId,
3450
+ openclaw_agent_id: input.agent.openclawAgentId,
3451
+ agent_profile: {
3452
+ display_name: input.agent.displayName,
3453
+ capabilities: input.agent.capabilities,
3454
+ supported_intents: ["no_op", "propose_user_conversation"]
3455
+ },
3456
+ sharing_policy: {
3457
+ allow_agent_questions: false,
3458
+ allow_board_posts: false,
3459
+ allow_proactive_conversation: input.sharingPolicy.allowProactiveConversation,
3460
+ max_context_level: input.sharingPolicy.maxContextLevel
3461
+ },
3462
+ heartbeat_preferences: {
3463
+ interval_seconds: Math.max(5, Math.round(input.pollIntervalMs / 1e3))
3464
+ },
3465
+ consent: {
3466
+ consent_revision: `consent_${randomUUID4()}`,
3467
+ approved_by_user_id: "openclaw-local-user",
3468
+ approved_at: (/* @__PURE__ */ new Date()).toISOString()
3469
+ }
3470
+ })
3471
+ });
3472
+ const body = await readJson2(response);
3473
+ if (!response.ok) {
3474
+ throw new Error(`community registration failed: ${response.status}`);
3475
+ }
3476
+ if (!isRegisterAgentResponse(body)) {
3477
+ throw new Error("community registration response is missing agent credentials");
3478
+ }
3479
+ return body;
3480
+ }
3481
+ function readAuthInput(value) {
3482
+ const record = readRecord(value);
3483
+ const method = requiredString(record?.method, "auth.method");
3484
+ if (method === "browser") {
3485
+ return {
3486
+ method,
3487
+ userAccessToken: requiredString(record?.userAccessToken, "auth.userAccessToken")
3488
+ };
3489
+ }
3490
+ if (method === "paste_token") {
3491
+ return {
3492
+ method,
3493
+ token: requiredString(record?.token, "auth.token")
3494
+ };
3495
+ }
3496
+ throw new Error(`unsupported auth method: ${method}`);
3497
+ }
3498
+ function readAgentInput(value) {
3499
+ const record = readRecord(value);
3500
+ return {
3501
+ openclawWorkspaceId: requiredString(record?.openclawWorkspaceId, "agent.openclawWorkspaceId"),
3502
+ openclawAgentId: requiredString(record?.openclawAgentId, "agent.openclawAgentId"),
3503
+ displayName: requiredString(record?.displayName, "agent.displayName"),
3504
+ capabilities: readStringArray(record?.capabilities)
3505
+ };
3506
+ }
3507
+ function readCommunitySubagentInput(value, openclawAgentId) {
3508
+ const record = readRecord(value);
3509
+ return {
3510
+ enabled: readBoolean(record?.enabled) ?? true,
3511
+ autoApprove: readBoolean(record?.autoApprove) ?? false,
3512
+ agentId: readString4(record?.agentId) ?? defaultCommunitySubagentId(openclawAgentId),
3513
+ displayName: readString4(record?.displayName) ?? "AgentComm Community Persona",
3514
+ model: readString4(record?.model)
3515
+ };
3516
+ }
3517
+ async function readJson2(response) {
3518
+ const text = await response.text();
3519
+ return text.trim().length > 0 ? JSON.parse(text) : null;
3520
+ }
3521
+ function isRegisterAgentResponse(value) {
3522
+ return isRecord4(value) && typeof value.community_agent_id === "string" && typeof value.bridge_token === "string";
3523
+ }
3524
+ function requiredString(value, field) {
3525
+ if (typeof value !== "string" || value.trim().length === 0) {
3526
+ throw new Error(`${field} must be a non-empty string`);
3527
+ }
3528
+ return value.trim();
3529
+ }
3530
+ function readStringArray(value) {
3531
+ if (!Array.isArray(value)) {
3532
+ return [];
3533
+ }
3534
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
3535
+ }
3536
+ function readBoolean(value) {
3537
+ return typeof value === "boolean" ? value : void 0;
3538
+ }
3539
+ function readNumber2(value) {
3540
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3541
+ }
3542
+ function readString4(value) {
3543
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3544
+ }
3545
+ function readRecord(value) {
3546
+ return isRecord4(value) ? value : void 0;
3547
+ }
3548
+ function isRecord4(value) {
3549
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
3550
+ }
3551
+ function trimTrailingSlash2(value) {
3552
+ return value.replace(/\/+$/, "");
3553
+ }
3554
+ function defaultCommunitySubagentId(openclawAgentId) {
3555
+ return `agentcomm-community-${sanitizeSegment4(openclawAgentId)}`;
3556
+ }
3557
+ function sanitizeSegment4(value) {
3558
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
3559
+ }
3560
+ function secondsToMs(value) {
3561
+ return typeof value === "number" && Number.isFinite(value) ? value * 1e3 : void 0;
3562
+ }
3563
+
3564
+ // index.ts
3565
+ var BRIDGE_CONFIG_SCHEMA = {
3566
+ type: "object",
3567
+ additionalProperties: false,
3568
+ properties: {
3569
+ enabled: { type: "boolean", default: false },
3570
+ serverUrl: { type: "string" },
3571
+ communityAgentId: { type: "string" },
3572
+ keepaliveToken: { type: "string" },
3573
+ bridgeToken: { type: "string" },
3574
+ bridgeTokenRef: { type: "string" },
3575
+ pollIntervalMs: {
3576
+ type: "number",
3577
+ minimum: 5e3,
3578
+ default: 3e4
3579
+ },
3580
+ communitySubagent: {
3581
+ type: "object",
3582
+ additionalProperties: false,
3583
+ properties: {
3584
+ enabled: { type: "boolean", default: true },
3585
+ agentId: { type: "string" },
3586
+ workspaceDir: { type: "string" },
3587
+ agentDir: { type: "string" }
3588
+ }
3589
+ },
3590
+ autonomousLoop: {
3591
+ type: "object",
3592
+ additionalProperties: false,
3593
+ properties: {
3594
+ enabled: { type: "boolean", default: false },
3595
+ maxItemsPerPoll: {
3596
+ type: "number",
3597
+ minimum: 1,
3598
+ maximum: 5,
3599
+ default: 1
3600
+ },
3601
+ timeoutSeconds: {
3602
+ type: "number",
3603
+ minimum: 10,
3604
+ maximum: 600,
3605
+ default: 120
3606
+ }
3607
+ }
3608
+ },
3609
+ acceptanceDemo: {
3610
+ type: "object",
3611
+ additionalProperties: false,
3612
+ properties: {
3613
+ enabled: { type: "boolean", default: false },
3614
+ sessionKey: { type: "string" },
3615
+ label: { type: "string" }
3616
+ }
3617
+ }
3618
+ }
3619
+ };
3620
+ var EmptyParamsSchema = {
3621
+ type: "object",
3622
+ additionalProperties: false,
3623
+ properties: {}
3624
+ };
3625
+ var toolEntry = defineToolPlugin({
3626
+ id: "openclaw-bridge",
3627
+ name: "Politeia Bridge",
3628
+ description: "Maintain keepalive connectivity and route structured community envelopes for an OpenClaw agent.",
3629
+ activation: {
3630
+ onStartup: true,
3631
+ onConfigPaths: ["plugins.entries.openclaw-bridge"]
3632
+ },
3633
+ configSchema: BRIDGE_CONFIG_SCHEMA,
3634
+ tools: (tool) => [
3635
+ tool({
3636
+ name: "agentcomm_list_agents",
3637
+ description: "List AgentComm agents registered on the connected platform, including display names and online status.",
3638
+ parameters: EmptyParamsSchema,
3639
+ execute: async (_params, _config, context) => {
3640
+ const bridge = createBridgeForApi(context.api);
3641
+ return bridge.listAgents();
3642
+ }
3643
+ }),
3644
+ tool({
3645
+ name: "agentcomm_dm_send",
3646
+ description: "Send a direct message through this OpenClaw agent's AgentComm community persona.",
3647
+ parameters: {
3648
+ type: "object",
3649
+ additionalProperties: false,
3650
+ required: ["targetAgentId", "message"],
3651
+ properties: {
3652
+ targetAgentId: {
3653
+ type: "string",
3654
+ description: "Target AgentComm community agent id, for example agt_agent_openclaw_main."
3655
+ },
3656
+ message: {
3657
+ type: "string",
3658
+ description: "Message body to send."
3659
+ }
3660
+ }
3661
+ },
3662
+ execute: async (params, _config, context) => {
3663
+ const record = params;
3664
+ const bridge = createBridgeForApi(context.api);
3665
+ return bridge.dmSend({
3666
+ targetAgentId: record.targetAgentId,
3667
+ message: record.message,
3668
+ intent: "agent-tool.dm-send"
3669
+ });
3670
+ }
3671
+ }),
3672
+ tool({
3673
+ name: "agentcomm_check_inbox",
3674
+ description: "Check this local AgentComm workspace for recent inbound DMs and pending user decisions.",
3675
+ parameters: EmptyParamsSchema,
3676
+ execute: async (_params, _config, context) => {
3677
+ const bridge = createBridgeForApi(context.api);
3678
+ return bridge.checkInbox();
3679
+ }
3680
+ }),
3681
+ tool({
3682
+ name: "agentcomm_doctor",
3683
+ description: "Diagnose the local AgentComm Bridge configuration and OpenClaw workspace resolver state.",
3684
+ parameters: EmptyParamsSchema,
3685
+ execute: async (_params, _config, context) => {
3686
+ const api = context.api;
3687
+ return runBridgeDoctor(readBridgeConfig(api.pluginConfig), {
3688
+ workspaceDir: api.workspaceDir,
3689
+ agentDir: api.agentDir,
3690
+ stateDir: api.stateDir,
3691
+ homeDir: api.homeDir,
3692
+ agentId: api.agentId
3693
+ });
3694
+ }
3695
+ }),
3696
+ tool({
3697
+ name: "agentcomm_schedule_propose",
3698
+ description: "Start a deterministic AgentComm schedule.negotiate meeting-time proposal with another agent.",
3699
+ parameters: {
3700
+ type: "object",
3701
+ additionalProperties: false,
3702
+ required: ["targetAgentId", "topic", "durationMinutes", "candidateWindows"],
3703
+ properties: {
3704
+ targetAgentId: {
3705
+ type: "string",
3706
+ description: "Target AgentComm community agent id."
3707
+ },
3708
+ topic: {
3709
+ type: "string",
3710
+ description: "Meeting topic."
3711
+ },
3712
+ durationMinutes: {
3713
+ type: "number",
3714
+ minimum: 1,
3715
+ description: "Meeting duration in minutes."
3716
+ },
3717
+ candidateWindows: {
3718
+ type: "array",
3719
+ items: {
3720
+ type: "object",
3721
+ additionalProperties: false,
3722
+ required: ["start", "end"],
3723
+ properties: {
3724
+ start: { type: "string" },
3725
+ end: { type: "string" }
3726
+ }
3727
+ }
3728
+ }
3729
+ }
3730
+ },
3731
+ execute: async (params, _config, context) => {
3732
+ const record = parseScheduleProposeParams(params);
3733
+ const bridge = createBridgeForApi(context.api);
3734
+ return bridge.schedulePropose(record);
3735
+ }
3736
+ }),
3737
+ tool({
3738
+ name: "agentcomm_room_send",
3739
+ description: "Send a text message into an AgentComm meeting room as this local community persona.",
3740
+ parameters: {
3741
+ type: "object",
3742
+ additionalProperties: false,
3743
+ required: ["roomId", "message"],
3744
+ properties: {
3745
+ roomId: {
3746
+ type: "string",
3747
+ description: "Target AgentComm room id."
3748
+ },
3749
+ message: {
3750
+ type: "string",
3751
+ description: "Message body to send into the room."
3752
+ }
3753
+ }
3754
+ },
3755
+ execute: async (params, _config, context) => {
3756
+ const bridge = createBridgeForApi(context.api);
3757
+ return bridge.roomSend({
3758
+ ...parseRoomSendParams(params),
3759
+ speakerKind: "agent",
3760
+ intent: "agent-tool.room.send"
3761
+ });
3762
+ }
3763
+ })
3764
+ ]
3765
+ });
3766
+ var registerToolEntry = toolEntry.register.bind(toolEntry);
3767
+ toolEntry.register = (api) => {
3768
+ registerToolEntry(api);
3769
+ if (api.registrationMode === "cli-metadata") {
3770
+ return;
3771
+ }
3772
+ let hostPaths = {
3773
+ workspaceDir: api.workspaceDir,
3774
+ agentDir: api.agentDir,
3775
+ homeDir: api.homeDir,
3776
+ agentId: api.agentId
3777
+ };
3778
+ const bridge = createCommunityBridgeService(
3779
+ () => readBridgeConfig(api.pluginConfig),
3780
+ () => hostPaths
3781
+ );
3782
+ api.registerGatewayMethod("communityBridge.status", async ({ respond }) => {
3783
+ respond(true, bridge.getStatus());
3784
+ });
3785
+ api.registerGatewayMethod("communityBridge.setup.describe", async ({ respond }) => {
3786
+ respond(true, describeCommunityBridgeSetup());
3787
+ });
3788
+ api.registerGatewayMethod("communityBridge.protocol", async ({ respond }) => {
3789
+ respond(true, {
3790
+ ok: true,
3791
+ protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
3792
+ bridgeVersion: AGENTCOMM_BRIDGE_PACKAGE_VERSION
3793
+ });
3794
+ });
3795
+ api.registerGatewayMethod("communityBridge.doctor", async ({ respond }) => {
3796
+ respond(true, await runBridgeDoctor(readBridgeConfig(api.pluginConfig), hostPaths));
3797
+ });
3798
+ api.registerGatewayMethod("communityBridge.setup.register", async ({ params, respond }) => {
3799
+ respond(
3800
+ true,
3801
+ await runCommunityBridgeSetup(parseSetupInput(params), {
3802
+ hostPaths
3803
+ })
3804
+ );
3805
+ });
3806
+ api.registerGatewayMethod("communityBridge.workspaceStatus", async ({ respond }) => {
3807
+ const config = readBridgeConfig(api.pluginConfig);
3808
+ const active = config.enabled && config.communityAgentId ? config.communityAgentId : void 0;
3809
+ if (!active) {
3810
+ respond(true, {
3811
+ ok: false,
3812
+ reason: "community agent id unavailable"
3813
+ });
3814
+ return;
3815
+ }
3816
+ const paths = await resolvePlatformWorkspacePaths({
3817
+ communityAgentId: active,
3818
+ workspaceDir: hostPaths.workspaceDir,
3819
+ agentDir: inferOpenClawAgentDir(hostPaths),
3820
+ stateDir: hostPaths.stateDir,
3821
+ homeDir: hostPaths.homeDir
3822
+ });
3823
+ const state = await readPlatformWorkspaceState(paths);
3824
+ const personaProfile = await readPersonaProfile(paths);
3825
+ const profileSubagent = isRecord5(personaProfile?.subagent) ? personaProfile.subagent : null;
3826
+ const communitySubagent = config.communitySubagent ?? profileSubagent;
3827
+ const subagentWorkspaceDir = isRecord5(communitySubagent) && typeof communitySubagent.workspaceDir === "string" ? communitySubagent.workspaceDir : null;
3828
+ respond(true, {
3829
+ ok: true,
3830
+ workspaceDir: hostPaths.workspaceDir ?? process.cwd(),
3831
+ agentDir: inferOpenClawAgentDir(hostPaths) ?? hostPaths.agentDir ?? null,
3832
+ stateDir: hostPaths.stateDir ?? null,
3833
+ strategy: paths.strategy,
3834
+ rootDir: paths.rootDir,
3835
+ personaProfilePath: paths.personaProfilePath,
3836
+ pendingActionsPath: paths.pendingActionsPath,
3837
+ communitySubagent,
3838
+ subagentHandoff: subagentWorkspaceDir ? resolveSubagentHandoffPaths(subagentWorkspaceDir) : null,
3839
+ personaProfile,
3840
+ state
3841
+ });
3842
+ });
3843
+ api.registerGatewayMethod("communityBridge.pollOnce", async ({ respond }) => {
3844
+ respond(true, await bridge.pollOnce());
3845
+ });
3846
+ api.registerGatewayMethod("communityBridge.autonomousLoopOnce", async ({ params, respond }) => {
3847
+ const record = isRecord5(params) ? params : {};
3848
+ const maxItems = typeof record.maxItems === "number" && Number.isInteger(record.maxItems) && record.maxItems > 0 ? Math.min(record.maxItems, 5) : void 0;
3849
+ const handoffId = typeof record.handoffId === "string" && record.handoffId.trim().length > 0 ? record.handoffId.trim() : void 0;
3850
+ respond(true, await bridge.runAutonomousLoopOnce({ maxItems, handoffId }));
3851
+ });
3852
+ api.registerGatewayMethod("communityBridge.applyProposal", async ({ params, respond }) => {
3853
+ respond(true, await bridge.applyProposal(parseApplyProposalParams(params)));
3854
+ });
3855
+ api.registerGatewayMethod("communityBridge.listAgents", async ({ respond }) => {
3856
+ respond(true, await bridge.listAgents());
3857
+ });
3858
+ api.registerGatewayMethod("communityBridge.checkInbox", async ({ respond }) => {
3859
+ respond(true, await bridge.checkInbox());
3860
+ });
3861
+ api.registerGatewayMethod("communityBridge.postPublish", async ({ params, respond }) => {
3862
+ if (!params || typeof params !== "object") {
3863
+ throw new Error("postPublish params must be an object");
3864
+ }
3865
+ const record = params;
3866
+ if (typeof record.title !== "string" || record.title.trim().length === 0) {
3867
+ throw new Error("title must be a non-empty string");
3868
+ }
3869
+ if (typeof record.body !== "string" || record.body.trim().length === 0) {
3870
+ throw new Error("body must be a non-empty string");
3871
+ }
3872
+ respond(
3873
+ true,
3874
+ await bridge.postPublish({
3875
+ title: record.title.trim(),
3876
+ body: record.body.trim(),
3877
+ intent: typeof record.intent === "string" && record.intent.trim().length > 0 ? record.intent.trim() : void 0,
3878
+ tags: Array.isArray(record.tags) ? record.tags.filter((item) => typeof item === "string" && item.trim().length > 0) : void 0
3879
+ })
3880
+ );
3881
+ });
3882
+ api.registerGatewayMethod("communityBridge.dmSend", async ({ params, respond }) => {
3883
+ if (!params || typeof params !== "object") {
3884
+ throw new Error("dmSend params must be an object");
3885
+ }
3886
+ const record = params;
3887
+ if (typeof record.targetAgentId !== "string" || record.targetAgentId.trim().length === 0) {
3888
+ throw new Error("targetAgentId must be a non-empty string");
3889
+ }
3890
+ if (typeof record.message !== "string" || record.message.trim().length === 0) {
3891
+ throw new Error("message must be a non-empty string");
3892
+ }
3893
+ respond(
3894
+ true,
3895
+ await bridge.dmSend({
3896
+ targetAgentId: record.targetAgentId.trim(),
3897
+ message: record.message.trim(),
3898
+ intent: typeof record.intent === "string" && record.intent.trim().length > 0 ? record.intent.trim() : void 0
3899
+ })
3900
+ );
3901
+ });
3902
+ api.registerGatewayMethod("communityBridge.schedulePropose", async ({ params, respond }) => {
3903
+ respond(true, await bridge.schedulePropose(parseScheduleProposeParams(params)));
3904
+ });
3905
+ api.registerGatewayMethod("communityBridge.roomSend", async ({ params, respond }) => {
3906
+ respond(true, await bridge.roomSend(parseRoomSendParams(params)));
3907
+ });
3908
+ api.registerGatewayMethod("communityBridge.prepareMeetingSummary", async ({ params, respond }) => {
3909
+ respond(true, await bridge.prepareMeetingSummary(parseMeetingSummaryParams(params)));
3910
+ });
3911
+ api.registerGatewayMethod("communityBridge.skillQuery", async ({ params, respond }) => {
3912
+ if (!params || typeof params !== "object") {
3913
+ throw new Error("skillQuery params must be an object");
3914
+ }
3915
+ const record = params;
3916
+ if (typeof record.need !== "string" || record.need.trim().length === 0) {
3917
+ throw new Error("need must be a non-empty string");
3918
+ }
3919
+ respond(
3920
+ true,
3921
+ await bridge.skillQuery({
3922
+ need: record.need.trim(),
3923
+ context: typeof record.context === "string" && record.context.trim().length > 0 ? record.context.trim() : void 0,
3924
+ intent: typeof record.intent === "string" && record.intent.trim().length > 0 ? record.intent.trim() : void 0,
3925
+ tags: Array.isArray(record.tags) ? record.tags.filter((item) => typeof item === "string" && item.trim().length > 0) : void 0
3926
+ })
3927
+ );
3928
+ });
3929
+ api.registerGatewayMethod("communityBridge.skillInstall", async ({ params, respond }) => {
3930
+ if (!params || typeof params !== "object") {
3931
+ throw new Error("skillInstall params must be an object");
3932
+ }
3933
+ const record = params;
3934
+ const offerId = typeof record.offerId === "string" && record.offerId.trim().length > 0 ? record.offerId.trim() : void 0;
3935
+ const skillSlug = typeof record.skillSlug === "string" && record.skillSlug.trim().length > 0 ? record.skillSlug.trim() : void 0;
3936
+ if (!offerId && !skillSlug) {
3937
+ throw new Error("offerId or skillSlug is required");
3938
+ }
3939
+ respond(
3940
+ true,
3941
+ await bridge.skillInstall({
3942
+ offerId,
3943
+ skillSlug,
3944
+ agentId: typeof record.agentId === "string" && record.agentId.trim().length > 0 ? record.agentId.trim() : void 0
3945
+ })
3946
+ );
3947
+ });
3948
+ api.registerGatewayMethod("communityBridge.confirmAction", async ({ params, respond }) => {
3949
+ const input = parseActionDecisionParams(params);
3950
+ respond(
3951
+ true,
3952
+ await bridge.confirmAction({
3953
+ actionId: input.actionId,
3954
+ reason: input.reason,
3955
+ decidedBy: input.decidedBy
3956
+ })
3957
+ );
3958
+ });
3959
+ api.registerGatewayMethod("communityBridge.rejectAction", async ({ params, respond }) => {
3960
+ const input = parseActionDecisionParams(params);
3961
+ respond(
3962
+ true,
3963
+ await bridge.rejectAction({
3964
+ actionId: input.actionId,
3965
+ reason: input.reason,
3966
+ decidedBy: input.decidedBy
3967
+ })
3968
+ );
3969
+ });
3970
+ api.registerGatewayMethod("communityBridge.applyAcceptedAction", async ({ params, respond }) => {
3971
+ if (!isRecord5(params)) {
3972
+ throw new Error("applyAcceptedAction params must be an object");
3973
+ }
3974
+ const actionId = typeof params.actionId === "string" ? params.actionId.trim() : "";
3975
+ if (!actionId) {
3976
+ throw new Error("actionId must be a non-empty string");
3977
+ }
3978
+ respond(
3979
+ true,
3980
+ await bridge.applyAcceptedAction({
3981
+ actionId,
3982
+ agentId: typeof params.agentId === "string" && params.agentId.trim().length > 0 ? params.agentId.trim() : void 0
3983
+ })
3984
+ );
3985
+ });
3986
+ if (api.registrationMode === "full") {
3987
+ api.registerService({
3988
+ id: "openclaw-bridge",
3989
+ start: (ctx) => {
3990
+ const runtimeContext = ctx;
3991
+ hostPaths = mergeHostPaths(hostPaths, {
3992
+ workspaceDir: runtimeContext.workspaceDir,
3993
+ agentDir: runtimeContext.agentDir,
3994
+ stateDir: runtimeContext.stateDir,
3995
+ homeDir: runtimeContext.homeDir,
3996
+ agentId: runtimeContext.agentId
3997
+ });
3998
+ return bridge.start();
3999
+ },
4000
+ stop: () => bridge.stop()
4001
+ });
4002
+ }
4003
+ };
4004
+ var index_default = toolEntry;
4005
+ function createBridgeForApi(api) {
4006
+ const hostPaths = {
4007
+ workspaceDir: api.workspaceDir,
4008
+ agentDir: api.agentDir,
4009
+ stateDir: api.stateDir,
4010
+ homeDir: api.homeDir,
4011
+ agentId: api.agentId
4012
+ };
4013
+ return createCommunityBridgeService(
4014
+ () => readBridgeConfig(api.pluginConfig),
4015
+ () => hostPaths
4016
+ );
4017
+ }
4018
+ async function runBridgeDoctor(config, hostPaths) {
4019
+ const checks = [];
4020
+ const activeToken = getActiveConfig(config)?.connectionToken;
4021
+ const inferredAgentDir = inferOpenClawAgentDir(hostPaths);
4022
+ checks.push({
4023
+ name: "protocol_version",
4024
+ status: "passed",
4025
+ detail: AGENTCOMM_PROTOCOL_VERSION
4026
+ });
4027
+ checks.push({
4028
+ name: "enabled",
4029
+ status: config.enabled ? "passed" : "failed",
4030
+ detail: config.enabled ? "bridge enabled" : "plugins.entries.openclaw-bridge.config.enabled is not true"
4031
+ });
4032
+ checks.push({
4033
+ name: "server_url",
4034
+ status: isValidServerUrl(config.serverUrl) ? "passed" : "failed",
4035
+ detail: config.serverUrl ?? "missing serverUrl"
4036
+ });
4037
+ checks.push({
4038
+ name: "community_agent_id",
4039
+ status: config.communityAgentId ? "passed" : "failed",
4040
+ detail: config.communityAgentId ?? "missing communityAgentId"
4041
+ });
4042
+ checks.push({
4043
+ name: "bridge_token",
4044
+ status: activeToken ? "passed" : "failed",
4045
+ detail: activeToken ? `configured len=${activeToken.length}` : "missing keepaliveToken/bridgeToken or unresolved bridgeTokenRef"
4046
+ });
4047
+ checks.push({
4048
+ name: "host_home",
4049
+ status: hostPaths.homeDir ? "passed" : "warning",
4050
+ detail: hostPaths.homeDir ?? "host did not provide homeDir; falling back to process environment"
4051
+ });
4052
+ checks.push({
4053
+ name: "agent_dir",
4054
+ status: inferredAgentDir ? "passed" : "warning",
4055
+ detail: inferredAgentDir ?? "agentDir was not provided and could not be inferred yet"
4056
+ });
4057
+ let workspace = null;
4058
+ if (config.communityAgentId) {
4059
+ try {
4060
+ const paths = await resolvePlatformWorkspacePaths({
4061
+ communityAgentId: config.communityAgentId,
4062
+ workspaceDir: hostPaths.workspaceDir,
4063
+ agentDir: inferredAgentDir,
4064
+ stateDir: hostPaths.stateDir,
4065
+ homeDir: hostPaths.homeDir
4066
+ });
4067
+ const state = await readPlatformWorkspaceState(paths);
4068
+ const personaProfile = await readPersonaProfile(paths);
4069
+ workspace = {
4070
+ ok: true,
4071
+ strategy: paths.strategy,
4072
+ rootDir: paths.rootDir,
4073
+ stateExists: Boolean(state),
4074
+ personaProfileExists: Boolean(personaProfile)
4075
+ };
4076
+ checks.push({
4077
+ name: "workspace_resolver",
4078
+ status: "passed",
4079
+ detail: `${paths.strategy}: ${paths.rootDir}`
4080
+ });
4081
+ } catch (error) {
4082
+ workspace = {
4083
+ ok: false,
4084
+ error: error instanceof Error ? error.message : String(error)
4085
+ };
4086
+ checks.push({
4087
+ name: "workspace_resolver",
4088
+ status: "failed",
4089
+ detail: workspace.error
4090
+ });
4091
+ }
4092
+ }
4093
+ return {
4094
+ ok: checks.every((check) => check.status !== "failed"),
4095
+ protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
4096
+ bridgeVersion: AGENTCOMM_BRIDGE_PACKAGE_VERSION,
4097
+ checks,
4098
+ hostPaths: {
4099
+ workspaceDir: hostPaths.workspaceDir ?? null,
4100
+ agentDir: hostPaths.agentDir ?? null,
4101
+ inferredAgentDir: inferredAgentDir ?? null,
4102
+ stateDir: hostPaths.stateDir ?? null,
4103
+ homeDir: hostPaths.homeDir ?? null,
4104
+ agentId: hostPaths.agentId ?? null
4105
+ },
4106
+ config: {
4107
+ enabled: config.enabled,
4108
+ serverUrl: config.serverUrl ?? null,
4109
+ communityAgentId: config.communityAgentId ?? null,
4110
+ pollIntervalMs: config.pollIntervalMs,
4111
+ hasToken: Boolean(activeToken)
4112
+ },
4113
+ workspace
4114
+ };
4115
+ }
4116
+ function isValidServerUrl(value) {
4117
+ if (!value) return false;
4118
+ try {
4119
+ const url = new URL(value);
4120
+ return url.protocol === "http:" || url.protocol === "https:";
4121
+ } catch {
4122
+ return false;
4123
+ }
4124
+ }
4125
+ function isRecord5(value) {
4126
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
4127
+ }
4128
+ function parseActionDecisionParams(params) {
4129
+ if (!isRecord5(params)) {
4130
+ throw new Error("action decision params must be an object");
4131
+ }
4132
+ const actionId = typeof params.actionId === "string" ? params.actionId.trim() : "";
4133
+ if (!actionId) {
4134
+ throw new Error("actionId must be a non-empty string");
4135
+ }
4136
+ const decidedBy = params.decidedBy === "user" || params.decidedBy === "platform" || params.decidedBy === "bridge" ? params.decidedBy : void 0;
4137
+ return {
4138
+ actionId,
4139
+ reason: typeof params.reason === "string" && params.reason.trim().length > 0 ? params.reason.trim() : void 0,
4140
+ decidedBy
4141
+ };
4142
+ }
4143
+ function parseApplyProposalParams(params) {
4144
+ if (!isRecord5(params)) {
4145
+ throw new Error("applyProposal params must be an object");
4146
+ }
4147
+ const handoffId = typeof params.handoffId === "string" ? params.handoffId.trim() : "";
4148
+ if (!handoffId) {
4149
+ throw new Error("handoffId must be a non-empty string");
4150
+ }
4151
+ const proposalRecord = isRecord5(params.proposal) ? params.proposal : params;
4152
+ const action = proposalRecord.action;
4153
+ if (action === "meeting.summary") {
4154
+ const roomId = typeof proposalRecord.room_id === "string" ? proposalRecord.room_id.trim() : "";
4155
+ const summary = typeof proposalRecord.summary === "string" ? proposalRecord.summary.trim() : "";
4156
+ const todos = Array.isArray(proposalRecord.todos) ? proposalRecord.todos.map((todo) => {
4157
+ if (typeof todo === "string") {
4158
+ const text2 = todo.trim();
4159
+ return text2 ? { text: text2, due_at: null, owner: null } : null;
4160
+ }
4161
+ if (!isRecord5(todo)) {
4162
+ return null;
4163
+ }
4164
+ const text = typeof todo.text === "string" ? todo.text.trim() : "";
4165
+ if (!text) {
4166
+ return null;
4167
+ }
4168
+ return {
4169
+ text,
4170
+ due_at: typeof todo.due_at === "string" && todo.due_at.trim().length > 0 ? todo.due_at.trim() : null,
4171
+ owner: typeof todo.owner === "string" && todo.owner.trim().length > 0 ? todo.owner.trim() : null
4172
+ };
4173
+ }).filter((todo) => Boolean(todo)) : [];
4174
+ if (!roomId) throw new Error("proposal.room_id must be a non-empty string");
4175
+ if (!summary) throw new Error("proposal.summary must be a non-empty string");
4176
+ if (todos.length === 0) throw new Error("proposal.todos must contain at least one item");
4177
+ return {
4178
+ handoffId,
4179
+ proposal: {
4180
+ action,
4181
+ room_id: roomId,
4182
+ summary,
4183
+ todos,
4184
+ reason: typeof proposalRecord.reason === "string" && proposalRecord.reason.trim().length > 0 ? proposalRecord.reason.trim() : void 0
4185
+ },
4186
+ proposalMeta: isRecord5(params.proposalMeta) ? params.proposalMeta : void 0
4187
+ };
4188
+ }
4189
+ const risk = proposalRecord.risk;
4190
+ const draft = typeof proposalRecord.draft === "string" ? proposalRecord.draft.trim() : "";
4191
+ if (action !== "dm.reply") {
4192
+ throw new Error(`unsupported proposal action: ${String(action)}`);
4193
+ }
4194
+ if (risk !== "low" && risk !== "medium" && risk !== "high") {
4195
+ throw new Error(`unsupported proposal risk: ${String(risk)}`);
4196
+ }
4197
+ if (!draft) {
4198
+ throw new Error("proposal.draft must be a non-empty string");
4199
+ }
4200
+ return {
4201
+ handoffId,
4202
+ proposal: {
4203
+ action,
4204
+ risk,
4205
+ draft,
4206
+ reason: typeof proposalRecord.reason === "string" && proposalRecord.reason.trim().length > 0 ? proposalRecord.reason.trim() : void 0
4207
+ },
4208
+ proposalMeta: isRecord5(params.proposalMeta) ? params.proposalMeta : void 0
4209
+ };
4210
+ }
4211
+ function parseMeetingSummaryParams(params) {
4212
+ if (!isRecord5(params)) {
4213
+ throw new Error("prepareMeetingSummary params must be an object");
4214
+ }
4215
+ const roomId = typeof params.roomId === "string" ? params.roomId.trim() : "";
4216
+ if (!roomId) {
4217
+ throw new Error("roomId must be a non-empty string");
4218
+ }
4219
+ return { roomId };
4220
+ }
4221
+ function parseScheduleProposeParams(params) {
4222
+ if (!isRecord5(params)) {
4223
+ throw new Error("schedulePropose params must be an object");
4224
+ }
4225
+ const targetAgentId = typeof params.targetAgentId === "string" ? params.targetAgentId.trim() : "";
4226
+ const topic = typeof params.topic === "string" ? params.topic.trim() : "";
4227
+ const durationMinutes = typeof params.durationMinutes === "number" && Number.isFinite(params.durationMinutes) ? params.durationMinutes : 0;
4228
+ const candidateWindows = Array.isArray(params.candidateWindows) ? params.candidateWindows.filter(isRecord5).map((item) => ({
4229
+ start: typeof item.start === "string" ? item.start.trim() : "",
4230
+ end: typeof item.end === "string" ? item.end.trim() : ""
4231
+ })).filter((item) => item.start.length > 0 && item.end.length > 0) : [];
4232
+ if (!targetAgentId) throw new Error("targetAgentId must be a non-empty string");
4233
+ if (!topic) throw new Error("topic must be a non-empty string");
4234
+ if (durationMinutes <= 0) throw new Error("durationMinutes must be a positive number");
4235
+ if (candidateWindows.length === 0) throw new Error("candidateWindows must contain at least one window");
4236
+ return {
4237
+ targetAgentId,
4238
+ topic,
4239
+ durationMinutes,
4240
+ candidateWindows,
4241
+ actingFor: typeof params.actingFor === "string" && params.actingFor.trim().length > 0 ? params.actingFor.trim() : void 0
4242
+ };
4243
+ }
4244
+ function parseRoomSendParams(params) {
4245
+ if (!isRecord5(params)) {
4246
+ throw new Error("roomSend params must be an object");
4247
+ }
4248
+ const roomId = typeof params.roomId === "string" ? params.roomId.trim() : "";
4249
+ const message = typeof params.message === "string" ? params.message.trim() : "";
4250
+ if (!roomId) throw new Error("roomId must be a non-empty string");
4251
+ if (!message) throw new Error("message must be a non-empty string");
4252
+ return {
4253
+ roomId,
4254
+ message,
4255
+ speakerKind: params.speakerKind === "agent" || params.speakerKind === "human" ? params.speakerKind : void 0,
4256
+ speakerLabel: typeof params.speakerLabel === "string" && params.speakerLabel.trim().length > 0 ? params.speakerLabel.trim() : void 0,
4257
+ intent: typeof params.intent === "string" && params.intent.trim().length > 0 ? params.intent.trim() : void 0
4258
+ };
4259
+ }
4260
+ export {
4261
+ index_default as default
4262
+ };