openclaw-remote 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +218 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2746,6 +2746,43 @@ async function getHeartbeat(account) {
2746
2746
  }
2747
2747
  );
2748
2748
  }
2749
+ async function listRoles(account) {
2750
+ return apiFetch(
2751
+ buildUrl(account, "/api/v1/roles"),
2752
+ {
2753
+ method: "GET",
2754
+ headers: buildHeaders(account)
2755
+ }
2756
+ );
2757
+ }
2758
+ async function listEpics(account) {
2759
+ return apiFetch(
2760
+ buildUrl(account, "/api/v1/epics"),
2761
+ {
2762
+ method: "GET",
2763
+ headers: buildHeaders(account)
2764
+ }
2765
+ );
2766
+ }
2767
+ async function createEpic(account, epic) {
2768
+ return apiFetch(
2769
+ buildUrl(account, "/api/v1/epics"),
2770
+ {
2771
+ method: "POST",
2772
+ headers: buildHeaders(account),
2773
+ body: JSON.stringify(epic)
2774
+ }
2775
+ );
2776
+ }
2777
+ async function getBoardHealth(account) {
2778
+ return apiFetch(
2779
+ buildUrl(account, "/api/v1/board/health"),
2780
+ {
2781
+ method: "GET",
2782
+ headers: buildHeaders(account)
2783
+ }
2784
+ );
2785
+ }
2749
2786
 
2750
2787
  // src/realtime-listener.ts
2751
2788
  import { createClient } from "@supabase/supabase-js";
@@ -3205,6 +3242,9 @@ function createRemotePlugin() {
3205
3242
  }),
3206
3243
  assigned_role_id: Type.Optional(
3207
3244
  Type.String({ description: "Role ID to assign the task to" })
3245
+ ),
3246
+ epic_id: Type.Optional(
3247
+ Type.String({ description: "Epic ID to group the task under" })
3208
3248
  )
3209
3249
  }),
3210
3250
  execute: async (_toolCallId, args) => {
@@ -3214,7 +3254,8 @@ function createRemotePlugin() {
3214
3254
  description: args.description,
3215
3255
  type: args.type,
3216
3256
  priority: args.priority,
3217
- assigned_role_id: args.assigned_role_id
3257
+ assigned_role_id: args.assigned_role_id,
3258
+ epic_id: args.epic_id
3218
3259
  });
