@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/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 — 53 tools loaded");
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
+ }