multiclaws 0.4.41 → 0.4.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +2 -0
  2. package/dist/gateway/handlers.d.ts +4 -4
  3. package/dist/gateway/handlers.js +239 -239
  4. package/dist/index.d.ts +8 -8
  5. package/dist/index.js +710 -710
  6. package/dist/infra/frp.d.ts +55 -55
  7. package/dist/infra/frp.js +398 -398
  8. package/dist/infra/gateway-client.d.ts +27 -27
  9. package/dist/infra/gateway-client.js +136 -136
  10. package/dist/infra/json-store.d.ts +4 -4
  11. package/dist/infra/json-store.js +57 -57
  12. package/dist/infra/logger.d.ts +14 -14
  13. package/dist/infra/logger.js +25 -25
  14. package/dist/infra/rate-limiter.d.ts +19 -19
  15. package/dist/infra/rate-limiter.js +69 -69
  16. package/dist/infra/tailscale.d.ts +19 -19
  17. package/dist/infra/tailscale.js +120 -120
  18. package/dist/infra/telemetry.d.ts +3 -3
  19. package/dist/infra/telemetry.js +17 -17
  20. package/dist/infra/version.d.ts +1 -1
  21. package/dist/infra/version.js +19 -19
  22. package/dist/service/a2a-adapter.d.ts +80 -80
  23. package/dist/service/a2a-adapter.js +505 -505
  24. package/dist/service/agent-profile.d.ts +17 -17
  25. package/dist/service/agent-profile.js +58 -58
  26. package/dist/service/agent-registry.d.ts +29 -29
  27. package/dist/service/agent-registry.js +131 -131
  28. package/dist/service/multiclaws-service.d.ts +150 -150
  29. package/dist/service/multiclaws-service.js +1137 -1137
  30. package/dist/service/session-store.d.ts +46 -46
  31. package/dist/service/session-store.js +143 -143
  32. package/dist/task/tracker.d.ts +46 -46
  33. package/dist/task/tracker.js +191 -191
  34. package/dist/team/team-store.d.ts +42 -42
  35. package/dist/team/team-store.js +195 -195
  36. package/dist/types/openclaw.d.ts +109 -109
  37. package/dist/types/openclaw.js +2 -2
  38. package/package.json +1 -1
  39. package/skills/meeting-scheduler/SKILL.md +112 -105
