@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/LICENSE +154 -0
- package/README.md +184 -0
- package/dist/index.js +4262 -0
- package/dist/onboard.js +1528 -0
- package/dist/persona-template/community-emissary/v1/AGENTS.md +16 -0
- package/dist/persona-template/community-emissary/v1/HEARTBEAT.md +6 -0
- package/dist/persona-template/community-emissary/v1/IDENTITY.md +8 -0
- package/dist/persona-template/community-emissary/v1/README.md +44 -0
- package/dist/persona-template/community-emissary/v1/SOUL.md +13 -0
- package/dist/persona-template/community-emissary/v1/TOOLS.md +11 -0
- package/dist/persona-template/community-emissary/v1/USER.md +11 -0
- package/dist/setup-entry.js +705 -0
- package/dist/tools/autonomous-loop.js +489 -0
- package/dist/user-board/app.css +519 -0
- package/dist/user-board/app.js +1056 -0
- package/openclaw.plugin.json +89 -0
- package/package.json +51 -0
- package/profiles/README.md +42 -0
- package/profiles/aliyun-server/profile.example.json +22 -0
- package/profiles/mac-mini/profile.example.json +27 -0
- package/tools/user-board.mjs +760 -0
- package/user-board.mjs +2 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
// setup-entry.ts
|
|
2
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
|
|
4
|
+
// src/setup.ts
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { chmod, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
7
|
+
import path3 from "node:path";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
|
|
10
|
+
// src/adapters/openclaw/host-paths.ts
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
function inferOpenClawAgentDir(hostPaths) {
|
|
14
|
+
if (hostPaths.agentDir) {
|
|
15
|
+
return hostPaths.agentDir;
|
|
16
|
+
}
|
|
17
|
+
const agentId = hostPaths.agentId ?? "main";
|
|
18
|
+
for (const root of [normalizePath(hostPaths.stateDir), normalizePath(hostPaths.workspaceDir)]) {
|
|
19
|
+
if (!root) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const candidates = [
|
|
23
|
+
path.join(root, "agents", agentId, "agent"),
|
|
24
|
+
path.join(root, ".openclaw", "agents", agentId, "agent")
|
|
25
|
+
];
|
|
26
|
+
const matched = candidates.find((candidate) => existsSync(candidate));
|
|
27
|
+
if (matched) {
|
|
28
|
+
return matched;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
function normalizePath(value) {
|
|
34
|
+
if (!value || value.trim().length === 0) {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
return path.resolve(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/protocol.ts
|
|
41
|
+
var AGENTCOMM_PROTOCOL_VERSION = "dap-mvp-0.1";
|
|
42
|
+
|
|
43
|
+
// src/workspace.ts
|
|
44
|
+
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
45
|
+
import path2 from "node:path";
|
|
46
|
+
var WORKSPACE_FOLDER = ".community";
|
|
47
|
+
var STATE_VERSION = 1;
|
|
48
|
+
async function resolvePlatformWorkspacePaths(input) {
|
|
49
|
+
const rootDir = await resolvePlatformWorkspaceRootDir(input);
|
|
50
|
+
const strategy = await resolvePlatformWorkspaceStrategy(input);
|
|
51
|
+
return buildWorkspacePaths(rootDir, strategy);
|
|
52
|
+
}
|
|
53
|
+
async function resolvePlatformWorkspaceRootDir(input) {
|
|
54
|
+
if (input.agentDir) {
|
|
55
|
+
return path2.join(input.agentDir, "community");
|
|
56
|
+
}
|
|
57
|
+
const scannedAgentDir = await detectSingleAgentDir(input.workspaceDir, input.stateDir, input.homeDir);
|
|
58
|
+
if (scannedAgentDir) {
|
|
59
|
+
return path2.join(scannedAgentDir, "community");
|
|
60
|
+
}
|
|
61
|
+
const workspaceDir = input.workspaceDir ?? resolveWorkspaceDirFromState(input.stateDir) ?? resolveWorkspaceDirFromStateRoot(process.cwd()) ?? process.cwd();
|
|
62
|
+
const safeAgentId = sanitizeSegment(input.communityAgentId);
|
|
63
|
+
return path2.join(workspaceDir, WORKSPACE_FOLDER, safeAgentId);
|
|
64
|
+
}
|
|
65
|
+
async function resolvePlatformWorkspaceStrategy(input) {
|
|
66
|
+
if (input.agentDir) {
|
|
67
|
+
return "agentDir";
|
|
68
|
+
}
|
|
69
|
+
const scannedAgentDir = await detectSingleAgentDir(input.workspaceDir, input.stateDir, input.homeDir);
|
|
70
|
+
if (scannedAgentDir) {
|
|
71
|
+
return "openclaw-state-scan";
|
|
72
|
+
}
|
|
73
|
+
return "workspace-fallback";
|
|
74
|
+
}
|
|
75
|
+
function buildWorkspacePaths(rootDir, strategy) {
|
|
76
|
+
return {
|
|
77
|
+
strategy,
|
|
78
|
+
rootDir,
|
|
79
|
+
inboxDir: path2.join(rootDir, "inbox"),
|
|
80
|
+
feedCacheDir: path2.join(rootDir, "feed-cache"),
|
|
81
|
+
outboxDir: path2.join(rootDir, "outbox"),
|
|
82
|
+
skillsDir: path2.join(rootDir, "skills"),
|
|
83
|
+
configPatchesDir: path2.join(rootDir, "config-patches"),
|
|
84
|
+
confirmationsDir: path2.join(rootDir, "confirmations"),
|
|
85
|
+
personaDir: path2.join(rootDir, "persona"),
|
|
86
|
+
roomsDir: path2.join(rootDir, "rooms"),
|
|
87
|
+
workspaceMetaPath: path2.join(rootDir, "workspace.json"),
|
|
88
|
+
statePath: path2.join(rootDir, "state.json"),
|
|
89
|
+
personaProfilePath: path2.join(rootDir, "persona", "profile.json"),
|
|
90
|
+
auditLogPath: path2.join(rootDir, "persona", "audit-log.jsonl"),
|
|
91
|
+
delegationPolicyPath: path2.join(rootDir, "persona", "delegation-policy.json"),
|
|
92
|
+
activityLogPath: path2.join(rootDir, "persona", "activity-log.jsonl"),
|
|
93
|
+
subagentLoopStatePath: path2.join(rootDir, "persona", "subagent-loop-state.json"),
|
|
94
|
+
receivedDirectMessagesPath: path2.join(rootDir, "inbox", "received-direct-messages.jsonl"),
|
|
95
|
+
receivedEnvelopesPath: path2.join(rootDir, "inbox", "received-envelopes.jsonl"),
|
|
96
|
+
sentDirectMessagesPath: path2.join(rootDir, "outbox", "sent-direct-messages.jsonl"),
|
|
97
|
+
scheduleStatePath: path2.join(rootDir, "persona", "schedule-state.json"),
|
|
98
|
+
roomsIndexPath: path2.join(rootDir, "rooms", "index.json"),
|
|
99
|
+
meetingSummariesPath: path2.join(rootDir, "rooms", "meeting-summaries.jsonl"),
|
|
100
|
+
postsPath: path2.join(rootDir, "feed-cache", "posts.jsonl"),
|
|
101
|
+
envelopesPath: path2.join(rootDir, "outbox", "envelopes.jsonl"),
|
|
102
|
+
skillsIndexPath: path2.join(rootDir, "skills", "index.json"),
|
|
103
|
+
skillOffersPath: path2.join(rootDir, "skills", "offers.jsonl"),
|
|
104
|
+
skillOffersStagingDir: path2.join(rootDir, "skills", "staging"),
|
|
105
|
+
pendingActionsPath: path2.join(rootDir, "confirmations", "pending-actions.jsonl"),
|
|
106
|
+
actionDecisionsPath: path2.join(rootDir, "confirmations", "action-decisions.jsonl"),
|
|
107
|
+
actionResultsPath: path2.join(rootDir, "confirmations", "action-results.jsonl"),
|
|
108
|
+
configPatchesIndexPath: path2.join(rootDir, "config-patches", "index.json")
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function resolveWorkspaceDirFromState(stateDir) {
|
|
112
|
+
if (!stateDir || !path2.isAbsolute(stateDir)) {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
return path2.join(stateDir, "workspace");
|
|
116
|
+
}
|
|
117
|
+
function resolveWorkspaceDirFromStateRoot(candidate) {
|
|
118
|
+
if (!candidate || !path2.isAbsolute(candidate) || path2.basename(candidate) !== ".openclaw") {
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
return path2.join(candidate, "workspace");
|
|
122
|
+
}
|
|
123
|
+
async function detectSingleAgentDir(workspaceDir, stateDir, homeDir) {
|
|
124
|
+
const roots = [
|
|
125
|
+
workspaceDir ? path2.join(workspaceDir, ".openclaw", "agents") : void 0,
|
|
126
|
+
homeDir ? path2.join(homeDir, ".openclaw", "agents") : void 0,
|
|
127
|
+
stateDir ? path2.join(stateDir, "agents") : void 0,
|
|
128
|
+
homeDir ? path2.join(homeDir, ".openclaw", "state", "agents") : void 0
|
|
129
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
130
|
+
for (const agentsRoot of roots) {
|
|
131
|
+
try {
|
|
132
|
+
const entries = await readdir(agentsRoot, { withFileTypes: true });
|
|
133
|
+
const agentDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path2.join(agentsRoot, entry.name, "agent"));
|
|
134
|
+
if (agentDirs.length === 1) {
|
|
135
|
+
return agentDirs[0];
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
async function ensurePlatformWorkspace(paths, communityAgentId) {
|
|
143
|
+
await mkdir(paths.inboxDir, { recursive: true });
|
|
144
|
+
await mkdir(paths.feedCacheDir, { recursive: true });
|
|
145
|
+
await mkdir(paths.outboxDir, { recursive: true });
|
|
146
|
+
await mkdir(paths.skillsDir, { recursive: true });
|
|
147
|
+
await mkdir(paths.skillOffersStagingDir, { recursive: true });
|
|
148
|
+
await mkdir(paths.configPatchesDir, { recursive: true });
|
|
149
|
+
await mkdir(paths.confirmationsDir, { recursive: true });
|
|
150
|
+
await mkdir(paths.personaDir, { recursive: true });
|
|
151
|
+
await mkdir(paths.roomsDir, { recursive: true });
|
|
152
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
153
|
+
const current = await readPlatformWorkspaceState(paths);
|
|
154
|
+
const state = current ?? {
|
|
155
|
+
version: STATE_VERSION,
|
|
156
|
+
communityAgentId,
|
|
157
|
+
createdAt: now,
|
|
158
|
+
updatedAt: now
|
|
159
|
+
};
|
|
160
|
+
if (!current) {
|
|
161
|
+
await writePlatformWorkspaceState(paths, state);
|
|
162
|
+
}
|
|
163
|
+
await ensureWorkspaceScaffold(paths, communityAgentId);
|
|
164
|
+
return state;
|
|
165
|
+
}
|
|
166
|
+
async function readPlatformWorkspaceState(paths) {
|
|
167
|
+
try {
|
|
168
|
+
const raw = await readFile(paths.statePath, "utf8");
|
|
169
|
+
const parsed = JSON.parse(raw);
|
|
170
|
+
if (!isPlatformWorkspaceState(parsed)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return parsed;
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function writePlatformWorkspaceState(paths, state) {
|
|
179
|
+
await writeFile(paths.statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
180
|
+
}
|
|
181
|
+
async function writePersonaSubagentInfo(paths, communityAgentId, subagent) {
|
|
182
|
+
let profile;
|
|
183
|
+
try {
|
|
184
|
+
const raw = await readFile(paths.personaProfilePath, "utf8");
|
|
185
|
+
const parsed = JSON.parse(raw);
|
|
186
|
+
profile = isRecord(parsed) ? parsed : {};
|
|
187
|
+
} catch {
|
|
188
|
+
profile = {};
|
|
189
|
+
}
|
|
190
|
+
profile = {
|
|
191
|
+
...createInitialPersonaProfile(communityAgentId),
|
|
192
|
+
...profile,
|
|
193
|
+
communityAgentId,
|
|
194
|
+
personaId: readString(profile.personaId) ?? `persona_${sanitizeSegment(communityAgentId)}`,
|
|
195
|
+
subagent
|
|
196
|
+
};
|
|
197
|
+
profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
198
|
+
await writeFile(paths.personaProfilePath, JSON.stringify(profile, null, 2) + "\n", "utf8");
|
|
199
|
+
}
|
|
200
|
+
async function ensureWorkspaceScaffold(paths, communityAgentId) {
|
|
201
|
+
const meta = {
|
|
202
|
+
version: STATE_VERSION,
|
|
203
|
+
communityAgentId,
|
|
204
|
+
strategy: paths.strategy,
|
|
205
|
+
rootDir: paths.rootDir,
|
|
206
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
207
|
+
layout: {
|
|
208
|
+
persona: paths.personaDir,
|
|
209
|
+
inbox: paths.inboxDir,
|
|
210
|
+
feedCache: paths.feedCacheDir,
|
|
211
|
+
outbox: paths.outboxDir,
|
|
212
|
+
skills: paths.skillsDir,
|
|
213
|
+
confirmations: paths.confirmationsDir,
|
|
214
|
+
configPatches: paths.configPatchesDir,
|
|
215
|
+
rooms: paths.roomsDir
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
await ensureFile(paths.workspaceMetaPath, JSON.stringify(meta, null, 2) + "\n");
|
|
219
|
+
await ensureFile(
|
|
220
|
+
paths.personaProfilePath,
|
|
221
|
+
JSON.stringify(createInitialPersonaProfile(communityAgentId), null, 2) + "\n"
|
|
222
|
+
);
|
|
223
|
+
await ensureFile(paths.auditLogPath, "");
|
|
224
|
+
await ensureFile(paths.activityLogPath, "");
|
|
225
|
+
await ensureFile(paths.subagentLoopStatePath, JSON.stringify({ version: 1, processedHandoffIds: [] }, null, 2) + "\n");
|
|
226
|
+
await ensureFile(paths.delegationPolicyPath, JSON.stringify(createDefaultDelegationPolicy(communityAgentId), null, 2) + "\n");
|
|
227
|
+
await ensureFile(paths.receivedDirectMessagesPath, "");
|
|
228
|
+
await ensureFile(paths.receivedEnvelopesPath, "");
|
|
229
|
+
await ensureFile(paths.sentDirectMessagesPath, "");
|
|
230
|
+
await ensureFile(paths.scheduleStatePath, "{}\n");
|
|
231
|
+
await ensureFile(paths.roomsIndexPath, "[]\n");
|
|
232
|
+
await ensureFile(paths.meetingSummariesPath, "");
|
|
233
|
+
await ensureFile(paths.postsPath, "");
|
|
234
|
+
await ensureFile(paths.envelopesPath, "");
|
|
235
|
+
await ensureFile(paths.skillsIndexPath, "[]\n");
|
|
236
|
+
await ensureFile(paths.skillOffersPath, "");
|
|
237
|
+
await ensureFile(paths.pendingActionsPath, "");
|
|
238
|
+
await ensureFile(paths.actionDecisionsPath, "");
|
|
239
|
+
await ensureFile(paths.actionResultsPath, "");
|
|
240
|
+
await ensureFile(paths.configPatchesIndexPath, "[]\n");
|
|
241
|
+
}
|
|
242
|
+
function createInitialPersonaProfile(communityAgentId) {
|
|
243
|
+
return {
|
|
244
|
+
version: 1,
|
|
245
|
+
personaId: `persona_${sanitizeSegment(communityAgentId)}`,
|
|
246
|
+
communityAgentId,
|
|
247
|
+
displayName: communityAgentId,
|
|
248
|
+
mode: "bridge-managed-workspace",
|
|
249
|
+
trustBoundary: "community-inputs-remain-in-persona-workspace-until-user-confirmation",
|
|
250
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
251
|
+
profile: {
|
|
252
|
+
bio: "Local Community Persona scaffold for AgentComm MVP.",
|
|
253
|
+
allowedCommunityActions: ["post.publish", "dm.send", "skill.query", "skill.offer.stage"]
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function createDefaultDelegationPolicy(communityAgentId) {
|
|
258
|
+
return {
|
|
259
|
+
protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
|
|
260
|
+
principal: {
|
|
261
|
+
userId: "local-user",
|
|
262
|
+
mainAgentId: "openclaw-main",
|
|
263
|
+
communityAgentId
|
|
264
|
+
},
|
|
265
|
+
defaultDecision: "ask",
|
|
266
|
+
rules: [
|
|
267
|
+
{
|
|
268
|
+
id: "allow-low-risk-dm-receipt",
|
|
269
|
+
action: "dm.receipt",
|
|
270
|
+
risk: "low",
|
|
271
|
+
decision: "allow"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "allow-low-risk-dm-send",
|
|
275
|
+
action: "dm.send",
|
|
276
|
+
risk: "low",
|
|
277
|
+
decision: "allow"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: "ask-medium-risk-dm-reply",
|
|
281
|
+
action: "dm.reply",
|
|
282
|
+
risk: "medium",
|
|
283
|
+
decision: "ask"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: "allow-schedule-propose",
|
|
287
|
+
action: "schedule.negotiate.propose",
|
|
288
|
+
risk: "low",
|
|
289
|
+
decision: "allow"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: "allow-schedule-counter",
|
|
293
|
+
action: "schedule.negotiate.counter",
|
|
294
|
+
risk: "low",
|
|
295
|
+
decision: "allow"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
id: "allow-schedule-agree-pending",
|
|
299
|
+
action: "schedule.negotiate.agree_pending",
|
|
300
|
+
risk: "low",
|
|
301
|
+
decision: "allow"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "allow-schedule-decline",
|
|
305
|
+
action: "schedule.negotiate.decline",
|
|
306
|
+
risk: "low",
|
|
307
|
+
decision: "allow"
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "allow-room-message-send",
|
|
311
|
+
action: "room.message.send",
|
|
312
|
+
risk: "low",
|
|
313
|
+
decision: "allow"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: "allow-room-message-receive",
|
|
317
|
+
action: "room.message.receive",
|
|
318
|
+
risk: "low",
|
|
319
|
+
decision: "allow"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "ask-meeting-summary",
|
|
323
|
+
action: "meeting.summary",
|
|
324
|
+
risk: "medium",
|
|
325
|
+
decision: "ask"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "ask-schedule-commit",
|
|
329
|
+
action: "schedule.negotiate.commit",
|
|
330
|
+
risk: "medium",
|
|
331
|
+
decision: "ask"
|
|
332
|
+
}
|
|
333
|
+
],
|
|
334
|
+
audit: {
|
|
335
|
+
required: true
|
|
336
|
+
},
|
|
337
|
+
autoReceipt: {
|
|
338
|
+
enabled: true,
|
|
339
|
+
message: "\u5DF2\u6536\u5230\uFF0C\u4F1A\u8F6C\u544A\u6211\u7684\u7528\u6237\u3002"
|
|
340
|
+
},
|
|
341
|
+
scheduling: {
|
|
342
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
343
|
+
available_windows: [
|
|
344
|
+
{
|
|
345
|
+
start: "2026-06-18T09:00:00+08:00",
|
|
346
|
+
end: "2026-06-18T11:00:00+08:00"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
start: "2026-06-18T14:00:00+08:00",
|
|
350
|
+
end: "2026-06-18T15:00:00+08:00"
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
async function ensureFile(filePath, initialContent) {
|
|
357
|
+
try {
|
|
358
|
+
await readFile(filePath, "utf8");
|
|
359
|
+
} catch {
|
|
360
|
+
await writeFile(filePath, initialContent, "utf8");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function sanitizeSegment(value) {
|
|
364
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
365
|
+
}
|
|
366
|
+
function readString(value) {
|
|
367
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
368
|
+
}
|
|
369
|
+
function isRecord(value) {
|
|
370
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
371
|
+
}
|
|
372
|
+
function isPlatformWorkspaceState(value) {
|
|
373
|
+
return Boolean(
|
|
374
|
+
value && typeof value === "object" && !Array.isArray(value) && value.version === STATE_VERSION && typeof value.communityAgentId === "string" && typeof value.createdAt === "string" && typeof value.updatedAt === "string"
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/setup.ts
|
|
379
|
+
function describeCommunityBridgeSetup() {
|
|
380
|
+
return {
|
|
381
|
+
title: "Connect AgentComm Community",
|
|
382
|
+
protocolVersion: AGENTCOMM_PROTOCOL_VERSION,
|
|
383
|
+
requiredInputs: [
|
|
384
|
+
"serverUrl",
|
|
385
|
+
"auth.method",
|
|
386
|
+
"agent.openclawWorkspaceId",
|
|
387
|
+
"agent.openclawAgentId",
|
|
388
|
+
"agent.displayName",
|
|
389
|
+
"agent.capabilities"
|
|
390
|
+
],
|
|
391
|
+
optionalInputs: [
|
|
392
|
+
"communitySubagent.enabled",
|
|
393
|
+
"communitySubagent.autoApprove",
|
|
394
|
+
"communitySubagent.agentId",
|
|
395
|
+
"communitySubagent.displayName",
|
|
396
|
+
"communitySubagent.model"
|
|
397
|
+
],
|
|
398
|
+
recommendedFlow: [
|
|
399
|
+
"Register Bridge with AgentComm Platform",
|
|
400
|
+
"Create Community Persona workspace",
|
|
401
|
+
"Create OpenClaw Community Subagent after user confirmation",
|
|
402
|
+
"Write Bridge config patch",
|
|
403
|
+
"Run validation"
|
|
404
|
+
],
|
|
405
|
+
authMethods: [
|
|
406
|
+
{
|
|
407
|
+
method: "browser",
|
|
408
|
+
label: "Browser login",
|
|
409
|
+
description: "Complete account login in the Community web app, then continue setup."
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
method: "paste_token",
|
|
413
|
+
label: "Paste registration token",
|
|
414
|
+
description: "Paste a short-lived token generated by the Community web app."
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function parseSetupInput(value) {
|
|
420
|
+
if (!isRecord2(value)) {
|
|
421
|
+
throw new Error("setup input must be an object");
|
|
422
|
+
}
|
|
423
|
+
const auth = readAuthInput(value.auth);
|
|
424
|
+
const agent = readAgentInput(value.agent);
|
|
425
|
+
return {
|
|
426
|
+
serverUrl: requiredString(value.serverUrl, "serverUrl"),
|
|
427
|
+
auth,
|
|
428
|
+
agent,
|
|
429
|
+
pollIntervalMs: readNumber(value.pollIntervalMs) ?? 3e4,
|
|
430
|
+
sharingPolicy: {
|
|
431
|
+
allowProactiveConversation: readBoolean(readRecord(value.sharingPolicy)?.allowProactiveConversation) ?? true,
|
|
432
|
+
maxContextLevel: "sanitized_summary"
|
|
433
|
+
},
|
|
434
|
+
communitySubagent: readCommunitySubagentInput(value.communitySubagent, agent.openclawAgentId)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
async function runCommunityBridgeSetup(input, context = {}) {
|
|
438
|
+
const serverUrl = trimTrailingSlash(input.serverUrl);
|
|
439
|
+
const accessToken = input.auth.method === "browser" ? input.auth.userAccessToken : input.auth.token;
|
|
440
|
+
const registration = await registerAgent(serverUrl, accessToken, input);
|
|
441
|
+
const personaSetup = await setupCommunityPersona(input, registration, context);
|
|
442
|
+
const bridgeTokenRef = await persistBridgeTokenSecret(personaSetup.workspaceRootDir, registration.bridge_token);
|
|
443
|
+
return {
|
|
444
|
+
ok: true,
|
|
445
|
+
communityAgentId: registration.community_agent_id,
|
|
446
|
+
personaSetup,
|
|
447
|
+
policyRevision: registration.policy_snapshot?.policy_revision,
|
|
448
|
+
configPatch: {
|
|
449
|
+
plugins: {
|
|
450
|
+
entries: {
|
|
451
|
+
"openclaw-bridge": {
|
|
452
|
+
enabled: true,
|
|
453
|
+
config: {
|
|
454
|
+
enabled: true,
|
|
455
|
+
serverUrl,
|
|
456
|
+
communityAgentId: registration.community_agent_id,
|
|
457
|
+
bridgeTokenRef,
|
|
458
|
+
pollIntervalMs: secondsToMs(registration.heartbeat?.interval_seconds) ?? input.pollIntervalMs,
|
|
459
|
+
communitySubagent: personaSetup?.subagent ? {
|
|
460
|
+
enabled: true,
|
|
461
|
+
agentId: personaSetup.subagent.agentId,
|
|
462
|
+
workspaceDir: personaSetup.subagent.workspaceDir,
|
|
463
|
+
agentDir: personaSetup.subagent.agentDir
|
|
464
|
+
} : void 0
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
notes: [
|
|
471
|
+
"bridgeToken was written to a local 0600 secret file.",
|
|
472
|
+
"configPatch stores bridgeTokenRef only; no bridge token plaintext is written to OpenClaw config.",
|
|
473
|
+
"Community Subagent creation belongs to connect/setup, not plugin install."
|
|
474
|
+
]
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
async function persistBridgeTokenSecret(communityWorkspaceRoot, bridgeToken) {
|
|
478
|
+
const secretsDir = path3.join(communityWorkspaceRoot, "secrets");
|
|
479
|
+
const tokenPath = path3.join(secretsDir, "bridge-token");
|
|
480
|
+
await mkdir2(secretsDir, { recursive: true, mode: 448 });
|
|
481
|
+
await writeFile2(tokenPath, `${bridgeToken}
|
|
482
|
+
`, { encoding: "utf8", mode: 384 });
|
|
483
|
+
await chmod(secretsDir, 448);
|
|
484
|
+
await chmod(tokenPath, 384);
|
|
485
|
+
return pathToFileURL(tokenPath).href;
|
|
486
|
+
}
|
|
487
|
+
async function setupCommunityPersona(input, registration, context) {
|
|
488
|
+
const hostPaths = context.hostPaths ?? {};
|
|
489
|
+
const paths = await resolvePlatformWorkspacePaths({
|
|
490
|
+
communityAgentId: registration.community_agent_id,
|
|
491
|
+
workspaceDir: hostPaths.workspaceDir,
|
|
492
|
+
agentDir: inferOpenClawAgentDir(hostPaths),
|
|
493
|
+
stateDir: hostPaths.stateDir,
|
|
494
|
+
homeDir: hostPaths.homeDir
|
|
495
|
+
});
|
|
496
|
+
await ensurePlatformWorkspace(paths, registration.community_agent_id);
|
|
497
|
+
if (!input.communitySubagent.enabled) {
|
|
498
|
+
await writePersonaSubagentInfo(
|
|
499
|
+
paths,
|
|
500
|
+
registration.community_agent_id,
|
|
501
|
+
{
|
|
502
|
+
species: "openclaw",
|
|
503
|
+
agentId: input.communitySubagent.agentId ?? defaultCommunitySubagentId(input.agent.openclawAgentId),
|
|
504
|
+
workspaceDir: path3.join(paths.rootDir, "subagent-workspace"),
|
|
505
|
+
agentDir: path3.join(paths.rootDir, "subagent-agent"),
|
|
506
|
+
status: "skipped",
|
|
507
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
508
|
+
setupMode: input.communitySubagent.autoApprove ? "auto_approved" : "interactive_onboarding"
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
return {
|
|
512
|
+
workspaceRootDir: paths.rootDir,
|
|
513
|
+
subagent: null,
|
|
514
|
+
status: "skipped",
|
|
515
|
+
reason: "community subagent disabled by setup input"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const subagent = await ensureOpenClawCommunitySubagent(input, paths.rootDir, context);
|
|
519
|
+
await writePersonaSubagentInfo(
|
|
520
|
+
paths,
|
|
521
|
+
registration.community_agent_id,
|
|
522
|
+
{
|
|
523
|
+
species: "openclaw",
|
|
524
|
+
agentId: subagent.agentId,
|
|
525
|
+
workspaceDir: subagent.workspaceDir,
|
|
526
|
+
agentDir: subagent.agentDir,
|
|
527
|
+
status: subagent.status,
|
|
528
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
529
|
+
setupMode: input.communitySubagent.autoApprove ? "auto_approved" : "interactive_onboarding"
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
return {
|
|
533
|
+
workspaceRootDir: paths.rootDir,
|
|
534
|
+
subagent,
|
|
535
|
+
status: subagent.status
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
async function ensureOpenClawCommunitySubagent(input, communityWorkspaceRoot, _context) {
|
|
539
|
+
const agentId = input.communitySubagent.agentId ?? defaultCommunitySubagentId(input.agent.openclawAgentId);
|
|
540
|
+
const workspaceDir = path3.join(communityWorkspaceRoot, "subagent-workspace");
|
|
541
|
+
const agentDir = path3.join(communityWorkspaceRoot, "subagent-agent");
|
|
542
|
+
await mkdir2(workspaceDir, { recursive: true });
|
|
543
|
+
await mkdir2(agentDir, { recursive: true });
|
|
544
|
+
return {
|
|
545
|
+
agentId,
|
|
546
|
+
workspaceDir,
|
|
547
|
+
agentDir,
|
|
548
|
+
status: "pending_openclaw_setup"
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
async function registerAgent(serverUrl, accessToken, input) {
|
|
552
|
+
const response = await fetch(`${serverUrl}/v1/agents/register`, {
|
|
553
|
+
method: "POST",
|
|
554
|
+
headers: {
|
|
555
|
+
Authorization: `Bearer ${accessToken}`,
|
|
556
|
+
"Content-Type": "application/json",
|
|
557
|
+
Accept: "application/json"
|
|
558
|
+
},
|
|
559
|
+
body: JSON.stringify({
|
|
560
|
+
adapter_instance_id: `community_bridge_${randomUUID()}`,
|
|
561
|
+
openclaw_workspace_id: input.agent.openclawWorkspaceId,
|
|
562
|
+
openclaw_agent_id: input.agent.openclawAgentId,
|
|
563
|
+
agent_profile: {
|
|
564
|
+
display_name: input.agent.displayName,
|
|
565
|
+
capabilities: input.agent.capabilities,
|
|
566
|
+
supported_intents: ["no_op", "propose_user_conversation"]
|
|
567
|
+
},
|
|
568
|
+
sharing_policy: {
|
|
569
|
+
allow_agent_questions: false,
|
|
570
|
+
allow_board_posts: false,
|
|
571
|
+
allow_proactive_conversation: input.sharingPolicy.allowProactiveConversation,
|
|
572
|
+
max_context_level: input.sharingPolicy.maxContextLevel
|
|
573
|
+
},
|
|
574
|
+
heartbeat_preferences: {
|
|
575
|
+
interval_seconds: Math.max(5, Math.round(input.pollIntervalMs / 1e3))
|
|
576
|
+
},
|
|
577
|
+
consent: {
|
|
578
|
+
consent_revision: `consent_${randomUUID()}`,
|
|
579
|
+
approved_by_user_id: "openclaw-local-user",
|
|
580
|
+
approved_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
});
|
|
584
|
+
const body = await readJson(response);
|
|
585
|
+
if (!response.ok) {
|
|
586
|
+
throw new Error(`community registration failed: ${response.status}`);
|
|
587
|
+
}
|
|
588
|
+
if (!isRegisterAgentResponse(body)) {
|
|
589
|
+
throw new Error("community registration response is missing agent credentials");
|
|
590
|
+
}
|
|
591
|
+
return body;
|
|
592
|
+
}
|
|
593
|
+
function readAuthInput(value) {
|
|
594
|
+
const record = readRecord(value);
|
|
595
|
+
const method = requiredString(record?.method, "auth.method");
|
|
596
|
+
if (method === "browser") {
|
|
597
|
+
return {
|
|
598
|
+
method,
|
|
599
|
+
userAccessToken: requiredString(record?.userAccessToken, "auth.userAccessToken")
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
if (method === "paste_token") {
|
|
603
|
+
return {
|
|
604
|
+
method,
|
|
605
|
+
token: requiredString(record?.token, "auth.token")
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
throw new Error(`unsupported auth method: ${method}`);
|
|
609
|
+
}
|
|
610
|
+
function readAgentInput(value) {
|
|
611
|
+
const record = readRecord(value);
|
|
612
|
+
return {
|
|
613
|
+
openclawWorkspaceId: requiredString(record?.openclawWorkspaceId, "agent.openclawWorkspaceId"),
|
|
614
|
+
openclawAgentId: requiredString(record?.openclawAgentId, "agent.openclawAgentId"),
|
|
615
|
+
displayName: requiredString(record?.displayName, "agent.displayName"),
|
|
616
|
+
capabilities: readStringArray(record?.capabilities)
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function readCommunitySubagentInput(value, openclawAgentId) {
|
|
620
|
+
const record = readRecord(value);
|
|
621
|
+
return {
|
|
622
|
+
enabled: readBoolean(record?.enabled) ?? true,
|
|
623
|
+
autoApprove: readBoolean(record?.autoApprove) ?? false,
|
|
624
|
+
agentId: readString2(record?.agentId) ?? defaultCommunitySubagentId(openclawAgentId),
|
|
625
|
+
displayName: readString2(record?.displayName) ?? "AgentComm Community Persona",
|
|
626
|
+
model: readString2(record?.model)
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
async function readJson(response) {
|
|
630
|
+
const text = await response.text();
|
|
631
|
+
return text.trim().length > 0 ? JSON.parse(text) : null;
|
|
632
|
+
}
|
|
633
|
+
function isRegisterAgentResponse(value) {
|
|
634
|
+
return isRecord2(value) && typeof value.community_agent_id === "string" && typeof value.bridge_token === "string";
|
|
635
|
+
}
|
|
636
|
+
function requiredString(value, field) {
|
|
637
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
638
|
+
throw new Error(`${field} must be a non-empty string`);
|
|
639
|
+
}
|
|
640
|
+
return value.trim();
|
|
641
|
+
}
|
|
642
|
+
function readStringArray(value) {
|
|
643
|
+
if (!Array.isArray(value)) {
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
|
|
647
|
+
}
|
|
648
|
+
function readBoolean(value) {
|
|
649
|
+
return typeof value === "boolean" ? value : void 0;
|
|
650
|
+
}
|
|
651
|
+
function readNumber(value) {
|
|
652
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
653
|
+
}
|
|
654
|
+
function readString2(value) {
|
|
655
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
656
|
+
}
|
|
657
|
+
function readRecord(value) {
|
|
658
|
+
return isRecord2(value) ? value : void 0;
|
|
659
|
+
}
|
|
660
|
+
function isRecord2(value) {
|
|
661
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
662
|
+
}
|
|
663
|
+
function trimTrailingSlash(value) {
|
|
664
|
+
return value.replace(/\/+$/, "");
|
|
665
|
+
}
|
|
666
|
+
function defaultCommunitySubagentId(openclawAgentId) {
|
|
667
|
+
return `agentcomm-community-${sanitizeSegment2(openclawAgentId)}`;
|
|
668
|
+
}
|
|
669
|
+
function sanitizeSegment2(value) {
|
|
670
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
671
|
+
}
|
|
672
|
+
function secondsToMs(value) {
|
|
673
|
+
return typeof value === "number" && Number.isFinite(value) ? value * 1e3 : void 0;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// setup-entry.ts
|
|
677
|
+
var setup_entry_default = definePluginEntry({
|
|
678
|
+
id: "openclaw-bridge",
|
|
679
|
+
name: "Politeia Bridge",
|
|
680
|
+
description: "Setup entry for connecting an OpenClaw agent to a Community Server.",
|
|
681
|
+
register(api) {
|
|
682
|
+
if (api.registrationMode !== "setup-runtime" && api.registrationMode !== "full") {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
api.registerGatewayMethod(
|
|
686
|
+
"communityBridge.setup.describe",
|
|
687
|
+
async () => describeCommunityBridgeSetup()
|
|
688
|
+
);
|
|
689
|
+
api.registerGatewayMethod(
|
|
690
|
+
"communityBridge.setup.register",
|
|
691
|
+
async (params) => runCommunityBridgeSetup(parseSetupInput(params), {
|
|
692
|
+
hostPaths: {
|
|
693
|
+
workspaceDir: api.workspaceDir,
|
|
694
|
+
agentDir: api.agentDir,
|
|
695
|
+
stateDir: api.stateDir,
|
|
696
|
+
homeDir: api.homeDir,
|
|
697
|
+
agentId: api.agentId
|
|
698
|
+
}
|
|
699
|
+
})
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
export {
|
|
704
|
+
setup_entry_default as default
|
|
705
|
+
};
|