openclaw-remote 0.2.1 → 0.3.1

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 +265 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2746,6 +2746,52 @@ 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
+ }
2786
+ async function getTeam(account) {
2787
+ return apiFetch(
2788
+ buildUrl(account, "/api/v1/team"),
2789
+ {
2790
+ method: "GET",
2791
+ headers: buildHeaders(account)
2792
+ }
2793
+ );
2794
+ }
2749
2795
 
2750
2796
  // src/realtime-listener.ts
2751
2797
  import { createClient } from "@supabase/supabase-js";
@@ -3205,6 +3251,9 @@ function createRemotePlugin() {
3205
3251
  }),
3206
3252
  assigned_role_id: Type.Optional(
3207
3253
  Type.String({ description: "Role ID to assign the task to" })
3254
+ ),
3255
+ epic_id: Type.Optional(
3256
+ Type.String({ description: "Epic ID to group the task under" })
3208
3257
  )
3209
3258
  }),
3210
3259
  execute: async (_toolCallId, args) => {
@@ -3214,7 +3263,8 @@ function createRemotePlugin() {
3214
3263
  description: args.description,
3215
3264
  type: args.type,
3216
3265
  priority: args.priority,
3217
- assigned_role_id: args.assigned_role_id
3266
+ assigned_role_id: args.assigned_role_id,
3267
+ epic_id: args.epic_id
3218
3268
  });