package/dist/index.js CHANGED
@@ -1,703 +1,703 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const handlers_1 = require("./gateway/handlers");
4
- const multiclaws_service_1 = require("./service/multiclaws-service");
5
- const logger_1 = require("./infra/logger");
6
- const telemetry_1 = require("./infra/telemetry");
7
- const version_1 = require("./infra/version");
8
- /** Default FRP tunnel config for demo/testing */
9
- const DEFAULT_TUNNEL = {
10
- type: "frp",
11
- serverAddr: "39.105.143.2",
12
- serverPort: 7000,
13
- token: "jushi@5202fRp",
14
- portRangeStart: 7011,
15
- portRangeEnd: 7020,
16
- };
17
- function readConfig(api) {
18
- const raw = (api.pluginConfig ?? {});
19
- let tunnel;
20
- const rawTunnel = raw.tunnel;
21
- if (rawTunnel && rawTunnel.type === "frp") {
22
- tunnel = {
23
- type: "frp",
24
- serverAddr: typeof rawTunnel.serverAddr === "string" ? rawTunnel.serverAddr : DEFAULT_TUNNEL.serverAddr,
25
- serverPort: typeof rawTunnel.serverPort === "number" ? rawTunnel.serverPort : DEFAULT_TUNNEL.serverPort,
26
- token: typeof rawTunnel.token === "string" ? rawTunnel.token : DEFAULT_TUNNEL.token,
27
- portRangeStart: typeof rawTunnel.portRangeStart === "number" ? rawTunnel.portRangeStart : DEFAULT_TUNNEL.portRangeStart,
28
- portRangeEnd: typeof rawTunnel.portRangeEnd === "number" ? rawTunnel.portRangeEnd : DEFAULT_TUNNEL.portRangeEnd,
29
- };
30
- }
31
- else {
32
- // No tunnel configured — use built-in default for demo
33
- tunnel = { ...DEFAULT_TUNNEL };
34
- }
35
- return {
36
- port: typeof raw.port === "number" ? raw.port : undefined,
37
- displayName: typeof raw.displayName === "string" ? raw.displayName : undefined,
38
- selfUrl: typeof raw.selfUrl === "string" ? raw.selfUrl : undefined,
39
- tunnel,
40
- telemetry: {
41
- consoleExporter: typeof raw.telemetry?.consoleExporter === "boolean"
42
- ? Boolean(raw.telemetry.consoleExporter)
43
- : undefined,
44
- },
45
- };
46
- }
47
- function textResult(text, details) {
48
- return {
49
- content: [{ type: "text", text }],
50
- ...(details === undefined ? {} : { details }),
51
- };
52
- }
53
- function requireService(service) {
54
- if (!service) {
55
- throw new Error("multiclaws service is not running yet");
56
- }
57
- return service;
58
- }
59
- function createTools(getService, logger) {
60
- const log = (level, msg) => {
61
- const fn = level === "debug" ? logger.debug : logger[level];
62
- fn?.(`[multiclaws] ${msg}`);
63
- };
64
- /* ── Agent tools ──────────────────────────────────────────────── */
65
- const multiclawsAgents = {
66
- name: "multiclaws_agents",
67
- description: "List known A2A agents and their capabilities.",
68
- parameters: {
69
- type: "object",
70
- additionalProperties: false,
71
- properties: {},
72
- },
73
- execute: async () => {
74
- log("debug", "tool:multiclaws_agents");
75
- try {
76
- const service = requireService(getService());
77
- const agents = await service.listAgents();
78
- return textResult(JSON.stringify({ agents }, null, 2), { agents });
79
- }
80
- catch (err) {
81
- log("error", `tool:multiclaws_agents failed: ${err instanceof Error ? err.message : String(err)}`);
82
- throw err;
83
- }
84
- },
85
- };
86
- const multiclawsAddAgent = {
87
- name: "multiclaws_add_agent",
88
- description: "Add a remote A2A agent by URL. Automatically fetches its Agent Card.",
89
- parameters: {
90
- type: "object",
91
- additionalProperties: false,
92
- properties: {
93
- url: { type: "string" },
94
- apiKey: { type: "string" },
95
- },
96
- required: ["url"],
97
- },
98
- execute: async (_toolCallId, args) => {
99
- const url = typeof args.url === "string" ? args.url.trim() : "";
100
- log("debug", `tool:multiclaws_add_agent(url=${url})`);
101
- try {
102
- const service = requireService(getService());
103
- if (!url)
104
- throw new Error("url is required");
105
- const apiKey = typeof args.apiKey === "string" ? args.apiKey.trim() : undefined;
106
- const agent = await service.addAgent({ url, apiKey });
107
- return textResult(`Agent added: ${agent.name} (${agent.url})`, agent);
108
- }
109
- catch (err) {
110
- log("error", `tool:multiclaws_add_agent failed: ${err instanceof Error ? err.message : String(err)}`);
111
- throw err;
112
- }
113
- },
114
- };
115
- const multiclawsRemoveAgent = {
116
- name: "multiclaws_remove_agent",
117
- description: "Remove a known A2A agent by URL.",
118
- parameters: {
119
- type: "object",
120
- additionalProperties: false,
121
- properties: {
122
- url: { type: "string" },
123
- },
124
- required: ["url"],
125
- },
126
- execute: async (_toolCallId, args) => {
127
- const url = typeof args.url === "string" ? args.url.trim() : "";
128
- log("debug", `tool:multiclaws_remove_agent(url=${url})`);
129
- try {
130
- const service = requireService(getService());
131
- if (!url)
132
- throw new Error("url is required");
133
- const removed = await service.removeAgent(url);
134
- return textResult(removed ? `Agent ${url} removed.` : `Agent ${url} not found.`);
135
- }
136
- catch (err) {
137
- log("error", `tool:multiclaws_remove_agent failed: ${err instanceof Error ? err.message : String(err)}`);
138
- throw err;
139
- }
140
- },
141
- };
142
- const multiclawsDelegate = {
143
- name: "multiclaws_delegate",
144
- description: "Delegate a task to a remote A2A agent and wait for the result inline. " +
145
- "Sends the task synchronously via A2A and returns the output directly in the current session. " +
146
- "For long-running tasks this may take several minutes. " +
147
- "Do NOT use multiclaws_delegate_send directly — use this tool instead.",
148
- parameters: {
149
- type: "object",
150
- additionalProperties: false,
151
- properties: {
152
- agentUrl: { type: "string" },
153
- task: { type: "string" },
154
- },
155
- required: ["agentUrl", "task"],
156
- },
157
- execute: async (_toolCallId, args) => {
158
- const agentUrl = typeof args.agentUrl === "string" ? args.agentUrl.trim() : "";
159
- log("info", `tool:multiclaws_delegate(agentUrl=${agentUrl})`);
160
- try {
161
- const service = requireService(getService());
162
- const task = typeof args.task === "string" ? args.task.trim() : "";
163
- if (!agentUrl || !task)
164
- throw new Error("agentUrl and task are required");
165
- const result = await service.delegateTaskSync({ agentUrl, task });
166
- const summary = result.output
167
- ? result.output
168
- : result.error
169
- ? `任务失败:${result.error}`
170
- : `任务状态:${result.status}`;
171
- return textResult(summary, result);
172
- }
173
- catch (err) {
174
- log("error", `tool:multiclaws_delegate failed: ${err instanceof Error ? err.message : String(err)}`);
175
- throw err;
176
- }
177
- },
178
- };
179
- const multiclawsDelegateSend = {
180
- name: "multiclaws_delegate_send",
181
- description: "Send a task to a remote A2A agent and wait for the result synchronously. " +
182
- "Low-level primitive used by sub-agents or advanced orchestration flows. " +
183
- "In most cases use multiclaws_delegate instead, which handles this automatically.",
184
- parameters: {
185
- type: "object",
186
- additionalProperties: false,
187
- properties: {
188
- agentUrl: { type: "string" },
189
- task: { type: "string" },
190
- },
191
- required: ["agentUrl", "task"],
192
- },
193
- execute: async (_toolCallId, args) => {
194
- const agentUrl = typeof args.agentUrl === "string" ? args.agentUrl.trim() : "";
195
- log("info", `tool:multiclaws_delegate_send(agentUrl=${agentUrl})`);
196
- try {
197
- const service = requireService(getService());
198
- const task = typeof args.task === "string" ? args.task.trim() : "";
199
- if (!agentUrl || !task)
200
- throw new Error("agentUrl and task are required");
201
- const result = await service.delegateTaskSync({ agentUrl, task });
202
- return textResult(JSON.stringify(result, null, 2), result);
203
- }
204
- catch (err) {
205
- log("error", `tool:multiclaws_delegate_send failed: ${err instanceof Error ? err.message : String(err)}`);
206
- throw err;
207
- }
208
- },
209
- };
210
- const multiclawsA2ACallback = {
211
- name: "multiclaws_a2a_callback",
212
- description: "Report the result of an incoming A2A delegated task. " +
213
- "Called by sub-agents spawned to handle remote tasks. " +
214
- "Do NOT call this directly.",
215
- parameters: {
216
- type: "object",
217
- additionalProperties: false,
218
- properties: {
219
- taskId: { type: "string" },
220
- result: { type: "string" },
221
- },
222
- required: ["taskId", "result"],
223
- },
224
- execute: async (_toolCallId, args) => {
225
- const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
226
- const result = typeof args.result === "string" ? args.result : "";
227
- log("info", `tool:multiclaws_a2a_callback(taskId=${taskId})`);
228
- try {
229
- const service = requireService(getService());
230
- if (!taskId || !result)
231
- throw new Error("taskId and result are required");
232
- const resolved = service.resolveA2ACallback(taskId, result);
233
- if (!resolved) {
234
- return textResult(`No pending callback for task ${taskId}. It may have timed out.`);
235
- }
236
- return textResult(`Task ${taskId} result reported successfully.`);
237
- }
238
- catch (err) {
239
- log("error", `tool:multiclaws_a2a_callback failed: ${err instanceof Error ? err.message : String(err)}`);
240
- throw err;
241
- }
242
- },
243
- };
244
- const multiclawsNotify = {
245
- name: "multiclaws_notify",
246
- description: "Send a notification message to the local user's WebUI. " +
247
- "Used by sub-agents to deliver delegation results back to the user. " +
248
- "Broadcasts to all known channels so the user sees the message regardless of which channel they are on.",
249
- parameters: {
250
- type: "object",
251
- additionalProperties: false,
252
- properties: {
253
- message: { type: "string", description: "The message to send to the user." },
254
- },
255
- required: ["message"],
256
- },
257
- execute: async (_toolCallId, args) => {
258
- const msg = typeof args.message === "string" ? args.message.trim() : "";
259
- log("info", `tool:multiclaws_notify(len=${msg.length})`);
260
- try {
261
- const service = requireService(getService());
262
- if (!msg)
263
- throw new Error("message is required");
264
- await service.notifyUser(msg);
265
- return textResult("Notification sent.");
266
- }
267
- catch (err) {
268
- log("error", `tool:multiclaws_notify failed: ${err instanceof Error ? err.message : String(err)}`);
269
- throw err;
270
- }
271
- },
272
- };
273
- const multiclawsTaskRespond = {
274
- name: "multiclaws_task_respond",
275
- description: "Approve or reject a pending incoming delegated task that requires human authorization. " +
276
- "Call with approved=true to allow execution, approved=false to reject. " +
277
- "Use this when the user responds to an approval request for a risky incoming task.",
278
- parameters: {
279
- type: "object",
280
- additionalProperties: false,
281
- properties: {
282
- taskId: { type: "string", description: "The taskId from the approval request." },
283
- approved: { type: "boolean", description: "true to approve, false to reject." },
284
- },
285
- required: ["taskId", "approved"],
286
- },
287
- execute: async (_toolCallId, args) => {
288
- const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
289
- const approved = typeof args.approved === "boolean" ? args.approved : false;
290
- log("info", `tool:multiclaws_task_respond(taskId=${taskId}, approved=${approved})`);
291
- try {
292
- const service = requireService(getService());
293
- if (!taskId)
294
- throw new Error("taskId is required");
295
- const resolved = service.respondToTask(taskId, approved);
296
- if (!resolved) {
297
- return textResult(`未找到任务 ${taskId} 的待审批记录,可能已超时或不存在。`);
298
- }
299
- return textResult(approved
300
- ? `✅ 已授权任务 ${taskId},执行中…`
301
- : `❌ 已拒绝任务 ${taskId}。`);
302
- }
303
- catch (err) {
304
- log("error", `tool:multiclaws_task_respond failed: ${err instanceof Error ? err.message : String(err)}`);
305
- throw err;
306
- }
307
- },
308
- };
309
- const multiclawsTaskStatus = {
310
- name: "multiclaws_task_status",
311
- description: "Check the status of a delegated task.",
312
- parameters: {
313
- type: "object",
314
- additionalProperties: false,
315
- properties: {
316
- taskId: { type: "string" },
317
- },
318
- required: ["taskId"],
319
- },
320
- execute: async (_toolCallId, args) => {
321
- const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
322
- log("debug", `tool:multiclaws_task_status(taskId=${taskId})`);
323
- try {
324
- const service = requireService(getService());
325
- if (!taskId)
326
- throw new Error("taskId is required");
327
- const task = service.getTaskStatus(taskId);
328
- if (!task)
329
- throw new Error(`task not found: ${taskId}`);
330
- return textResult(JSON.stringify(task, null, 2), task);
331
- }
332
- catch (err) {
333
- log("error", `tool:multiclaws_task_status failed: ${err instanceof Error ? err.message : String(err)}`);
334
- throw err;
335
- }
336
- },
337
- };
338
- /* ── Team tools ───────────────────────────────────────────────── */
339
- const multiclawsTeamCreate = {
340
- name: "multiclaws_team_create",
341
- description: "Create a new team. Returns teamId and invite code.",
342
- parameters: {
343
- type: "object",
344
- additionalProperties: false,
345
- properties: {
346
- name: { type: "string" },
347
- },
348
- required: ["name"],
349
- },
350
- execute: async (_toolCallId, args) => {
351
- const name = typeof args.name === "string" ? args.name.trim() : "";
352
- log("info", `tool:multiclaws_team_create(name=${name})`);
353
- try {
354
- const service = requireService(getService());
355
- if (!name)
356
- throw new Error("name is required");
357
- const team = await service.createTeam(name);
358
- const invite = await service.createInvite(team.teamId);
359
- return textResult(`Team "${team.teamName}" created (${team.teamId}).\nInvite code: ${invite}\n\n⚠️ 请只将邀请码分享给完全信任的用户。持有邀请码的人可以加入团队并向你的 AI 委派任务。权限管理模块正在开发中。`, { team, inviteCode: invite });
360
- }
361
- catch (err) {
362
- log("error", `tool:multiclaws_team_create failed: ${err instanceof Error ? err.message : String(err)}`);
363
- throw err;
364
- }
365
- },
366
- };
367
- const multiclawsTeamJoin = {
368
- name: "multiclaws_team_join",
369
- description: "Join a team using an invite code. Automatically syncs all team members as agents.",
370
- parameters: {
371
- type: "object",
372
- additionalProperties: false,
373
- properties: {
374
- inviteCode: { type: "string" },
375
- },
376
- required: ["inviteCode"],
377
- },
378
- execute: async (_toolCallId, args) => {
379
- log("info", "tool:multiclaws_team_join");
380
- try {
381
- const service = requireService(getService());
382
- const inviteCode = typeof args.inviteCode === "string" ? args.inviteCode.trim() : "";
383
- if (!inviteCode)
384
- throw new Error("inviteCode is required");
385
- const team = await service.joinTeam(inviteCode);
386
- const memberNames = team.members.map((m) => m.name).join(", ");
387
- return textResult(`Joined team "${team.teamName}" with ${team.members.length} members: ${memberNames}`, { team });
388
- }
389
- catch (err) {
390
- log("error", `tool:multiclaws_team_join failed: ${err instanceof Error ? err.message : String(err)}`);
391
- throw err;
392
- }
393
- },
394
- };
395
- const multiclawsTeamLeave = {
396
- name: "multiclaws_team_leave",
397
- description: "Leave a team. Notifies all members and removes them from local agent registry.",
398
- parameters: {
399
- type: "object",
400
- additionalProperties: false,
401
- properties: {
402
- teamId: { type: "string" },
403
- },
404
- },
405
- execute: async (_toolCallId, args) => {
406
- const teamId = typeof args.teamId === "string" ? args.teamId.trim() : undefined;
407
- log("info", `tool:multiclaws_team_leave(teamId=${teamId ?? "first"})`);
408
- try {
409
- const service = requireService(getService());
410
- await service.leaveTeam(teamId || undefined);
411
- return textResult("Left team successfully.");
412
- }
413
- catch (err) {
414
- log("error", `tool:multiclaws_team_leave failed: ${err instanceof Error ? err.message : String(err)}`);
415
- throw err;
416
- }
417
- },
418
- };
419
- const multiclawsTeamMembers = {
420
- name: "multiclaws_team_members",
421
- description: "List all members of a team. If teamId is omitted, returns all teams and their members.",
422
- parameters: {
423
- type: "object",
424
- additionalProperties: false,
425
- properties: {
426
- teamId: { type: "string" },
427
- },
428
- },
429
- execute: async (_toolCallId, args) => {
430
- log("debug", "tool:multiclaws_team_members");
431
- try {
432
- const service = requireService(getService());
433
- const teamId = typeof args.teamId === "string" ? args.teamId.trim() : undefined;
434
- const result = await service.listTeamMembers(teamId || undefined);
435
- if (!result) {
436
- return textResult("No team found.");
437
- }
438
- return textResult(JSON.stringify(result, null, 2), result);
439
- }
440
- catch (err) {
441
- log("error", `tool:multiclaws_team_members failed: ${err instanceof Error ? err.message : String(err)}`);
442
- throw err;
443
- }
444
- },
445
- };
446
- /* ── Profile tools ──────────────────────────────────────────── */
447
- const multiclawsProfileSet = {
448
- name: "multiclaws_profile_set",
449
- description: "Set or update the owner profile (name and bio). Bio is free-form markdown describing role, capabilities, data sources, etc. Broadcasts to team members.",
450
- parameters: {
451
- type: "object",
452
- additionalProperties: false,
453
- properties: {
454
- ownerName: { type: "string" },
455
- bio: { type: "string" },
456
- },
457
- },
458
- execute: async (_toolCallId, args) => {
459
- log("debug", "tool:multiclaws_profile_set");
460
- try {
461
- const service = requireService(getService());
462
- const patch = {};
463
- if (typeof args.ownerName === "string")
464
- patch.ownerName = args.ownerName.trim();
465
- if (typeof args.bio === "string")
466
- patch.bio = args.bio;
467
- const profile = await service.setProfile(patch);
468
- return textResult(JSON.stringify(profile, null, 2), profile);
469
- }
470
- catch (err) {
471
- log("error", `tool:multiclaws_profile_set failed: ${err instanceof Error ? err.message : String(err)}`);
472
- throw err;
473
- }
474
- },
475
- };
476
- const multiclawsProfileShow = {
477
- name: "multiclaws_profile_show",
478
- description: "Show the current owner profile.",
479
- parameters: {
480
- type: "object",
481
- additionalProperties: false,
482
- properties: {},
483
- },
484
- execute: async () => {
485
- log("debug", "tool:multiclaws_profile_show");
486
- try {
487
- const service = requireService(getService());
488
- const profile = await service.getProfile();
489
- return textResult(JSON.stringify(profile, null, 2), profile);
490
- }
491
- catch (err) {
492
- log("error", `tool:multiclaws_profile_show failed: ${err instanceof Error ? err.message : String(err)}`);
493
- throw err;
494
- }
495
- },
496
- };
497
- const multiclawsProfilePendingReview = {
498
- name: "multiclaws_profile_pending_review",
499
- description: "Check if the user's profile was just initialized and is pending review. If pending, returns profile and a message to show the user and ask if they want to adjust.",
500
- parameters: {
501
- type: "object",
502
- additionalProperties: false,
503
- properties: {},
504
- },
505
- execute: async () => {
506
- log("debug", "tool:multiclaws_profile_pending_review");
507
- try {
508
- const service = requireService(getService());
509
- const result = await service.getPendingProfileReview();
510
- return textResult(JSON.stringify(result, null, 2), result);
511
- }
512
- catch (err) {
513
- log("error", `tool:multiclaws_profile_pending_review failed: ${err instanceof Error ? err.message : String(err)}`);
514
- throw err;
515
- }
516
- },
517
- };
518
- const multiclawsProfileClearPendingReview = {
519
- name: "multiclaws_profile_clear_pending_review",
520
- description: "Clear the pending profile review flag after the user has confirmed or finished adjusting their profile.",
521
- parameters: {
522
- type: "object",
523
- additionalProperties: false,
524
- properties: {},
525
- },
526
- execute: async () => {
527
- log("debug", "tool:multiclaws_profile_clear_pending_review");
528
- try {
529
- const service = requireService(getService());
530
- await service.clearPendingProfileReview();
531
- return textResult("Pending profile review cleared.");
532
- }
533
- catch (err) {
534
- log("error", `tool:multiclaws_profile_clear_pending_review failed: ${err instanceof Error ? err.message : String(err)}`);
535
- throw err;
536
- }
537
- },
538
- };
539
- return [
540
- multiclawsAgents,
541
- multiclawsAddAgent,
542
- multiclawsRemoveAgent,
543
- multiclawsDelegate,
544
- multiclawsDelegateSend,
545
- multiclawsA2ACallback,
546
- multiclawsNotify,
547
- multiclawsTaskRespond,
548
- multiclawsTaskStatus,
549
- multiclawsTeamCreate,
550
- multiclawsTeamJoin,
551
- multiclawsTeamLeave,
552
- multiclawsTeamMembers,
553
- multiclawsProfileSet,
554
- multiclawsProfileShow,
555
- multiclawsProfilePendingReview,
556
- multiclawsProfileClearPendingReview,
557
- ];
558
- }
559
- const plugin = {
560
- id: "multiclaws",
561
- name: "MultiClaws",
562
- version: version_1.PLUGIN_VERSION,
563
- register(api) {
564
- const config = readConfig(api);
565
- (0, telemetry_1.initializeTelemetry)({ enableConsoleExporter: config.telemetry?.consoleExporter });
566
- const structured = (0, logger_1.createStructuredLogger)(api.logger, "multiclaws");
567
- let service = null;
568
- // Ensure required tools are in gateway.tools.allow at registration time
569
- // so the gateway starts with them already present (no restart needed).
570
- //
571
- // Two categories:
572
- // 1. Adapter-internal tools: sessions_spawn, sessions_history, message
573
- // — needed by a2a-adapter itself to spawn/poll/notify.
574
- // 2. A2A execution tools: exec, read, write, glob, grep
575
- // — needed by spawned sub-agents to actually perform delegated tasks.
576
- // Without these the sub-agent session hits "permission denied" because
577
- // gateway.tools.allow restricts which tools the session can invoke.
578
- //
579
- // Users can override the execution tool list via plugin config:
580
- // plugins.multiclaws.a2aAllowedTools: ["exec", "read", "write", ...]
581
- if (api.config) {
582
- const gw = api.config.gateway;
583
- if (gw) {
584
- const tools = (gw.tools ?? {});
585
- const allow = Array.isArray(tools.allow) ? tools.allow : [];
586
- const adapterRequired = ["sessions_spawn", "sessions_list", "sessions_send", "sessions_history", "message", "chat.send"];
587
- const defaultA2AExecutionTools = ["exec", "read", "write", "edit", "process"];
588
- const pluginConf = api.pluginConfig ?? {};
589
- const a2aExecTools = Array.isArray(pluginConf.a2aAllowedTools)
590
- ? pluginConf.a2aAllowedTools
591
- : defaultA2AExecutionTools;
592
- const required = [...new Set([...adapterRequired, ...a2aExecTools])];
593
- const missing = required.filter((t) => !allow.includes(t));
594
- if (missing.length > 0) {
595
- tools.allow = [...allow, ...missing];
596
- gw.tools = tools;
597
- structured.logger.info(`auto-added gateway tools: ${missing.join(", ")}`);
598
- }
599
- }
600
- }
601
- const gatewayConfig = (() => {
602
- const gw = api.config?.gateway;
603
- const port = typeof gw?.port === "number" ? gw.port : 18789;
604
- const token = typeof gw?.auth?.token === "string" ? gw.auth.token : null;
605
- if (!token)
606
- return null;
607
- return { port, token };
608
- })();
609
- const pluginService = {
610
- id: "multiclaws-service",
611
- start: async (ctx) => {
612
- structured.logger.info("[multiclaws] service starting");
613
- try {
614
- service = new multiclaws_service_1.MulticlawsService({
615
- stateDir: ctx.stateDir,
616
- port: config.port,
617
- displayName: config.displayName,
618
- selfUrl: config.selfUrl,
619
- cwd: process.cwd(),
620
- tunnel: config.tunnel,
621
- gatewayConfig: gatewayConfig ?? undefined,
622
- logger: structured.logger,
623
- });
624
- await service.start();
625
- }
626
- catch (err) {
627
- structured.logger.error(`[multiclaws] service start failed: ${err instanceof Error ? err.message : String(err)}`);
628
- throw err;
629
- }
630
- },
631
- stop: async () => {
632
- structured.logger.info("[multiclaws] service stopping");
633
- try {
634
- if (service) {
635
- await service.stop();
636
- service = null;
637
- }
638
- structured.logger.info("[multiclaws] service stopped");
639
- }
640
- catch (err) {
641
- structured.logger.error(`[multiclaws] service stop failed: ${err instanceof Error ? err.message : String(err)}`);
642
- throw err;
643
- }
644
- },
645
- };
646
- api.registerService(pluginService);
647
- const gatewayHandlers = (0, handlers_1.createGatewayHandlers)(() => requireService(service), structured.logger);
648
- for (const [method, handler] of Object.entries(gatewayHandlers)) {
649
- api.registerGatewayMethod(method, handler);
650
- }
651
- for (const tool of createTools(() => service, structured.logger)) {
652
- api.registerTool(tool);
653
- }
654
- api.registerHttpRoute({
655
- path: "/multiclaws/health",
656
- auth: "plugin",
657
- handler: (_req, res) => {
658
- const running = service !== null;
659
- res.statusCode = running ? 200 : 503;
660
- res.end(JSON.stringify({
661
- ok: running,
662
- plugin: "multiclaws",
663
- }));
664
- },
665
- });
666
- api.on("gateway_start", () => {
667
- structured.logger.info("[multiclaws] gateway_start observed");
668
- });
669
- api.on("gateway_stop", () => {
670
- structured.logger.info("[multiclaws] gateway_stop observed");
671
- });
672
- // Collect notification targets from incoming messages.
673
- // WebChat is intentionally excluded here: it's registered via
674
- // before_prompt_build (type="web") using sessions_send, which correctly
675
- // injects messages into the active session. Registering it here too would
676
- // cause duplicate notifications.
677
- api.on("message_received", (_event, ctx) => {
678
- if (!service || !ctx.channelId)
679
- return;
680
- if (ctx.channelId !== "webchat" && ctx.conversationId) {
681
- // External channels only (Telegram, Discord, etc.)
682
- service.addNotificationTarget(`${ctx.channelId}:${ctx.conversationId}`, { type: "channel", conversationId: ctx.conversationId });
683
- }
684
- });
685
- // Inject onboarding prompt when profile is pending first-run setup
686
- // Also capture web session targets for notifications (skip internal sub-agent sessions)
687
- // Skip when channelId is set — those are already handled by message_received hook
688
- const INTERNAL_SESSION_PREFIXES = ["delegate-", "a2a-"];
689
- api.on("before_prompt_build", async (_event, ctx) => {
690
- if (service && ctx.sessionKey && !ctx.channelId &&
691
- !INTERNAL_SESSION_PREFIXES.some((p) => ctx.sessionKey.startsWith(p))) {
692
- service.addNotificationTarget(`web:${ctx.sessionKey}`, { type: "web", sessionKey: ctx.sessionKey });
693
- }
694
- if (!service)
695
- return;
696
- try {
697
- const review = await service.getPendingProfileReview();
698
- if (!review.pending)
699
- return;
700
- return {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const handlers_1 = require("./gateway/handlers");
4
+ const multiclaws_service_1 = require("./service/multiclaws-service");
5
+ const logger_1 = require("./infra/logger");
6
+ const telemetry_1 = require("./infra/telemetry");
7
+ const version_1 = require("./infra/version");
8
+ /** Default FRP tunnel config for demo/testing */
9
+ const DEFAULT_TUNNEL = {
10
+ type: "frp",
11
+ serverAddr: "39.105.143.2",
12
+ serverPort: 7000,
13
+ token: "jushi@5202fRp",
14
+ portRangeStart: 7011,
15
+ portRangeEnd: 7020,
16
+ };
17
+ function readConfig(api) {
18
+ const raw = (api.pluginConfig ?? {});
19
+ let tunnel;
20
+ const rawTunnel = raw.tunnel;
21
+ if (rawTunnel && rawTunnel.type === "frp") {
22
+ tunnel = {
23
+ type: "frp",
24
+ serverAddr: typeof rawTunnel.serverAddr === "string" ? rawTunnel.serverAddr : DEFAULT_TUNNEL.serverAddr,
25
+ serverPort: typeof rawTunnel.serverPort === "number" ? rawTunnel.serverPort : DEFAULT_TUNNEL.serverPort,
26
+ token: typeof rawTunnel.token === "string" ? rawTunnel.token : DEFAULT_TUNNEL.token,
27
+ portRangeStart: typeof rawTunnel.portRangeStart === "number" ? rawTunnel.portRangeStart : DEFAULT_TUNNEL.portRangeStart,
28
+ portRangeEnd: typeof rawTunnel.portRangeEnd === "number" ? rawTunnel.portRangeEnd : DEFAULT_TUNNEL.portRangeEnd,
29
+ };
30
+ }
31
+ else {
32
+ // No tunnel configured — use built-in default for demo
33
+ tunnel = { ...DEFAULT_TUNNEL };
34
+ }
35
+ return {
36
+ port: typeof raw.port === "number" ? raw.port : undefined,
37
+ displayName: typeof raw.displayName === "string" ? raw.displayName : undefined,
38
+ selfUrl: typeof raw.selfUrl === "string" ? raw.selfUrl : undefined,
39
+ tunnel,
40
+ telemetry: {
41
+ consoleExporter: typeof raw.telemetry?.consoleExporter === "boolean"
42
+ ? Boolean(raw.telemetry.consoleExporter)
43
+ : undefined,
44
+ },
45
+ };
46
+ }
47
+ function textResult(text, details) {
48
+ return {
49
+ content: [{ type: "text", text }],
50
+ ...(details === undefined ? {} : { details }),
51
+ };
52
+ }
53
+ function requireService(service) {
54
+ if (!service) {
55
+ throw new Error("multiclaws service is not running yet");
56
+ }
57
+ return service;
58
+ }
59
+ function createTools(getService, logger) {
60
+ const log = (level, msg) => {
61
+ const fn = level === "debug" ? logger.debug : logger[level];
62
+ fn?.(`[multiclaws] ${msg}`);
63
+ };
64
+ /* ── Agent tools ──────────────────────────────────────────────── */
65
+ const multiclawsAgents = {
66
+ name: "multiclaws_agents",
67
+ description: "List known A2A agents and their capabilities.",
68
+ parameters: {
69
+ type: "object",
70
+ additionalProperties: false,
71
+ properties: {},
72
+ },
73
+ execute: async () => {
74
+ log("debug", "tool:multiclaws_agents");
75
+ try {
76
+ const service = requireService(getService());
77
+ const agents = await service.listAgents();
78
+ return textResult(JSON.stringify({ agents }, null, 2), { agents });
79
+ }
80
+ catch (err) {
81
+ log("error", `tool:multiclaws_agents failed: ${err instanceof Error ? err.message : String(err)}`);
82
+ throw err;
83
+ }
84
+ },
85
+ };
86
+ const multiclawsAddAgent = {
87
+ name: "multiclaws_add_agent",
88
+ description: "Add a remote A2A agent by URL. Automatically fetches its Agent Card.",
89
+ parameters: {
90
+ type: "object",
91
+ additionalProperties: false,
92
+ properties: {
93
+ url: { type: "string" },
94
+ apiKey: { type: "string" },
95
+ },
96
+ required: ["url"],
97
+ },
98
+ execute: async (_toolCallId, args) => {
99
+ const url = typeof args.url === "string" ? args.url.trim() : "";
100
+ log("debug", `tool:multiclaws_add_agent(url=${url})`);
101
+ try {
102
+ const service = requireService(getService());
103
+ if (!url)
104
+ throw new Error("url is required");
105
+ const apiKey = typeof args.apiKey === "string" ? args.apiKey.trim() : undefined;
106
+ const agent = await service.addAgent({ url, apiKey });
107
+ return textResult(`Agent added: ${agent.name} (${agent.url})`, agent);
108
+ }
109
+ catch (err) {
110
+ log("error", `tool:multiclaws_add_agent failed: ${err instanceof Error ? err.message : String(err)}`);
111
+ throw err;
112
+ }
113
+ },
114
+ };
115
+ const multiclawsRemoveAgent = {
116
+ name: "multiclaws_remove_agent",
117
+ description: "Remove a known A2A agent by URL.",
118
+ parameters: {
119
+ type: "object",
120
+ additionalProperties: false,
121
+ properties: {
122
+ url: { type: "string" },
123
+ },
124
+ required: ["url"],
125
+ },
126
+ execute: async (_toolCallId, args) => {
127
+ const url = typeof args.url === "string" ? args.url.trim() : "";
128
+ log("debug", `tool:multiclaws_remove_agent(url=${url})`);
129
+ try {
130
+ const service = requireService(getService());
131
+ if (!url)
132
+ throw new Error("url is required");
133
+ const removed = await service.removeAgent(url);
134
+ return textResult(removed ? `Agent ${url} removed.` : `Agent ${url} not found.`);
135
+ }
136
+ catch (err) {
137
+ log("error", `tool:multiclaws_remove_agent failed: ${err instanceof Error ? err.message : String(err)}`);
138
+ throw err;
139
+ }
140
+ },
141
+ };
142
+ const multiclawsDelegate = {
143
+ name: "multiclaws_delegate",
144
+ description: "Delegate a task to a remote A2A agent and wait for the result inline. " +
145
+ "Sends the task synchronously via A2A and returns the output directly in the current session. " +
146
+ "For long-running tasks this may take several minutes. " +
147
+ "Do NOT use multiclaws_delegate_send directly — use this tool instead.",
148
+ parameters: {
149
+ type: "object",
150
+ additionalProperties: false,
151
+ properties: {
152
+ agentUrl: { type: "string" },
153
+ task: { type: "string" },
154
+ },
155
+ required: ["agentUrl", "task"],
156
+ },
157
+ execute: async (_toolCallId, args) => {
158
+ const agentUrl = typeof args.agentUrl === "string" ? args.agentUrl.trim() : "";
159
+ log("info", `tool:multiclaws_delegate(agentUrl=${agentUrl})`);
160
+ try {
161
+ const service = requireService(getService());
162
+ const task = typeof args.task === "string" ? args.task.trim() : "";
163
+ if (!agentUrl || !task)
164
+ throw new Error("agentUrl and task are required");
165
+ const result = await service.delegateTaskSync({ agentUrl, task });
166
+ const summary = result.output
167
+ ? result.output
168
+ : result.error
169
+ ? `任务失败:${result.error}`
170
+ : `任务状态:${result.status}`;
171
+ return textResult(summary, result);
172
+ }
173
+ catch (err) {
174
+ log("error", `tool:multiclaws_delegate failed: ${err instanceof Error ? err.message : String(err)}`);
175
+ throw err;
176
+ }
177
+ },
178
+ };
179
+ const multiclawsDelegateSend = {
180
+ name: "multiclaws_delegate_send",
181
+ description: "Send a task to a remote A2A agent and wait for the result synchronously. " +
182
+ "Low-level primitive used by sub-agents or advanced orchestration flows. " +
183
+ "In most cases use multiclaws_delegate instead, which handles this automatically.",
184
+ parameters: {
185
+ type: "object",
186
+ additionalProperties: false,
187
+ properties: {
188
+ agentUrl: { type: "string" },
189
+ task: { type: "string" },
190
+ },
191
+ required: ["agentUrl", "task"],
192
+ },
193
+ execute: async (_toolCallId, args) => {
194
+ const agentUrl = typeof args.agentUrl === "string" ? args.agentUrl.trim() : "";
195
+ log("info", `tool:multiclaws_delegate_send(agentUrl=${agentUrl})`);
196
+ try {
197
+ const service = requireService(getService());
198
+ const task = typeof args.task === "string" ? args.task.trim() : "";
199
+ if (!agentUrl || !task)
200
+ throw new Error("agentUrl and task are required");
201
+ const result = await service.delegateTaskSync({ agentUrl, task });
202
+ return textResult(JSON.stringify(result, null, 2), result);
203
+ }
204
+ catch (err) {
205
+ log("error", `tool:multiclaws_delegate_send failed: ${err instanceof Error ? err.message : String(err)}`);
206
+ throw err;
207
+ }
208
+ },
209
+ };
210
+ const multiclawsA2ACallback = {
211
+ name: "multiclaws_a2a_callback",
212
+ description: "Report the result of an incoming A2A delegated task. " +
213
+ "Called by sub-agents spawned to handle remote tasks. " +
214
+ "Do NOT call this directly.",
215
+ parameters: {
216
+ type: "object",
217
+ additionalProperties: false,
218
+ properties: {
219
+ taskId: { type: "string" },
220
+ result: { type: "string" },
221
+ },
222
+ required: ["taskId", "result"],
223
+ },
224
+ execute: async (_toolCallId, args) => {
225
+ const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
226
+ const result = typeof args.result === "string" ? args.result : "";
227
+ log("info", `tool:multiclaws_a2a_callback(taskId=${taskId})`);
228
+ try {
229
+ const service = requireService(getService());
230
+ if (!taskId || !result)
231
+ throw new Error("taskId and result are required");
232
+ const resolved = service.resolveA2ACallback(taskId, result);
233
+ if (!resolved) {
234
+ return textResult(`No pending callback for task ${taskId}. It may have timed out.`);
235
+ }
236
+ return textResult(`Task ${taskId} result reported successfully.`);
237
+ }
238
+ catch (err) {
239
+ log("error", `tool:multiclaws_a2a_callback failed: ${err instanceof Error ? err.message : String(err)}`);
240
+ throw err;
241
+ }
242
+ },
243
+ };
244
+ const multiclawsNotify = {
245
+ name: "multiclaws_notify",
246
+ description: "Send a notification message to the local user's WebUI. " +
247
+ "Used by sub-agents to deliver delegation results back to the user. " +
248
+ "Broadcasts to all known channels so the user sees the message regardless of which channel they are on.",
249
+ parameters: {
250
+ type: "object",
251
+ additionalProperties: false,
252
+ properties: {
253
+ message: { type: "string", description: "The message to send to the user." },
254
+ },
255
+ required: ["message"],
256
+ },
257
+ execute: async (_toolCallId, args) => {
258
+ const msg = typeof args.message === "string" ? args.message.trim() : "";
259
+ log("info", `tool:multiclaws_notify(len=${msg.length})`);
260
+ try {
261
+ const service = requireService(getService());
262
+ if (!msg)
263
+ throw new Error("message is required");
264
+ await service.notifyUser(msg);
265
+ return textResult("Notification sent.");
266
+ }
267
+ catch (err) {
268
+ log("error", `tool:multiclaws_notify failed: ${err instanceof Error ? err.message : String(err)}`);
269
+ throw err;
270
+ }
271
+ },
272
+ };
273
+ const multiclawsTaskRespond = {
274
+ name: "multiclaws_task_respond",
275
+ description: "Approve or reject a pending incoming delegated task that requires human authorization. " +
276
+ "Call with approved=true to allow execution, approved=false to reject. " +
277
+ "Use this when the user responds to an approval request for a risky incoming task.",
278
+ parameters: {
279
+ type: "object",
280
+ additionalProperties: false,
281
+ properties: {
282
+ taskId: { type: "string", description: "The taskId from the approval request." },
283
+ approved: { type: "boolean", description: "true to approve, false to reject." },
284
+ },
285
+ required: ["taskId", "approved"],
286
+ },
287
+ execute: async (_toolCallId, args) => {
288
+ const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
289
+ const approved = typeof args.approved === "boolean" ? args.approved : false;
290
+ log("info", `tool:multiclaws_task_respond(taskId=${taskId}, approved=${approved})`);
291
+ try {
292
+ const service = requireService(getService());
293
+ if (!taskId)
294
+ throw new Error("taskId is required");
295
+ const resolved = service.respondToTask(taskId, approved);
296
+ if (!resolved) {
297
+ return textResult(`未找到任务 ${taskId} 的待审批记录,可能已超时或不存在。`);
298
+ }
299
+ return textResult(approved
300
+ ? `✅ 已授权任务 ${taskId},执行中…`
301
+ : `❌ 已拒绝任务 ${taskId}。`);
302
+ }
303
+ catch (err) {
304
+ log("error", `tool:multiclaws_task_respond failed: ${err instanceof Error ? err.message : String(err)}`);
305
+ throw err;
306
+ }
307
+ },
308
+ };
309
+ const multiclawsTaskStatus = {
310
+ name: "multiclaws_task_status",
311
+ description: "Check the status of a delegated task.",
312
+ parameters: {
313
+ type: "object",
314
+ additionalProperties: false,
315
+ properties: {
316
+ taskId: { type: "string" },
317
+ },
318
+ required: ["taskId"],
319
+ },
320
+ execute: async (_toolCallId, args) => {
321
+ const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
322
+ log("debug", `tool:multiclaws_task_status(taskId=${taskId})`);
323
+ try {
324
+ const service = requireService(getService());
325
+ if (!taskId)
326
+ throw new Error("taskId is required");
327
+ const task = service.getTaskStatus(taskId);
328
+ if (!task)
329
+ throw new Error(`task not found: ${taskId}`);
330
+ return textResult(JSON.stringify(task, null, 2), task);
331
+ }
332
+ catch (err) {
333
+ log("error", `tool:multiclaws_task_status failed: ${err instanceof Error ? err.message : String(err)}`);
334
+ throw err;
335
+ }
336
+ },
337
+ };
338
+ /* ── Team tools ───────────────────────────────────────────────── */
339
+ const multiclawsTeamCreate = {
340
+ name: "multiclaws_team_create",
341
+ description: "Create a new team. Returns teamId and invite code.",
342
+ parameters: {
343
+ type: "object",
344
+ additionalProperties: false,
345
+ properties: {
346
+ name: { type: "string" },
347
+ },
348
+ required: ["name"],
349
+ },
350
+ execute: async (_toolCallId, args) => {
351
+ const name = typeof args.name === "string" ? args.name.trim() : "";
352
+ log("info", `tool:multiclaws_team_create(name=${name})`);
353
+ try {
354
+ const service = requireService(getService());
355
+ if (!name)
356
+ throw new Error("name is required");
357
+ const team = await service.createTeam(name);
358
+ const invite = await service.createInvite(team.teamId);
359
+ return textResult(`Team "${team.teamName}" created (${team.teamId}).\nInvite code: ${invite}\n\n⚠️ 请只将邀请码分享给完全信任的用户。持有邀请码的人可以加入团队并向你的 AI 委派任务。权限管理模块正在开发中。`, { team, inviteCode: invite });
360
+ }
361
+ catch (err) {
362
+ log("error", `tool:multiclaws_team_create failed: ${err instanceof Error ? err.message : String(err)}`);
363
+ throw err;
364
+ }
365
+ },
366
+ };
367
+ const multiclawsTeamJoin = {
368
+ name: "multiclaws_team_join",
369
+ description: "Join a team using an invite code. Automatically syncs all team members as agents.",
370
+ parameters: {
371
+ type: "object",
372
+ additionalProperties: false,
373
+ properties: {
374
+ inviteCode: { type: "string" },
375
+ },
376
+ required: ["inviteCode"],
377
+ },
378
+ execute: async (_toolCallId, args) => {
379
+ log("info", "tool:multiclaws_team_join");
380
+ try {
381
+ const service = requireService(getService());
382
+ const inviteCode = typeof args.inviteCode === "string" ? args.inviteCode.trim() : "";
383
+ if (!inviteCode)
384
+ throw new Error("inviteCode is required");
385
+ const team = await service.joinTeam(inviteCode);
386
+ const memberNames = team.members.map((m) => m.name).join(", ");
387
+ return textResult(`Joined team "${team.teamName}" with ${team.members.length} members: ${memberNames}`, { team });
388
+ }
389
+ catch (err) {
390
+ log("error", `tool:multiclaws_team_join failed: ${err instanceof Error ? err.message : String(err)}`);
391
+ throw err;
392
+ }
393
+ },
394
+ };
395
+ const multiclawsTeamLeave = {
396
+ name: "multiclaws_team_leave",
397
+ description: "Leave a team. Notifies all members and removes them from local agent registry.",
398
+ parameters: {
399
+ type: "object",
400
+ additionalProperties: false,
401
+ properties: {
402
+ teamId: { type: "string" },
403
+ },
404
+ },
405
+ execute: async (_toolCallId, args) => {
406
+ const teamId = typeof args.teamId === "string" ? args.teamId.trim() : undefined;
407
+ log("info", `tool:multiclaws_team_leave(teamId=${teamId ?? "first"})`);
408
+ try {
409
+ const service = requireService(getService());
410
+ await service.leaveTeam(teamId || undefined);
411
+ return textResult("Left team successfully.");
412
+ }
413
+ catch (err) {
414
+ log("error", `tool:multiclaws_team_leave failed: ${err instanceof Error ? err.message : String(err)}`);
415
+ throw err;
416
+ }
417
+ },
418
+ };
419
+ const multiclawsTeamMembers = {
420
+ name: "multiclaws_team_members",
421
+ description: "List all members of a team. If teamId is omitted, returns all teams and their members.",
422
+ parameters: {
423
+ type: "object",
424
+ additionalProperties: false,
425
+ properties: {
426
+ teamId: { type: "string" },
427
+ },
428
+ },
429
+ execute: async (_toolCallId, args) => {
430
+ log("debug", "tool:multiclaws_team_members");
431
+ try {
432
+ const service = requireService(getService());
433
+ const teamId = typeof args.teamId === "string" ? args.teamId.trim() : undefined;
434
+ const result = await service.listTeamMembers(teamId || undefined);
435
+ if (!result) {
436
+ return textResult("No team found.");
437
+ }
438
+ return textResult(JSON.stringify(result, null, 2), result);
439
+ }
440
+ catch (err) {
441
+ log("error", `tool:multiclaws_team_members failed: ${err instanceof Error ? err.message : String(err)}`);
442
+ throw err;
443
+ }
444
+ },
445
+ };
446
+ /* ── Profile tools ──────────────────────────────────────────── */
447
+ const multiclawsProfileSet = {
448
+ name: "multiclaws_profile_set",
449
+ description: "Set or update the owner profile (name and bio). Bio is free-form markdown describing role, capabilities, data sources, etc. Broadcasts to team members.",
450
+ parameters: {
451
+ type: "object",
452
+ additionalProperties: false,
453
+ properties: {
454
+ ownerName: { type: "string" },
455
+ bio: { type: "string" },
456
+ },
457
+ },
458
+ execute: async (_toolCallId, args) => {
459
+ log("debug", "tool:multiclaws_profile_set");
460
+ try {
461
+ const service = requireService(getService());
462
+ const patch = {};
463
+ if (typeof args.ownerName === "string")
464
+ patch.ownerName = args.ownerName.trim();
465
+ if (typeof args.bio === "string")
466
+ patch.bio = args.bio;
467
+ const profile = await service.setProfile(patch);
468
+ return textResult(JSON.stringify(profile, null, 2), profile);
469
+ }
470
+ catch (err) {
471
+ log("error", `tool:multiclaws_profile_set failed: ${err instanceof Error ? err.message : String(err)}`);
472
+ throw err;
473
+ }
474
+ },
475
+ };
476
+ const multiclawsProfileShow = {
477
+ name: "multiclaws_profile_show",
478
+ description: "Show the current owner profile.",
479
+ parameters: {
480
+ type: "object",
481
+ additionalProperties: false,
482
+ properties: {},
483
+ },
484
+ execute: async () => {
485
+ log("debug", "tool:multiclaws_profile_show");
486
+ try {
487
+ const service = requireService(getService());
488
+ const profile = await service.getProfile();
489
+ return textResult(JSON.stringify(profile, null, 2), profile);
490
+ }
491
+ catch (err) {
492
+ log("error", `tool:multiclaws_profile_show failed: ${err instanceof Error ? err.message : String(err)}`);
493
+ throw err;
494
+ }
495
+ },
496
+ };
497
+ const multiclawsProfilePendingReview = {
498
+ name: "multiclaws_profile_pending_review",
499
+ description: "Check if the user's profile was just initialized and is pending review. If pending, returns profile and a message to show the user and ask if they want to adjust.",
500
+ parameters: {
501
+ type: "object",
502
+ additionalProperties: false,
503
+ properties: {},
504
+ },
505
+ execute: async () => {
506
+ log("debug", "tool:multiclaws_profile_pending_review");
507
+ try {
508
+ const service = requireService(getService());
509
+ const result = await service.getPendingProfileReview();
510
+ return textResult(JSON.stringify(result, null, 2), result);
511
+ }
512
+ catch (err) {
513
+ log("error", `tool:multiclaws_profile_pending_review failed: ${err instanceof Error ? err.message : String(err)}`);
514
+ throw err;
515
+ }
516
+ },
517
+ };
518
+ const multiclawsProfileClearPendingReview = {
519
+ name: "multiclaws_profile_clear_pending_review",
520
+ description: "Clear the pending profile review flag after the user has confirmed or finished adjusting their profile.",
521
+ parameters: {
522
+ type: "object",
523
+ additionalProperties: false,
524
+ properties: {},
525
+ },
526
+ execute: async () => {
527
+ log("debug", "tool:multiclaws_profile_clear_pending_review");
528
+ try {
529
+ const service = requireService(getService());
530
+ await service.clearPendingProfileReview();
531
+ return textResult("Pending profile review cleared.");
532
+ }
533
+ catch (err) {
534
+ log("error", `tool:multiclaws_profile_clear_pending_review failed: ${err instanceof Error ? err.message : String(err)}`);
535
+ throw err;
536
+ }
537
+ },
538
+ };
539
+ return [
540
+ multiclawsAgents,
541
+ multiclawsAddAgent,
542
+ multiclawsRemoveAgent,
543
+ multiclawsDelegate,
544
+ multiclawsDelegateSend,
545
+ multiclawsA2ACallback,
546
+ multiclawsNotify,
547
+ multiclawsTaskRespond,
548
+ multiclawsTaskStatus,
549
+ multiclawsTeamCreate,
550
+ multiclawsTeamJoin,
551
+ multiclawsTeamLeave,
552
+ multiclawsTeamMembers,
553
+ multiclawsProfileSet,
554
+ multiclawsProfileShow,
555
+ multiclawsProfilePendingReview,
556
+ multiclawsProfileClearPendingReview,
557
+ ];
558
+ }
559
+ const plugin = {
560
+ id: "multiclaws",
561
+ name: "MultiClaws",
562
+ version: version_1.PLUGIN_VERSION,
563
+ register(api) {
564
+ const config = readConfig(api);
565
+ (0, telemetry_1.initializeTelemetry)({ enableConsoleExporter: config.telemetry?.consoleExporter });
566
+ const structured = (0, logger_1.createStructuredLogger)(api.logger, "multiclaws");
567
+ let service = null;
568
+ // Ensure required tools are in gateway.tools.allow at registration time
569
+ // so the gateway starts with them already present (no restart needed).
570
+ //
571
+ // Two categories:
572
+ // 1. Adapter-internal tools: sessions_spawn, sessions_history, message
573
+ // — needed by a2a-adapter itself to spawn/poll/notify.
574
+ // 2. A2A execution tools: exec, read, write, glob, grep
575
+ // — needed by spawned sub-agents to actually perform delegated tasks.
576
+ // Without these the sub-agent session hits "permission denied" because
577
+ // gateway.tools.allow restricts which tools the session can invoke.
578
+ //
579
+ // Users can override the execution tool list via plugin config:
580
+ // plugins.multiclaws.a2aAllowedTools: ["exec", "read", "write", ...]
581
+ if (api.config) {
582
+ const gw = api.config.gateway;
583
+ if (gw) {
584
+ const tools = (gw.tools ?? {});
585
+ const allow = Array.isArray(tools.allow) ? tools.allow : [];
586
+ const adapterRequired = ["sessions_spawn", "sessions_list", "sessions_send", "sessions_history", "message", "chat.send"];
587
+ const defaultA2AExecutionTools = ["exec", "read", "write", "edit", "process"];
588
+ const pluginConf = api.pluginConfig ?? {};
589
+ const a2aExecTools = Array.isArray(pluginConf.a2aAllowedTools)
590
+ ? pluginConf.a2aAllowedTools
591
+ : defaultA2AExecutionTools;
592
+ const required = [...new Set([...adapterRequired, ...a2aExecTools])];
593
+ const missing = required.filter((t) => !allow.includes(t));
594
+ if (missing.length > 0) {
595
+ tools.allow = [...allow, ...missing];
596
+ gw.tools = tools;
597
+ structured.logger.info(`auto-added gateway tools: ${missing.join(", ")}`);
598
+ }
599
+ }
600
+ }
601
+ const gatewayConfig = (() => {
602
+ const gw = api.config?.gateway;
603
+ const port = typeof gw?.port === "number" ? gw.port : 18789;
604
+ const token = typeof gw?.auth?.token === "string" ? gw.auth.token : null;
605
+ if (!token)
606
+ return null;
607
+ return { port, token };
608
+ })();
609
+ const pluginService = {
610
+ id: "multiclaws-service",
611
+ start: async (ctx) => {
612
+ structured.logger.info("[multiclaws] service starting");
613
+ try {
614
+ service = new multiclaws_service_1.MulticlawsService({
615
+ stateDir: ctx.stateDir,
616
+ port: config.port,
617
+ displayName: config.displayName,
618
+ selfUrl: config.selfUrl,
619
+ cwd: process.cwd(),
620
+ tunnel: config.tunnel,
621
+ gatewayConfig: gatewayConfig ?? undefined,
622
+ logger: structured.logger,
623
+ });
624
+ await service.start();
625
+ }
626
+ catch (err) {
627
+ structured.logger.error(`[multiclaws] service start failed: ${err instanceof Error ? err.message : String(err)}`);
628
+ throw err;
629
+ }
630
+ },
631
+ stop: async () => {
632
+ structured.logger.info("[multiclaws] service stopping");
633
+ try {
634
+ if (service) {
635
+ await service.stop();
636
+ service = null;
637
+ }
638
+ structured.logger.info("[multiclaws] service stopped");
639
+ }
640
+ catch (err) {
641
+ structured.logger.error(`[multiclaws] service stop failed: ${err instanceof Error ? err.message : String(err)}`);
642
+ throw err;
643
+ }
644
+ },
645
+ };
646
+ api.registerService(pluginService);
647
+ const gatewayHandlers = (0, handlers_1.createGatewayHandlers)(() => requireService(service), structured.logger);
648
+ for (const [method, handler] of Object.entries(gatewayHandlers)) {
649
+ api.registerGatewayMethod(method, handler);
650
+ }
651
+ for (const tool of createTools(() => service, structured.logger)) {
652
+ api.registerTool(tool);
653
+ }
654
+ api.registerHttpRoute({
655
+ path: "/multiclaws/health",
656
+ auth: "plugin",
657
+ handler: (_req, res) => {
658
+ const running = service !== null;
659
+ res.statusCode = running ? 200 : 503;
660
+ res.end(JSON.stringify({
661
+ ok: running,
662
+ plugin: "multiclaws",
663
+ }));
664
+ },
665
+ });
666
+ api.on("gateway_start", () => {
667
+ structured.logger.info("[multiclaws] gateway_start observed");
668
+ });
669
+ api.on("gateway_stop", () => {
670
+ structured.logger.info("[multiclaws] gateway_stop observed");
671
+ });
672
+ // Collect notification targets from incoming messages.
673
+ // WebChat is intentionally excluded here: it's registered via
674
+ // before_prompt_build (type="web") using sessions_send, which correctly
675
+ // injects messages into the active session. Registering it here too would
676
+ // cause duplicate notifications.
677
+ api.on("message_received", (_event, ctx) => {
678
+ if (!service || !ctx.channelId)
679
+ return;
680
+ if (ctx.channelId !== "webchat" && ctx.conversationId) {
681
+ // External channels only (Telegram, Discord, etc.)
682
+ service.addNotificationTarget(`${ctx.channelId}:${ctx.conversationId}`, { type: "channel", conversationId: ctx.conversationId });
683
+ }
684
+ });
685
+ // Inject onboarding prompt when profile is pending first-run setup
686
+ // Also capture web session targets for notifications (skip internal sub-agent sessions)
687
+ // Skip when channelId is set — those are already handled by message_received hook
688
+ const INTERNAL_SESSION_PREFIXES = ["delegate-", "a2a-"];
689
+ api.on("before_prompt_build", async (_event, ctx) => {
690
+ if (service && ctx.sessionKey && !ctx.channelId &&
691
+ !INTERNAL_SESSION_PREFIXES.some((p) => ctx.sessionKey.startsWith(p))) {
692
+ service.addNotificationTarget(`web:${ctx.sessionKey}`, { type: "web", sessionKey: ctx.sessionKey });
693
+ }
694
+ if (!service)
695
+ return;
696
+ try {
697
+ const review = await service.getPendingProfileReview();
698
+ if (!review.pending)
699
+ return;
700
+ return {
701
701
  appendSystemContext: `## MultiClaws 首次安装 — 需要完成档案设置
702
702
 
703
703
  multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程(优先处理,不管用户说了什么):
@@ -727,13 +727,13 @@ multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程
727
727
 
728
728
  5. **网络情况告知**(无需用户回答):告知用户「所有实例通过 FRP 隧道通信,需在插件配置中设置 tunnel 字段(包含 frps 服务器地址、端口、token 和可用端口范围),frpc 会自动下载安装」
729
729
 
730
- **注意**:只有名字需要用户明确确认;bio 自动生成直接保存无需确认;网络情况仅告知无需回答。`,
731
- };
732
- }
733
- catch (err) {
734
- structured.logger.warn("[multiclaws] before_prompt_build: failed to check pending review: " + String(err));
735
- }
736
- });
737
- },
738
- };
739
- exports.default = plugin;
730
+ **注意**:只有名字需要用户明确确认;bio 自动生成直接保存无需确认;网络情况仅告知无需回答。`,
731
+ };
732
+ }
733
+ catch (err) {
734
+ structured.logger.warn("[multiclaws] before_prompt_build: failed to check pending review: " + String(err));
735
+ }
736
+ });
737
+ },
738
+ };
739
+ exports.default = plugin;