openclaw-remote 0.3.0 → 0.4.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.
- package/dist/index.js +116 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2783,9 +2783,47 @@ async function getBoardHealth(account) {
|
|
|
2783
2783
|
}
|
|
2784
2784
|
);
|
|
2785
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
|
+
}
|
|
2786
2795
|
|
|
2787
2796
|
// src/realtime-listener.ts
|
|
2788
2797
|
import { createClient } from "@supabase/supabase-js";
|
|
2798
|
+
var leaderTimers = /* @__PURE__ */ new Map();
|
|
2799
|
+
var LEADER_DEBOUNCE_MS = 6e4;
|
|
2800
|
+
var leaderRolesCache = [];
|
|
2801
|
+
var leaderRolesCacheTime = 0;
|
|
2802
|
+
var LEADER_CACHE_TTL = 5 * 6e4;
|
|
2803
|
+
async function fetchLeaderRoles(supabase, log) {
|
|
2804
|
+
const now = Date.now();
|
|
2805
|
+
if (now - leaderRolesCacheTime < LEADER_CACHE_TTL && leaderRolesCache.length > 0) {
|
|
2806
|
+
return leaderRolesCache;
|
|
2807
|
+
}
|
|
2808
|
+
const { data: roles } = await supabase.from("project_roles").select("id").eq("is_leader", true);
|
|
2809
|
+
if (!roles || roles.length === 0) {
|
|
2810
|
+
leaderRolesCache = [];
|
|
2811
|
+
leaderRolesCacheTime = now;
|
|
2812
|
+
return [];
|
|
2813
|
+
}
|
|
2814
|
+
const roleIds = roles.map((r) => r.id);
|
|
2815
|
+
const { data: assignments } = await supabase.from("agent_role_assignments").select("project_role_id, agent_id").in("project_role_id", roleIds);
|
|
2816
|
+
const map = /* @__PURE__ */ new Map();
|
|
2817
|
+
for (const a of assignments ?? []) {
|
|
2818
|
+
const list = map.get(a.project_role_id) || [];
|
|
2819
|
+
list.push(a.agent_id);
|
|
2820
|
+
map.set(a.project_role_id, list);
|
|
2821
|
+
}
|
|
2822
|
+
leaderRolesCache = roleIds.map((id) => ({ roleId: id, agentIds: map.get(id) || [] }));
|
|
2823
|
+
leaderRolesCacheTime = now;
|
|
2824
|
+
log?.info?.(`Leader roles cache refreshed: ${leaderRolesCache.length} leader role(s)`);
|
|
2825
|
+
return leaderRolesCache;
|
|
2826
|
+
}
|
|
2789
2827
|
function connectRealtime(account, onEvent, onError, log) {
|
|
2790
2828
|
const { supabaseUrl, supabaseKey } = account;
|
|
2791
2829
|
if (!supabaseUrl || !supabaseKey) {
|
|
@@ -2810,6 +2848,7 @@ function connectRealtime(account, onEvent, onError, log) {
|
|
|
2810
2848
|
const row = payload.new;
|
|
2811
2849
|
if (row.agent_author_id && !row.author_id) {
|
|
2812
2850
|
log?.info?.(`Skipping agent-authored comment on task ${row.task_id} (echo suppression)`);
|
|
2851
|
+
scheduleLeaderNotification(row.task_id, row.agent_author_id, supabase, log);
|
|
2813
2852
|
return;
|
|
2814
2853
|
}
|
|
2815
2854
|
const event = {
|
|
@@ -2845,11 +2884,49 @@ function connectRealtime(account, onEvent, onError, log) {
|
|
|
2845
2884
|
return {
|
|
2846
2885
|
unsubscribe: async () => {
|
|
2847
2886
|
log?.info?.("Unsubscribing from Supabase Realtime...");
|
|
2887
|
+
for (const timer of leaderTimers.values()) clearTimeout(timer);
|
|
2888
|
+
leaderTimers.clear();
|
|
2848
2889
|
await supabase.removeChannel(channel);
|
|
2849
2890
|
log?.info?.("Realtime listener shut down cleanly.");
|
|
2850
2891
|
}
|
|
2851
2892
|
};
|
|
2852
2893
|
}
|
|
2894
|
+
async function scheduleLeaderNotification(taskId, commentingAgentId, supabase, log) {
|
|
2895
|
+
const existing = leaderTimers.get(taskId);
|
|
2896
|
+
if (existing) clearTimeout(existing);
|
|
2897
|
+
const timer = setTimeout(async () => {
|
|
2898
|
+
leaderTimers.delete(taskId);
|
|
2899
|
+
try {
|
|
2900
|
+
const leaders = await fetchLeaderRoles(supabase, log);
|
|
2901
|
+
if (leaders.length === 0) return;
|
|
2902
|
+
const { data: task } = await supabase.from("tasks").select("id, title, assigned_role_id").eq("id", taskId).single();
|
|
2903
|
+
if (!task) return;
|
|
2904
|
+
for (const leader of leaders) {
|
|
2905
|
+
if (task.assigned_role_id === leader.roleId) {
|
|
2906
|
+
log?.info?.(`Skipping leader notification for task ${taskId} \u2014 assigned to leader role ${leader.roleId}`);
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
if (leader.agentIds.includes(commentingAgentId)) continue;
|
|
2910
|
+
if (leader.agentIds.length === 0) continue;
|
|
2911
|
+
const { error } = await supabase.from("task_comments").insert({
|
|
2912
|
+
task_id: taskId,
|
|
2913
|
+
author_id: null,
|
|
2914
|
+
agent_author_id: null,
|
|
2915
|
+
body: `\u{1F440} Activity on this task \u2014 leader review needed`
|
|
2916
|
+
});
|
|
2917
|
+
if (error) {
|
|
2918
|
+
log?.warn?.(`Failed to insert leader notification for task ${taskId}: ${error.message}`);
|
|
2919
|
+
} else {
|
|
2920
|
+
log?.info?.(`Leader notification sent for task ${taskId}`);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
} catch (err) {
|
|
2924
|
+
log?.warn?.(`Leader notification error for task ${taskId}: ${err}`);
|
|
2925
|
+
}
|
|
2926
|
+
}, LEADER_DEBOUNCE_MS);
|
|
2927
|
+
leaderTimers.set(taskId, timer);
|
|
2928
|
+
log?.info?.(`Leader debounce timer set for task ${taskId} (60s)`);
|
|
2929
|
+
}
|
|
2853
2930
|
|
|
2854
2931
|
// src/event-formatter.ts
|
|
2855
2932
|
function formatEvent(event) {
|
|
@@ -3560,6 +3637,41 @@ function createRemotePlugin() {
|
|
|
3560
3637
|
details: { ok: true, epic }
|
|
3561
3638
|
};
|
|
3562
3639
|
}
|
|
3640
|
+
},
|
|
3641
|
+
// 8. remote_list_team
|
|
3642
|
+
{
|
|
3643
|
+
name: "remote_list_team",
|
|
3644
|
+
label: "List team members",
|
|
3645
|
+
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.",
|
|
3646
|
+
parameters: Type.Object({}),
|
|
3647
|
+
execute: async (_toolCallId, _args) => {
|
|
3648
|
+
const account = resolveAccount(cfg ?? {});
|
|
3649
|
+
const result = await getTeam(account);
|
|
3650
|
+
if (!result.ok) {
|
|
3651
|
+
return {
|
|
3652
|
+
content: [{ type: "text", text: `\u274C Failed to list team: ${result.error}` }],
|
|
3653
|
+
details: { ok: false, error: result.error }
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
const { team, total, humans, agents } = result.data;
|
|
3657
|
+
if (team.length === 0) {
|
|
3658
|
+
return {
|
|
3659
|
+
content: [{ type: "text", text: "\u{1F465} No team members found." }],
|
|
3660
|
+
details: { ok: true, team: [] }
|
|
3661
|
+
};
|
|
3662
|
+
}
|
|
3663
|
+
const lines = [`\u{1F465} **Team** (${total} members: ${humans} human, ${agents} agent)`, ""];
|
|
3664
|
+
for (const m of team) {
|
|
3665
|
+
const roles = m.project_roles.length > 0 ? m.project_roles.map((r) => r.name).join(", ") : "no role";
|
|
3666
|
+
const me = m.is_me ? " \u2B50 (you)" : "";
|
|
3667
|
+
const status = m.type === "agent" && m.status ? ` [${m.status}]` : "";
|
|
3668
|
+
lines.push(`- **${m.name}** (${m.type}${status}) \u2014 ${roles} \u2014 mention: \`${m.mention}\`${me}`);
|
|
3669
|
+
}
|
|
3670
|
+
return {
|
|
3671
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
3672
|
+
details: { ok: true, team, total, humans, agents }
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3563
3675
|
}
|
|
3564
3676
|
];
|
|
3565
3677
|
}),
|
|
@@ -3579,6 +3691,7 @@ function createRemotePlugin() {
|
|
|
3579
3691
|
"- `remote_list_roles` \u2014 List project roles with vacancy info (find valid assigned_role_id values)",
|
|
3580
3692
|
"- `remote_list_epics` \u2014 List epics with task counts (find valid epic_id values)",
|
|
3581
3693
|
"- `remote_create_epic` \u2014 Create a new epic (name, description, color)",
|
|
3694
|
+
"- `remote_list_team` \u2014 List all team members with roles and @mention handles",
|
|
3582
3695
|
"",
|
|
3583
3696
|
"**Task lifecycle**: todo \u2192 in_progress \u2192 review \u2192 done",
|
|
3584
3697
|
"**Task types**: feature, task, bug",
|
|
@@ -3590,7 +3703,9 @@ function createRemotePlugin() {
|
|
|
3590
3703
|
"- Move tasks to review when ready for review, done when complete",
|
|
3591
3704
|
"- Keep task descriptions and comments clear and actionable",
|
|
3592
3705
|
"- 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"
|
|
3706
|
+
"- Use `remote_list_epics` before creating tasks to find valid epic IDs for grouping",
|
|
3707
|
+
"- Use `remote_list_team` to find teammates and their @mention handles for tagging in comments",
|
|
3708
|
+
"- Tag team members with @name in comments when you need their input or want to delegate"
|
|
3594
3709
|
]
|
|
3595
3710
|
}
|
|
3596
3711
|
};
|