openclaw-remote 0.3.1 → 0.4.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.
- package/dist/index.js +119 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2795,7 +2795,67 @@ async function getTeam(account) {
|
|
|
2795
2795
|
|
|
2796
2796
|
// src/realtime-listener.ts
|
|
2797
2797
|
import { createClient } from "@supabase/supabase-js";
|
|
2798
|
-
|
|
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 resolveAgentIdentity(account, log) {
|
|
2804
|
+
try {
|
|
2805
|
+
const res = await fetch(`${account.baseUrl}/api/v1/roles`, {
|
|
2806
|
+
headers: { Authorization: `Bearer ${account.apiKey}`, Accept: "application/json" },
|
|
2807
|
+
signal: AbortSignal.timeout(1e4)
|
|
2808
|
+
});
|
|
2809
|
+
if (!res.ok) {
|
|
2810
|
+
log?.warn?.(`Failed to resolve agent identity: HTTP ${res.status}`);
|
|
2811
|
+
return null;
|
|
2812
|
+
}
|
|
2813
|
+
const data = await res.json();
|
|
2814
|
+
const roles = data.roles || [];
|
|
2815
|
+
const myRoles = roles.filter((r) => r.is_mine);
|
|
2816
|
+
const myRoleIds = myRoles.map((r) => r.id);
|
|
2817
|
+
const isLeader = myRoles.some((r) => r.is_leader);
|
|
2818
|
+
const hbRes = await fetch(`${account.baseUrl}/api/v1/heartbeat`, {
|
|
2819
|
+
headers: { Authorization: `Bearer ${account.apiKey}`, Accept: "application/json" },
|
|
2820
|
+
signal: AbortSignal.timeout(1e4)
|
|
2821
|
+
});
|
|
2822
|
+
let agentName = "";
|
|
2823
|
+
if (hbRes.ok) {
|
|
2824
|
+
const hb = await hbRes.json();
|
|
2825
|
+
agentName = hb.agent_name || "";
|
|
2826
|
+
}
|
|
2827
|
+
log?.info?.(`Agent identity resolved: "${agentName}", roles: [${myRoleIds.join(", ")}], leader: ${isLeader}`);
|
|
2828
|
+
return { agentName, myRoleIds, isLeader };
|
|
2829
|
+
} catch (err) {
|
|
2830
|
+
log?.warn?.(`Failed to resolve agent identity: ${err}`);
|
|
2831
|
+
return null;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
async function fetchLeaderRoles(supabase, log) {
|
|
2835
|
+
const now = Date.now();
|
|
2836
|
+
if (now - leaderRolesCacheTime < LEADER_CACHE_TTL && leaderRolesCache.length > 0) {
|
|
2837
|
+
return leaderRolesCache;
|
|
2838
|
+
}
|
|
2839
|
+
const { data: roles } = await supabase.from("project_roles").select("id").eq("is_leader", true);
|
|
2840
|
+
if (!roles || roles.length === 0) {
|
|
2841
|
+
leaderRolesCache = [];
|
|
2842
|
+
leaderRolesCacheTime = now;
|
|
2843
|
+
return [];
|
|
2844
|
+
}
|
|
2845
|
+
const roleIds = roles.map((r) => r.id);
|
|
2846
|
+
const { data: assignments } = await supabase.from("agent_role_assignments").select("project_role_id, agent_id").in("project_role_id", roleIds);
|
|
2847
|
+
const map = /* @__PURE__ */ new Map();
|
|
2848
|
+
for (const a of assignments ?? []) {
|
|
2849
|
+
const list = map.get(a.project_role_id) || [];
|
|
2850
|
+
list.push(a.agent_id);
|
|
2851
|
+
map.set(a.project_role_id, list);
|
|
2852
|
+
}
|
|
2853
|
+
leaderRolesCache = roleIds.map((id) => ({ roleId: id, agentIds: map.get(id) || [] }));
|
|
2854
|
+
leaderRolesCacheTime = now;
|
|
2855
|
+
log?.info?.(`Leader roles cache refreshed: ${leaderRolesCache.length} leader role(s)`);
|
|
2856
|
+
return leaderRolesCache;
|
|
2857
|
+
}
|
|
2858
|
+
function connectRealtime(account, identity, onEvent, onError, log) {
|
|
2799
2859
|
const { supabaseUrl, supabaseKey } = account;
|
|
2800
2860
|
if (!supabaseUrl || !supabaseKey) {
|
|
2801
2861
|
throw new Error("Supabase URL and key are required for Realtime listener. Set supabaseUrl and supabaseKey in channels.remote config.");
|
|
@@ -2819,14 +2879,31 @@ function connectRealtime(account, onEvent, onError, log) {
|
|
|
2819
2879
|
const row = payload.new;
|
|
2820
2880
|
if (row.agent_author_id && !row.author_id) {
|
|
2821
2881
|
log?.info?.(`Skipping agent-authored comment on task ${row.task_id} (echo suppression)`);
|
|
2882
|
+
scheduleLeaderNotification(row.task_id, row.agent_author_id, supabase, log);
|
|
2822
2883
|
return;
|
|
2823
2884
|
}
|
|
2885
|
+
const body = row.body || row.content || "";
|
|
2886
|
+
if (!row.author_id && !row.agent_author_id && identity) {
|
|
2887
|
+
if (body.startsWith("\u{1F4CC} Assigned to ")) {
|
|
2888
|
+
const targetName = body.replace("\u{1F4CC} Assigned to ", "").trim();
|
|
2889
|
+
if (targetName.toLowerCase() !== identity.agentName.toLowerCase()) {
|
|
2890
|
+
log?.info?.(`Skipping assignment notification for ${targetName} (I am ${identity.agentName})`);
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
if (body.startsWith("\u{1F440} ")) {
|
|
2895
|
+
if (!identity.isLeader) {
|
|
2896
|
+
log?.info?.(`Skipping leader notification (I am not a leader)`);
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2824
2901
|
const event = {
|
|
2825
2902
|
type: "task.commented",
|
|
2826
2903
|
task_id: row.task_id,
|
|
2827
2904
|
comment: {
|
|
2828
2905
|
id: row.id,
|
|
2829
|
-
content:
|
|
2906
|
+
content: body,
|
|
2830
2907
|
author_name: row.author_name || "System",
|
|
2831
2908
|
author_id: row.author_id || "",
|
|
2832
2909
|
created_at: row.created_at
|
|
@@ -2854,11 +2931,49 @@ function connectRealtime(account, onEvent, onError, log) {
|
|
|
2854
2931
|
return {
|
|
2855
2932
|
unsubscribe: async () => {
|
|
2856
2933
|
log?.info?.("Unsubscribing from Supabase Realtime...");
|
|
2934
|
+
for (const timer of leaderTimers.values()) clearTimeout(timer);
|
|
2935
|
+
leaderTimers.clear();
|
|
2857
2936
|
await supabase.removeChannel(channel);
|
|
2858
2937
|
log?.info?.("Realtime listener shut down cleanly.");
|
|
2859
2938
|
}
|
|
2860
2939
|
};
|
|
2861
2940
|
}
|
|
2941
|
+
async function scheduleLeaderNotification(taskId, commentingAgentId, supabase, log) {
|
|
2942
|
+
const existing = leaderTimers.get(taskId);
|
|
2943
|
+
if (existing) clearTimeout(existing);
|
|
2944
|
+
const timer = setTimeout(async () => {
|
|
2945
|
+
leaderTimers.delete(taskId);
|
|
2946
|
+
try {
|
|
2947
|
+
const leaders = await fetchLeaderRoles(supabase, log);
|
|
2948
|
+
if (leaders.length === 0) return;
|
|
2949
|
+
const { data: task } = await supabase.from("tasks").select("id, title, assigned_role_id").eq("id", taskId).single();
|
|
2950
|
+
if (!task) return;
|
|
2951
|
+
for (const leader of leaders) {
|
|
2952
|
+
if (task.assigned_role_id === leader.roleId) {
|
|
2953
|
+
log?.info?.(`Skipping leader notification for task ${taskId} \u2014 assigned to leader role ${leader.roleId}`);
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
if (leader.agentIds.includes(commentingAgentId)) continue;
|
|
2957
|
+
if (leader.agentIds.length === 0) continue;
|
|
2958
|
+
const { error } = await supabase.from("task_comments").insert({
|
|
2959
|
+
task_id: taskId,
|
|
2960
|
+
author_id: null,
|
|
2961
|
+
agent_author_id: null,
|
|
2962
|
+
body: `\u{1F440} Activity on this task \u2014 leader review needed`
|
|
2963
|
+
});
|
|
2964
|
+
if (error) {
|
|
2965
|
+
log?.warn?.(`Failed to insert leader notification for task ${taskId}: ${error.message}`);
|
|
2966
|
+
} else {
|
|
2967
|
+
log?.info?.(`Leader notification sent for task ${taskId}`);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
} catch (err) {
|
|
2971
|
+
log?.warn?.(`Leader notification error for task ${taskId}: ${err}`);
|
|
2972
|
+
}
|
|
2973
|
+
}, LEADER_DEBOUNCE_MS);
|
|
2974
|
+
leaderTimers.set(taskId, timer);
|
|
2975
|
+
log?.info?.(`Leader debounce timer set for task ${taskId} (60s)`);
|
|
2976
|
+
}
|
|
2862
2977
|
|
|
2863
2978
|
// src/event-formatter.ts
|
|
2864
2979
|
function formatEvent(event) {
|
|
@@ -3163,8 +3278,10 @@ function createRemotePlugin() {
|
|
|
3163
3278
|
let realtimeHandle = null;
|
|
3164
3279
|
if (account.supabaseUrl && account.supabaseKey) {
|
|
3165
3280
|
try {
|
|
3281
|
+
const identity = await resolveAgentIdentity(account, log);
|
|
3166
3282
|
realtimeHandle = connectRealtime(
|
|
3167
3283
|
account,
|
|
3284
|
+
identity,
|
|
3168
3285
|
async (event) => {
|
|
3169
3286
|
const formatted = formatEvent(event);
|
|
3170
3287
|
if (!formatted) return;
|