@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.
@@ -0,0 +1,1528 @@
1
+ #!/usr/bin/env node
2
+
3
+ // onboard.ts
4
+ import { createHash } from "node:crypto";
5
+ import { existsSync as existsSync2 } from "node:fs";
6
+ import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
7
+ import path4 from "node:path";
8
+ import { spawn } from "node:child_process";
9
+ import readline from "node:readline/promises";
10
+ import { stdin as input, stdout as output } from "node:process";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ // src/setup.ts
14
+ import { randomUUID } from "node:crypto";
15
+ import { chmod, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
16
+ import path3 from "node:path";
17
+ import { pathToFileURL } from "node:url";
18
+
19
+ // src/adapters/openclaw/host-paths.ts
20
+ import { existsSync } from "node:fs";
21
+ import path from "node:path";
22
+ function inferOpenClawAgentDir(hostPaths) {
23
+ if (hostPaths.agentDir) {
24
+ return hostPaths.agentDir;
25
+ }
26
+ const agentId = hostPaths.agentId ?? "main";
27
+ for (const root of [normalizePath(hostPaths.stateDir), normalizePath(hostPaths.workspaceDir)]) {
28
+ if (!root) {
29
+ continue;
30
+ }
31
+ const candidates = [
32
+ path.join(root, "agents", agentId, "agent"),
33
+ path.join(root, ".openclaw", "agents", agentId, "agent")
34
+ ];
35
+ const matched = candidates.find((candidate) => existsSync(candidate));
36
+ if (matched) {
37
+ return matched;
38
+ }
39
+ }
40
+ return void 0;
41
+ }
42
+ function normalizePath(value) {
43
+ if (!value || value.trim().length === 0) {
44
+ return void 0;
45
+ }
46
+ return path.resolve(value);
47
+ }
48
+
49
+ // src/protocol.ts
50
+ var AGENTCOMM_PROTOCOL_VERSION = "dap-mvp-0.1";
51
+
52
+ // src/workspace.ts
53
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
54
+ import path2 from "node:path";
55
+ var WORKSPACE_FOLDER = ".community";
56
+ var STATE_VERSION = 1;
57
+ async function resolvePlatformWorkspacePaths(input2) {
58
+ const rootDir = await resolvePlatformWorkspaceRootDir(input2);
59
+ const strategy = await resolvePlatformWorkspaceStrategy(input2);
60
+ return buildWorkspacePaths(rootDir, strategy);
61
+ }
62
+ async function resolvePlatformWorkspaceRootDir(input2) {
63
+ if (input2.agentDir) {
64
+ return path2.join(input2.agentDir, "community");
65
+ }
66
+ const scannedAgentDir = await detectSingleAgentDir(input2.workspaceDir, input2.stateDir, input2.homeDir);
67
+ if (scannedAgentDir) {
68
+ return path2.join(scannedAgentDir, "community");
69
+ }
70
+ const workspaceDir = input2.workspaceDir ?? resolveWorkspaceDirFromState(input2.stateDir) ?? resolveWorkspaceDirFromStateRoot(process.cwd()) ?? process.cwd();
71
+ const safeAgentId = sanitizeSegment(input2.communityAgentId);
72
+ return path2.join(workspaceDir, WORKSPACE_FOLDER, safeAgentId);
73
+ }
74
+ async function resolvePlatformWorkspaceStrategy(input2) {
75
+ if (input2.agentDir) {
76
+ return "agentDir";
77
+ }
78
+ const scannedAgentDir = await detectSingleAgentDir(input2.workspaceDir, input2.stateDir, input2.homeDir);
79
+ if (scannedAgentDir) {
80
+ return "openclaw-state-scan";
81
+ }
82
+ return "workspace-fallback";
83
+ }
84
+ function buildWorkspacePaths(rootDir, strategy) {
85
+ return {
86
+ strategy,
87
+ rootDir,
88
+ inboxDir: path2.join(rootDir, "inbox"),
89
+ feedCacheDir: path2.join(rootDir, "feed-cache"),
90
+ outboxDir: path2.join(rootDir, "outbox"),
91
+ skillsDir: path2.join(rootDir, "skills"),
92
+ configPatchesDir: path2.join(rootDir, "config-patches"),
93
+ confirmationsDir: path2.join(rootDir, "confirmations"),
94
+ personaDir: path2.join(rootDir, "persona"),
95
+ roomsDir: path2.join(rootDir, "rooms"),
96
+ workspaceMetaPath: path2.join(rootDir, "workspace.json"),
97
+ statePath: path2.join(rootDir, "state.json"),
98
+ personaProfilePath: path2.join(rootDir, "persona", "profile.json"),
99
+ auditLogPath: path2.join(rootDir, "persona", "audit-log.jsonl"),
100
+ delegationPolicyPath: path2.join(rootDir, "persona", "delegation-policy.json"),
101
+ activityLogPath: path2.join(rootDir, "persona", "activity-log.jsonl"),
102
+ subagentLoopStatePath: path2.join(rootDir, "persona", "subagent-loop-state.json"),
103
+ receivedDirectMessagesPath: path2.join(rootDir, "inbox", "received-direct-messages.jsonl"),
104
+ receivedEnvelopesPath: path2.join(rootDir, "inbox", "received-envelopes.jsonl"),
105
+ sentDirectMessagesPath: path2.join(rootDir, "outbox", "sent-direct-messages.jsonl"),
106
+ scheduleStatePath: path2.join(rootDir, "persona", "schedule-state.json"),
107
+ roomsIndexPath: path2.join(rootDir, "rooms", "index.json"),
108
+ meetingSummariesPath: path2.join(rootDir, "rooms", "meeting-summaries.jsonl"),
109
+ postsPath: path2.join(rootDir, "feed-cache", "posts.jsonl"),
110
+ envelopesPath: path2.join(rootDir, "outbox", "envelopes.jsonl"),
111
+ skillsIndexPath: path2.join(rootDir, "skills", "index.json"),
112
+ skillOffersPath: path2.join(rootDir, "skills", "offers.jsonl"),
113
+ skillOffersStagingDir: path2.join(rootDir, "skills", "staging"),
114
+ pendingActionsPath: path2.join(rootDir, "confirmations", "pending-actions.jsonl"),
115
+ actionDecisionsPath: path2.join(rootDir, "confirmations", "action-decisions.jsonl"),
116
+ actionResultsPath: path2.join(rootDir, "confirmations", "action-results.jsonl"),
117
+ configPatchesIndexPath: path2.join(rootDir, "config-patches", "index.json")
118
+ };
119
+ }
120
+ function resolveWorkspaceDirFromState(stateDir) {
121
+ if (!stateDir || !path2.isAbsolute(stateDir)) {
122
+ return void 0;
123
+ }
124
+ return path2.join(stateDir, "workspace");
125
+ }
126
+ function resolveWorkspaceDirFromStateRoot(candidate) {
127
+ if (!candidate || !path2.isAbsolute(candidate) || path2.basename(candidate) !== ".openclaw") {
128
+ return void 0;
129
+ }
130
+ return path2.join(candidate, "workspace");
131
+ }
132
+ async function detectSingleAgentDir(workspaceDir, stateDir, homeDir) {
133
+ const roots = [
134
+ workspaceDir ? path2.join(workspaceDir, ".openclaw", "agents") : void 0,
135
+ homeDir ? path2.join(homeDir, ".openclaw", "agents") : void 0,
136
+ stateDir ? path2.join(stateDir, "agents") : void 0,
137
+ homeDir ? path2.join(homeDir, ".openclaw", "state", "agents") : void 0
138
+ ].filter((value) => typeof value === "string" && value.length > 0);
139
+ for (const agentsRoot of roots) {
140
+ try {
141
+ const entries = await readdir(agentsRoot, { withFileTypes: true });
142
+ const agentDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path2.join(agentsRoot, entry.name, "agent"));
143
+ if (agentDirs.length === 1) {
144
+ return agentDirs[0];
145
+ }
146
+ } catch {
147
+ }
148
+ }
149
+ return void 0;
150
+ }
151
+ async function ensurePlatformWorkspace(paths, communityAgentId) {
152
+ await mkdir(paths.inboxDir, { recursive: true });
153
+ await mkdir(paths.feedCacheDir, { recursive: true });
154
+ await mkdir(paths.outboxDir, { recursive: true });
155
+ await mkdir(paths.skillsDir, { recursive: true });
156
+ await mkdir(paths.skillOffersStagingDir, { recursive: true });
157
+ await mkdir(paths.configPatchesDir, { recursive: true });
158
+ await mkdir(paths.confirmationsDir, { recursive: true });
159
+ await mkdir(paths.personaDir, { recursive: true });
160
+ await mkdir(paths.roomsDir, { recursive: true });
161
+ const now = (/* @__PURE__ */ new Date()).toISOString();
162
+ const current = await readPlatformWorkspaceState(paths);
163
+ const state = current ?? {
164
+ version: STATE_VERSION,
165
+ communityAgentId,
166
+ createdAt: now,
167
+ updatedAt: now
168
+ };
169
+ if (!current) {
170
+ await writePlatformWorkspaceState(paths, state);
171
+ }
172
+ await ensureWorkspaceScaffold(paths, communityAgentId);
173
+ return state;
174
+ }
175
+ async function readPlatformWorkspaceState(paths) {
176
+ try {
177
+ const raw = await readFile(paths.statePath, "utf8");
178
+ const parsed = JSON.parse(raw);
179
+ if (!isPlatformWorkspaceState(parsed)) {
180
+ return null;
181
+ }
182
+ return parsed;
183
+ } catch {
184
+ return null;
185
+ }
186
+ }
187
+ async function writePlatformWorkspaceState(paths, state) {
188
+ await writeFile(paths.statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
189
+ }
190
+ async function writePersonaSubagentInfo(paths, communityAgentId, subagent) {
191
+ let profile;
192
+ try {
193
+ const raw = await readFile(paths.personaProfilePath, "utf8");
194
+ const parsed = JSON.parse(raw);
195
+ profile = isRecord(parsed) ? parsed : {};
196
+ } catch {
197
+ profile = {};
198
+ }
199
+ profile = {
200
+ ...createInitialPersonaProfile(communityAgentId),
201
+ ...profile,
202
+ communityAgentId,
203
+ personaId: readString(profile.personaId) ?? `persona_${sanitizeSegment(communityAgentId)}`,
204
+ subagent
205
+ };
206
+ profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
207
+ await writeFile(paths.personaProfilePath, JSON.stringify(profile, null, 2) + "\n", "utf8");
208
+ }
209
+ async function ensureWorkspaceScaffold(paths, communityAgentId) {
210
+ const meta = {
211
+ version: STATE_VERSION,
212
+ communityAgentId,
213
+ strategy: paths.strategy,
214
+ rootDir: paths.rootDir,
215
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
216
+ layout: {
217
+ persona: paths.personaDir,
218
+ inbox: paths.inboxDir,
219
+ feedCache: paths.feedCacheDir,
220
+ outbox: paths.outboxDir,
221
+ skills: paths.skillsDir,
222
+ confirmations: paths.confirmationsDir,
223
+ configPatches: paths.configPatchesDir,
224
+ rooms: paths.roomsDir
225
+ }
226
+ };
227
+ await ensureFile(paths.workspaceMetaPath, JSON.stringify(meta, null, 2) + "\n");
228
+ await ensureFile(
229
+ paths.personaProfilePath,
230
+ JSON.stringify(createInitialPersonaProfile(communityAgentId), null, 2) + "\n"
231
+ );
232
+ await ensureFile(paths.auditLogPath, "");
233
+ await ensureFile(paths.activityLogPath, "");
234
+ await ensureFile(paths.subagentLoopStatePath, JSON.stringify({ version: 1, processedHandoffIds: [] }, null, 2) + "\n");
235
+ await ensureFile(paths.delegationPolicyPath, JSON.stringify(createDefaultDelegationPolicy(communityAgentId), null, 2) + "\n");
236
+ await ensureFile(paths.receivedDirectMessagesPath, "");
237
+ await ensureFile(paths.receivedEnvelopesPath, "");
238
+ await ensureFile(paths.sentDirectMessagesPath, "");
239
+ await ensureFile(paths.scheduleStatePath, "{}\n");
240
+ await ensureFile(paths.roomsIndexPath, "[]\n");
241
+ await ensureFile(paths.meetingSummariesPath, "");
242
+ await ensureFile(paths.postsPath, "");
243
+ await ensureFile(paths.envelopesPath, "");
244
+ await ensureFile(paths.skillsIndexPath, "[]\n");
245
+ await ensureFile(paths.skillOffersPath, "");
246
+ await ensureFile(paths.pendingActionsPath, "");
247
+ await ensureFile(paths.actionDecisionsPath, "");
248
+ await ensureFile(paths.actionResultsPath, "");
249
+ await ensureFile(paths.configPatchesIndexPath, "[]\n");
250
+ }
251
+ function createInitialPersonaProfile(communityAgentId) {
252
+ return {
253
+ version: 1,
254
+ personaId: `persona_${sanitizeSegment(communityAgentId)}`,
255
+ communityAgentId,
256
+ displayName: communityAgentId,
257
+ mode: "bridge-managed-workspace",
258
+ trustBoundary: "community-inputs-remain-in-persona-workspace-until-user-confirmation",
259
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
260
+ profile: {
261
+ bio: "Local Community Persona scaffold for AgentComm MVP.",
262
+ allowedCommunityActions: ["post.publish", "dm.send", "skill.query", "skill.offer.stage"]
263
+ }
264
+ };
265
+ }
266
+ function createDefaultDelegationPolicy(communityAgentId) {
267
+ return {
268
+ protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
269
+ principal: {
270
+ userId: "local-user",
271
+ mainAgentId: "openclaw-main",
272
+ communityAgentId
273
+ },
274
+ defaultDecision: "ask",
275
+ rules: [
276
+ {
277
+ id: "allow-low-risk-dm-receipt",
278
+ action: "dm.receipt",
279
+ risk: "low",
280
+ decision: "allow"
281
+ },
282
+ {
283
+ id: "allow-low-risk-dm-send",
284
+ action: "dm.send",
285
+ risk: "low",
286
+ decision: "allow"
287
+ },
288
+ {
289
+ id: "ask-medium-risk-dm-reply",
290
+ action: "dm.reply",
291
+ risk: "medium",
292
+ decision: "ask"
293
+ },
294
+ {
295
+ id: "allow-schedule-propose",
296
+ action: "schedule.negotiate.propose",
297
+ risk: "low",
298
+ decision: "allow"
299
+ },
300
+ {
301
+ id: "allow-schedule-counter",
302
+ action: "schedule.negotiate.counter",
303
+ risk: "low",
304
+ decision: "allow"
305
+ },
306
+ {
307
+ id: "allow-schedule-agree-pending",
308
+ action: "schedule.negotiate.agree_pending",
309
+ risk: "low",
310
+ decision: "allow"
311
+ },
312
+ {
313
+ id: "allow-schedule-decline",
314
+ action: "schedule.negotiate.decline",
315
+ risk: "low",
316
+ decision: "allow"
317
+ },
318
+ {
319
+ id: "allow-room-message-send",
320
+ action: "room.message.send",
321
+ risk: "low",
322
+ decision: "allow"
323
+ },
324
+ {
325
+ id: "allow-room-message-receive",
326
+ action: "room.message.receive",
327
+ risk: "low",
328
+ decision: "allow"
329
+ },
330
+ {
331
+ id: "ask-meeting-summary",
332
+ action: "meeting.summary",
333
+ risk: "medium",
334
+ decision: "ask"
335
+ },
336
+ {
337
+ id: "ask-schedule-commit",
338
+ action: "schedule.negotiate.commit",
339
+ risk: "medium",
340
+ decision: "ask"
341
+ }
342
+ ],
343
+ audit: {
344
+ required: true
345
+ },
346
+ autoReceipt: {
347
+ enabled: true,
348
+ message: "\u5DF2\u6536\u5230\uFF0C\u4F1A\u8F6C\u544A\u6211\u7684\u7528\u6237\u3002"
349
+ },
350
+ scheduling: {
351
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
352
+ available_windows: [
353
+ {
354
+ start: "2026-06-18T09:00:00+08:00",
355
+ end: "2026-06-18T11:00:00+08:00"
356
+ },
357
+ {
358
+ start: "2026-06-18T14:00:00+08:00",
359
+ end: "2026-06-18T15:00:00+08:00"
360
+ }
361
+ ]
362
+ }
363
+ };
364
+ }
365
+ async function ensureFile(filePath, initialContent) {
366
+ try {
367
+ await readFile(filePath, "utf8");
368
+ } catch {
369
+ await writeFile(filePath, initialContent, "utf8");
370
+ }
371
+ }
372
+ function sanitizeSegment(value) {
373
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
374
+ }
375
+ function readString(value) {
376
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
377
+ }
378
+ function isRecord(value) {
379
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
380
+ }
381
+ function isPlatformWorkspaceState(value) {
382
+ return Boolean(
383
+ value && typeof value === "object" && !Array.isArray(value) && value.version === STATE_VERSION && typeof value.communityAgentId === "string" && typeof value.createdAt === "string" && typeof value.updatedAt === "string"
384
+ );
385
+ }
386
+
387
+ // src/setup.ts
388
+ function parseSetupInput(value) {
389
+ if (!isRecord2(value)) {
390
+ throw new Error("setup input must be an object");
391
+ }
392
+ const auth = readAuthInput(value.auth);
393
+ const agent = readAgentInput(value.agent);
394
+ return {
395
+ serverUrl: requiredString(value.serverUrl, "serverUrl"),
396
+ auth,
397
+ agent,
398
+ pollIntervalMs: readNumber(value.pollIntervalMs) ?? 3e4,
399
+ sharingPolicy: {
400
+ allowProactiveConversation: readBoolean(readRecord(value.sharingPolicy)?.allowProactiveConversation) ?? true,
401
+ maxContextLevel: "sanitized_summary"
402
+ },
403
+ communitySubagent: readCommunitySubagentInput(value.communitySubagent, agent.openclawAgentId)
404
+ };
405
+ }
406
+ async function runCommunityBridgeSetup(input2, context = {}) {
407
+ const serverUrl = trimTrailingSlash(input2.serverUrl);
408
+ const accessToken = input2.auth.method === "browser" ? input2.auth.userAccessToken : input2.auth.token;
409
+ const registration = await registerAgent(serverUrl, accessToken, input2);
410
+ const personaSetup = await setupCommunityPersona(input2, registration, context);
411
+ const bridgeTokenRef = await persistBridgeTokenSecret(personaSetup.workspaceRootDir, registration.bridge_token);
412
+ return {
413
+ ok: true,
414
+ communityAgentId: registration.community_agent_id,
415
+ personaSetup,
416
+ policyRevision: registration.policy_snapshot?.policy_revision,
417
+ configPatch: {
418
+ plugins: {
419
+ entries: {
420
+ "openclaw-bridge": {
421
+ enabled: true,
422
+ config: {
423
+ enabled: true,
424
+ serverUrl,
425
+ communityAgentId: registration.community_agent_id,
426
+ bridgeTokenRef,
427
+ pollIntervalMs: secondsToMs(registration.heartbeat?.interval_seconds) ?? input2.pollIntervalMs,
428
+ communitySubagent: personaSetup?.subagent ? {
429
+ enabled: true,
430
+ agentId: personaSetup.subagent.agentId,
431
+ workspaceDir: personaSetup.subagent.workspaceDir,
432
+ agentDir: personaSetup.subagent.agentDir
433
+ } : void 0
434
+ }
435
+ }
436
+ }
437
+ }
438
+ },
439
+ notes: [
440
+ "bridgeToken was written to a local 0600 secret file.",
441
+ "configPatch stores bridgeTokenRef only; no bridge token plaintext is written to OpenClaw config.",
442
+ "Community Subagent creation belongs to connect/setup, not plugin install."
443
+ ]
444
+ };
445
+ }
446
+ async function persistBridgeTokenSecret(communityWorkspaceRoot, bridgeToken) {
447
+ const secretsDir = path3.join(communityWorkspaceRoot, "secrets");
448
+ const tokenPath = path3.join(secretsDir, "bridge-token");
449
+ await mkdir2(secretsDir, { recursive: true, mode: 448 });
450
+ await writeFile2(tokenPath, `${bridgeToken}
451
+ `, { encoding: "utf8", mode: 384 });
452
+ await chmod(secretsDir, 448);
453
+ await chmod(tokenPath, 384);
454
+ return pathToFileURL(tokenPath).href;
455
+ }
456
+ async function setupCommunityPersona(input2, registration, context) {
457
+ const hostPaths = context.hostPaths ?? {};
458
+ const paths = await resolvePlatformWorkspacePaths({
459
+ communityAgentId: registration.community_agent_id,
460
+ workspaceDir: hostPaths.workspaceDir,
461
+ agentDir: inferOpenClawAgentDir(hostPaths),
462
+ stateDir: hostPaths.stateDir,
463
+ homeDir: hostPaths.homeDir
464
+ });
465
+ await ensurePlatformWorkspace(paths, registration.community_agent_id);
466
+ if (!input2.communitySubagent.enabled) {
467
+ await writePersonaSubagentInfo(
468
+ paths,
469
+ registration.community_agent_id,
470
+ {
471
+ species: "openclaw",
472
+ agentId: input2.communitySubagent.agentId ?? defaultCommunitySubagentId(input2.agent.openclawAgentId),
473
+ workspaceDir: path3.join(paths.rootDir, "subagent-workspace"),
474
+ agentDir: path3.join(paths.rootDir, "subagent-agent"),
475
+ status: "skipped",
476
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
477
+ setupMode: input2.communitySubagent.autoApprove ? "auto_approved" : "interactive_onboarding"
478
+ }
479
+ );
480
+ return {
481
+ workspaceRootDir: paths.rootDir,
482
+ subagent: null,
483
+ status: "skipped",
484
+ reason: "community subagent disabled by setup input"
485
+ };
486
+ }
487
+ const subagent = await ensureOpenClawCommunitySubagent(input2, paths.rootDir, context);
488
+ await writePersonaSubagentInfo(
489
+ paths,
490
+ registration.community_agent_id,
491
+ {
492
+ species: "openclaw",
493
+ agentId: subagent.agentId,
494
+ workspaceDir: subagent.workspaceDir,
495
+ agentDir: subagent.agentDir,
496
+ status: subagent.status,
497
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
498
+ setupMode: input2.communitySubagent.autoApprove ? "auto_approved" : "interactive_onboarding"
499
+ }
500
+ );
501
+ return {
502
+ workspaceRootDir: paths.rootDir,
503
+ subagent,
504
+ status: subagent.status
505
+ };
506
+ }
507
+ async function ensureOpenClawCommunitySubagent(input2, communityWorkspaceRoot, _context) {
508
+ const agentId = input2.communitySubagent.agentId ?? defaultCommunitySubagentId(input2.agent.openclawAgentId);
509
+ const workspaceDir = path3.join(communityWorkspaceRoot, "subagent-workspace");
510
+ const agentDir = path3.join(communityWorkspaceRoot, "subagent-agent");
511
+ await mkdir2(workspaceDir, { recursive: true });
512
+ await mkdir2(agentDir, { recursive: true });
513
+ return {
514
+ agentId,
515
+ workspaceDir,
516
+ agentDir,
517
+ status: "pending_openclaw_setup"
518
+ };
519
+ }
520
+ async function registerAgent(serverUrl, accessToken, input2) {
521
+ const response = await fetch(`${serverUrl}/v1/agents/register`, {
522
+ method: "POST",
523
+ headers: {
524
+ Authorization: `Bearer ${accessToken}`,
525
+ "Content-Type": "application/json",
526
+ Accept: "application/json"
527
+ },
528
+ body: JSON.stringify({
529
+ adapter_instance_id: `community_bridge_${randomUUID()}`,
530
+ openclaw_workspace_id: input2.agent.openclawWorkspaceId,
531
+ openclaw_agent_id: input2.agent.openclawAgentId,
532
+ agent_profile: {
533
+ display_name: input2.agent.displayName,
534
+ capabilities: input2.agent.capabilities,
535
+ supported_intents: ["no_op", "propose_user_conversation"]
536
+ },
537
+ sharing_policy: {
538
+ allow_agent_questions: false,
539
+ allow_board_posts: false,
540
+ allow_proactive_conversation: input2.sharingPolicy.allowProactiveConversation,
541
+ max_context_level: input2.sharingPolicy.maxContextLevel
542
+ },
543
+ heartbeat_preferences: {
544
+ interval_seconds: Math.max(5, Math.round(input2.pollIntervalMs / 1e3))
545
+ },
546
+ consent: {
547
+ consent_revision: `consent_${randomUUID()}`,
548
+ approved_by_user_id: "openclaw-local-user",
549
+ approved_at: (/* @__PURE__ */ new Date()).toISOString()
550
+ }
551
+ })
552
+ });
553
+ const body = await readJson(response);
554
+ if (!response.ok) {
555
+ throw new Error(`community registration failed: ${response.status}`);
556
+ }
557
+ if (!isRegisterAgentResponse(body)) {
558
+ throw new Error("community registration response is missing agent credentials");
559
+ }
560
+ return body;
561
+ }
562
+ function readAuthInput(value) {
563
+ const record = readRecord(value);
564
+ const method = requiredString(record?.method, "auth.method");
565
+ if (method === "browser") {
566
+ return {
567
+ method,
568
+ userAccessToken: requiredString(record?.userAccessToken, "auth.userAccessToken")
569
+ };
570
+ }
571
+ if (method === "paste_token") {
572
+ return {
573
+ method,
574
+ token: requiredString(record?.token, "auth.token")
575
+ };
576
+ }
577
+ throw new Error(`unsupported auth method: ${method}`);
578
+ }
579
+ function readAgentInput(value) {
580
+ const record = readRecord(value);
581
+ return {
582
+ openclawWorkspaceId: requiredString(record?.openclawWorkspaceId, "agent.openclawWorkspaceId"),
583
+ openclawAgentId: requiredString(record?.openclawAgentId, "agent.openclawAgentId"),
584
+ displayName: requiredString(record?.displayName, "agent.displayName"),
585
+ capabilities: readStringArray(record?.capabilities)
586
+ };
587
+ }
588
+ function readCommunitySubagentInput(value, openclawAgentId) {
589
+ const record = readRecord(value);
590
+ return {
591
+ enabled: readBoolean(record?.enabled) ?? true,
592
+ autoApprove: readBoolean(record?.autoApprove) ?? false,
593
+ agentId: readString2(record?.agentId) ?? defaultCommunitySubagentId(openclawAgentId),
594
+ displayName: readString2(record?.displayName) ?? "AgentComm Community Persona",
595
+ model: readString2(record?.model)
596
+ };
597
+ }
598
+ async function readJson(response) {
599
+ const text = await response.text();
600
+ return text.trim().length > 0 ? JSON.parse(text) : null;
601
+ }
602
+ function isRegisterAgentResponse(value) {
603
+ return isRecord2(value) && typeof value.community_agent_id === "string" && typeof value.bridge_token === "string";
604
+ }
605
+ function requiredString(value, field) {
606
+ if (typeof value !== "string" || value.trim().length === 0) {
607
+ throw new Error(`${field} must be a non-empty string`);
608
+ }
609
+ return value.trim();
610
+ }
611
+ function readStringArray(value) {
612
+ if (!Array.isArray(value)) {
613
+ return [];
614
+ }
615
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
616
+ }
617
+ function readBoolean(value) {
618
+ return typeof value === "boolean" ? value : void 0;
619
+ }
620
+ function readNumber(value) {
621
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
622
+ }
623
+ function readString2(value) {
624
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
625
+ }
626
+ function readRecord(value) {
627
+ return isRecord2(value) ? value : void 0;
628
+ }
629
+ function isRecord2(value) {
630
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
631
+ }
632
+ function trimTrailingSlash(value) {
633
+ return value.replace(/\/+$/, "");
634
+ }
635
+ function defaultCommunitySubagentId(openclawAgentId) {
636
+ return `agentcomm-community-${sanitizeSegment2(openclawAgentId)}`;
637
+ }
638
+ function sanitizeSegment2(value) {
639
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
640
+ }
641
+ function secondsToMs(value) {
642
+ return typeof value === "number" && Number.isFinite(value) ? value * 1e3 : void 0;
643
+ }
644
+
645
+ // onboard.ts
646
+ var DEFAULT_SERVER_URL = "https://community.politeias.com";
647
+ var DEFAULT_CAPABILITIES = ["post.publish", "dm.send", "skill.query"];
648
+ var PERSONA_TEMPLATE_VERSION = "community-emissary/v1";
649
+ var AUTHORITY_LABELS = {
650
+ conservative: "\u4FDD\u5B88",
651
+ balanced: "\u5E73\u8861",
652
+ hands_off: "\u653E\u624B"
653
+ };
654
+ var PERMISSION_TIER_RULES = {
655
+ conservative: [
656
+ "- allow\uFF08\u81EA\u52A8\uFF09\uFF1A\u53EA\u8BFB\u7C7B\u2014\u2014\u8BFB\u793E\u533A\u6D88\u606F\u3001\u8BFB\u4F1A\u8BAE transcript\u3001\u67E5 Skill \u76EE\u5F55\u3002",
657
+ "- ask\uFF08\u5347\u7EA7\u4E3B\u4EBA\uFF09\uFF1A\u51E0\u4E4E\u6240\u6709\u5BF9\u5916\u52A8\u4F5C\u2014\u2014DM \u56DE\u590D\u3001\u6392\u4F1A\u63D0\u8BAE\u3001\u53C2\u4E0E\u4F1A\u8BAE\u53D1\u8A00\u3001\u751F\u6210\u6458\u8981\u843D\u5B9A\u3002",
658
+ "- deny / \u6C38\u4E0D\u81EA\u52A8\uFF1A\u4EFB\u4F55\u5BF9\u5916\u627F\u8BFA\u3001\u5B89\u88C5 Skill\u3001\u5199\u4E3B\u4EBA\u771F\u5B9E\u5DE5\u4F5C\u533A\u3002"
659
+ ].join("\n"),
660
+ balanced: [
661
+ "- allow\uFF08\u81EA\u52A8\uFF09\uFF1A\u4F4E\u98CE\u9669\u2014\u2014\u8BFB\u6D88\u606F\u3001\u81EA\u52A8\u56DE\u6267\u3001\u67E5 Skill\u3001\u751F\u6210\u6458\u8981\u8349\u7A3F\uFF08\u4E0D\u843D\u5B9A\uFF09\u3002",
662
+ "- ask\uFF08\u5347\u7EA7\u4E3B\u4EBA\uFF09\uFF1A\u4E2D\u98CE\u9669\u2014\u2014DM \u5B9E\u8D28\u56DE\u590D\u3001\u6392\u4F1A\u63D0\u8BAE\u3001\u4F1A\u8BAE\u53D1\u8A00\u3001\u6458\u8981/\u5F85\u529E\u843D\u5B9A\u3002",
663
+ "- deny / ask\uFF1A\u9AD8\u98CE\u9669\u2014\u2014\u5BF9\u5916\u6B63\u5F0F\u627F\u8BFA\u3001\u5B89\u88C5 Skill\u3001\u5199\u4E3B\u4EBA\u771F\u5B9E\u5DE5\u4F5C\u533A\uFF08\u6052 ask\uFF09\u3002"
664
+ ].join("\n"),
665
+ hands_off: [
666
+ "- allow\uFF08\u81EA\u52A8\uFF09\uFF1A\u4F4E + \u90E8\u5206\u4E2D\u98CE\u9669\u2014\u2014\u5E38\u89C4 DM \u56DE\u590D\u3001\u6392\u4F1A\u63D0\u8BAE\u3001\u751F\u6210\u5E76\u6682\u5B58\u6458\u8981\u5F85\u529E\u3002",
667
+ "- ask\uFF08\u5347\u7EA7\u4E3B\u4EBA\uFF09\uFF1A\u627F\u8BFA\u7C7B\u3001\u5B89\u88C5 Skill\u3001\u5199\u4E3B\u4EBA\u771F\u5B9E\u5DE5\u4F5C\u533A\u3001\u5206\u4EAB\u654F\u611F\u4FE1\u606F\u3002",
668
+ "- deny\uFF1A\u65E0\u9759\u9ED8\u9AD8\u98CE\u9669\uFF1B\u9AD8\u98CE\u9669\u4E00\u5F8B ask\uFF0C\u4E0D\u81EA\u52A8\u3002"
669
+ ].join("\n")
670
+ };
671
+ async function main() {
672
+ const args = parseArgs(process.argv.slice(2));
673
+ if (args.help) {
674
+ printHelp();
675
+ return;
676
+ }
677
+ const rl = readline.createInterface({ input, output });
678
+ try {
679
+ await runOnboard(args, rl);
680
+ } finally {
681
+ rl.close();
682
+ }
683
+ }
684
+ async function runOnboard(args, rl) {
685
+ console.log("AgentComm Community onboarding");
686
+ console.log("\u63D2\u4EF6\u4F5C\u7528\u57DF\u5411\u5BFC\uFF1A\u53EA\u914D\u7F6E openclaw-bridge\uFF0C\u4E0D\u8FD0\u884C openclaw setup/onboard/configure\u3002");
687
+ console.log("");
688
+ const preflight = await runPreflight();
689
+ printPreflight(preflight);
690
+ if (!preflight.openclaw.ok) {
691
+ console.log("");
692
+ console.log("\u6CA1\u6709\u627E\u5230\u53EF\u7528\u7684 openclaw CLI\u3002\u8BF7\u5148\u5B8C\u6210 OpenClaw \u57FA\u5EA7\u5B89\u88C5\uFF0C\u518D\u91CD\u65B0\u8FD0\u884C politeia-connect\u3002");
693
+ process.exitCode = 2;
694
+ return;
695
+ }
696
+ if (preflight.configFile) {
697
+ console.log(`OpenClaw config: ${preflight.configFile}`);
698
+ }
699
+ const existingConfig = preflight.bridgeConfig;
700
+ if (existingConfig || preflight.legacyCommunityAgents.length > 0) {
701
+ const decision = await chooseExistingConfigAction(preflight, args, rl);
702
+ if (decision === "doctor") {
703
+ await runDoctorAndPrint(args);
704
+ return;
705
+ }
706
+ if (decision === "clean_reinstall") {
707
+ const cleaned = await cleanLegacyCommunityAgents(preflight.legacyCommunityAgents, args, rl);
708
+ if (!cleaned) {
709
+ console.log("\u672A\u6E05\u7406\u9057\u7559 agent\uFF0C\u5DF2\u53D6\u6D88\u3002");
710
+ return;
711
+ }
712
+ }
713
+ if (decision === "exit") {
714
+ console.log("\u672A\u4FEE\u6539\u914D\u7F6E\u3002");
715
+ return;
716
+ }
717
+ }
718
+ const agents = preflight.agents;
719
+ const mainAgent = pickMainAgent(agents);
720
+ const mainModel = mainAgent?.model ?? "openai/gpt-5.5";
721
+ const serverUrl = await askText(rl, args, "server-url", `Community server URL`, DEFAULT_SERVER_URL);
722
+ const mode = await chooseMode(args, rl);
723
+ const selectedAgent = mode === "bind_existing" ? await chooseExistingAgent(agents, args, rl) : null;
724
+ if (selectedAgent?.isDefault || selectedAgent?.id === "main") {
725
+ const confirmed = await askConfirm(
726
+ rl,
727
+ args,
728
+ "confirm-main-agent",
729
+ "\u4F60\u9009\u62E9\u7684\u662F main/default agent\u3002\u793E\u533A\u8EAB\u4EFD\u6302\u5728\u4E3B agent \u4E0A\u4F1A\u524A\u5F31\u9694\u79BB\uFF0C\u4ECD\u8981\u7EE7\u7EED\u5417\uFF1F",
730
+ false
731
+ );
732
+ if (!confirmed) {
733
+ console.log("\u5DF2\u53D6\u6D88\u3002\u5EFA\u8BAE\u91CD\u65B0\u8FD0\u884C\u5E76\u9009\u62E9\u9ED8\u8BA4\u7684\u65B0\u5EFA\u72EC\u7ACB Community Sub-agent\u3002");
734
+ return;
735
+ }
736
+ }
737
+ const nickname = await askRequiredText(rl, args, "nickname", "\u793E\u533A\u6635\u79F0 Nickname\uFF08\u8FD9\u5C31\u662F\u4F60\u7684\u7528\u6237\u540D\uFF09");
738
+ const token = readToken(args) ?? await askRequiredText(rl, args, "token", "\u9080\u8BF7 token\uFF08\u7C98\u8D34\u540E\u56DE\u8F66\uFF09");
739
+ const authorityPreset = await chooseAuthorityPreset(args, rl);
740
+ const capabilities = await askCapabilities(args, rl);
741
+ const model = await askText(rl, args, "model", "\u9A71\u52A8\u6A21\u578B", mainModel);
742
+ const plannedAgentId = mode === "bind_existing" ? selectedAgent?.id ?? "main" : sanitizeId(`agt_community_${nickname}`, "_");
743
+ const setupInput = {
744
+ serverUrl,
745
+ auth: {
746
+ method: "paste_token",
747
+ token
748
+ },
749
+ agent: {
750
+ openclawWorkspaceId: selectedAgent?.workspace ?? mainAgent?.workspace ?? "openclaw-local-workspace",
751
+ openclawAgentId: plannedAgentId,
752
+ displayName: nickname,
753
+ capabilities
754
+ },
755
+ pollIntervalMs: 3e4,
756
+ sharingPolicy: {
757
+ allowProactiveConversation: true,
758
+ maxContextLevel: "sanitized_summary"
759
+ },
760
+ communitySubagent: {
761
+ enabled: true,
762
+ autoApprove: false,
763
+ agentId: plannedAgentId,
764
+ displayName: nickname,
765
+ model
766
+ }
767
+ };
768
+ console.log("");
769
+ console.log("\u5373\u5C06\u6CE8\u518C Community persona\uFF0C\u5E76\u53EA\u5199\u5165 openclaw-bridge config patch\uFF1A");
770
+ console.log(`- \u6A21\u5F0F\uFF1A${mode === "new_subagent" ? "\u65B0\u5EFA\u72EC\u7ACB Community Sub-agent" : "\u7ED1\u5B9A\u73B0\u6709 agent"}`);
771
+ console.log(`- \u6635\u79F0\uFF1A${nickname}`);
772
+ console.log(`- \u6743\u9650\u6863\uFF1A${AUTHORITY_LABELS[authorityPreset]}`);
773
+ console.log(`- capabilities\uFF1A${capabilities.join(", ")}`);
774
+ console.log(`- \u6A21\u578B\uFF1A${model}`);
775
+ console.log(`- \u9694\u79BB Worker\uFF1A\u5F3A\u5236\u542F\u7528`);
776
+ if (mode === "new_subagent") {
777
+ console.log(`- \u5C06\u521B\u5EFA\u4E13\u7528 OpenClaw agent\uFF1A${plannedAgentId}`);
778
+ console.log(`- \u53EF\u5220\u9664\u8DEF\u5F84\uFF1Aopenclaw agents delete ${plannedAgentId} --force`);
779
+ }
780
+ const proceed = await askConfirm(rl, args, "yes", "\u7EE7\u7EED\uFF1F", false);
781
+ if (!proceed) {
782
+ console.log("\u5DF2\u53D6\u6D88\uFF0C\u672A\u4FEE\u6539\u914D\u7F6E\u3002");
783
+ return;
784
+ }
785
+ const beforeConfig = preflight.configFile ? await readOptionalFile(preflight.configFile) : null;
786
+ const setupResult = await runCommunityBridgeSetup(parseSetupInput(setupInput), {
787
+ hostPaths: {
788
+ workspaceDir: selectedAgent?.workspace ?? mainAgent?.workspace,
789
+ agentDir: void 0,
790
+ homeDir: void 0,
791
+ agentId: selectedAgent?.id ?? mainAgent?.id
792
+ }
793
+ });
794
+ const personaSetup = setupResult.personaSetup;
795
+ let subagentStatus = "skipped";
796
+ if (mode === "new_subagent") {
797
+ subagentStatus = await materializeNativeOpenClawSubagent({
798
+ agentId: plannedAgentId,
799
+ workspaceDir: personaSetup.subagent?.workspaceDir,
800
+ agentDir: personaSetup.subagent?.agentDir,
801
+ model,
802
+ nickname,
803
+ communityAgentId: setupResult.communityAgentId,
804
+ authorityPreset,
805
+ capabilities
806
+ });
807
+ } else {
808
+ subagentStatus = "bound_existing";
809
+ overrideConfigPatchSubagent(setupResult.configPatch, {
810
+ enabled: true,
811
+ agentId: selectedAgent?.id,
812
+ workspaceDir: selectedAgent?.workspace,
813
+ agentDir: selectedAgent?.agentDir
814
+ });
815
+ }
816
+ if (personaSetup.workspaceRootDir) {
817
+ await writeOnboardPersonaMetadata(personaSetup.workspaceRootDir, {
818
+ communityAgentId: setupResult.communityAgentId,
819
+ nickname,
820
+ capabilities,
821
+ model,
822
+ authorityPreset,
823
+ mode,
824
+ subagentStatus,
825
+ selectedAgent,
826
+ configPatchSha256: sha256Json(setupResult.configPatch)
827
+ });
828
+ await writeDelegationPolicyPreset(personaSetup.workspaceRootDir, {
829
+ communityAgentId: setupResult.communityAgentId,
830
+ mainAgentId: mainAgent?.id ?? "main",
831
+ preset: authorityPreset
832
+ });
833
+ }
834
+ const patchResult = await runOpenClaw(["config", "patch", "--stdin"], {
835
+ input: JSON.stringify(setupResult.configPatch, null, 2)
836
+ });
837
+ printCommand("openclaw config patch --stdin", patchResult, { redact: false });
838
+ if (!patchResult.ok) {
839
+ throw new Error("openclaw config patch failed; platform registration succeeded but local config was not written");
840
+ }
841
+ const validateResult = await runOpenClaw(["config", "validate"]);
842
+ printCommand("openclaw config validate", validateResult, { redact: false });
843
+ const afterConfig = preflight.configFile ? await readOptionalFile(preflight.configFile) : null;
844
+ const configDiff = summarizeCommunityBridgeOnlyDiff(beforeConfig, afterConfig);
845
+ console.log("");
846
+ console.log("\u914D\u7F6E\u53D8\u66F4\u6458\u8981\uFF1A");
847
+ console.log(configDiff);
848
+ console.log("");
849
+ console.log("\u6CE8\u518C\u7ED3\u679C\uFF1A");
850
+ console.log(JSON.stringify({
851
+ ok: setupResult.ok,
852
+ communityAgentId: setupResult.communityAgentId,
853
+ workspaceRootDir: personaSetup.workspaceRootDir,
854
+ subagent: {
855
+ agentId: plannedAgentId,
856
+ status: subagentStatus,
857
+ workspaceDir: mode === "bind_existing" ? selectedAgent?.workspace : personaSetup.subagent?.workspaceDir,
858
+ agentDir: mode === "bind_existing" ? selectedAgent?.agentDir : personaSetup.subagent?.agentDir
859
+ },
860
+ bridgeTokenRefOnly: Boolean(getBridgeConfigFromPatch(setupResult.configPatch)?.bridgeTokenRef)
861
+ }, null, 2));
862
+ await runDoctorAndPrint(args);
863
+ console.log("");
864
+ console.log("\u5B8C\u6210\u3002\u53EF\u91CD\u8DD1 politeia-connect \u505A doctor\u3001\u4FEE\u590D\u6216\u91CD\u65B0\u6CE8\u518C\u3002\u6743\u9650\u6863\u53EF\u5728 persona/delegation-policy.json \u8C03\u6574\u3002");
865
+ if (mode === "new_subagent") {
866
+ console.log(`\u4E13\u7528 agent \u53EF\u9006\u5220\u9664\uFF1Aopenclaw agents delete ${plannedAgentId} --force`);
867
+ }
868
+ }
869
+ async function runPreflight() {
870
+ const openclaw = await runCommand("openclaw", ["--version"]);
871
+ const packageInfo = await readPackageInfo();
872
+ const configFileResult = openclaw.ok ? await runOpenClaw(["config", "file"]) : null;
873
+ const configFile = configFileResult?.ok ? lastPathLikeLine(configFileResult.stdout) : null;
874
+ const validate = openclaw.ok ? await runOpenClaw(["config", "validate"]) : null;
875
+ const agentsResult = openclaw.ok ? await runOpenClaw(["agents", "list", "--json"]) : null;
876
+ const agents = agentsResult?.ok ? parseJsonFromMixedOutput(agentsResult.stdout) ?? [] : [];
877
+ const bridgeConfigResult = openclaw.ok ? await runOpenClaw(["config", "get", "plugins.entries.openclaw-bridge", "--json"]) : null;
878
+ const bridgeConfig = bridgeConfigResult?.ok ? parseJsonFromMixedOutput(bridgeConfigResult.stdout) : null;
879
+ return {
880
+ openclaw,
881
+ packageInfo,
882
+ configFile,
883
+ validate,
884
+ agents,
885
+ legacyCommunityAgents: findLegacyCommunityAgents(agents),
886
+ personaTemplateRoot: resolvePersonaTemplateRoot({ throwOnMissing: false }),
887
+ bridgeConfig
888
+ };
889
+ }
890
+ function printPreflight(preflight) {
891
+ console.log("\u524D\u7F6E\u68C0\u67E5\uFF1A");
892
+ console.log(`- openclaw CLI\uFF1A${preflight.openclaw.ok ? "ok" : "missing"}`);
893
+ console.log(`- openclaw-bridge package\uFF1A${preflight.packageInfo.version ?? "unknown"} (${preflight.packageInfo.rootDir})`);
894
+ console.log(`- persona template\uFF1A${preflight.personaTemplateRoot ? "ok" : "missing"}`);
895
+ console.log(`- config validate\uFF1A${preflight.validate?.ok ? "ok" : "warning/failed"}`);
896
+ console.log(`- agents\uFF1A${preflight.agents.length}`);
897
+ console.log(`- legacy community agents\uFF1A${preflight.legacyCommunityAgents.length}`);
898
+ for (const agent of preflight.legacyCommunityAgents) {
899
+ console.log(` legacy: ${agent.id}${agent.workspace ? ` workspace=${agent.workspace}` : ""}`);
900
+ }
901
+ const bridge = readNestedRecord(preflight.bridgeConfig, ["config"]);
902
+ if (!preflight.bridgeConfig) {
903
+ console.log("- openclaw-bridge \u914D\u7F6E\uFF1A\u672A\u53D1\u73B0");
904
+ return;
905
+ }
906
+ const enabled = bridge?.enabled === true;
907
+ const hasAgent = typeof bridge?.communityAgentId === "string";
908
+ const hasTokenRef = typeof bridge?.bridgeTokenRef === "string";
909
+ const hasPlainToken = typeof bridge?.bridgeToken === "string" || typeof bridge?.keepaliveToken === "string";
910
+ const residue = !enabled || !hasAgent || !hasTokenRef && !hasPlainToken;
911
+ console.log(`- openclaw-bridge \u914D\u7F6E\uFF1A${residue ? "\u6B8B\u7559/\u672A\u5B8C\u6210" : "\u5DF2\u5B58\u5728"}`);
912
+ if (hasPlainToken) {
913
+ console.log(" warning: \u68C0\u6D4B\u5230\u660E\u6587 token \u5B57\u6BB5\uFF0C\u5EFA\u8BAE\u901A\u8FC7\u672C\u5411\u5BFC\u91CD\u8FDE\u4E3A bridgeTokenRef\u3002");
914
+ }
915
+ }
916
+ async function chooseExistingConfigAction(preflight, args, rl) {
917
+ if (args["clean-legacy-agents"] === true) {
918
+ return "clean_reinstall";
919
+ }
920
+ if (args["replace-existing"] === true || args["non-interactive"] === true) {
921
+ return "replace";
922
+ }
923
+ const config = preflight.bridgeConfig;
924
+ const bridge = readNestedRecord(config, ["config"]);
925
+ const active = bridge?.enabled === true && typeof bridge?.communityAgentId === "string";
926
+ const hasLegacyAgents = preflight.legacyCommunityAgents.length > 0;
927
+ console.log("");
928
+ if (config) {
929
+ console.log(active ? "\u68C0\u6D4B\u5230\u5DF2\u6709 openclaw-bridge \u914D\u7F6E\u3002" : "\u68C0\u6D4B\u5230 openclaw-bridge \u6B8B\u7559/\u672A\u5B8C\u6210\u914D\u7F6E\u3002");
930
+ }
931
+ if (hasLegacyAgents) {
932
+ console.log("\u68C0\u6D4B\u5230\u65E7 pilot \u9057\u7559 Community agent\u3002\u9ED8\u8BA4\u4E0D\u4F1A\u5220\u9664\uFF1B\u9009\u62E9\u6E05\u7406\u65F6\u4F1A\u5148\u786E\u8BA4\u518D\u8C03\u7528 openclaw agents delete --force\u3002");
933
+ }
934
+ const choices = [
935
+ ["replace", active ? "\u91CD\u65B0\u6CE8\u518C/\u4FEE\u590D\u8FDE\u63A5" : "\u5408\u5E76\u4FEE\u590D\u5E76\u7EE7\u7EED\u6CE8\u518C"]
936
+ ];
937
+ if (active && !hasLegacyAgents) {
938
+ choices.unshift(["doctor", "\u53EA\u8FD0\u884C doctor/status"]);
939
+ }
940
+ if (hasLegacyAgents) {
941
+ choices.push(["clean_reinstall", "\u5B89\u5168\u5220\u9664\u65E7 pilot agent\uFF0C\u7136\u540E\u91CD\u65B0\u6CE8\u518C/\u91CD\u5EFA"]);
942
+ }
943
+ choices.push(["exit", "\u9000\u51FA\uFF0C\u4E0D\u4FEE\u6539"]);
944
+ const fallback = active && !hasLegacyAgents ? "doctor" : "replace";
945
+ return askChoice(rl, "\u8BF7\u9009\u62E9\uFF1A", choices, fallback);
946
+ }
947
+ async function cleanLegacyCommunityAgents(agents, args, rl) {
948
+ if (agents.length === 0) {
949
+ return true;
950
+ }
951
+ console.log("");
952
+ console.log("\u5C06\u6E05\u7406\u4EE5\u4E0B\u65E7 pilot Community agent\uFF1A");
953
+ for (const agent of agents) {
954
+ console.log(`- ${agent.id}${agent.workspace ? ` workspace=${agent.workspace}` : ""}${agent.agentDir ? ` agentDir=${agent.agentDir}` : ""}`);
955
+ }
956
+ const confirmed = await askConfirm(
957
+ rl,
958
+ args,
959
+ "confirm-clean-legacy-agents",
960
+ "\u786E\u8BA4\u5220\u9664\u8FD9\u4E9B\u65E7 pilot agent\uFF1F\u8FD9\u4E0D\u4F1A\u5220\u9664\u65B0\u7684 agt_community_* \u771F\u5B9E\u4F7F\u8005\u3002",
961
+ false
962
+ );
963
+ if (!confirmed) {
964
+ return false;
965
+ }
966
+ for (const agent of agents) {
967
+ const result = await runOpenClaw(["agents", "delete", agent.id, "--force"]);
968
+ printCommand(`openclaw agents delete ${agent.id} --force`, result, { redact: false });
969
+ if (!result.ok) {
970
+ console.log("");
971
+ console.log(`OpenClaw \u62D2\u7EDD\u5220\u9664\u65E7 agent ${agent.id}\uFF0C\u5DF2\u505C\u6B62\uFF0C\u907F\u514D\u7EE7\u7EED\u5199\u5165\u9020\u6210\u810F\u72B6\u6001\u3002`);
972
+ console.log("\u8BF7\u5148\u67E5\u770B\u4E0A\u65B9 rejected payload / OpenClaw config \u5199\u4FDD\u62A4\u63D0\u793A\uFF0C\u4FEE\u590D\u540E\u91CD\u8DD1\u672C\u5411\u5BFC\u3002");
973
+ return false;
974
+ }
975
+ }
976
+ return true;
977
+ }
978
+ async function chooseMode(args, rl) {
979
+ if (args.mode === "bind") return "bind_existing";
980
+ if (args.mode === "new" || args["non-interactive"] === true) return "new_subagent";
981
+ const answer = await askChoice(rl, "\u793E\u533A\u8EAB\u4EFD\u653E\u5728\u54EA\u91CC\uFF1F", [
982
+ ["new_subagent", "[\u63A8\u8350] \u65B0\u5EFA\u72EC\u7ACB Community Sub-agent"],
983
+ ["bind_existing", "[\u8FDB\u9636] \u7ED1\u5B9A\u73B0\u6709 agent"]
984
+ ], "new_subagent");
985
+ return answer;
986
+ }
987
+ async function chooseExistingAgent(agents, args, rl) {
988
+ const requested = typeof args["agent-id"] === "string" ? args["agent-id"] : void 0;
989
+ if (requested) {
990
+ return agents.find((agent) => agent.id === requested) ?? { id: requested };
991
+ }
992
+ if (agents.length === 0) {
993
+ console.log("\u672A\u80FD\u5217\u51FA\u73B0\u6709 agents\uFF0C\u5C06\u6309 main \u7EE7\u7EED\uFF1B\u5982\u679C\u8FD9\u4E0E\u5B9E\u9645\u4E0D\u7B26\uFF0C\u5EFA\u8BAE\u5148\u8FD0\u884C openclaw agents list \u68C0\u67E5\u3002");
994
+ return { id: "main", isDefault: true };
995
+ }
996
+ const choices = agents.map((agent) => [
997
+ agent.id,
998
+ `${agent.id}${agent.identityName ? ` / ${agent.identityName}` : ""}${agent.isDefault ? " [default]" : ""}${agent.model ? ` / ${agent.model}` : ""}`
999
+ ]);
1000
+ const selectedId = await askChoice(rl, "\u9009\u62E9\u8981\u7ED1\u5B9A\u7684 agent\uFF1A", choices, agents.find((agent) => agent.isDefault)?.id ?? agents[0]?.id);
1001
+ return agents.find((agent) => agent.id === selectedId) ?? { id: selectedId };
1002
+ }
1003
+ async function chooseAuthorityPreset(args, rl) {
1004
+ const value = typeof args["authority"] === "string" ? args["authority"] : void 0;
1005
+ if (value === "conservative" || value === "balanced" || value === "hands_off") return value;
1006
+ if (args["non-interactive"] === true) return "balanced";
1007
+ const answer = await askChoice(rl, "\u6743\u9650\u6863\uFF1A", [
1008
+ ["conservative", "\u4FDD\u5B88\uFF1A\u51E0\u4E4E\u4E00\u5207 ASK\uFF0C\u4EC5\u7410\u788E\u53EA\u8BFB\u81EA\u52A8"],
1009
+ ["balanced", "\u5E73\u8861 [\u63A8\u8350]\uFF1A\u4F4E\u98CE\u9669\u81EA\u52A8\uFF0C\u4E2D/\u9AD8\u98CE\u9669 ASK"],
1010
+ ["hands_off", "\u653E\u624B\uFF1A\u4F4E + \u90E8\u5206\u4E2D\u98CE\u9669\u81EA\u52A8\uFF0C\u9AD8\u98CE\u9669\u4ECD ASK"]
1011
+ ], "balanced");
1012
+ return answer;
1013
+ }
1014
+ async function askCapabilities(args, rl) {
1015
+ const value = typeof args.capabilities === "string" ? args.capabilities : void 0;
1016
+ if (value) {
1017
+ return parseCsv(value);
1018
+ }
1019
+ if (args["non-interactive"] === true) {
1020
+ return DEFAULT_CAPABILITIES;
1021
+ }
1022
+ const answer = await askTextValue(rl, `capabilities`, DEFAULT_CAPABILITIES.join(", "));
1023
+ return parseCsv(answer || DEFAULT_CAPABILITIES.join(", "));
1024
+ }
1025
+ async function askText(rl, args, key, label, fallback) {
1026
+ const value = args[key];
1027
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
1028
+ if (args["non-interactive"] === true) return fallback;
1029
+ return askTextValue(rl, label, fallback);
1030
+ }
1031
+ async function askRequiredText(rl, args, key, label) {
1032
+ const value = args[key];
1033
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
1034
+ if (args["non-interactive"] === true) {
1035
+ throw new Error(`--${key} is required in --non-interactive mode`);
1036
+ }
1037
+ while (true) {
1038
+ const answer = await askTextValue(rl, label, "");
1039
+ if (answer.trim().length > 0) return answer.trim();
1040
+ console.log("\u6B64\u9879\u5FC5\u586B\u3002");
1041
+ }
1042
+ }
1043
+ async function askTextValue(rl, label, fallback) {
1044
+ const suffix = fallback ? ` (${fallback})` : "";
1045
+ const answer = await rl.question(`${label}${suffix}: `);
1046
+ return answer.trim() || fallback;
1047
+ }
1048
+ async function askConfirm(rl, args, key, label, fallback) {
1049
+ if (args[key] === true) return true;
1050
+ if (args["non-interactive"] === true) return fallback || args.yes === true;
1051
+ const suffix = fallback ? "Y/n" : "y/N";
1052
+ const answer = (await rl.question(`${label} [${suffix}]: `)).trim().toLowerCase();
1053
+ if (!answer) return fallback;
1054
+ return answer === "y" || answer === "yes";
1055
+ }
1056
+ async function askChoice(rl, label, choices, fallback) {
1057
+ console.log(label);
1058
+ choices.forEach(([, text], index) => console.log(` ${index + 1}. ${text}`));
1059
+ while (true) {
1060
+ const answer = (await rl.question(`\u9009\u62E9 [${choices.findIndex(([value]) => value === fallback) + 1}]: `)).trim();
1061
+ if (!answer) return fallback;
1062
+ const index = Number(answer);
1063
+ if (Number.isInteger(index) && index >= 1 && index <= choices.length) {
1064
+ return choices[index - 1][0];
1065
+ }
1066
+ const matched = choices.find(([value]) => value === answer);
1067
+ if (matched) return matched[0];
1068
+ console.log("\u65E0\u6CD5\u8BC6\u522B\uFF0C\u8BF7\u8F93\u5165\u5E8F\u53F7\u3002");
1069
+ }
1070
+ }
1071
+ function readToken(args) {
1072
+ if (typeof args["token-env"] === "string") {
1073
+ const value = process.env[args["token-env"]];
1074
+ if (!value) throw new Error(`environment variable ${args["token-env"]} is empty`);
1075
+ return value.trim();
1076
+ }
1077
+ if (typeof args.token === "string") {
1078
+ return args.token.trim();
1079
+ }
1080
+ return void 0;
1081
+ }
1082
+ async function materializeNativeOpenClawSubagent(input2) {
1083
+ if (!input2.workspaceDir || !input2.agentDir) {
1084
+ throw new Error("missing subagent workspaceDir/agentDir; cannot materialize native OpenClaw agent");
1085
+ }
1086
+ const existingAgents = await listOpenClawAgents();
1087
+ const existing = existingAgents.find((agent) => agent.id === input2.agentId);
1088
+ if (existing) {
1089
+ if (path4.resolve(existing.workspace ?? "") !== path4.resolve(input2.workspaceDir) || path4.resolve(existing.agentDir ?? "") !== path4.resolve(input2.agentDir)) {
1090
+ throw new Error(
1091
+ `OpenClaw agent ${input2.agentId} already exists at a different path; refusing to modify it. Choose another nickname or delete it manually with: openclaw agents delete ${input2.agentId} --force`
1092
+ );
1093
+ }
1094
+ await writePersonaTemplate(input2);
1095
+ return "already_exists";
1096
+ }
1097
+ const addResult = await runOpenClaw([
1098
+ "agents",
1099
+ "add",
1100
+ input2.agentId,
1101
+ "--non-interactive",
1102
+ "--workspace",
1103
+ input2.workspaceDir,
1104
+ "--agent-dir",
1105
+ input2.agentDir,
1106
+ "--model",
1107
+ input2.model,
1108
+ "--json"
1109
+ ]);
1110
+ printCommand("openclaw agents add", addResult, { redact: false });
1111
+ if (!addResult.ok) {
1112
+ throw new Error(`openclaw agents add failed for ${input2.agentId}`);
1113
+ }
1114
+ await writePersonaTemplate(input2);
1115
+ return "created";
1116
+ }
1117
+ async function listOpenClawAgents() {
1118
+ const result = await runOpenClaw(["agents", "list", "--json"]);
1119
+ if (!result.ok) {
1120
+ return [];
1121
+ }
1122
+ return parseJsonFromMixedOutput(result.stdout) ?? [];
1123
+ }
1124
+ async function writePersonaTemplate(input2) {
1125
+ await mkdir3(input2.workspaceDir, { recursive: true });
1126
+ const templateRoot = resolvePersonaTemplateRoot();
1127
+ if (!templateRoot) {
1128
+ throw new Error("persona template not found");
1129
+ }
1130
+ const slots = {
1131
+ "{{NICKNAME}}": input2.nickname,
1132
+ "{{COMMUNITY_AGENT_ID}}": input2.communityAgentId,
1133
+ "{{PERMISSION_TIER}}": AUTHORITY_LABELS[input2.authorityPreset],
1134
+ "{{PERMISSION_TIER_RULES}}": PERMISSION_TIER_RULES[input2.authorityPreset],
1135
+ "{{CAPABILITIES}}": input2.capabilities.join(", ")
1136
+ };
1137
+ const files = ["IDENTITY.md", "SOUL.md", "AGENTS.md", "USER.md", "TOOLS.md", "HEARTBEAT.md"];
1138
+ for (const file of files) {
1139
+ let body = await readFile2(path4.join(templateRoot, file), "utf8");
1140
+ for (const [slot, value] of Object.entries(slots)) {
1141
+ body = body.split(slot).join(value);
1142
+ }
1143
+ await writeFile3(path4.join(input2.workspaceDir, file), body, "utf8");
1144
+ }
1145
+ await writeFile3(
1146
+ path4.join(input2.workspaceDir, ".agentcomm-persona-template.json"),
1147
+ JSON.stringify({
1148
+ template: PERSONA_TEMPLATE_VERSION,
1149
+ materializedAt: (/* @__PURE__ */ new Date()).toISOString(),
1150
+ communityAgentId: input2.communityAgentId,
1151
+ authorityPreset: input2.authorityPreset,
1152
+ capabilities: input2.capabilities
1153
+ }, null, 2) + "\n",
1154
+ "utf8"
1155
+ );
1156
+ }
1157
+ function resolvePackageRoot() {
1158
+ const currentDir = path4.dirname(fileURLToPath(import.meta.url));
1159
+ return path4.basename(currentDir) === "dist" ? path4.dirname(currentDir) : currentDir;
1160
+ }
1161
+ async function readPackageInfo() {
1162
+ const rootDir = resolvePackageRoot();
1163
+ const raw = await readOptionalFile(path4.join(rootDir, "package.json"));
1164
+ const parsed = raw ? parseJsonLoose(raw) : null;
1165
+ return {
1166
+ rootDir,
1167
+ version: typeof parsed?.version === "string" ? parsed.version : void 0
1168
+ };
1169
+ }
1170
+ function findLegacyCommunityAgents(agents) {
1171
+ return agents.filter(
1172
+ (agent) => agent.id.startsWith("agentcomm-community-") || agent.id.startsWith("agentcomm-") && (agent.workspace?.includes("/.community/") || agent.agentDir?.includes("/.community/"))
1173
+ );
1174
+ }
1175
+ function resolvePersonaTemplateRoot(options = {}) {
1176
+ const packageRoot = resolvePackageRoot();
1177
+ const candidates = [
1178
+ path4.join(packageRoot, "dist", "persona-template", "community-emissary", "v1"),
1179
+ path4.join(packageRoot, "persona-template", "community-emissary", "v1"),
1180
+ path4.join(packageRoot, "..", "..", "personas", "community-emissary", "v1")
1181
+ ];
1182
+ const found = candidates.find((candidate) => existsSync2(candidate));
1183
+ if (!found && options.throwOnMissing !== false) {
1184
+ throw new Error(
1185
+ `persona template not found; checked ${candidates.join(", ")}. Reinstall openclaw-bridge >=0.1.2 or rebuild the plugin with npm run build so dist/persona-template is present.`
1186
+ );
1187
+ }
1188
+ return found ?? null;
1189
+ }
1190
+ async function runDoctorAndPrint(args) {
1191
+ const timeout = typeof args["doctor-timeout-ms"] === "string" ? args["doctor-timeout-ms"] : "15000";
1192
+ const command = [
1193
+ "gateway",
1194
+ "call",
1195
+ "communityBridge.doctor",
1196
+ "--json",
1197
+ "--expect-final",
1198
+ "--timeout",
1199
+ timeout
1200
+ ];
1201
+ if (typeof args["gateway-url"] === "string" && args["gateway-url"].trim().length > 0) {
1202
+ command.push("--url", args["gateway-url"].trim());
1203
+ }
1204
+ const doctor = await runOpenClaw(command);
1205
+ printCommand("openclaw gateway call communityBridge.doctor --json --expect-final", doctor, { redact: true });
1206
+ if (!doctor.ok) {
1207
+ console.log("doctor \u672A\u901A\u8FC7\u6216 gateway \u672A\u8FD0\u884C\uFF1B\u914D\u7F6E\u5DF2\u5199\u5165\uFF0C\u53EF\u542F\u52A8/\u91CD\u542F OpenClaw Gateway \u540E\u91CD\u8DD1 politeia-connect\u3002");
1208
+ }
1209
+ }
1210
+ async function runOpenClaw(args, options = {}) {
1211
+ return runCommand("openclaw", args, options);
1212
+ }
1213
+ async function runCommand(command, args, options = {}) {
1214
+ return new Promise((resolve) => {
1215
+ const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"], env: process.env });
1216
+ let stdout = "";
1217
+ let stderr = "";
1218
+ child.stdout.on("data", (chunk) => {
1219
+ stdout += String(chunk);
1220
+ });
1221
+ child.stderr.on("data", (chunk) => {
1222
+ stderr += String(chunk);
1223
+ });
1224
+ child.on("error", (error) => {
1225
+ resolve({ ok: false, code: null, stdout, stderr: `${stderr}${error.message}` });
1226
+ });
1227
+ child.on("close", (code) => {
1228
+ resolve({ ok: code === 0, code, stdout, stderr });
1229
+ });
1230
+ if (options.input) {
1231
+ child.stdin.write(options.input);
1232
+ }
1233
+ child.stdin.end();
1234
+ });
1235
+ }
1236
+ function printCommand(label, result, options) {
1237
+ console.log("");
1238
+ console.log(`$ ${label}`);
1239
+ console.log(`exit=${result.code} ok=${result.ok}`);
1240
+ const stdout = options.redact ? redactSecrets(result.stdout) : result.stdout;
1241
+ const stderr = options.redact ? redactSecrets(result.stderr) : result.stderr;
1242
+ if (stdout.trim()) console.log(stdout.trim());
1243
+ if (stderr.trim()) console.error(stderr.trim());
1244
+ }
1245
+ function redactSecrets(value) {
1246
+ return value.replace(/("(?:bridgeToken|keepaliveToken|token|Authorization)"\s*:\s*)"[^"]*"/gi, '$1"<redacted>"').replace(/\b((?:bridgeToken|keepaliveToken|token|Authorization)=)[^\s]+/gi, "$1<redacted>");
1247
+ }
1248
+ async function writeOnboardPersonaMetadata(rootDir, input2) {
1249
+ const profilePath = path4.join(rootDir, "persona", "profile.json");
1250
+ const current = await readJsonFile(profilePath);
1251
+ const subagent = readRecord2(current?.subagent) ? current?.subagent : {};
1252
+ const next = {
1253
+ ...current ?? {},
1254
+ communityAgentId: input2.communityAgentId,
1255
+ displayName: input2.nickname,
1256
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1257
+ onboarding: {
1258
+ version: 1,
1259
+ protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
1260
+ mode: input2.mode,
1261
+ authorityPreset: input2.authorityPreset,
1262
+ capabilities: input2.capabilities,
1263
+ model: input2.model,
1264
+ isolationWorkerRequired: true,
1265
+ autonomousLoopConfigured: false,
1266
+ nativeMaterialization: input2.mode === "new_subagent",
1267
+ personaTemplate: input2.mode === "new_subagent" ? PERSONA_TEMPLATE_VERSION : void 0,
1268
+ configPatchSha256: input2.configPatchSha256
1269
+ },
1270
+ subagent: {
1271
+ ...subagent,
1272
+ status: input2.subagentStatus === "bound_existing" ? "already_exists" : input2.subagentStatus,
1273
+ nativeMaterializationStatus: input2.subagentStatus,
1274
+ bindExisting: input2.mode === "bind_existing",
1275
+ selectedAgent: input2.selectedAgent ? {
1276
+ id: input2.selectedAgent.id,
1277
+ workspace: input2.selectedAgent.workspace,
1278
+ agentDir: input2.selectedAgent.agentDir,
1279
+ model: input2.selectedAgent.model
1280
+ } : void 0
1281
+ }
1282
+ };
1283
+ await writeFile3(profilePath, `${JSON.stringify(next, null, 2)}
1284
+ `, "utf8");
1285
+ }
1286
+ async function writeDelegationPolicyPreset(rootDir, input2) {
1287
+ const policyPath = path4.join(rootDir, "persona", "delegation-policy.json");
1288
+ const rules = {
1289
+ conservative: [
1290
+ rule("allow-low-risk-receipt", "dm.receipt", "low", "allow"),
1291
+ rule("ask-dm-send", "dm.send", "low", "ask"),
1292
+ rule("ask-schedule-propose", "schedule.negotiate.propose", "low", "ask"),
1293
+ rule("ask-skill-query", "skill.query", "low", "ask"),
1294
+ rule("deny-high-risk", "*", "high", "deny")
1295
+ ],
1296
+ balanced: [
1297
+ rule("allow-low-risk-receipt", "dm.receipt", "low", "allow"),
1298
+ rule("allow-low-risk-skill-query", "skill.query", "low", "allow"),
1299
+ rule("allow-low-risk-schedule-propose", "schedule.negotiate.propose", "low", "allow"),
1300
+ rule("ask-medium-risk-dm-reply", "dm.reply", "medium", "ask"),
1301
+ rule("ask-medium-risk-post-publish", "post.publish", "medium", "ask"),
1302
+ rule("ask-high-risk", "*", "high", "ask")
1303
+ ],
1304
+ hands_off: [
1305
+ rule("allow-low-risk-receipt", "dm.receipt", "low", "allow"),
1306
+ rule("allow-low-risk-skill-query", "skill.query", "low", "allow"),
1307
+ rule("allow-low-risk-schedule-propose", "schedule.negotiate.propose", "low", "allow"),
1308
+ rule("allow-medium-risk-dm-reply", "dm.reply", "medium", "allow"),
1309
+ rule("ask-medium-risk-post-publish", "post.publish", "medium", "ask"),
1310
+ rule("ask-high-risk", "*", "high", "ask")
1311
+ ]
1312
+ };
1313
+ const policy = {
1314
+ protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
1315
+ authorityPreset: input2.preset,
1316
+ principal: {
1317
+ userId: "local-user",
1318
+ mainAgentId: input2.mainAgentId,
1319
+ communityAgentId: input2.communityAgentId
1320
+ },
1321
+ defaultDecision: "ask",
1322
+ rules: rules[input2.preset],
1323
+ audit: { required: true },
1324
+ autoReceipt: {
1325
+ enabled: input2.preset !== "conservative",
1326
+ message: "\u5DF2\u6536\u5230\uFF0C\u4F1A\u8F6C\u544A\u6211\u7684\u7528\u6237\u3002"
1327
+ }
1328
+ };
1329
+ await writeFile3(policyPath, `${JSON.stringify(policy, null, 2)}
1330
+ `, "utf8");
1331
+ }
1332
+ function rule(id, action, risk, decision) {
1333
+ return { id, action, risk, decision };
1334
+ }
1335
+ function overrideConfigPatchSubagent(patch, subagent) {
1336
+ const config = getBridgeConfigFromPatch(patch);
1337
+ if (config) {
1338
+ config.communitySubagent = subagent;
1339
+ }
1340
+ }
1341
+ function getBridgeConfigFromPatch(patch) {
1342
+ return readNestedRecord(patch, ["plugins", "entries", "openclaw-bridge", "config"]);
1343
+ }
1344
+ function summarizeCommunityBridgeOnlyDiff(beforeRaw, afterRaw) {
1345
+ if (!beforeRaw || !afterRaw) return "\u672A\u80FD\u8BFB\u53D6\u5B8C\u6574 config \u6587\u4EF6\uFF1B\u5DF2\u4F7F\u7528 openclaw config patch \u5199\u5165\u3002";
1346
+ const before = parseJsonLoose(beforeRaw);
1347
+ const after = parseJsonLoose(afterRaw);
1348
+ if (!before || !after) return "config \u6587\u4EF6\u4E0D\u662F\u4E25\u683C JSON\uFF1B\u8BF7\u7528 openclaw config get plugins.entries.openclaw-bridge --json \u590D\u6838\u3002";
1349
+ const normalizedBefore = normalizeConfigForScopedDiff(before);
1350
+ const normalizedAfter = normalizeConfigForScopedDiff(after);
1351
+ const changedPaths = diffLeafPaths(normalizedBefore, normalizedAfter);
1352
+ const agentsDelta = summarizeAgentsListDelta(before, after);
1353
+ const bridgeChanged = JSON.stringify(getBridgeEntry(before)) !== JSON.stringify(getBridgeEntry(after));
1354
+ const allowed = changedPaths.every(
1355
+ (item) => item.startsWith("plugins.entries.openclaw-bridge") || item.startsWith("agents.list")
1356
+ ) && bridgeChanged && agentsDelta.added === 1 && agentsDelta.existingUnchanged;
1357
+ return JSON.stringify({
1358
+ allowedOnboardingConfigChanged: allowed,
1359
+ changedPaths,
1360
+ bridgeChanged,
1361
+ agentsList: agentsDelta
1362
+ }, null, 2);
1363
+ }
1364
+ function normalizeConfigForScopedDiff(value) {
1365
+ const clone = JSON.parse(JSON.stringify(value));
1366
+ const meta = readRecord2(clone.meta) ? clone.meta : null;
1367
+ if (meta) {
1368
+ delete meta.lastTouchedAt;
1369
+ if (Object.keys(meta).length === 0) {
1370
+ delete clone.meta;
1371
+ }
1372
+ }
1373
+ return clone;
1374
+ }
1375
+ function diffLeafPaths(before, after, prefix = "") {
1376
+ if (JSON.stringify(before) === JSON.stringify(after)) {
1377
+ return [];
1378
+ }
1379
+ if (!readRecord2(before) || !readRecord2(after)) {
1380
+ return [prefix || "<root>"];
1381
+ }
1382
+ const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
1383
+ return [...keys].flatMap((key) => diffLeafPaths(before[key], after[key], prefix ? `${prefix}.${key}` : key));
1384
+ }
1385
+ function summarizeAgentsListDelta(before, after) {
1386
+ let beforeAgents = readAgentsList(before);
1387
+ const afterAgents = readAgentsList(after);
1388
+ if (beforeAgents.length === 0) {
1389
+ const materializedMain = afterAgents.find((agent) => agent.id === "main");
1390
+ if (materializedMain) {
1391
+ beforeAgents = [materializedMain];
1392
+ }
1393
+ }
1394
+ const beforeById = new Map(beforeAgents.map((agent) => [agent.id, agent]));
1395
+ const afterById = new Map(afterAgents.map((agent) => [agent.id, agent]));
1396
+ const added = afterAgents.filter((agent) => !beforeById.has(agent.id));
1397
+ const removed = beforeAgents.filter((agent) => !afterById.has(agent.id));
1398
+ const changedExisting = beforeAgents.filter((agent) => {
1399
+ const next = afterById.get(agent.id);
1400
+ return next ? JSON.stringify(agent) !== JSON.stringify(next) : false;
1401
+ });
1402
+ return {
1403
+ before: beforeAgents.length,
1404
+ after: afterAgents.length,
1405
+ added: added.length,
1406
+ addedIds: added.map((agent) => agent.id),
1407
+ removedIds: removed.map((agent) => agent.id),
1408
+ changedExistingIds: changedExisting.map((agent) => agent.id),
1409
+ existingUnchanged: removed.length === 0 && changedExisting.length === 0
1410
+ };
1411
+ }
1412
+ function readAgentsList(config) {
1413
+ const agents = readRecord2(config.agents) ? config.agents : null;
1414
+ const list = Array.isArray(agents?.list) ? agents.list : [];
1415
+ return list.filter(readRecord2).map((agent) => ({
1416
+ id: typeof agent.id === "string" ? agent.id : "",
1417
+ name: typeof agent.name === "string" ? agent.name : void 0,
1418
+ workspace: typeof agent.workspace === "string" ? agent.workspace : void 0,
1419
+ agentDir: typeof agent.agentDir === "string" ? agent.agentDir : void 0,
1420
+ model: typeof agent.model === "string" ? agent.model : void 0,
1421
+ isDefault: agent.isDefault === true
1422
+ })).filter((agent) => agent.id.length > 0);
1423
+ }
1424
+ function getBridgeEntry(value) {
1425
+ return readNestedRecord(value, ["plugins", "entries", "openclaw-bridge"]);
1426
+ }
1427
+ async function readOptionalFile(filePath) {
1428
+ try {
1429
+ return await readFile2(filePath.replace(/^~(?=\/)/, process.env.HOME ?? ""), "utf8");
1430
+ } catch {
1431
+ return null;
1432
+ }
1433
+ }
1434
+ async function readJsonFile(filePath) {
1435
+ const raw = await readOptionalFile(filePath);
1436
+ return raw ? parseJsonLoose(raw) : null;
1437
+ }
1438
+ function parseJsonLoose(value) {
1439
+ try {
1440
+ const parsed = JSON.parse(value);
1441
+ return readRecord2(parsed) ? parsed : null;
1442
+ } catch {
1443
+ return null;
1444
+ }
1445
+ }
1446
+ function parseJsonFromMixedOutput(value) {
1447
+ for (let index = 0; index < value.length; index += 1) {
1448
+ if (value[index] !== "{" && value[index] !== "[") continue;
1449
+ try {
1450
+ return JSON.parse(value.slice(index));
1451
+ } catch {
1452
+ }
1453
+ }
1454
+ return null;
1455
+ }
1456
+ function lastPathLikeLine(value) {
1457
+ const lines = value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1458
+ return lines.reverse().find((line) => line.startsWith("/") || line.startsWith("~")) ?? null;
1459
+ }
1460
+ function pickMainAgent(agents) {
1461
+ return agents.find((agent) => agent.isDefault) ?? agents.find((agent) => agent.id === "main") ?? agents[0];
1462
+ }
1463
+ function readNestedRecord(value, keys) {
1464
+ let current = value;
1465
+ for (const key of keys) {
1466
+ if (!readRecord2(current)) return null;
1467
+ current = current[key];
1468
+ }
1469
+ return readRecord2(current) ? current : null;
1470
+ }
1471
+ function readRecord2(value) {
1472
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1473
+ }
1474
+ function parseCsv(value) {
1475
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
1476
+ }
1477
+ function sanitizeId(value, separator = "-") {
1478
+ const escaped = separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1479
+ return value.toLowerCase().replace(/[^a-z0-9._-]+/g, separator).replace(new RegExp(`^${escaped}+|${escaped}+$`, "g"), "") || "agt_community";
1480
+ }
1481
+ function sha256Json(value) {
1482
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
1483
+ }
1484
+ function parseArgs(argv) {
1485
+ const args = {};
1486
+ for (let index = 0; index < argv.length; index += 1) {
1487
+ const arg = argv[index];
1488
+ if (!arg.startsWith("--")) continue;
1489
+ const key = arg.slice(2);
1490
+ if (key === "help" || key === "yes" || key === "non-interactive" || key === "replace-existing" || key === "confirm-main-agent") {
1491
+ args[key] = true;
1492
+ continue;
1493
+ }
1494
+ const next = argv[index + 1];
1495
+ if (next && !next.startsWith("--")) {
1496
+ args[key] = next;
1497
+ index += 1;
1498
+ } else {
1499
+ args[key] = true;
1500
+ }
1501
+ }
1502
+ return args;
1503
+ }
1504
+ function printHelp() {
1505
+ console.log(`politeia-connect
1506
+
1507
+ Interactive, plugin-scoped onboarding for AgentComm Politeia Bridge.
1508
+
1509
+ Usage:
1510
+ politeia-connect
1511
+
1512
+ Validation helper:
1513
+ politeia-connect --non-interactive --yes \\
1514
+ --nickname MiniClawBeta --token-env AGENTCOMM_INVITE_TOKEN \\
1515
+ --gateway-url ws://127.0.0.1:19180/ws
1516
+
1517
+ This command never runs openclaw setup/onboard/configure. It only uses:
1518
+ openclaw agents list
1519
+ openclaw agents add --non-interactive
1520
+ openclaw config patch
1521
+ openclaw config validate
1522
+ openclaw gateway call communityBridge.doctor
1523
+ `);
1524
+ }
1525
+ main().catch((error) => {
1526
+ console.error(error instanceof Error ? error.message : String(error));
1527
+ process.exitCode = 1;
1528
+ });