3219
3260
  if (!result.ok) {
3220
3261
  return {
@@ -3241,7 +3282,7 @@ function createRemotePlugin() {
3241
3282
  {
3242
3283
  name: "remote_update_task",
3243
3284
  label: "Update a task on the Remote project board",
3244
- description: "Update an existing task on the Remote project board. Specify the task_id and any fields to change: status (todo/in_progress/review/done), priority, assigned_to, title, or description.",
3285
+ description: "Update an existing task on the Remote project board. Specify the task_id and any fields to change: status (todo/in_progress/review/done), priority, assigned_to, title, description, type, epic_id, or assigned_role_id.",
3245
3286
  parameters: Type.Object({
3246
3287
  task_id: Type.String({ description: "ID of the task to update" }),
3247
3288
  status: optionalStringEnum(["todo", "in_progress", "review", "done"], {
@@ -3254,7 +3295,16 @@ function createRemotePlugin() {
3254
3295
  Type.String({ description: "User or role to assign the task to" })
3255
3296
  ),
3256
3297
  title: Type.Optional(Type.String({ description: "New task title" })),
3257
- description: Type.Optional(Type.String({ description: "New task description" }))
3298
+ description: Type.Optional(Type.String({ description: "New task description" })),
3299
+ type: optionalStringEnum(["feature", "task", "bug"], {
3300
+ description: "New type: feature, task, or bug"
3301
+ }),
3302
+ epic_id: Type.Optional(
3303
+ Type.String({ description: "Epic ID to group the task under (or null to remove)" })
3304
+ ),
3305
+ assigned_role_id: Type.Optional(
3306
+ Type.String({ description: "Role ID to assign the task to" })
3307
+ )
3258
3308
  }),
3259
3309
  execute: async (_toolCallId, args) => {
3260
3310
  const account = resolveAccount(cfg ?? {});
@@ -3344,27 +3394,170 @@ function createRemotePlugin() {
3344
3394
  parameters: Type.Object({}),
3345
3395
  execute: async (_toolCallId, _args) => {
3346
3396
  const account = resolveAccount(cfg ?? {});
3347
- const result = await getHeartbeat(account);
3397
+ const result = await getBoardHealth(account);
3348
3398
  if (!result.ok) {
3399
+ const legacyResult = await getHeartbeat(account);
3400
+ if (!legacyResult.ok) {
3401
+ return {
3402
+ content: [{ type: "text", text: `\u274C Failed to get board health: ${result.error}` }],
3403
+ details: { ok: false, error: result.error }
3404
+ };
3405
+ }
3406
+ const h2 = legacyResult.data;
3407
+ const text = [
3408
+ `\u{1F4CA} **Board Health Summary** (legacy)`,
3409
+ "",
3410
+ `- **Pending tasks**: ${h2.pending_tasks}`,
3411
+ `- **In progress**: ${h2.in_progress_tasks}`,
3412
+ `- **Unassigned**: ${h2.unassigned_tasks}`,
3413
+ `- **Activity (24h)**: ${h2.recent_activity_24h}`,
3414
+ `- **My roles**: ${h2.my_roles?.join(", ") || "none"}`
3415
+ ].join("\n");
3349
3416
  return {
3350
- content: [{ type: "text", text: `\u274C Failed to get board health: ${result.error}` }],
3351
- details: { ok: false, error: result.error }
3417
+ content: [{ type: "text", text }],
3418
+ details: { ok: true, heartbeat: h2 }
3352
3419
  };
3353
3420
  }
3354
3421
  const h = result.data;
3355
- const text = [
3356
- `\u{1F4CA} **Board Health Summary**`,
3422
+ const s = h.stats;
3423
+ const lines = [
3424
+ `\u{1F4CA} **Board Health \u2014 ${h.project.name}**`,
3357
3425
  "",
3358
- `- **Pending tasks**: ${h.pending_tasks}`,
3359
- `- **In progress**: ${h.in_progress_tasks}`,
3360
- `- **Unassigned**: ${h.unassigned_tasks}`,
3361
- `- **Activity (24h)**: ${h.recent_activity_24h}`,
3362
- `- **My roles**: ${h.my_roles?.join(", ") || "none"}`,
3363
- `- **Checked at**: ${h.checked_at}`
3364
- ].join("\n");
3426
+ `**Tasks** (${s.total_tasks} total):`,
3427
+ ` todo: ${s.by_status.todo} | in_progress: ${s.by_status.in_progress} | review: ${s.by_status.review} | done: ${s.by_status.done}`,
3428
+ ` unassigned: ${s.unassigned}`,
3429
+ "",
3430
+ `**By priority**: urgent: ${s.by_priority.urgent} | high: ${s.by_priority.high} | medium: ${s.by_priority.medium} | low: ${s.by_priority.low}`
3431
+ ];
3432
+ if (h.epics.length > 0) {
3433
+ lines.push("", "**Epics**:");
3434
+ for (const e of h.epics) {
3435
+ lines.push(` - ${e.name} (${e.task_count} tasks)`);
3436
+ }
3437
+ }
3438
+ if (h.roles.length > 0) {
3439
+ lines.push("", "**Roles**:");
3440
+ for (const r of h.roles) {
3441
+ const assignee = r.assigned_to ? `${r.assigned_to.type}: ${r.assigned_to.name}` : "vacant";
3442
+ lines.push(` - ${r.name} [${r.category}] \u2192 ${assignee}`);
3443
+ }
3444
+ }
3445
+ if (h.recent_activity.length > 0) {
3446
+ lines.push("", `**Recent activity** (last ${h.recent_activity.length}):`);
3447
+ for (const a of h.recent_activity.slice(0, 5)) {
3448
+ const meta = a.metadata;
3449
+ lines.push(` - ${a.action}: ${meta?.title || a.target_id} (${a.actor_type})`);
3450
+ }
3451
+ }
3452
+ return {
3453
+ content: [{ type: "text", text: lines.join("\n") }],
3454
+ details: { ok: true, health: h }
3455
+ };
3456
+ }
3457
+ },
3458
+ // 5. remote_list_roles
3459
+ {
3460
+ name: "remote_list_roles",
3461
+ label: "List project roles",
3462
+ description: "List project roles with assignment info. Use to find valid assigned_role_id values for task creation.",
3463
+ parameters: Type.Object({}),
3464
+ execute: async (_toolCallId, _args) => {
3465
+ const account = resolveAccount(cfg ?? {});
3466
+ const result = await listRoles(account);
3467
+ if (!result.ok) {
3468
+ return {
3469
+ content: [{ type: "text", text: `\u274C Failed to list roles: ${result.error}` }],
3470
+ details: { ok: false, error: result.error }
3471
+ };
3472
+ }
3473
+ const roles = result.data.roles;
3474
+ if (roles.length === 0) {
3475
+ return {
3476
+ content: [{ type: "text", text: "\u{1F4CB} No roles configured for this project." }],
3477
+ details: { ok: true, roles: [] }
3478
+ };
3479
+ }
3480
+ const lines = [`\u{1F465} **${roles.length} role(s):**`, ""];
3481
+ for (const r of roles) {
3482
+ const assignee = r.assigned_to ? `${r.assigned_to.type}: ${r.assigned_to.name}` : "\u{1F534} vacant";
3483
+ const mine = r.is_mine ? " \u2B50" : "";
3484
+ lines.push(`- **${r.name}** [${r.category}] \u2192 ${assignee}${mine} (id: ${r.id})`);
3485
+ }
3486
+ return {
3487
+ content: [{ type: "text", text: lines.join("\n") }],
3488
+ details: { ok: true, roles }
3489
+ };
3490
+ }
3491
+ },
3492
+ // 6. remote_list_epics
3493
+ {
3494
+ name: "remote_list_epics",
3495
+ label: "List epics",
3496
+ description: "List epics for the project. Use to find valid epic_id values for task grouping.",
3497
+ parameters: Type.Object({}),
3498
+ execute: async (_toolCallId, _args) => {
3499
+ const account = resolveAccount(cfg ?? {});
3500
+ const result = await listEpics(account);
3501
+ if (!result.ok) {
3502
+ return {
3503
+ content: [{ type: "text", text: `\u274C Failed to list epics: ${result.error}` }],
3504
+ details: { ok: false, error: result.error }
3505
+ };
3506
+ }
3507
+ const epics = result.data.epics;
3508
+ if (epics.length === 0) {
3509
+ return {
3510
+ content: [{ type: "text", text: "\u{1F4CB} No epics found. Use `remote_create_epic` to create one." }],
3511
+ details: { ok: true, epics: [] }
3512
+ };
3513
+ }
3514
+ const lines = [`\u{1F4E6} **${epics.length} epic(s):**`, ""];
3515
+ for (const e of epics) {
3516
+ lines.push(`- **${e.name}** (${e.task_count ?? 0} tasks) (id: ${e.id})`);
3517
+ if (e.description) {
3518
+ const desc = e.description.length > 80 ? e.description.slice(0, 80) + "\u2026" : e.description;
3519
+ lines.push(` ${desc}`);
3520
+ }
3521
+ }
3522
+ return {
3523
+ content: [{ type: "text", text: lines.join("\n") }],
3524
+ details: { ok: true, epics }
3525
+ };
3526
+ }
3527
+ },
3528
+ // 7. remote_create_epic
3529
+ {
3530
+ name: "remote_create_epic",
3531
+ label: "Create an epic",
3532
+ description: "Create a new epic on the Remote project board. Specify name, and optionally description and color.",
3533
+ parameters: Type.Object({
3534
+ name: Type.String({ description: "Epic name" }),
3535
+ description: Type.Optional(Type.String({ description: "Epic description" })),
3536
+ color: Type.Optional(Type.String({ description: "Epic color (hex, e.g. #6366f1)" }))
3537
+ }),
3538
+ execute: async (_toolCallId, args) => {
3539
+ const account = resolveAccount(cfg ?? {});
3540
+ const result = await createEpic(account, {
3541
+ name: args.name,
3542
+ description: args.description,
3543
+ color: args.color
3544
+ });
3545
+ if (!result.ok) {
3546
+ return {
3547
+ content: [{ type: "text", text: `\u274C Failed to create epic: ${result.error}` }],
3548
+ details: { ok: false, error: result.error }
3549
+ };
3550
+ }
3551
+ const epic = result.data.epic;
3552
+ const text = [
3553
+ `\u2705 Epic created!`,
3554
+ `- **Name**: ${epic.name}`,
3555
+ `- **ID**: ${epic.id}`,
3556
+ epic.color ? `- **Color**: ${epic.color}` : null
3557
+ ].filter(Boolean).join("\n");
3365
3558
  return {
3366
3559
  content: [{ type: "text", text }],
3367
- details: { ok: true, heartbeat: h }
3560
+ details: { ok: true, epic }
3368
3561
  };
3369
3562
  }
3370
3563
  }
@@ -3379,10 +3572,13 @@ function createRemotePlugin() {
3379
3572
  "**Replying**: When you reply to a task notification, your reply is posted as a comment on that task.",
3380
3573
  "",
3381
3574
  "**Available tools**:",
3382
- "- `remote_create_task` \u2014 Create a new task (specify title, type, priority, etc.)",
3383
- "- `remote_update_task` \u2014 Update a task (change status, priority, assignment, etc.)",
3575
+ "- `remote_create_task` \u2014 Create a new task (title, type, priority, assigned_role_id, epic_id)",
3576
+ "- `remote_update_task` \u2014 Update a task (status, priority, title, description, type, epic_id, assigned_role_id)",
3384
3577
  "- `remote_list_tasks` \u2014 List/filter tasks on the board",
3385
- "- `remote_board_health` \u2014 Get board health stats (pending, in-progress, unassigned counts)",
3578
+ "- `remote_board_health` \u2014 Get board health stats (task counts, epics, roles, recent activity)",
3579
+ "- `remote_list_roles` \u2014 List project roles with vacancy info (find valid assigned_role_id values)",
3580
+ "- `remote_list_epics` \u2014 List epics with task counts (find valid epic_id values)",
3581
+ "- `remote_create_epic` \u2014 Create a new epic (name, description, color)",
3386
3582
  "",
3387
3583
  "**Task lifecycle**: todo \u2192 in_progress \u2192 review \u2192 done",
3388
3584
  "**Task types**: feature, task, bug",
@@ -3392,7 +3588,9 @@ function createRemotePlugin() {
3392
3588
  "- When assigned a task, acknowledge it and move to in_progress",
3393
3589
  "- Use comments to communicate progress and blockers",
3394
3590
  "- Move tasks to review when ready for review, done when complete",
3395
- "- Keep task descriptions and comments clear and actionable"
3591
+ "- Keep task descriptions and comments clear and actionable",
3592
+ "- Use `remote_list_roles` before creating tasks to find valid role IDs for assignment",
3593
+ "- Use `remote_list_epics` before creating tasks to find valid epic IDs for grouping"
3396
3594
  ]
3397
3595
  }
3398
3596
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-remote",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Remote project board channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",