openclaw-remote 0.3.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +68 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2795,6 +2795,35 @@ async function getTeam(account) {
2795
2795
 
2796
2796
  // src/realtime-listener.ts
2797
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
+ }
2798
2827
  function connectRealtime(account, onEvent, onError, log) {
2799
2828
  const { supabaseUrl, supabaseKey } = account;
2800
2829
  if (!supabaseUrl || !supabaseKey) {
@@ -2819,6 +2848,7 @@ function connectRealtime(account, onEvent, onError, log) {
2819
2848
  const row = payload.new;
2820
2849
  if (row.agent_author_id && !row.author_id) {
2821
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);
2822
2852
  return;
2823
2853
  }
2824
2854
  const event = {
@@ -2854,11 +2884,49 @@ function connectRealtime(account, onEvent, onError, log) {
2854
2884
  return {
2855
2885
  unsubscribe: async () => {
2856
2886
  log?.info?.("Unsubscribing from Supabase Realtime...");
2887
+ for (const timer of leaderTimers.values()) clearTimeout(timer);
2888
+ leaderTimers.clear();
2857
2889
  await supabase.removeChannel(channel);
2858
2890
  log?.info?.("Realtime listener shut down cleanly.");
2859
2891
  }
2860
2892
  };
2861
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
+ }
2862
2930
 
2863
2931
  // src/event-formatter.ts
2864
2932
  function formatEvent(event) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-remote",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Remote project board channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",