@kanvas/openclaw-plugin 0.1.16 → 0.1.18
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/domains/deals/index.js +103 -0
- package/dist/domains/deals/types.js +1 -0
- package/dist/domains/events/index.js +157 -0
- package/dist/domains/events/types.js +1 -0
- package/dist/domains/nervousSystem/index.js +203 -0
- package/dist/domains/nervousSystem/types.js +1 -0
- package/dist/domains/orders/index.js +145 -0
- package/dist/domains/orders/types.js +1 -0
- package/dist/index.js +58 -1
- package/dist/tools/deals.js +91 -0
- package/dist/tools/events.js +134 -0
- package/dist/tools/nervousSystem.js +217 -0
- package/dist/tools/orders.js +185 -0
- package/package.json +1 -1
- package/skills/kanvas-nervous-system/SKILL.md +317 -0
package/dist/index.js
CHANGED
|
@@ -5,11 +5,17 @@ import { InventoryService } from "./domains/inventory/index.js";
|
|
|
5
5
|
import { OrdersService } from "./domains/orders/index.js";
|
|
6
6
|
import { SocialService } from "./domains/social/index.js";
|
|
7
7
|
import { EcosystemService } from "./domains/ecosystem/index.js";
|
|
8
|
+
import { DealsService } from "./domains/deals/index.js";
|
|
9
|
+
import { EventsService } from "./domains/events/index.js";
|
|
10
|
+
import { NervousSystemService } from "./domains/nervousSystem/index.js";
|
|
8
11
|
import { registerCrmTools } from "./tools/crm.js";
|
|
9
12
|
import { registerInventoryTools } from "./tools/inventory.js";
|
|
10
13
|
import { registerOrdersTools } from "./tools/orders.js";
|
|
11
14
|
import { registerSocialTools } from "./tools/social.js";
|
|
12
15
|
import { registerEcosystemTools } from "./tools/ecosystem.js";
|
|
16
|
+
import { registerDealsTools } from "./tools/deals.js";
|
|
17
|
+
import { registerEventsTools } from "./tools/events.js";
|
|
18
|
+
import { registerNervousSystemTools } from "./tools/nervousSystem.js";
|
|
13
19
|
import { toolResult } from "./tools/helpers.js";
|
|
14
20
|
const DEFAULT_API_URL = "https://graphapi.kanvas.dev/graphql";
|
|
15
21
|
// OpenClaw v2026.4.5+ calls register() per-agent-context (main, subagents,
|
|
@@ -23,6 +29,9 @@ let sharedInventory = null;
|
|
|
23
29
|
let sharedOrders = null;
|
|
24
30
|
let sharedSocial = null;
|
|
25
31
|
let sharedEcosystem = null;
|
|
32
|
+
let sharedDeals = null;
|
|
33
|
+
let sharedEvents = null;
|
|
34
|
+
let sharedNervousSystem = null;
|
|
26
35
|
let startupBannerShown = false;
|
|
27
36
|
let skipBannerShown = false;
|
|
28
37
|
function resolveConfig(pluginConfig) {
|
|
@@ -115,6 +124,9 @@ export default {
|
|
|
115
124
|
sharedOrders = new OrdersService(sharedClient);
|
|
116
125
|
sharedSocial = new SocialService(sharedClient);
|
|
117
126
|
sharedEcosystem = new EcosystemService(sharedClient);
|
|
127
|
+
sharedDeals = new DealsService(sharedClient);
|
|
128
|
+
sharedEvents = new EventsService(sharedClient);
|
|
129
|
+
sharedNervousSystem = new NervousSystemService(sharedClient);
|
|
118
130
|
}
|
|
119
131
|
// Tools and hooks must be registered on every api object — each one is
|
|
120
132
|
// a separate agent context (main, subagents, cron lanes).
|
|
@@ -124,6 +136,9 @@ export default {
|
|
|
124
136
|
registerOrdersTools(api, sharedOrders, ensureAuth);
|
|
125
137
|
registerSocialTools(api, sharedSocial, ensureAuth);
|
|
126
138
|
registerEcosystemTools(api, sharedEcosystem, ensureAuth);
|
|
139
|
+
registerDealsTools(api, sharedDeals, ensureAuth);
|
|
140
|
+
registerEventsTools(api, sharedEvents, ensureAuth);
|
|
141
|
+
registerNervousSystemTools(api, sharedNervousSystem, ensureAuth);
|
|
127
142
|
api.registerTool({
|
|
128
143
|
name: "kanvas_test_connection",
|
|
129
144
|
label: "Test Connection",
|
|
@@ -148,7 +163,7 @@ export default {
|
|
|
148
163
|
}, { commands: ["setup"] });
|
|
149
164
|
if (!startupBannerShown) {
|
|
150
165
|
startupBannerShown = true;
|
|
151
|
-
api.logger.info("Kanvas plugin registered —
|
|
166
|
+
api.logger.info("Kanvas plugin registered — 84 tools loaded");
|
|
152
167
|
}
|
|
153
168
|
},
|
|
154
169
|
};
|
|
@@ -222,6 +237,16 @@ Use when the user asks about products, stock, warehouses, or catalog.
|
|
|
222
237
|
Use when the user asks about orders, purchases, or sales.
|
|
223
238
|
- \`kanvas_search_orders\` → find orders by number or keyword
|
|
224
239
|
- \`kanvas_get_order\` → full order detail with items, customer, status
|
|
240
|
+
- \`kanvas_list_order_statuses\` → get status slugs for transitions
|
|
241
|
+
- \`kanvas_list_order_types\` → order type pipelines (standard, subscription, etc.)
|
|
242
|
+
- \`kanvas_list_regions\` → regions for order creation (get region_id)
|
|
243
|
+
- \`kanvas_create_draft_order\` → create a draft order. Requires email, customer, region_id (from kanvas_list_regions), and items with variant_id+quantity (from kanvas_list_variants)
|
|
244
|
+
- \`kanvas_update_order\` → update items, status, metadata
|
|
245
|
+
- \`kanvas_update_draft_order_status\` → change draft order status (PENDING, COMPLETED, DRAFT, CANCELED, FAILED)
|
|
246
|
+
- \`kanvas_transition_order_status\` → move through the order status pipeline (use status_slug from kanvas_list_order_statuses)
|
|
247
|
+
- \`kanvas_order_change_customer\` → change the customer on an order (requires xKanvasKey)
|
|
248
|
+
- \`kanvas_delete_order\` → delete an order
|
|
249
|
+
- \`kanvas_send_order_email\` → send a confirmation/receipt email (requires xKanvasKey)
|
|
225
250
|
|
|
226
251
|
**Follow-ups & Reminders**
|
|
227
252
|
ALWAYS schedule follow-ups in Kanvas so the human team can see them — NEVER store them only in local memory.
|
|
@@ -229,6 +254,23 @@ ALWAYS schedule follow-ups in Kanvas so the human team can see them — NEVER st
|
|
|
229
254
|
- \`kanvas_list_events\` → list scheduled events/follow-ups
|
|
230
255
|
- For structured follow-up data (tracking status, priority, custom fields), use \`kanvas_create_message\` with verb "follow_up" and a JSON payload containing { due_date, lead_id, action, status, priority }.
|
|
231
256
|
|
|
257
|
+
**Deals (Sales Opportunities)**
|
|
258
|
+
Use when the user asks about deals, opportunities, sales tracking. A deal is a potential sale, often tied to a lead/person/organization and moved through a pipeline.
|
|
259
|
+
- \`kanvas_list_deals\` → list/search deals with owner, pipeline, stage, status
|
|
260
|
+
- \`kanvas_get_deal\` → full deal detail including tags and custom fields
|
|
261
|
+
- \`kanvas_create_deal\` → create a deal (only title required; optionally link to lead, person, org, pipeline, owner)
|
|
262
|
+
- \`kanvas_update_deal\` → update title, description, pipeline stage, owner, linked entities
|
|
263
|
+
- \`kanvas_delete_deal\` → delete a deal
|
|
264
|
+
|
|
265
|
+
**Events (Full event management)**
|
|
266
|
+
For simple follow-ups linked to a lead, prefer \`kanvas_create_follow_up\`. Use these when the user wants richer event management (categories, types, resources, tags).
|
|
267
|
+
- \`kanvas_list_events_full\` → list events with versions and dates
|
|
268
|
+
- \`kanvas_get_event\` → event detail including versions, tags, custom fields
|
|
269
|
+
- \`kanvas_create_event\` → create an event with one or more dates. Supports linking to resources (leads, deals), categories, types, tags.
|
|
270
|
+
- \`kanvas_update_event\` → update name, description, dates, status
|
|
271
|
+
- \`kanvas_delete_event\` → delete an event
|
|
272
|
+
- \`kanvas_follow_event\` / \`kanvas_unfollow_event\` → subscribe/unsubscribe user to event updates
|
|
273
|
+
|
|
232
274
|
**Ecosystem (Companies, Branches, Roles, Users)**
|
|
233
275
|
Use when the user asks about companies, branches/locations, user management, roles, or permissions.
|
|
234
276
|
- \`kanvas_list_companies\` → list/search companies
|
|
@@ -242,6 +284,21 @@ Use when the user asks about companies, branches/locations, user management, rol
|
|
|
242
284
|
- \`kanvas_assign_role_to_user\` → assign roles (e.g. super admin). Call \`kanvas_list_roles\` first to get role IDs.
|
|
243
285
|
- \`kanvas_remove_role_from_user\` → remove a role from a user
|
|
244
286
|
|
|
287
|
+
**Nervous System (Plans, Tasks, Activities)**
|
|
288
|
+
This is how humans assign work to you. Read the \`nervous-system-working\` skill for the full protocol. Treat every non-trivial request as a Plan; plan first, execute second. Move statuses deliberately — humans watch the kanban.
|
|
289
|
+
- \`kanvas_list_my_plans\` → find work assigned to you (filter by statuses: draft/awaiting_approval/active/blocked)
|
|
290
|
+
- \`kanvas_get_plan\` → full plan detail (description, input, tasks, files, parent)
|
|
291
|
+
- \`kanvas_create_plan\` → create plan or sub-plan, optionally with seeded tasks
|
|
292
|
+
- \`kanvas_update_plan\` → transition status (draft→active→done/blocked/failed), set output JSON, attach files
|
|
293
|
+
- \`kanvas_approve_plan\` → approve/reject a plan in awaiting_approval
|
|
294
|
+
- \`kanvas_add_task\` → append a task to the checklist (sequence determines order)
|
|
295
|
+
- \`kanvas_update_task_status\` → pending → in_progress → done (or blocked/failed)
|
|
296
|
+
- \`kanvas_list_agent_capabilities\` → list skills/tools granted to an agent
|
|
297
|
+
- \`kanvas_delete_plan\` / \`kanvas_delete_task\` → delete
|
|
298
|
+
- For Activities (the conversation channel), use \`kanvas_create_message\` with \`channel_slug = plan.uuid\` and \`message_verb: "comment"\`. Read with \`kanvas_list_channel_messages\` using the same slug.
|
|
299
|
+
- **Always re-read the channel** before acting on a plan you've worked on before.
|
|
300
|
+
- **Never act** on a plan with \`requires_human_approval=true\` until it's flipped to active.
|
|
301
|
+
|
|
245
302
|
**Diagnostics**
|
|
246
303
|
- \`kanvas_test_connection\` → verify the API is reachable
|
|
247
304
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { toolResult } from "./helpers.js";
|
|
3
|
+
const WhereClause = Type.Optional(Type.Array(Type.Object({
|
|
4
|
+
column: Type.String(),
|
|
5
|
+
operator: Type.String({ description: 'e.g. "EQ", "LIKE"' }),
|
|
6
|
+
value: Type.Unknown(),
|
|
7
|
+
}), { description: "Filter conditions" }));
|
|
8
|
+
export function registerDealsTools(api, service, ensureAuth) {
|
|
9
|
+
api.registerTool({
|
|
10
|
+
name: "kanvas_list_deals",
|
|
11
|
+
label: "List Deals",
|
|
12
|
+
description: "List or search deals. A deal represents a potential sale/opportunity linked to a lead, person, or organization.",
|
|
13
|
+
parameters: Type.Object({
|
|
14
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
|
|
15
|
+
search: Type.Optional(Type.String({ description: "Search keyword" })),
|
|
16
|
+
where: WhereClause,
|
|
17
|
+
}),
|
|
18
|
+
async execute(_id, params) {
|
|
19
|
+
await ensureAuth();
|
|
20
|
+
return toolResult(await service.listDeals(params.first, params.search, params.where));
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
api.registerTool({
|
|
24
|
+
name: "kanvas_get_deal",
|
|
25
|
+
label: "Get Deal",
|
|
26
|
+
description: "Get full details for a deal by ID, including pipeline stage, owner, linked lead/person/org, tags, and custom fields.",
|
|
27
|
+
parameters: Type.Object({
|
|
28
|
+
id: Type.String({ description: "Deal ID" }),
|
|
29
|
+
}),
|
|
30
|
+
async execute(_id, params) {
|
|
31
|
+
await ensureAuth();
|
|
32
|
+
return toolResult(await service.getDeal(params.id));
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
api.registerTool({
|
|
36
|
+
name: "kanvas_create_deal",
|
|
37
|
+
label: "Create Deal",
|
|
38
|
+
description: "Create a deal. Only title is required. Optionally link to a lead, person, organization, or pipeline stage.",
|
|
39
|
+
parameters: Type.Object({
|
|
40
|
+
title: Type.String({ description: "Deal title" }),
|
|
41
|
+
description: Type.Optional(Type.String()),
|
|
42
|
+
leads_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Linked lead ID" })),
|
|
43
|
+
people_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Linked person ID" })),
|
|
44
|
+
organization_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
45
|
+
owner_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Deal owner user ID" })),
|
|
46
|
+
pipeline_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
47
|
+
pipeline_stage_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
48
|
+
status_id: Type.Optional(Type.Number()),
|
|
49
|
+
companies_branches_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
50
|
+
}),
|
|
51
|
+
async execute(_id, params) {
|
|
52
|
+
await ensureAuth();
|
|
53
|
+
return toolResult(await service.createDeal(params));
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
api.registerTool({
|
|
57
|
+
name: "kanvas_update_deal",
|
|
58
|
+
label: "Update Deal",
|
|
59
|
+
description: "Update a deal's title, description, pipeline stage, owner, or linked entities.",
|
|
60
|
+
parameters: Type.Object({
|
|
61
|
+
id: Type.String({ description: "Deal ID" }),
|
|
62
|
+
title: Type.Optional(Type.String()),
|
|
63
|
+
description: Type.Optional(Type.String()),
|
|
64
|
+
leads_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
65
|
+
people_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
66
|
+
organization_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
67
|
+
owner_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
68
|
+
pipeline_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
69
|
+
pipeline_stage_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
70
|
+
status_id: Type.Optional(Type.Number()),
|
|
71
|
+
companies_branches_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
72
|
+
}),
|
|
73
|
+
async execute(_id, params) {
|
|
74
|
+
await ensureAuth();
|
|
75
|
+
const { id, ...input } = params;
|
|
76
|
+
return toolResult(await service.updateDeal(id, input));
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
api.registerTool({
|
|
80
|
+
name: "kanvas_delete_deal",
|
|
81
|
+
label: "Delete Deal",
|
|
82
|
+
description: "Delete a deal by ID.",
|
|
83
|
+
parameters: Type.Object({
|
|
84
|
+
id: Type.String({ description: "Deal ID" }),
|
|
85
|
+
}),
|
|
86
|
+
async execute(_id, params) {
|
|
87
|
+
await ensureAuth();
|
|
88
|
+
return toolResult(await service.deleteDeal(params.id));
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { toolResult } from "./helpers.js";
|
|
3
|
+
const WhereClause = Type.Optional(Type.Array(Type.Object({
|
|
4
|
+
column: Type.String(),
|
|
5
|
+
operator: Type.String({ description: 'e.g. "EQ", "LIKE"' }),
|
|
6
|
+
value: Type.Unknown(),
|
|
7
|
+
}), { description: "Filter conditions" }));
|
|
8
|
+
const EventDateInput = Type.Object({
|
|
9
|
+
date: Type.String({ description: "YYYY-MM-DD" }),
|
|
10
|
+
start_time: Type.String({ description: "HH:MM (24h)" }),
|
|
11
|
+
end_time: Type.String({ description: "HH:MM (24h)" }),
|
|
12
|
+
});
|
|
13
|
+
const EventResourceInput = Type.Object({
|
|
14
|
+
resources_id: Type.String({ description: "Entity ID this event is attached to" }),
|
|
15
|
+
resources_type: Type.String({ description: 'e.g. "lead", "deal"' }),
|
|
16
|
+
});
|
|
17
|
+
const TagInput = Type.Object({
|
|
18
|
+
name: Type.String(),
|
|
19
|
+
slug: Type.Optional(Type.String()),
|
|
20
|
+
});
|
|
21
|
+
export function registerEventsTools(api, service, ensureAuth) {
|
|
22
|
+
api.registerTool({
|
|
23
|
+
name: "kanvas_list_events_full",
|
|
24
|
+
label: "List Events (full)",
|
|
25
|
+
description: "List or search events with full details. For follow-ups only, see kanvas_list_events.",
|
|
26
|
+
parameters: Type.Object({
|
|
27
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
|
|
28
|
+
search: Type.Optional(Type.String({ description: "Search keyword" })),
|
|
29
|
+
where: WhereClause,
|
|
30
|
+
}),
|
|
31
|
+
async execute(_id, params) {
|
|
32
|
+
await ensureAuth();
|
|
33
|
+
return toolResult(await service.listEvents(params.first, params.search, params.where));
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
api.registerTool({
|
|
37
|
+
name: "kanvas_get_event",
|
|
38
|
+
label: "Get Event",
|
|
39
|
+
description: "Get full details for an event by ID, including versions, dates, tags, and custom fields.",
|
|
40
|
+
parameters: Type.Object({
|
|
41
|
+
id: Type.String({ description: "Event ID" }),
|
|
42
|
+
}),
|
|
43
|
+
async execute(_id, params) {
|
|
44
|
+
await ensureAuth();
|
|
45
|
+
return toolResult(await service.getEvent(params.id));
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
api.registerTool({
|
|
49
|
+
name: "kanvas_create_event",
|
|
50
|
+
label: "Create Event",
|
|
51
|
+
description: "Create an event with dates. Supports linking to resources (leads, deals) and categorization. For a simple lead follow-up, use kanvas_create_follow_up.",
|
|
52
|
+
parameters: Type.Object({
|
|
53
|
+
name: Type.String({ description: "Event name" }),
|
|
54
|
+
slug: Type.Optional(Type.String()),
|
|
55
|
+
description: Type.Optional(Type.String()),
|
|
56
|
+
dates: Type.Array(EventDateInput, { description: "At least one date (YYYY-MM-DD + HH:MM times)" }),
|
|
57
|
+
type_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
58
|
+
status_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
59
|
+
class_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
60
|
+
category_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
61
|
+
theme_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
62
|
+
theme_area_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
63
|
+
resources: Type.Optional(Type.Array(EventResourceInput)),
|
|
64
|
+
participants: Type.Optional(Type.Array(Type.String())),
|
|
65
|
+
tags: Type.Optional(Type.Array(TagInput)),
|
|
66
|
+
custom_fields: Type.Optional(Type.Array(Type.Record(Type.String(), Type.Unknown()))),
|
|
67
|
+
}),
|
|
68
|
+
async execute(_id, params) {
|
|
69
|
+
await ensureAuth();
|
|
70
|
+
return toolResult(await service.createEvent(params));
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
api.registerTool({
|
|
74
|
+
name: "kanvas_update_event",
|
|
75
|
+
label: "Update Event",
|
|
76
|
+
description: "Update an event's name, description, dates, status, or linked resources.",
|
|
77
|
+
parameters: Type.Object({
|
|
78
|
+
id: Type.String({ description: "Event ID" }),
|
|
79
|
+
name: Type.Optional(Type.String()),
|
|
80
|
+
description: Type.Optional(Type.String()),
|
|
81
|
+
dates: Type.Optional(Type.Array(EventDateInput)),
|
|
82
|
+
type_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
83
|
+
status_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
84
|
+
class_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
85
|
+
category_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
86
|
+
resources_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
87
|
+
resources_type: Type.Optional(Type.String()),
|
|
88
|
+
tags: Type.Optional(Type.Array(TagInput)),
|
|
89
|
+
}),
|
|
90
|
+
async execute(_id, params) {
|
|
91
|
+
await ensureAuth();
|
|
92
|
+
const { id, ...input } = params;
|
|
93
|
+
return toolResult(await service.updateEvent(id, input));
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
api.registerTool({
|
|
97
|
+
name: "kanvas_delete_event",
|
|
98
|
+
label: "Delete Event",
|
|
99
|
+
description: "Delete an event by ID.",
|
|
100
|
+
parameters: Type.Object({
|
|
101
|
+
id: Type.String({ description: "Event ID" }),
|
|
102
|
+
}),
|
|
103
|
+
async execute(_id, params) {
|
|
104
|
+
await ensureAuth();
|
|
105
|
+
return toolResult(await service.deleteEvent(params.id));
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
api.registerTool({
|
|
109
|
+
name: "kanvas_follow_event",
|
|
110
|
+
label: "Follow Event",
|
|
111
|
+
description: "Subscribe a user to event updates.",
|
|
112
|
+
parameters: Type.Object({
|
|
113
|
+
entity_id: Type.String({ description: "Event UUID" }),
|
|
114
|
+
user_id: Type.Union([Type.String(), Type.Number()], { description: "User ID" }),
|
|
115
|
+
}),
|
|
116
|
+
async execute(_id, params) {
|
|
117
|
+
await ensureAuth();
|
|
118
|
+
return toolResult(await service.followEvent(params));
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
api.registerTool({
|
|
122
|
+
name: "kanvas_unfollow_event",
|
|
123
|
+
label: "Unfollow Event",
|
|
124
|
+
description: "Unsubscribe a user from event updates.",
|
|
125
|
+
parameters: Type.Object({
|
|
126
|
+
entity_id: Type.String({ description: "Event UUID" }),
|
|
127
|
+
user_id: Type.Union([Type.String(), Type.Number()], { description: "User ID" }),
|
|
128
|
+
}),
|
|
129
|
+
async execute(_id, params) {
|
|
130
|
+
await ensureAuth();
|
|
131
|
+
return toolResult(await service.unFollowEvent(params));
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { toolResult } from "./helpers.js";
|
|
3
|
+
const TaskInputSchema = Type.Object({
|
|
4
|
+
title: Type.String({ description: "Task title (verb phrase, e.g. 'Crawl /features')" }),
|
|
5
|
+
sequence: Type.Optional(Type.Number({ description: "Execution order (0, 1, 2...)" })),
|
|
6
|
+
description: Type.Optional(Type.String()),
|
|
7
|
+
status: Type.Optional(Type.String({ description: "pending | in_progress | blocked | done | failed | cancelled" })),
|
|
8
|
+
result: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
9
|
+
blocked_reason: Type.Optional(Type.String()),
|
|
10
|
+
});
|
|
11
|
+
const FilesInputSchema = Type.Array(Type.Object({
|
|
12
|
+
url: Type.String({ description: "Public URL of the file" }),
|
|
13
|
+
name: Type.String({ description: "Display name" }),
|
|
14
|
+
field_name: Type.Optional(Type.String()),
|
|
15
|
+
}), { description: "Files to attach (appended, not replaced)" });
|
|
16
|
+
const PlanStatusSchema = Type.String({
|
|
17
|
+
description: "Plan status: draft | awaiting_approval | active | blocked | done | failed | cancelled",
|
|
18
|
+
});
|
|
19
|
+
const TaskStatusSchema = Type.String({
|
|
20
|
+
description: "Task status: pending | in_progress | blocked | done | failed | cancelled",
|
|
21
|
+
});
|
|
22
|
+
export function registerNervousSystemTools(api, service, ensureAuth) {
|
|
23
|
+
// ── Read ───────────────────────────────────────────────────
|
|
24
|
+
api.registerTool({
|
|
25
|
+
name: "kanvas_list_my_plans",
|
|
26
|
+
label: "List My Plans",
|
|
27
|
+
description: "List nervous-system plans assigned to an agent. Filter by statuses to see open work " +
|
|
28
|
+
"(e.g. ['draft','awaiting_approval','active','blocked']). Returns plans ordered by priority desc, deadline asc.",
|
|
29
|
+
parameters: Type.Object({
|
|
30
|
+
agent_id: Type.Union([Type.String(), Type.Number()], { description: "The agent's ID" }),
|
|
31
|
+
statuses: Type.Optional(Type.Array(Type.String(), {
|
|
32
|
+
description: "Statuses to include. Default: all.",
|
|
33
|
+
})),
|
|
34
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
35
|
+
}),
|
|
36
|
+
async execute(_id, params) {
|
|
37
|
+
await ensureAuth();
|
|
38
|
+
return toolResult(await service.listMyPlans(params));
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
api.registerTool({
|
|
42
|
+
name: "kanvas_list_plans",
|
|
43
|
+
label: "List Plans (admin)",
|
|
44
|
+
description: "List nervous-system plans with arbitrary where filters. Allowed where columns: " +
|
|
45
|
+
"ID, UUID, AGENT_ID, USERS_ID, ENTITY_NAMESPACE, ENTITY_ID, PLAN_TYPE, STATUS, PRIORITY, PARENT_PLAN_ID.",
|
|
46
|
+
parameters: Type.Object({
|
|
47
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
|
|
48
|
+
where: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
49
|
+
orderBy: Type.Optional(Type.Array(Type.Object({
|
|
50
|
+
column: Type.String(),
|
|
51
|
+
order: Type.String({ description: "ASC or DESC" }),
|
|
52
|
+
}))),
|
|
53
|
+
}),
|
|
54
|
+
async execute(_id, params) {
|
|
55
|
+
await ensureAuth();
|
|
56
|
+
return toolResult(await service.listPlans(params));
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
api.registerTool({
|
|
60
|
+
name: "kanvas_get_plan",
|
|
61
|
+
label: "Get Plan",
|
|
62
|
+
description: "Get full plan detail: description, input/output, tasks, files, tags, agent, user, parent, completion %.",
|
|
63
|
+
parameters: Type.Object({
|
|
64
|
+
id: Type.String({ description: "Plan ID" }),
|
|
65
|
+
}),
|
|
66
|
+
async execute(_id, params) {
|
|
67
|
+
await ensureAuth();
|
|
68
|
+
return toolResult(await service.getPlan(params.id));
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
api.registerTool({
|
|
72
|
+
name: "kanvas_list_agent_capabilities",
|
|
73
|
+
label: "List Agent Capabilities",
|
|
74
|
+
description: "List the skills and tools an agent has been granted. Use this to verify you can do something before agreeing to a plan.",
|
|
75
|
+
parameters: Type.Object({
|
|
76
|
+
agent_id: Type.String({ description: "Agent ID" }),
|
|
77
|
+
framework: Type.Optional(Type.String({ description: "Framework filter (optional)" })),
|
|
78
|
+
}),
|
|
79
|
+
async execute(_id, params) {
|
|
80
|
+
await ensureAuth();
|
|
81
|
+
return toolResult(await service.getAgentCapabilities(params.agent_id, params.framework));
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
// ── Plan write ─────────────────────────────────────────────
|
|
85
|
+
api.registerTool({
|
|
86
|
+
name: "kanvas_create_plan",
|
|
87
|
+
label: "Create Plan",
|
|
88
|
+
description: "Create a nervous-system plan (or sub-plan when parent_plan_id is provided). " +
|
|
89
|
+
"Include a tasks array to seed the checklist on creation. For sensitive work, set requires_human_approval=true " +
|
|
90
|
+
"to gate the plan in awaiting_approval status until a human approves.",
|
|
91
|
+
parameters: Type.Object({
|
|
92
|
+
title: Type.String(),
|
|
93
|
+
plan_type: Type.String({ description: 'e.g. "data_migration", "outreach", "report_generation"' }),
|
|
94
|
+
description: Type.Optional(Type.String()),
|
|
95
|
+
agent_id: Type.Optional(Type.Number()),
|
|
96
|
+
users_id: Type.Optional(Type.Number()),
|
|
97
|
+
parent_plan_id: Type.Optional(Type.Number()),
|
|
98
|
+
entity_namespace: Type.Optional(Type.String({ description: "FQCN of the related entity (e.g. App\\\\Models\\\\Lead)" })),
|
|
99
|
+
entity_id: Type.Optional(Type.Number()),
|
|
100
|
+
status: Type.Optional(PlanStatusSchema),
|
|
101
|
+
priority: Type.Optional(Type.Number({ description: "Higher = more important" })),
|
|
102
|
+
deadline_at: Type.Optional(Type.String({ description: "ISO datetime" })),
|
|
103
|
+
input: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
104
|
+
output: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
105
|
+
confidence_score: Type.Optional(Type.Number()),
|
|
106
|
+
requires_human_approval: Type.Optional(Type.Boolean()),
|
|
107
|
+
tasks: Type.Optional(Type.Array(TaskInputSchema)),
|
|
108
|
+
files: Type.Optional(FilesInputSchema),
|
|
109
|
+
}),
|
|
110
|
+
async execute(_id, params) {
|
|
111
|
+
await ensureAuth();
|
|
112
|
+
return toolResult(await service.createPlan(params));
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
api.registerTool({
|
|
116
|
+
name: "kanvas_update_plan",
|
|
117
|
+
label: "Update Plan",
|
|
118
|
+
description: "Update a plan's status, title, description, output, or attach files. " +
|
|
119
|
+
"Use status transitions to signal progress: draft→active to start, active→blocked when stuck, active→done/failed when finished.",
|
|
120
|
+
parameters: Type.Object({
|
|
121
|
+
id: Type.String({ description: "Plan ID" }),
|
|
122
|
+
title: Type.Optional(Type.String()),
|
|
123
|
+
description: Type.Optional(Type.String()),
|
|
124
|
+
status: Type.Optional(PlanStatusSchema),
|
|
125
|
+
priority: Type.Optional(Type.Number()),
|
|
126
|
+
deadline_at: Type.Optional(Type.String()),
|
|
127
|
+
input: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
128
|
+
output: Type.Optional(Type.Record(Type.String(), Type.Unknown()), {
|
|
129
|
+
description: "Final output JSON (set this before flipping to done)",
|
|
130
|
+
}),
|
|
131
|
+
confidence_score: Type.Optional(Type.Number()),
|
|
132
|
+
requires_human_approval: Type.Optional(Type.Boolean()),
|
|
133
|
+
files: Type.Optional(FilesInputSchema),
|
|
134
|
+
}),
|
|
135
|
+
async execute(_id, params) {
|
|
136
|
+
await ensureAuth();
|
|
137
|
+
const { id, ...input } = params;
|
|
138
|
+
return toolResult(await service.updatePlan(id, input));
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
api.registerTool({
|
|
142
|
+
name: "kanvas_approve_plan",
|
|
143
|
+
label: "Approve Plan",
|
|
144
|
+
description: "Approve or reject a plan that is in awaiting_approval status. Approving flips it to active; rejecting flips it to cancelled.",
|
|
145
|
+
parameters: Type.Object({
|
|
146
|
+
id: Type.String({ description: "Plan ID" }),
|
|
147
|
+
approved: Type.Boolean({ description: "true to approve, false to reject" }),
|
|
148
|
+
review_outcome: Type.Optional(Type.String({ description: "Optional reviewer note" })),
|
|
149
|
+
}),
|
|
150
|
+
async execute(_id, params) {
|
|
151
|
+
await ensureAuth();
|
|
152
|
+
return toolResult(await service.approvePlan(params.id, {
|
|
153
|
+
approved: params.approved,
|
|
154
|
+
review_outcome: params.review_outcome,
|
|
155
|
+
}));
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
api.registerTool({
|
|
159
|
+
name: "kanvas_delete_plan",
|
|
160
|
+
label: "Delete Plan",
|
|
161
|
+
description: "Delete a plan by ID.",
|
|
162
|
+
parameters: Type.Object({
|
|
163
|
+
id: Type.String({ description: "Plan ID" }),
|
|
164
|
+
}),
|
|
165
|
+
async execute(_id, params) {
|
|
166
|
+
await ensureAuth();
|
|
167
|
+
return toolResult(await service.deletePlan(params.id));
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
// ── Task write ─────────────────────────────────────────────
|
|
171
|
+
api.registerTool({
|
|
172
|
+
name: "kanvas_add_task",
|
|
173
|
+
label: "Add Task to Plan",
|
|
174
|
+
description: "Append a task to a plan's checklist. Sequence determines execution order (0, 1, 2...).",
|
|
175
|
+
parameters: Type.Object({
|
|
176
|
+
plan_id: Type.String({ description: "Plan ID" }),
|
|
177
|
+
title: Type.String({ description: "Task title (verb phrase)" }),
|
|
178
|
+
sequence: Type.Optional(Type.Number()),
|
|
179
|
+
description: Type.Optional(Type.String()),
|
|
180
|
+
status: Type.Optional(TaskStatusSchema),
|
|
181
|
+
}),
|
|
182
|
+
async execute(_id, params) {
|
|
183
|
+
await ensureAuth();
|
|
184
|
+
const { plan_id, ...input } = params;
|
|
185
|
+
return toolResult(await service.addTask(plan_id, input));
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
api.registerTool({
|
|
189
|
+
name: "kanvas_update_task_status",
|
|
190
|
+
label: "Update Task Status",
|
|
191
|
+
description: "Move a task through its lifecycle: pending → in_progress → done (or blocked/failed/cancelled). " +
|
|
192
|
+
"Set 'result' as JSON when done; set 'blocked_reason' when blocked.",
|
|
193
|
+
parameters: Type.Object({
|
|
194
|
+
id: Type.String({ description: "Task ID" }),
|
|
195
|
+
status: TaskStatusSchema,
|
|
196
|
+
result: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
197
|
+
blocked_reason: Type.Optional(Type.String()),
|
|
198
|
+
}),
|
|
199
|
+
async execute(_id, params) {
|
|
200
|
+
await ensureAuth();
|
|
201
|
+
const { id, ...input } = params;
|
|
202
|
+
return toolResult(await service.updateTaskStatus(id, input));
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
api.registerTool({
|
|
206
|
+
name: "kanvas_delete_task",
|
|
207
|
+
label: "Delete Task",
|
|
208
|
+
description: "Delete a task by ID.",
|
|
209
|
+
parameters: Type.Object({
|
|
210
|
+
id: Type.String({ description: "Task ID" }),
|
|
211
|
+
}),
|
|
212
|
+
async execute(_id, params) {
|
|
213
|
+
await ensureAuth();
|
|
214
|
+
return toolResult(await service.deleteTask(params.id));
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|