@botcord/botcord 0.2.2 → 0.2.4-beta.20260329125010
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/README.md +29 -4
- package/index.ts +3 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/botcord/SKILL.md +66 -334
- package/skills/botcord-account/SKILL.md +121 -0
- package/skills/botcord-messaging/SKILL.md +189 -0
- package/skills/botcord-payment/SKILL.md +89 -0
- package/skills/botcord-social/SKILL.md +107 -0
- package/src/client.ts +29 -0
- package/src/commands/bind.ts +4 -3
- package/src/constants.ts +1 -1
- package/src/loop-risk.ts +2 -0
- package/src/tools/account.ts +34 -33
- package/src/tools/api.ts +54 -0
- package/src/tools/bind.ts +10 -29
- package/src/tools/contacts.ts +50 -41
- package/src/tools/directory.ts +10 -31
- package/src/tools/messaging.ts +25 -43
- package/src/tools/notify.ts +30 -32
- package/src/tools/payment.ts +45 -41
- package/src/tools/register.ts +5 -4
- package/src/tools/reset-credential.ts +6 -5
- package/src/tools/rooms.ts +75 -50
- package/src/tools/subscription.ts +59 -46
- package/src/tools/tool-result.ts +122 -0
- package/src/tools/topics.ts +34 -34
- package/src/tools/with-client.ts +93 -0
package/src/tools/account.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_account — Manage the agent's own identity, profile, and settings.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
resolveAccountConfig,
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
12
6
|
|
|
13
7
|
export function createAccountTool() {
|
|
14
8
|
return {
|
|
@@ -41,57 +35,64 @@ export function createAccountTool() {
|
|
|
41
35
|
type: "string" as const,
|
|
42
36
|
description: "Message ID — for message_status",
|
|
43
37
|
},
|
|
38
|
+
dry_run: {
|
|
39
|
+
type: "boolean" as const,
|
|
40
|
+
description: "Preview the request without executing. Returns the API call that would be made.",
|
|
41
|
+
},
|
|
44
42
|
},
|
|
45
43
|
required: ["action"],
|
|
46
44
|
},
|
|
47
45
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
46
|
+
return withClient(async (client) => {
|
|
47
|
+
// Dry-run for write operations
|
|
48
|
+
if (args.dry_run) {
|
|
49
|
+
switch (args.action) {
|
|
50
|
+
case "update_profile": {
|
|
51
|
+
if (!args.display_name && !args.bio) return validationError("At least one of display_name or bio is required");
|
|
52
|
+
const params: Record<string, string> = {};
|
|
53
|
+
if (args.display_name) params.display_name = args.display_name;
|
|
54
|
+
if (args.bio) params.bio = args.bio;
|
|
55
|
+
return dryRunResult("PATCH", `/registry/agents/${client.getAgentId()}/profile`, params) as any;
|
|
56
|
+
}
|
|
57
|
+
case "set_policy":
|
|
58
|
+
if (!args.policy) return validationError("policy is required (open or contacts_only)");
|
|
59
|
+
return dryRunResult("PATCH", `/registry/agents/${client.getAgentId()}/policy`, { message_policy: args.policy }) as any;
|
|
60
|
+
default:
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
60
64
|
|
|
61
|
-
try {
|
|
62
65
|
switch (args.action) {
|
|
63
66
|
case "whoami":
|
|
64
|
-
return await client.resolve(client.getAgentId());
|
|
67
|
+
return await client.resolve(client.getAgentId()) as any;
|
|
65
68
|
|
|
66
69
|
case "update_profile": {
|
|
67
70
|
if (!args.display_name && !args.bio) {
|
|
68
|
-
return
|
|
71
|
+
return validationError("At least one of display_name or bio is required");
|
|
69
72
|
}
|
|
70
73
|
const params: { display_name?: string; bio?: string } = {};
|
|
71
74
|
if (args.display_name) params.display_name = args.display_name;
|
|
72
75
|
if (args.bio) params.bio = args.bio;
|
|
73
76
|
await client.updateProfile(params);
|
|
74
|
-
return { ok: true, updated: params };
|
|
77
|
+
return { ok: true, updated: params } as any;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
case "get_policy":
|
|
78
|
-
return await client.getPolicy();
|
|
81
|
+
return await client.getPolicy() as any;
|
|
79
82
|
|
|
80
83
|
case "set_policy":
|
|
81
|
-
if (!args.policy) return
|
|
84
|
+
if (!args.policy) return validationError("policy is required (open or contacts_only)");
|
|
82
85
|
await client.setPolicy(args.policy);
|
|
83
|
-
return { ok: true, policy: args.policy };
|
|
86
|
+
return { ok: true, policy: args.policy } as any;
|
|
84
87
|
|
|
85
88
|
case "message_status":
|
|
86
|
-
if (!args.msg_id) return
|
|
87
|
-
return await client.getMessageStatus(args.msg_id);
|
|
89
|
+
if (!args.msg_id) return validationError("msg_id is required");
|
|
90
|
+
return await client.getMessageStatus(args.msg_id) as any;
|
|
88
91
|
|
|
89
92
|
default:
|
|
90
|
-
return
|
|
93
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
91
94
|
}
|
|
92
|
-
}
|
|
93
|
-
return { error: `Account action failed: ${err.message}` };
|
|
94
|
-
}
|
|
95
|
+
});
|
|
95
96
|
},
|
|
96
97
|
};
|
|
97
98
|
}
|
package/src/tools/api.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* botcord_api — Raw Hub API access for advanced use cases.
|
|
3
|
+
*
|
|
4
|
+
* This is the "escape hatch" tool: when the structured tools don't cover
|
|
5
|
+
* a particular endpoint, agents can call the Hub API directly.
|
|
6
|
+
*/
|
|
7
|
+
import { withClient } from "./with-client.js";
|
|
8
|
+
import { validationError } from "./tool-result.js";
|
|
9
|
+
|
|
10
|
+
export function createApiTool() {
|
|
11
|
+
return {
|
|
12
|
+
name: "botcord_api",
|
|
13
|
+
label: "Raw API",
|
|
14
|
+
description:
|
|
15
|
+
"Execute a raw authenticated request against the BotCord Hub API. " +
|
|
16
|
+
"Use this when the structured tools (botcord_send, botcord_rooms, etc.) " +
|
|
17
|
+
"don't cover the endpoint you need. The request is automatically authenticated with your agent's JWT.",
|
|
18
|
+
parameters: {
|
|
19
|
+
type: "object" as const,
|
|
20
|
+
properties: {
|
|
21
|
+
method: {
|
|
22
|
+
type: "string" as const,
|
|
23
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
24
|
+
description: "HTTP method",
|
|
25
|
+
},
|
|
26
|
+
path: {
|
|
27
|
+
type: "string" as const,
|
|
28
|
+
description: "API path (e.g. /hub/inbox, /registry/agents/ag_xxx)",
|
|
29
|
+
},
|
|
30
|
+
query: {
|
|
31
|
+
type: "object" as const,
|
|
32
|
+
description: "Query parameters as key-value pairs",
|
|
33
|
+
},
|
|
34
|
+
data: {
|
|
35
|
+
type: "object" as const,
|
|
36
|
+
description: "Request body (for POST/PUT/PATCH)",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["method", "path"],
|
|
40
|
+
},
|
|
41
|
+
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
42
|
+
if (!args.method) return validationError("method is required");
|
|
43
|
+
if (!args.path) return validationError("path is required");
|
|
44
|
+
|
|
45
|
+
return withClient(async (client) => {
|
|
46
|
+
const result = await client.request(args.method, args.path, {
|
|
47
|
+
body: args.data,
|
|
48
|
+
query: args.query,
|
|
49
|
+
});
|
|
50
|
+
return { response: result } as any;
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
package/src/tools/bind.ts
CHANGED
|
@@ -4,14 +4,8 @@
|
|
|
4
4
|
* [POS]: plugin dashboard 认领执行器,把命令行参数翻译成稳定的绑定请求
|
|
5
5
|
* [PROTOCOL]: 变更时更新此头部,然后检查 README.md
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
resolveAccountConfig,
|
|
10
|
-
isAccountConfigured,
|
|
11
|
-
} from "../config.js";
|
|
12
|
-
import { BotCordClient } from "../client.js";
|
|
13
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
14
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
7
|
+
import { withClient } from "./with-client.js";
|
|
8
|
+
import { validationError } from "./tool-result.js";
|
|
15
9
|
|
|
16
10
|
const DEFAULT_DASHBOARD_URL = "https://www.botcord.chat";
|
|
17
11
|
|
|
@@ -21,21 +15,8 @@ const DEFAULT_DASHBOARD_URL = "https://www.botcord.chat";
|
|
|
21
15
|
export async function executeBind(
|
|
22
16
|
bindCredential: string,
|
|
23
17
|
dashboardUrl?: string,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
if (!cfg) return { error: "No configuration available" };
|
|
27
|
-
const singleAccountError = getSingleAccountModeError(cfg);
|
|
28
|
-
if (singleAccountError) return { error: singleAccountError };
|
|
29
|
-
|
|
30
|
-
const acct = resolveAccountConfig(cfg);
|
|
31
|
-
if (!isAccountConfigured(acct)) {
|
|
32
|
-
return { error: "BotCord is not configured." };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const client = new BotCordClient(acct);
|
|
36
|
-
attachTokenPersistence(client, acct);
|
|
37
|
-
|
|
38
|
-
try {
|
|
18
|
+
) {
|
|
19
|
+
return withClient(async (client) => {
|
|
39
20
|
const agentToken = await client.ensureToken();
|
|
40
21
|
const agentId = client.getAgentId();
|
|
41
22
|
|
|
@@ -62,13 +43,13 @@ export async function executeBind(
|
|
|
62
43
|
|
|
63
44
|
if (!res.ok) {
|
|
64
45
|
const msg = body?.error || body?.message || res.statusText;
|
|
65
|
-
|
|
46
|
+
const err = new Error(`Dashboard bind failed (${res.status}): ${msg}`);
|
|
47
|
+
(err as any).status = res.status;
|
|
48
|
+
throw err;
|
|
66
49
|
}
|
|
67
50
|
|
|
68
|
-
return { ok: true, ...body };
|
|
69
|
-
}
|
|
70
|
-
return { error: `Bind failed: ${err.message}` };
|
|
71
|
-
}
|
|
51
|
+
return { ok: true, ...body } as any;
|
|
52
|
+
});
|
|
72
53
|
}
|
|
73
54
|
|
|
74
55
|
export function createBindTool() {
|
|
@@ -93,7 +74,7 @@ export function createBindTool() {
|
|
|
93
74
|
},
|
|
94
75
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
95
76
|
if (!args.bind_ticket) {
|
|
96
|
-
return
|
|
77
|
+
return validationError("bind_ticket is required");
|
|
97
78
|
}
|
|
98
79
|
return executeBind(args.bind_ticket, args.dashboard_url);
|
|
99
80
|
},
|
package/src/tools/contacts.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_contacts — Manage social relationships: contacts, requests, blocks.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
resolveAccountConfig,
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
12
6
|
|
|
13
7
|
export function createContactsTool() {
|
|
14
8
|
return {
|
|
@@ -51,73 +45,88 @@ export function createContactsTool() {
|
|
|
51
45
|
enum: ["pending", "accepted", "rejected"],
|
|
52
46
|
description: "Filter by state — for received_requests, sent_requests",
|
|
53
47
|
},
|
|
48
|
+
dry_run: {
|
|
49
|
+
type: "boolean" as const,
|
|
50
|
+
description: "Preview the request without executing. Returns the API call that would be made.",
|
|
51
|
+
},
|
|
54
52
|
},
|
|
55
53
|
required: ["action"],
|
|
56
54
|
},
|
|
57
55
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
return withClient(async (client) => {
|
|
57
|
+
// Dry-run for write operations
|
|
58
|
+
if (args.dry_run) {
|
|
59
|
+
switch (args.action) {
|
|
60
|
+
case "send_request":
|
|
61
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
62
|
+
return dryRunResult("POST", "/hub/send", { to: args.agent_id, type: "contact_request", payload: args.message ? { text: args.message } : {} }) as any;
|
|
63
|
+
case "remove":
|
|
64
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
65
|
+
return dryRunResult("DELETE", `/registry/agents/{self}/contacts/${args.agent_id}`) as any;
|
|
66
|
+
case "accept_request":
|
|
67
|
+
if (!args.request_id) return validationError("request_id is required");
|
|
68
|
+
return dryRunResult("POST", `/registry/agents/{self}/contact-requests/${args.request_id}/accept`) as any;
|
|
69
|
+
case "reject_request":
|
|
70
|
+
if (!args.request_id) return validationError("request_id is required");
|
|
71
|
+
return dryRunResult("POST", `/registry/agents/{self}/contact-requests/${args.request_id}/reject`) as any;
|
|
72
|
+
case "block":
|
|
73
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
74
|
+
return dryRunResult("POST", `/registry/agents/{self}/blocks`, { blocked_agent_id: args.agent_id }) as any;
|
|
75
|
+
case "unblock":
|
|
76
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
77
|
+
return dryRunResult("DELETE", `/registry/agents/{self}/blocks/${args.agent_id}`) as any;
|
|
78
|
+
default:
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
70
82
|
|
|
71
|
-
try {
|
|
72
83
|
switch (args.action) {
|
|
73
84
|
case "list":
|
|
74
|
-
return await client.listContacts();
|
|
85
|
+
return { contacts: await client.listContacts() } as any;
|
|
75
86
|
|
|
76
87
|
case "remove":
|
|
77
|
-
if (!args.agent_id) return
|
|
88
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
78
89
|
await client.removeContact(args.agent_id);
|
|
79
|
-
return { ok: true, removed: args.agent_id };
|
|
90
|
+
return { ok: true, removed: args.agent_id } as any;
|
|
80
91
|
|
|
81
92
|
case "send_request":
|
|
82
|
-
if (!args.agent_id) return
|
|
93
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
83
94
|
await client.sendContactRequest(args.agent_id, args.message);
|
|
84
|
-
return { ok: true, sent_to: args.agent_id };
|
|
95
|
+
return { ok: true, sent_to: args.agent_id } as any;
|
|
85
96
|
|
|
86
97
|
case "received_requests":
|
|
87
|
-
return await client.listReceivedRequests(args.state);
|
|
98
|
+
return { requests: await client.listReceivedRequests(args.state) } as any;
|
|
88
99
|
|
|
89
100
|
case "sent_requests":
|
|
90
|
-
return await client.listSentRequests(args.state);
|
|
101
|
+
return { requests: await client.listSentRequests(args.state) } as any;
|
|
91
102
|
|
|
92
103
|
case "accept_request":
|
|
93
|
-
if (!args.request_id) return
|
|
104
|
+
if (!args.request_id) return validationError("request_id is required");
|
|
94
105
|
await client.acceptRequest(args.request_id);
|
|
95
|
-
return { ok: true, accepted: args.request_id };
|
|
106
|
+
return { ok: true, accepted: args.request_id } as any;
|
|
96
107
|
|
|
97
108
|
case "reject_request":
|
|
98
|
-
if (!args.request_id) return
|
|
109
|
+
if (!args.request_id) return validationError("request_id is required");
|
|
99
110
|
await client.rejectRequest(args.request_id);
|
|
100
|
-
return { ok: true, rejected: args.request_id };
|
|
111
|
+
return { ok: true, rejected: args.request_id } as any;
|
|
101
112
|
|
|
102
113
|
case "block":
|
|
103
|
-
if (!args.agent_id) return
|
|
114
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
104
115
|
await client.blockAgent(args.agent_id);
|
|
105
|
-
return { ok: true, blocked: args.agent_id };
|
|
116
|
+
return { ok: true, blocked: args.agent_id } as any;
|
|
106
117
|
|
|
107
118
|
case "unblock":
|
|
108
|
-
if (!args.agent_id) return
|
|
119
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
109
120
|
await client.unblockAgent(args.agent_id);
|
|
110
|
-
return { ok: true, unblocked: args.agent_id };
|
|
121
|
+
return { ok: true, unblocked: args.agent_id } as any;
|
|
111
122
|
|
|
112
123
|
case "list_blocks":
|
|
113
|
-
return await client.listBlocks();
|
|
124
|
+
return { blocks: await client.listBlocks() } as any;
|
|
114
125
|
|
|
115
126
|
default:
|
|
116
|
-
return
|
|
127
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
117
128
|
}
|
|
118
|
-
}
|
|
119
|
-
return { error: `Contact action failed: ${err.message}` };
|
|
120
|
-
}
|
|
129
|
+
});
|
|
121
130
|
},
|
|
122
131
|
};
|
|
123
132
|
}
|
package/src/tools/directory.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_directory — Read-only queries: resolve agents, discover rooms, message history.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
resolveAccountConfig,
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError } from "./tool-result.js";
|
|
12
6
|
|
|
13
7
|
export function createDirectoryTool() {
|
|
14
8
|
return {
|
|
@@ -63,30 +57,17 @@ export function createDirectoryTool() {
|
|
|
63
57
|
required: ["action"],
|
|
64
58
|
},
|
|
65
59
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
66
|
-
|
|
67
|
-
if (!cfg) return { error: "No configuration available" };
|
|
68
|
-
const singleAccountError = getSingleAccountModeError(cfg);
|
|
69
|
-
if (singleAccountError) return { error: singleAccountError };
|
|
70
|
-
|
|
71
|
-
const acct = resolveAccountConfig(cfg);
|
|
72
|
-
if (!isAccountConfigured(acct)) {
|
|
73
|
-
return { error: "BotCord is not configured." };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const client = new BotCordClient(acct);
|
|
77
|
-
attachTokenPersistence(client, acct);
|
|
78
|
-
|
|
79
|
-
try {
|
|
60
|
+
return withClient(async (client) => {
|
|
80
61
|
switch (args.action) {
|
|
81
62
|
case "resolve":
|
|
82
|
-
if (!args.agent_id) return
|
|
83
|
-
return await client.resolve(args.agent_id);
|
|
63
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
64
|
+
return await client.resolve(args.agent_id) as any;
|
|
84
65
|
|
|
85
66
|
case "discover_rooms":
|
|
86
|
-
return await client.discoverRooms(args.room_name);
|
|
67
|
+
return { rooms: await client.discoverRooms(args.room_name) } as any;
|
|
87
68
|
|
|
88
69
|
case "history":
|
|
89
|
-
return await client.getHistory({
|
|
70
|
+
return { history: await client.getHistory({
|
|
90
71
|
peer: args.peer,
|
|
91
72
|
roomId: args.room_id,
|
|
92
73
|
topic: args.topic,
|
|
@@ -94,14 +75,12 @@ export function createDirectoryTool() {
|
|
|
94
75
|
before: args.before,
|
|
95
76
|
after: args.after,
|
|
96
77
|
limit: args.limit || 20,
|
|
97
|
-
});
|
|
78
|
+
}) } as any;
|
|
98
79
|
|
|
99
80
|
default:
|
|
100
|
-
return
|
|
81
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
101
82
|
}
|
|
102
|
-
}
|
|
103
|
-
return { error: `Directory action failed: ${err.message}` };
|
|
104
|
-
}
|
|
83
|
+
});
|
|
105
84
|
},
|
|
106
85
|
};
|
|
107
86
|
}
|
package/src/tools/messaging.ts
CHANGED
|
@@ -3,15 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { basename } from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
resolveAccountConfig,
|
|
10
|
-
isAccountConfigured,
|
|
11
|
-
} from "../config.js";
|
|
12
|
-
import { BotCordClient } from "../client.js";
|
|
13
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
14
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
6
|
+
import { withClient } from "./with-client.js";
|
|
7
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
8
|
+
import type { BotCordClient } from "../client.js";
|
|
15
9
|
import type { MessageAttachment } from "../types.js";
|
|
16
10
|
|
|
17
11
|
/** Extract clean filename from a URL, stripping query string and hash. */
|
|
@@ -127,23 +121,27 @@ export function createMessagingTool() {
|
|
|
127
121
|
items: { type: "string" as const },
|
|
128
122
|
description: "URLs of already-hosted files to attach to the message",
|
|
129
123
|
},
|
|
124
|
+
dry_run: {
|
|
125
|
+
type: "boolean" as const,
|
|
126
|
+
description: "Preview the request without sending. Returns the API call that would be made.",
|
|
127
|
+
},
|
|
130
128
|
},
|
|
131
129
|
required: ["to", "text"],
|
|
132
130
|
},
|
|
133
131
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
if (args.dry_run) {
|
|
133
|
+
const msgType = args.type || "message";
|
|
134
|
+
const body: Record<string, unknown> = { to: args.to, text: args.text, type: msgType };
|
|
135
|
+
if (args.topic) body.topic = args.topic;
|
|
136
|
+
if (args.goal) body.goal = args.goal;
|
|
137
|
+
if (args.reply_to) body.reply_to = args.reply_to;
|
|
138
|
+
if (args.mentions) body.mentions = args.mentions;
|
|
139
|
+
if (args.file_paths) body.file_paths = args.file_paths;
|
|
140
|
+
if (args.file_urls) body.file_urls = args.file_urls;
|
|
141
|
+
return dryRunResult("POST", "/hub/send", body);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
const client = new BotCordClient(acct);
|
|
146
|
-
attachTokenPersistence(client, acct);
|
|
144
|
+
return withClient(async (client) => {
|
|
147
145
|
const msgType = args.type || "message";
|
|
148
146
|
|
|
149
147
|
// Collect attachments from both file_paths (upload first) and file_urls
|
|
@@ -172,7 +170,7 @@ export function createMessagingTool() {
|
|
|
172
170
|
mentions: args.mentions,
|
|
173
171
|
attachments: finalAttachments,
|
|
174
172
|
});
|
|
175
|
-
return { ok: true, hub_msg_id: result.hub_msg_id, to: args.to, attachments: finalAttachments };
|
|
173
|
+
return { ok: true, hub_msg_id: result.hub_msg_id, to: args.to, attachments: finalAttachments } as any;
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
// result/error types — use sendTypedMessage for topic termination
|
|
@@ -181,10 +179,8 @@ export function createMessagingTool() {
|
|
|
181
179
|
topic: args.topic,
|
|
182
180
|
attachments: finalAttachments,
|
|
183
181
|
});
|
|
184
|
-
return { ok: true, hub_msg_id: result.hub_msg_id, to: args.to, type: msgType, attachments: finalAttachments };
|
|
185
|
-
}
|
|
186
|
-
return { error: `Failed to send: ${err.message}` };
|
|
187
|
-
}
|
|
182
|
+
return { ok: true, hub_msg_id: result.hub_msg_id, to: args.to, type: msgType, attachments: finalAttachments } as any;
|
|
183
|
+
});
|
|
188
184
|
},
|
|
189
185
|
};
|
|
190
186
|
}
|
|
@@ -212,28 +208,14 @@ export function createUploadTool() {
|
|
|
212
208
|
required: ["file_paths"],
|
|
213
209
|
},
|
|
214
210
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
215
|
-
const cfg = getAppConfig();
|
|
216
|
-
if (!cfg) return { error: "No configuration available" };
|
|
217
|
-
const singleAccountError = getSingleAccountModeError(cfg);
|
|
218
|
-
if (singleAccountError) return { error: singleAccountError };
|
|
219
|
-
|
|
220
|
-
const acct = resolveAccountConfig(cfg);
|
|
221
|
-
if (!isAccountConfigured(acct)) {
|
|
222
|
-
return { error: "BotCord is not configured. Set hubUrl, agentId, keyId, and privateKey." };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
211
|
if (!args.file_paths || args.file_paths.length === 0) {
|
|
226
|
-
return
|
|
212
|
+
return validationError("file_paths is required and must not be empty");
|
|
227
213
|
}
|
|
228
214
|
|
|
229
|
-
|
|
230
|
-
const client = new BotCordClient(acct);
|
|
231
|
-
attachTokenPersistence(client, acct);
|
|
215
|
+
return withClient(async (client) => {
|
|
232
216
|
const uploaded = await uploadLocalFiles(client, args.file_paths);
|
|
233
|
-
return { ok: true, files: uploaded };
|
|
234
|
-
}
|
|
235
|
-
return { error: `Upload failed: ${err.message}` };
|
|
236
|
-
}
|
|
217
|
+
return { ok: true, files: uploaded } as any;
|
|
218
|
+
});
|
|
237
219
|
},
|
|
238
220
|
};
|
|
239
221
|
}
|
package/src/tools/notify.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* is important enough to warrant notifying the owner.
|
|
5
5
|
*/
|
|
6
6
|
import { getBotCordRuntime } from "../runtime.js";
|
|
7
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
8
|
-
import { getSingleAccountModeError, resolveAccountConfig } from "../config.js";
|
|
9
7
|
import { deliverNotification, normalizeNotifySessions } from "../inbound.js";
|
|
8
|
+
import { withConfig } from "./with-client.js";
|
|
9
|
+
import { validationError } from "./tool-result.js";
|
|
10
10
|
|
|
11
11
|
export function createNotifyTool() {
|
|
12
12
|
return {
|
|
@@ -28,40 +28,38 @@ export function createNotifyTool() {
|
|
|
28
28
|
required: ["text"],
|
|
29
29
|
},
|
|
30
30
|
execute: async (toolCallId: any, args: any) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (sessions.length === 0) {
|
|
39
|
-
return { error: "notifySession is not configured in channels.botcord" };
|
|
40
|
-
}
|
|
31
|
+
return withConfig(async (cfg, acct) => {
|
|
32
|
+
const sessions = normalizeNotifySessions(acct.notifySession);
|
|
33
|
+
if (sessions.length === 0) {
|
|
34
|
+
return validationError(
|
|
35
|
+
"notifySession is not configured in channels.botcord",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
const core = getBotCordRuntime();
|
|
40
|
+
const text = typeof args.text === "string" ? args.text.trim() : "";
|
|
41
|
+
if (!text) {
|
|
42
|
+
return validationError("text is required");
|
|
43
|
+
}
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
const errors: string[] = [];
|
|
46
|
+
for (const ns of sessions) {
|
|
47
|
+
try {
|
|
48
|
+
await deliverNotification(core, cfg, ns, text);
|
|
49
|
+
} catch (err: any) {
|
|
50
|
+
errors.push(`${ns}: ${err?.message ?? err}`);
|
|
51
|
+
}
|
|
54
52
|
}
|
|
55
|
-
}
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
if (errors.length > 0) {
|
|
55
|
+
return {
|
|
56
|
+
ok: errors.length < sessions.length,
|
|
57
|
+
notifySessions: sessions,
|
|
58
|
+
errors,
|
|
59
|
+
} as any;
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, notifySessions: sessions } as any;
|
|
62
|
+
});
|
|
65
63
|
},
|
|
66
64
|
};
|
|
67
65
|
}
|