3219
3269
  if (!result.ok) {
3220
3270
  return {
@@ -3241,7 +3291,7 @@ function createRemotePlugin() {
3241
3291
  {
3242
3292
  name: "remote_update_task",
3243
3293
  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.",
3294
+ 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
3295
  parameters: Type.Object({
3246
3296
  task_id: Type.String({ description: "ID of the task to update" }),
3247
3297
  status: optionalStringEnum(["todo", "in_progress", "review", "done"], {
@@ -3254,7 +3304,16 @@ function createRemotePlugin() {
3254
3304
  Type.String({ description: "User or role to assign the task to" })
3255
3305
  ),
3256
3306
  title: Type.Optional(Type.String({ description: "New task title" })),
3257
- description: Type.Optional(Type.String({ description: "New task description" }))
3307
+ description: Type.Optional(Type.String({ description: "New task description" })),
3308
+ type: optionalStringEnum(["feature", "task", "bug"], {
3309
+ description: "New type: feature, task, or bug"
3310
+ }),
3311
+ epic_id: Type.Optional(
3312
+ Type.String({ description: "Epic ID to group the task under (or null to remove)" })
3313
+ ),
3314
+ assigned_role_id: Type.Optional(
3315
+ Type.String({ description: "Role ID to assign the task to" })
3316
+ )
3258
3317
  }),
3259
3318
  execute: async (_toolCallId, args) => {
3260
3319
  const account = resolveAccount(cfg ?? {});
@@ -3344,27 +3403,205 @@ function createRemotePlugin() {
3344
3403
  parameters: Type.Object({}),
3345
3404
  execute: async (_toolCallId, _args) => {
3346
3405
  const account = resolveAccount(cfg ?? {});
3347
- const result = await getHeartbeat(account);
3406
+ const result = await getBoardHealth(account);
3348
3407
  if (!result.ok) {
3408
+ const legacyResult = await getHeartbeat(account);
3409
+ if (!legacyResult.ok) {
3410
+ return {
3411
+ content: [{ type: "text", text: `\u274C Failed to get board health: ${result.error}` }],
3412
+ details: { ok: false, error: result.error }
3413
+ };
3414
+ }
3415
+ const h2 = legacyResult.data;
3416
+ const text = [
3417
+ `\u{1F4CA} **Board Health Summary** (legacy)`,
3418
+ "",
3419
+ `- **Pending tasks**: ${h2.pending_tasks}`,
3420
+ `- **In progress**: ${h2.in_progress_tasks}`,
3421
+ `- **Unassigned**: ${h2.unassigned_tasks}`,
3422
+ `- **Activity (24h)**: ${h2.recent_activity_24h}`,
3423
+ `- **My roles**: ${h2.my_roles?.join(", ") || "none"}`
3424
+ ].join("\n");
3349
3425
  return {
3350
- content: [{ type: "text", text: `\u274C Failed to get board health: ${result.error}` }],
3351
- details: { ok: false, error: result.error }
3426
+ content: [{ type: "text", text }],
3427
+ details: { ok: true, heartbeat: h2 }
3352
3428
  };
3353
3429
  }
3354
3430
  const h = result.data;
3355
- const text = [
3356
- `\u{1F4CA} **Board Health Summary**`,
3431
+ const s = h.stats;
3432
+ const lines = [
3433
+ `\u{1F4CA} **Board Health \u2014 ${h.project.name}**`,
3357
3434
  "",
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");
3435
+ `**Tasks** (${s.total_tasks} total):`,
3436
+ ` todo: ${s.by_status.todo} | in_progress: ${s.by_status.in_progress} | review: ${s.by_status.review} | done: ${s.by_status.done}`,
3437
+ ` unassigned: ${s.unassigned}`,
3438
+ "",
3439
+ `**By priority**: urgent: ${s.by_priority.urgent} | high: ${s.by_priority.high} | medium: ${s.by_priority.medium} | low: ${s.by_priority.low}`
3440
+ ];
3441
+ if (h.epics.length > 0) {
3442
+ lines.push("", "**Epics**:");
3443
+ for (const e of h.epics) {
3444
+ lines.push(` - ${e.name} (${e.task_count} tasks)`);
3445
+ }
3446
+ }
3447
+ if (h.roles.length > 0) {
3448
+ lines.push("", "**Roles**:");
3449
+ for (const r of h.roles) {
3450
+ const assignee = r.assigned_to ? `${r.assigned_to.type}: ${r.assigned_to.name}` : "vacant";
3451
+ lines.push(` - ${r.name} [${r.category}] \u2192 ${assignee}`);
3452
+ }
3453
+ }
3454
+ if (h.recent_activity.length > 0) {
3455
+ lines.push("", `**Recent activity** (last ${h.recent_activity.length}):`);
3456
+ for (const a of h.recent_activity.slice(0, 5)) {
3457
+ const meta = a.metadata;
3458
+ lines.push(` - ${a.action}: ${meta?.title || a.target_id} (${a.actor_type})`);
3459
+ }
3460
+ }
3461
+ return {
3462
+ content: [{ type: "text", text: lines.join("\n") }],
3463
+ details: { ok: true, health: h }
3464
+ };
3465
+ }
3466
+ },
3467
+ // 5. remote_list_roles
3468
+ {
3469
+ name: "remote_list_roles",
3470
+ label: "List project roles",
3471
+ description: "List project roles with assignment info. Use to find valid assigned_role_id values for task creation.",
3472
+ parameters: Type.Object({}),
3473
+ execute: async (_toolCallId, _args) => {
3474
+ const account = resolveAccount(cfg ?? {});
3475
+ const result = await listRoles(account);
3476
+ if (!result.ok) {
3477
+ return {
3478
+ content: [{ type: "text", text: `\u274C Failed to list roles: ${result.error}` }],
3479
+ details: { ok: false, error: result.error }
3480
+ };
3481
+ }
3482
+ const roles = result.data.roles;
3483
+ if (roles.length === 0) {
3484
+ return {
3485
+ content: [{ type: "text", text: "\u{1F4CB} No roles configured for this project." }],
3486
+ details: { ok: true, roles: [] }
3487
+ };
3488
+ }
3489
+ const lines = [`\u{1F465} **${roles.length} role(s):**`, ""];
3490
+ for (const r of roles) {
3491
+ const assignee = r.assigned_to ? `${r.assigned_to.type}: ${r.assigned_to.name}` : "\u{1F534} vacant";
3492
+ const mine = r.is_mine ? " \u2B50" : "";
3493
+ lines.push(`- **${r.name}** [${r.category}] \u2192 ${assignee}${mine} (id: ${r.id})`);
3494
+ }
3495
+ return {
3496
+ content: [{ type: "text", text: lines.join("\n") }],
3497
+ details: { ok: true, roles }
3498
+ };
3499
+ }
3500
+ },
3501
+ // 6. remote_list_epics
3502
+ {
3503
+ name: "remote_list_epics",
3504
+ label: "List epics",
3505
+ description: "List epics for the project. Use to find valid epic_id values for task grouping.",
3506
+ parameters: Type.Object({}),
3507
+ execute: async (_toolCallId, _args) => {
3508
+ const account = resolveAccount(cfg ?? {});
3509
+ const result = await listEpics(account);
3510
+ if (!result.ok) {
3511
+ return {
3512
+ content: [{ type: "text", text: `\u274C Failed to list epics: ${result.error}` }],
3513
+ details: { ok: false, error: result.error }
3514
+ };
3515
+ }
3516
+ const epics = result.data.epics;
3517
+ if (epics.length === 0) {
3518
+ return {
3519
+ content: [{ type: "text", text: "\u{1F4CB} No epics found. Use `remote_create_epic` to create one." }],
3520
+ details: { ok: true, epics: [] }
3521
+ };
3522
+ }
3523
+ const lines = [`\u{1F4E6} **${epics.length} epic(s):**`, ""];
3524
+ for (const e of epics) {
3525
+ lines.push(`- **${e.name}** (${e.task_count ?? 0} tasks) (id: ${e.id})`);
3526
+ if (e.description) {
3527
+ const desc = e.description.length > 80 ? e.description.slice(0, 80) + "\u2026" : e.description;
3528
+ lines.push(` ${desc}`);
3529
+ }
3530
+ }
3531
+ return {
3532
+ content: [{ type: "text", text: lines.join("\n") }],
3533
+ details: { ok: true, epics }
3534
+ };
3535
+ }
3536
+ },
3537
+ // 7. remote_create_epic
3538
+ {
3539
+ name: "remote_create_epic",
3540
+ label: "Create an epic",
3541
+ description: "Create a new epic on the Remote project board. Specify name, and optionally description and color.",
3542
+ parameters: Type.Object({
3543
+ name: Type.String({ description: "Epic name" }),
3544
+ description: Type.Optional(Type.String({ description: "Epic description" })),
3545
+ color: Type.Optional(Type.String({ description: "Epic color (hex, e.g. #6366f1)" }))
3546
+ }),
3547
+ execute: async (_toolCallId, args) => {
3548
+ const account = resolveAccount(cfg ?? {});
3549
+ const result = await createEpic(account, {
3550
+ name: args.name,
3551
+ description: args.description,
3552
+ color: args.color
3553
+ });
3554
+ if (!result.ok) {
3555
+ return {
3556
+ content: [{ type: "text", text: `\u274C Failed to create epic: ${result.error}` }],
3557
+ details: { ok: false, error: result.error }
3558
+ };
3559
+ }
3560
+ const epic = result.data.epic;
3561
+ const text = [
3562
+ `\u2705 Epic created!`,
3563
+ `- **Name**: ${epic.name}`,
3564
+ `- **ID**: ${epic.id}`,
3565
+ epic.color ? `- **Color**: ${epic.color}` : null
3566
+ ].filter(Boolean).join("\n");
3365
3567
  return {
3366
3568
  content: [{ type: "text", text }],
3367
- details: { ok: true, heartbeat: h }
3569
+ details: { ok: true, epic }
3570
+ };
3571
+ }
3572
+ },
3573
+ // 8. remote_list_team
3574
+ {
3575
+ name: "remote_list_team",
3576
+ label: "List team members",
3577
+ description: "List all team members (humans and agents) with their roles and @mention handles. Use to find who to tag in comments or assign tasks to.",
3578
+ parameters: Type.Object({}),
3579
+ execute: async (_toolCallId, _args) => {
3580
+ const account = resolveAccount(cfg ?? {});
3581
+ const result = await getTeam(account);
3582
+ if (!result.ok) {
3583
+ return {
3584
+ content: [{ type: "text", text: `\u274C Failed to list team: ${result.error}` }],
3585
+ details: { ok: false, error: result.error }
3586
+ };
3587
+ }
3588
+ const { team, total, humans, agents } = result.data;
3589
+ if (team.length === 0) {
3590
+ return {
3591
+ content: [{ type: "text", text: "\u{1F465} No team members found." }],
3592
+ details: { ok: true, team: [] }
3593
+ };
3594
+ }
3595
+ const lines = [`\u{1F465} **Team** (${total} members: ${humans} human, ${agents} agent)`, ""];
3596
+ for (const m of team) {
3597
+ const roles = m.project_roles.length > 0 ? m.project_roles.map((r) => r.name).join(", ") : "no role";
3598
+ const me = m.is_me ? " \u2B50 (you)" : "";
3599
+ const status = m.type === "agent" && m.status ? ` [${m.status}]` : "";
3600
+ lines.push(`- **${m.name}** (${m.type}${status}) \u2014 ${roles} \u2014 mention: \`${m.mention}\`${me}`);
3601
+ }
3602
+ return {
3603
+ content: [{ type: "text", text: lines.join("\n") }],
3604
+ details: { ok: true, team, total, humans, agents }
3368
3605
  };
3369
3606
  }
3370
3607
  }
@@ -3379,10 +3616,14 @@ function createRemotePlugin() {
3379
3616
  "**Replying**: When you reply to a task notification, your reply is posted as a comment on that task.",
3380
3617
  "",
3381
3618
  "**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.)",
3619
+ "- `remote_create_task` \u2014 Create a new task (title, type, priority, assigned_role_id, epic_id)",
3620
+ "- `remote_update_task` \u2014 Update a task (status, priority, title, description, type, epic_id, assigned_role_id)",
3384
3621
  "- `remote_list_tasks` \u2014 List/filter tasks on the board",
3385
- "- `remote_board_health` \u2014 Get board health stats (pending, in-progress, unassigned counts)",
3622
+ "- `remote_board_health` \u2014 Get board health stats (task counts, epics, roles, recent activity)",
3623
+ "- `remote_list_roles` \u2014 List project roles with vacancy info (find valid assigned_role_id values)",
3624
+ "- `remote_list_epics` \u2014 List epics with task counts (find valid epic_id values)",
3625
+ "- `remote_create_epic` \u2014 Create a new epic (name, description, color)",
3626
+ "- `remote_list_team` \u2014 List all team members with roles and @mention handles",
3386
3627
  "",
3387
3628
  "**Task lifecycle**: todo \u2192 in_progress \u2192 review \u2192 done",
3388
3629
  "**Task types**: feature, task, bug",
@@ -3392,7 +3633,11 @@ function createRemotePlugin() {
3392
3633
  "- When assigned a task, acknowledge it and move to in_progress",
3393
3634
  "- Use comments to communicate progress and blockers",
3394
3635
  "- Move tasks to review when ready for review, done when complete",
3395
- "- Keep task descriptions and comments clear and actionable"
3636
+ "- Keep task descriptions and comments clear and actionable",
3637
+ "- Use `remote_list_roles` before creating tasks to find valid role IDs for assignment",
3638
+ "- Use `remote_list_epics` before creating tasks to find valid epic IDs for grouping",
3639
+ "- Use `remote_list_team` to find teammates and their @mention handles for tagging in comments",
3640
+ "- Tag team members with @name in comments when you need their input or want to delegate"
3396
3641
  ]
3397
3642
  }
3398
3643
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-remote",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Remote project board channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",