multiclaws 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway/handlers.js +41 -91
- package/dist/index.js +58 -156
- package/dist/infra/gateway-client.js +0 -10
- package/dist/infra/tailscale.d.ts +1 -1
- package/dist/infra/tailscale.js +5 -14
- package/dist/service/a2a-adapter.d.ts +0 -1
- package/dist/service/a2a-adapter.js +10 -56
- package/dist/service/agent-registry.d.ts +0 -9
- package/dist/service/agent-registry.js +1 -45
- package/dist/service/multiclaws-service.d.ts +18 -43
- package/dist/service/multiclaws-service.js +95 -379
- package/package.json +1 -1
- package/skills/multiclaws/SKILL.md +100 -89
package/dist/gateway/handlers.js
CHANGED
|
@@ -3,21 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createGatewayHandlers = createGatewayHandlers;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const nonEmptyString = zod_1.z.string().trim().min(1);
|
|
6
|
+
const agentAddSchema = zod_1.z.object({
|
|
7
|
+
url: nonEmptyString,
|
|
8
|
+
apiKey: zod_1.z.string().trim().min(1).optional(),
|
|
9
|
+
});
|
|
6
10
|
const agentRemoveSchema = zod_1.z.object({ url: nonEmptyString });
|
|
7
|
-
const
|
|
11
|
+
const taskDelegateSchema = zod_1.z.object({
|
|
8
12
|
agentUrl: nonEmptyString,
|
|
9
|
-
|
|
10
|
-
});
|
|
11
|
-
const sessionReplySchema = zod_1.z.object({
|
|
12
|
-
sessionId: nonEmptyString,
|
|
13
|
-
message: nonEmptyString,
|
|
14
|
-
});
|
|
15
|
-
const sessionStatusSchema = zod_1.z.object({ sessionId: zod_1.z.string().trim().min(1).optional() });
|
|
16
|
-
const sessionEndSchema = zod_1.z.object({ sessionId: nonEmptyString });
|
|
17
|
-
const sessionWaitAllSchema = zod_1.z.object({
|
|
18
|
-
sessionIds: zod_1.z.array(nonEmptyString).min(1),
|
|
19
|
-
timeoutMs: zod_1.z.number().positive().optional(),
|
|
13
|
+
task: nonEmptyString,
|
|
20
14
|
});
|
|
15
|
+
const taskStatusSchema = zod_1.z.object({ taskId: nonEmptyString });
|
|
21
16
|
const profileSetSchema = zod_1.z.object({
|
|
22
17
|
ownerName: zod_1.z.string().trim().optional(),
|
|
23
18
|
bio: zod_1.z.string().optional(),
|
|
@@ -36,13 +31,19 @@ function createGatewayHandlers(getService) {
|
|
|
36
31
|
const handlers = {
|
|
37
32
|
/* ── Agent handlers ─────────────────────────────────────────── */
|
|
38
33
|
"multiclaws.agent.list": async ({ respond }) => {
|
|
34
|
+
const service = getService();
|
|
35
|
+
const agents = await service.listAgents();
|
|
36
|
+
respond(true, { agents });
|
|
37
|
+
},
|
|
38
|
+
"multiclaws.agent.add": async ({ params, respond }) => {
|
|
39
39
|
try {
|
|
40
|
+
const parsed = agentAddSchema.parse(params);
|
|
40
41
|
const service = getService();
|
|
41
|
-
const
|
|
42
|
-
respond(true,
|
|
42
|
+
const agent = await service.addAgent(parsed);
|
|
43
|
+
respond(true, agent);
|
|
43
44
|
}
|
|
44
45
|
catch (error) {
|
|
45
|
-
safeHandle(respond, "
|
|
46
|
+
safeHandle(respond, "invalid_params", error);
|
|
46
47
|
}
|
|
47
48
|
},
|
|
48
49
|
"multiclaws.agent.remove": async ({ params, respond }) => {
|
|
@@ -56,70 +57,34 @@ function createGatewayHandlers(getService) {
|
|
|
56
57
|
safeHandle(respond, "invalid_params", error);
|
|
57
58
|
}
|
|
58
59
|
},
|
|
59
|
-
/* ──
|
|
60
|
-
"multiclaws.
|
|
61
|
-
try {
|
|
62
|
-
const parsed = sessionStartSchema.parse(params);
|
|
63
|
-
const service = getService();
|
|
64
|
-
const result = await service.startSession(parsed);
|
|
65
|
-
respond(true, result);
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
safeHandle(respond, "session_start_failed", error);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
"multiclaws.session.reply": async ({ params, respond }) => {
|
|
60
|
+
/* ── Task handlers ──────────────────────────────────────────── */
|
|
61
|
+
"multiclaws.task.delegate": async ({ params, respond }) => {
|
|
72
62
|
try {
|
|
73
|
-
const parsed =
|
|
63
|
+
const parsed = taskDelegateSchema.parse(params);
|
|
74
64
|
const service = getService();
|
|
75
|
-
const result = await service.
|
|
65
|
+
const result = await service.delegateTask(parsed);
|
|
76
66
|
respond(true, result);
|
|
77
67
|
}
|
|
78
68
|
catch (error) {
|
|
79
|
-
safeHandle(respond, "
|
|
69
|
+
safeHandle(respond, "task_delegate_failed", error);
|
|
80
70
|
}
|
|
81
71
|
},
|
|
82
|
-
"multiclaws.
|
|
72
|
+
"multiclaws.task.status": async ({ params, respond }) => {
|
|
83
73
|
try {
|
|
84
|
-
const parsed =
|
|
74
|
+
const parsed = taskStatusSchema.parse(params);
|
|
85
75
|
const service = getService();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
const sessions = service.listSessions();
|
|
96
|
-
respond(true, { sessions });
|
|
76
|
+
const task = service.getTaskStatus(parsed.taskId);
|
|
77
|
+
if (!task) {
|
|
78
|
+
respond(false, undefined, {
|
|
79
|
+
code: "not_found",
|
|
80
|
+
message: `task not found: ${parsed.taskId}`,
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
97
83
|
}
|
|
84
|
+
respond(true, { task });
|
|
98
85
|
}
|
|
99
86
|
catch (error) {
|
|
100
|
-
safeHandle(respond, "
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
"multiclaws.session.wait_all": async ({ params, respond }) => {
|
|
104
|
-
try {
|
|
105
|
-
const parsed = sessionWaitAllSchema.parse(params);
|
|
106
|
-
const service = getService();
|
|
107
|
-
const result = await service.waitForSessions(parsed);
|
|
108
|
-
respond(true, result);
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
safeHandle(respond, "session_wait_all_failed", error);
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
"multiclaws.session.end": async ({ params, respond }) => {
|
|
115
|
-
try {
|
|
116
|
-
const parsed = sessionEndSchema.parse(params);
|
|
117
|
-
const service = getService();
|
|
118
|
-
const ok = service.endSession(parsed.sessionId);
|
|
119
|
-
respond(true, { ended: ok });
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
safeHandle(respond, "session_end_failed", error);
|
|
87
|
+
safeHandle(respond, "task_status_failed", error);
|
|
123
88
|
}
|
|
124
89
|
},
|
|
125
90
|
/* ── Team handlers ──────────────────────────────────────────── */
|
|
@@ -177,34 +142,19 @@ function createGatewayHandlers(getService) {
|
|
|
177
142
|
},
|
|
178
143
|
/* ── Profile handlers ───────────────────────────────────────── */
|
|
179
144
|
"multiclaws.profile.show": async ({ respond }) => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
respond(true, profile);
|
|
184
|
-
}
|
|
185
|
-
catch (error) {
|
|
186
|
-
safeHandle(respond, "profile_show_failed", error);
|
|
187
|
-
}
|
|
145
|
+
const service = getService();
|
|
146
|
+
const profile = await service.getProfile();
|
|
147
|
+
respond(true, profile);
|
|
188
148
|
},
|
|
189
149
|
"multiclaws.profile.pending_review": async ({ respond }) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
respond(true, result);
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
safeHandle(respond, "profile_pending_review_failed", error);
|
|
197
|
-
}
|
|
150
|
+
const service = getService();
|
|
151
|
+
const result = await service.getPendingProfileReview();
|
|
152
|
+
respond(true, result);
|
|
198
153
|
},
|
|
199
154
|
"multiclaws.profile.clear_pending_review": async ({ respond }) => {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
respond(true, { cleared: true });
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
safeHandle(respond, "profile_clear_pending_review_failed", error);
|
|
207
|
-
}
|
|
155
|
+
const service = getService();
|
|
156
|
+
await service.clearPendingProfileReview();
|
|
157
|
+
respond(true, { cleared: true });
|
|
208
158
|
},
|
|
209
159
|
"multiclaws.profile.set": async ({ params, respond }) => {
|
|
210
160
|
try {
|
package/dist/index.js
CHANGED
|
@@ -45,14 +45,15 @@ function createTools(getService) {
|
|
|
45
45
|
return textResult(JSON.stringify({ agents }, null, 2), { agents });
|
|
46
46
|
},
|
|
47
47
|
};
|
|
48
|
-
const
|
|
49
|
-
name: "
|
|
50
|
-
description: "
|
|
48
|
+
const multiclawsAddAgent = {
|
|
49
|
+
name: "multiclaws_add_agent",
|
|
50
|
+
description: "Add a remote A2A agent by URL. Automatically fetches its Agent Card.",
|
|
51
51
|
parameters: {
|
|
52
52
|
type: "object",
|
|
53
53
|
additionalProperties: false,
|
|
54
54
|
properties: {
|
|
55
55
|
url: { type: "string" },
|
|
56
|
+
apiKey: { type: "string" },
|
|
56
57
|
},
|
|
57
58
|
required: ["url"],
|
|
58
59
|
},
|
|
@@ -61,120 +62,73 @@ function createTools(getService) {
|
|
|
61
62
|
const url = typeof args.url === "string" ? args.url.trim() : "";
|
|
62
63
|
if (!url)
|
|
63
64
|
throw new Error("url is required");
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
};
|
|
68
|
-
/* ── Session tools (multi-turn collaboration) ─────────────────── */
|
|
69
|
-
const multiclawsSessionStart = {
|
|
70
|
-
name: "multiclaws_session_start",
|
|
71
|
-
description: "Start a multi-turn collaboration session with a remote agent. Sends the first message and returns immediately with a sessionId (async). The agent's response will be pushed as a message when ready. Covers both single-turn and multi-turn use cases.",
|
|
72
|
-
parameters: {
|
|
73
|
-
type: "object",
|
|
74
|
-
additionalProperties: false,
|
|
75
|
-
properties: {
|
|
76
|
-
agentUrl: { type: "string" },
|
|
77
|
-
message: { type: "string" },
|
|
78
|
-
},
|
|
79
|
-
required: ["agentUrl", "message"],
|
|
80
|
-
},
|
|
81
|
-
execute: async (_toolCallId, args) => {
|
|
82
|
-
const service = requireService(getService());
|
|
83
|
-
const agentUrl = typeof args.agentUrl === "string" ? args.agentUrl.trim() : "";
|
|
84
|
-
const message = typeof args.message === "string" ? args.message.trim() : "";
|
|
85
|
-
if (!agentUrl || !message)
|
|
86
|
-
throw new Error("agentUrl and message are required");
|
|
87
|
-
const result = await service.startSession({ agentUrl, message });
|
|
88
|
-
return textResult(JSON.stringify(result, null, 2), result);
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
const multiclawsSessionReply = {
|
|
92
|
-
name: "multiclaws_session_reply",
|
|
93
|
-
description: "Send a follow-up message in an existing collaboration session. Use when the remote agent returns 'input-required' or to continue a multi-turn conversation.",
|
|
94
|
-
parameters: {
|
|
95
|
-
type: "object",
|
|
96
|
-
additionalProperties: false,
|
|
97
|
-
properties: {
|
|
98
|
-
sessionId: { type: "string" },
|
|
99
|
-
message: { type: "string" },
|
|
100
|
-
},
|
|
101
|
-
required: ["sessionId", "message"],
|
|
102
|
-
},
|
|
103
|
-
execute: async (_toolCallId, args) => {
|
|
104
|
-
const service = requireService(getService());
|
|
105
|
-
const sessionId = typeof args.sessionId === "string" ? args.sessionId.trim() : "";
|
|
106
|
-
const message = typeof args.message === "string" ? args.message.trim() : "";
|
|
107
|
-
if (!sessionId || !message)
|
|
108
|
-
throw new Error("sessionId and message are required");
|
|
109
|
-
const result = await service.sendSessionMessage({ sessionId, message });
|
|
110
|
-
return textResult(JSON.stringify(result, null, 2), result);
|
|
65
|
+
const apiKey = typeof args.apiKey === "string" ? args.apiKey.trim() : undefined;
|
|
66
|
+
const agent = await service.addAgent({ url, apiKey });
|
|
67
|
+
return textResult(`Agent added: ${agent.name} (${agent.url})`, agent);
|
|
111
68
|
},
|
|
112
69
|
};
|
|
113
|
-
const
|
|
114
|
-
name: "
|
|
115
|
-
description: "
|
|
70
|
+
const multiclawsRemoveAgent = {
|
|
71
|
+
name: "multiclaws_remove_agent",
|
|
72
|
+
description: "Remove a known A2A agent by URL.",
|
|
116
73
|
parameters: {
|
|
117
74
|
type: "object",
|
|
118
75
|
additionalProperties: false,
|
|
119
76
|
properties: {
|
|
120
|
-
|
|
77
|
+
url: { type: "string" },
|
|
121
78
|
},
|
|
79
|
+
required: ["url"],
|
|
122
80
|
},
|
|
123
81
|
execute: async (_toolCallId, args) => {
|
|
124
82
|
const service = requireService(getService());
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return textResult(JSON.stringify(session, null, 2), session);
|
|
131
|
-
}
|
|
132
|
-
const sessions = service.listSessions();
|
|
133
|
-
return textResult(JSON.stringify({ sessions }, null, 2), { sessions });
|
|
83
|
+
const url = typeof args.url === "string" ? args.url.trim() : "";
|
|
84
|
+
if (!url)
|
|
85
|
+
throw new Error("url is required");
|
|
86
|
+
const removed = await service.removeAgent(url);
|
|
87
|
+
return textResult(removed ? `Agent ${url} removed.` : `Agent ${url} not found.`);
|
|
134
88
|
},
|
|
135
89
|
};
|
|
136
|
-
const
|
|
137
|
-
name: "
|
|
138
|
-
description: "
|
|
90
|
+
const multiclawsDelegate = {
|
|
91
|
+
name: "multiclaws_delegate",
|
|
92
|
+
description: "Delegate a task to a remote A2A agent.",
|
|
139
93
|
parameters: {
|
|
140
94
|
type: "object",
|
|
141
95
|
additionalProperties: false,
|
|
142
96
|
properties: {
|
|
143
|
-
|
|
144
|
-
|
|
97
|
+
agentUrl: { type: "string" },
|
|
98
|
+
task: { type: "string" },
|
|
145
99
|
},
|
|
146
|
-
required: ["
|
|
100
|
+
required: ["agentUrl", "task"],
|
|
147
101
|
},
|
|
148
102
|
execute: async (_toolCallId, args) => {
|
|
149
103
|
const service = requireService(getService());
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const timeoutMs = typeof args.timeoutMs === "number" ? args.timeoutMs : undefined;
|
|
156
|
-
const result = await service.waitForSessions({ sessionIds, timeoutMs });
|
|
104
|
+
const agentUrl = typeof args.agentUrl === "string" ? args.agentUrl.trim() : "";
|
|
105
|
+
const task = typeof args.task === "string" ? args.task.trim() : "";
|
|
106
|
+
if (!agentUrl || !task)
|
|
107
|
+
throw new Error("agentUrl and task are required");
|
|
108
|
+
const result = await service.delegateTask({ agentUrl, task });
|
|
157
109
|
return textResult(JSON.stringify(result, null, 2), result);
|
|
158
110
|
},
|
|
159
111
|
};
|
|
160
|
-
const
|
|
161
|
-
name: "
|
|
162
|
-
description: "
|
|
112
|
+
const multiclawsTaskStatus = {
|
|
113
|
+
name: "multiclaws_task_status",
|
|
114
|
+
description: "Check the status of a delegated task.",
|
|
163
115
|
parameters: {
|
|
164
116
|
type: "object",
|
|
165
117
|
additionalProperties: false,
|
|
166
118
|
properties: {
|
|
167
|
-
|
|
119
|
+
taskId: { type: "string" },
|
|
168
120
|
},
|
|
169
|
-
required: ["
|
|
121
|
+
required: ["taskId"],
|
|
170
122
|
},
|
|
171
123
|
execute: async (_toolCallId, args) => {
|
|
172
124
|
const service = requireService(getService());
|
|
173
|
-
const
|
|
174
|
-
if (!
|
|
175
|
-
throw new Error("
|
|
176
|
-
const
|
|
177
|
-
|
|
125
|
+
const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
|
|
126
|
+
if (!taskId)
|
|
127
|
+
throw new Error("taskId is required");
|
|
128
|
+
const task = service.getTaskStatus(taskId);
|
|
129
|
+
if (!task)
|
|
130
|
+
throw new Error(`task not found: ${taskId}`);
|
|
131
|
+
return textResult(JSON.stringify(task, null, 2), task);
|
|
178
132
|
},
|
|
179
133
|
};
|
|
180
134
|
/* ── Team tools ───────────────────────────────────────────────── */
|
|
@@ -324,12 +278,10 @@ function createTools(getService) {
|
|
|
324
278
|
};
|
|
325
279
|
return [
|
|
326
280
|
multiclawsAgents,
|
|
281
|
+
multiclawsAddAgent,
|
|
327
282
|
multiclawsRemoveAgent,
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
multiclawsSessionStatus,
|
|
331
|
-
multiclawsSessionWaitAll,
|
|
332
|
-
multiclawsSessionEnd,
|
|
283
|
+
multiclawsDelegate,
|
|
284
|
+
multiclawsTaskStatus,
|
|
333
285
|
multiclawsTeamCreate,
|
|
334
286
|
multiclawsTeamJoin,
|
|
335
287
|
multiclawsTeamLeave,
|
|
@@ -343,40 +295,25 @@ function createTools(getService) {
|
|
|
343
295
|
const plugin = {
|
|
344
296
|
id: "multiclaws",
|
|
345
297
|
name: "MultiClaws",
|
|
346
|
-
version: "0.
|
|
298
|
+
version: "0.3.1",
|
|
347
299
|
register(api) {
|
|
348
300
|
const config = readConfig(api);
|
|
349
301
|
(0, telemetry_1.initializeTelemetry)({ enableConsoleExporter: config.telemetry?.consoleExporter });
|
|
350
302
|
const structured = (0, logger_1.createStructuredLogger)(api.logger, "multiclaws");
|
|
351
303
|
let service = null;
|
|
352
|
-
// Ensure
|
|
353
|
-
//
|
|
304
|
+
// Ensure required tools are in gateway.tools.allow at registration time
|
|
305
|
+
// so the gateway starts with them already present (no restart needed).
|
|
354
306
|
if (api.config) {
|
|
355
307
|
const gw = api.config.gateway;
|
|
356
308
|
if (gw) {
|
|
357
309
|
const tools = (gw.tools ?? {});
|
|
358
|
-
// 1. Gateway tools the plugin depends on → tools.allow
|
|
359
310
|
const allow = Array.isArray(tools.allow) ? tools.allow : [];
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
if (
|
|
363
|
-
tools.allow = [...allow, ...
|
|
364
|
-
|
|
365
|
-
// 2. Plugin's own tools → tools.alsoAllow (additive, works with any profile)
|
|
366
|
-
const alsoAllow = Array.isArray(tools.alsoAllow) ? tools.alsoAllow : [];
|
|
367
|
-
const pluginToolNames = [
|
|
368
|
-
"multiclaws_agents", "multiclaws_remove_agent",
|
|
369
|
-
"multiclaws_session_start", "multiclaws_session_reply", "multiclaws_session_status",
|
|
370
|
-
"multiclaws_session_wait_all", "multiclaws_session_end",
|
|
371
|
-
"multiclaws_team_create", "multiclaws_team_join", "multiclaws_team_leave", "multiclaws_team_members",
|
|
372
|
-
"multiclaws_profile_set", "multiclaws_profile_show",
|
|
373
|
-
"multiclaws_profile_pending_review", "multiclaws_profile_clear_pending_review",
|
|
374
|
-
];
|
|
375
|
-
const missingPlugin = pluginToolNames.filter((t) => !alsoAllow.includes(t));
|
|
376
|
-
if (missingPlugin.length > 0) {
|
|
377
|
-
tools.alsoAllow = [...alsoAllow, ...missingPlugin];
|
|
311
|
+
const required = ["sessions_spawn", "sessions_history"];
|
|
312
|
+
const missing = required.filter((t) => !allow.includes(t));
|
|
313
|
+
if (missing.length > 0) {
|
|
314
|
+
tools.allow = [...allow, ...missing];
|
|
315
|
+
gw.tools = tools;
|
|
378
316
|
}
|
|
379
|
-
gw.tools = tools;
|
|
380
317
|
}
|
|
381
318
|
}
|
|
382
319
|
const gatewayConfig = (() => {
|
|
@@ -429,17 +366,6 @@ const plugin = {
|
|
|
429
366
|
});
|
|
430
367
|
api.on("gateway_start", () => {
|
|
431
368
|
structured.logger.info("[multiclaws] gateway_start observed");
|
|
432
|
-
// Re-read gateway config in case token became available after initial registration
|
|
433
|
-
if (service && !gatewayConfig) {
|
|
434
|
-
const gw = api.config?.gateway;
|
|
435
|
-
const port = typeof gw?.port === "number" ? gw.port : 18789;
|
|
436
|
-
const token = typeof gw?.auth?.token === "string" ? gw.auth.token : null;
|
|
437
|
-
if (token) {
|
|
438
|
-
const newConfig = { port, token };
|
|
439
|
-
service.updateGatewayConfig(newConfig);
|
|
440
|
-
structured.logger.info("[multiclaws] gateway config updated from gateway_start event");
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
369
|
});
|
|
444
370
|
api.on("gateway_stop", () => {
|
|
445
371
|
structured.logger.info("[multiclaws] gateway_stop observed");
|
|
@@ -452,34 +378,10 @@ const plugin = {
|
|
|
452
378
|
const review = await service.getPendingProfileReview();
|
|
453
379
|
if (!review.pending)
|
|
454
380
|
return;
|
|
455
|
-
const tsStatus = service.getTailscaleStatus();
|
|
456
|
-
let networkLine;
|
|
457
|
-
if (tsStatus === "ready") {
|
|
458
|
-
networkLine = "Tailscale 已检测到,跨网络协作已就绪。";
|
|
459
|
-
}
|
|
460
|
-
else if (tsStatus === "needs_auth") {
|
|
461
|
-
networkLine = "Tailscale 已安装但未登录。跨网络协作需要先完成登录,登录后重启 OpenClaw。局域网内可直接使用。";
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
networkLine = "当前仅支持局域网协作。如需跨网络,安装 Tailscale(https://tailscale.com/download)并登录同一 tailnet,然后重启 OpenClaw。";
|
|
465
|
-
}
|
|
466
381
|
return {
|
|
467
|
-
appendSystemContext: `## MultiClaws 首次安装 —
|
|
468
|
-
|
|
469
|
-
multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程(优先处理,不管用户说了什么)。
|
|
470
|
-
**务必使用插件工具调用,不要通过命令行或 gateway 方法操作。**
|
|
471
|
-
|
|
472
|
-
### 第一步:向用户介绍 MultiClaws
|
|
473
|
-
|
|
474
|
-
简要说明插件功能:
|
|
475
|
-
- MultiClaws 让多个 OpenClaw 实例组成团队协作
|
|
476
|
-
- 你可以创建团队、邀请队友加入,然后把任务委派给队友的 AI
|
|
477
|
-
- 它会根据每个智能体的档案(名字、角色、能力、数据源)自动选择最合适的执行者
|
|
478
|
-
- 支持多轮对话、并发扇出、链式协作等模式
|
|
479
|
-
|
|
480
|
-
### 第二步:设置档案
|
|
382
|
+
appendSystemContext: `## MultiClaws 首次安装 — 需要完成档案设置
|
|
481
383
|
|
|
482
|
-
|
|
384
|
+
multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程(优先处理,不管用户说了什么):
|
|
483
385
|
|
|
484
386
|
1. **扫描当前环境**,自动生成 bio(markdown 格式),内容包括:
|
|
485
387
|
- 用户角色和职责
|
|
@@ -487,16 +389,16 @@ multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程
|
|
|
487
389
|
- 可访问的数据/系统(git 仓库、邮件、日历、Notion 等)
|
|
488
390
|
- 时区和语言
|
|
489
391
|
|
|
490
|
-
2.
|
|
392
|
+
2. 向用户展示生成的档案,并逐一确认以下三项(名字和 bio 需要用户明确回答):
|
|
491
393
|
- **名字**:展示推断出的名字,询问是否需要修改
|
|
492
394
|
- **Bio**:展示生成的 bio,询问是否需要修改
|
|
395
|
+
- **网络情况**:告知用户「同局域网开箱即用;跨网络需安装 Tailscale(https://tailscale.com/download)并重启 OpenClaw」,无需用户回答
|
|
493
396
|
|
|
494
|
-
3.
|
|
397
|
+
3. 根据用户对名字和 bio 的回答更新档案内容(如需修改),然后调用 \`multiclaws_profile_set(ownerName="...", bio="...")\` 保存。
|
|
495
398
|
|
|
496
|
-
|
|
399
|
+
4. 调用 \`multiclaws_profile_clear_pending_review()\` 完成初始化。
|
|
497
400
|
|
|
498
|
-
|
|
499
|
-
- **如何开始**:说「创建一个叫 xxx 的团队」创建团队,把邀请码分享给队友;或说「用邀请码 mc:xxxx 加入团队」加入队友的团队。`,
|
|
401
|
+
**注意**:名字和 bio 需要用户明确确认;网络情况仅告知无需回答。`,
|
|
500
402
|
};
|
|
501
403
|
}
|
|
502
404
|
catch (err) {
|
|
@@ -40,7 +40,6 @@ exports.invokeGatewayTool = invokeGatewayTool;
|
|
|
40
40
|
const opossum_1 = __importDefault(require("opossum"));
|
|
41
41
|
class NonRetryableError extends Error {
|
|
42
42
|
}
|
|
43
|
-
const MAX_BREAKERS = 50;
|
|
44
43
|
const breakerCache = new Map();
|
|
45
44
|
let pRetryModulePromise = null;
|
|
46
45
|
async function loadPRetry() {
|
|
@@ -54,15 +53,6 @@ function getBreaker(key, timeoutMs) {
|
|
|
54
53
|
if (existing) {
|
|
55
54
|
return existing;
|
|
56
55
|
}
|
|
57
|
-
// Evict oldest entries when cache is full
|
|
58
|
-
if (breakerCache.size >= MAX_BREAKERS) {
|
|
59
|
-
const oldest = breakerCache.keys().next().value;
|
|
60
|
-
if (oldest !== undefined) {
|
|
61
|
-
const old = breakerCache.get(oldest);
|
|
62
|
-
old?.shutdown();
|
|
63
|
-
breakerCache.delete(oldest);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
56
|
const breaker = new opossum_1.default((operation) => operation(), {
|
|
67
57
|
timeout: false, // timeout handled by AbortController in the operation
|
|
68
58
|
errorThresholdPercentage: 50,
|
|
@@ -10,7 +10,7 @@ export type TailscaleStatus = {
|
|
|
10
10
|
status: "unavailable";
|
|
11
11
|
reason: string;
|
|
12
12
|
};
|
|
13
|
-
/** Check network interfaces for a Tailscale IP (100.
|
|
13
|
+
/** Check network interfaces for a Tailscale IP (100.x.x.x) — exported for fast-path checks */
|
|
14
14
|
export declare function getTailscaleIpFromInterfaces(): string | null;
|
|
15
15
|
/**
|
|
16
16
|
* Detect Tailscale status — does NOT install or modify system state.
|
package/dist/infra/tailscale.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.getTailscaleIpFromInterfaces = getTailscaleIpFromInterfaces;
|
|
|
7
7
|
exports.detectTailscale = detectTailscale;
|
|
8
8
|
const node_child_process_1 = require("node:child_process");
|
|
9
9
|
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
-
const isWindows = process.platform === "win32";
|
|
11
10
|
function run(cmd, timeoutMs = 5_000) {
|
|
12
11
|
return (0, node_child_process_1.execSync)(cmd, { timeout: timeoutMs, stdio: ["ignore", "pipe", "pipe"] })
|
|
13
12
|
.toString()
|
|
@@ -15,30 +14,21 @@ function run(cmd, timeoutMs = 5_000) {
|
|
|
15
14
|
}
|
|
16
15
|
function commandExists(cmd) {
|
|
17
16
|
try {
|
|
18
|
-
run(
|
|
17
|
+
run(`which ${cmd}`);
|
|
19
18
|
return true;
|
|
20
19
|
}
|
|
21
20
|
catch {
|
|
22
21
|
return false;
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
|
-
/** Check
|
|
26
|
-
function isTailscaleCGNAT(ip) {
|
|
27
|
-
const parts = ip.split(".");
|
|
28
|
-
if (parts.length !== 4)
|
|
29
|
-
return false;
|
|
30
|
-
const first = parseInt(parts[0], 10);
|
|
31
|
-
const second = parseInt(parts[1], 10);
|
|
32
|
-
return first === 100 && second >= 64 && second <= 127;
|
|
33
|
-
}
|
|
34
|
-
/** Check network interfaces for a Tailscale IP (100.64.0.0/10) — exported for fast-path checks */
|
|
24
|
+
/** Check network interfaces for a Tailscale IP (100.x.x.x) — exported for fast-path checks */
|
|
35
25
|
function getTailscaleIpFromInterfaces() {
|
|
36
26
|
const interfaces = node_os_1.default.networkInterfaces();
|
|
37
27
|
for (const addrs of Object.values(interfaces)) {
|
|
38
28
|
if (!addrs)
|
|
39
29
|
continue;
|
|
40
30
|
for (const addr of addrs) {
|
|
41
|
-
if (addr.family === "IPv4" &&
|
|
31
|
+
if (addr.family === "IPv4" && addr.address.startsWith("100.")) {
|
|
42
32
|
return addr.address;
|
|
43
33
|
}
|
|
44
34
|
}
|
|
@@ -71,7 +61,8 @@ async function getAuthUrl() {
|
|
|
71
61
|
return new Promise((resolve) => {
|
|
72
62
|
try {
|
|
73
63
|
// tailscale up prints the auth URL to stderr
|
|
74
|
-
const
|
|
64
|
+
const { spawn } = require("node:child_process");
|
|
65
|
+
const proc = spawn("tailscale", ["up"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
75
66
|
let output = "";
|
|
76
67
|
let resolved = false;
|
|
77
68
|
const tryResolve = (text) => {
|
|
@@ -24,7 +24,6 @@ export declare class OpenClawAgentExecutor implements AgentExecutor {
|
|
|
24
24
|
private gatewayConfig;
|
|
25
25
|
private readonly taskTracker;
|
|
26
26
|
private readonly logger;
|
|
27
|
-
private readonly a2aToTracker;
|
|
28
27
|
constructor(options: A2AAdapterOptions);
|
|
29
28
|
execute(context: RequestContext, eventBus: ExecutionEventBus): Promise<void>;
|
|
30
29
|
/**
|