@syengup/friday-channel-next 0.1.36 → 0.1.38

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 (124) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/src/agent/dispatch-bridge.d.ts +1 -1
  3. package/dist/src/agent/node-pairing-bridge.d.ts +11 -8
  4. package/dist/src/agent/node-pairing-bridge.js +6 -2
  5. package/dist/src/agent/operator-scope.d.ts +19 -0
  6. package/dist/src/agent/operator-scope.js +54 -0
  7. package/dist/src/agent/subagent-registry.js +0 -3
  8. package/dist/src/channel-actions.js +3 -1
  9. package/dist/src/channel.js +0 -2
  10. package/dist/src/collect-message-media-paths.js +10 -1
  11. package/dist/src/friday-session.js +34 -10
  12. package/dist/src/history/normalize-message.js +22 -8
  13. package/dist/src/http/handlers/agent-config.js +10 -4
  14. package/dist/src/http/handlers/cancel.js +4 -2
  15. package/dist/src/http/handlers/device-approve.js +3 -1
  16. package/dist/src/http/handlers/files-download.js +6 -8
  17. package/dist/src/http/handlers/files.js +1 -1
  18. package/dist/src/http/handlers/health.js +18 -4
  19. package/dist/src/http/handlers/history-messages.js +1 -1
  20. package/dist/src/http/handlers/history-sessions.js +5 -3
  21. package/dist/src/http/handlers/messages.js +34 -11
  22. package/dist/src/http/handlers/models-list.js +1 -1
  23. package/dist/src/http/handlers/nodes-approve.js +1 -6
  24. package/dist/src/http/handlers/plugin-info.js +1 -1
  25. package/dist/src/http/server.js +4 -2
  26. package/dist/src/link-preview/og-parse.js +3 -1
  27. package/dist/src/plugin-install-info.js +4 -1
  28. package/dist/src/session/session-manager.js +9 -3
  29. package/dist/src/session-usage-store.js +3 -1
  30. package/dist/src/skills-discovery.d.ts +5 -4
  31. package/dist/src/skills-discovery.js +27 -22
  32. package/dist/src/sse/offline-queue.js +4 -1
  33. package/dist/src/tool-catalog.js +2 -3
  34. package/dist/src/upgrade-runtime.d.ts +1 -1
  35. package/dist/src/version.js +3 -1
  36. package/index.ts +43 -35
  37. package/install.js +131 -43
  38. package/package.json +10 -1
  39. package/src/agent/abort-run.ts +2 -3
  40. package/src/agent/dispatch-bridge.ts +2 -1
  41. package/src/agent/media-bridge.ts +9 -2
  42. package/src/agent/node-pairing-bridge.ts +29 -15
  43. package/src/agent/operator-scope.test.ts +66 -0
  44. package/src/agent/operator-scope.ts +63 -0
  45. package/src/agent/run-usage-accumulator.ts +4 -2
  46. package/src/agent/subagent-registry.ts +0 -4
  47. package/src/agent-run-context-bridge.ts +3 -1
  48. package/src/channel-actions.test.ts +10 -4
  49. package/src/channel-actions.ts +3 -1
  50. package/src/channel.outbound.test.ts +18 -4
  51. package/src/channel.ts +121 -123
  52. package/src/collect-message-media-paths.ts +15 -6
  53. package/src/config.ts +1 -4
  54. package/src/e2e/agents-list.e2e.test.ts +9 -2
  55. package/src/e2e/attachments-inbound.e2e.test.ts +5 -1
  56. package/src/e2e/attachments-outbound.e2e.test.ts +7 -2
  57. package/src/e2e/auto-approve.integration.test.ts +13 -7
  58. package/src/e2e/cancel-reconnect-errors.e2e.test.ts +18 -3
  59. package/src/e2e/connect-and-connected.e2e.test.ts +5 -1
  60. package/src/e2e/offline-replay.e2e.test.ts +17 -3
  61. package/src/e2e/send-text.e2e.test.ts +11 -2
  62. package/src/e2e/slash-commands.e2e.test.ts +5 -1
  63. package/src/e2e/status-cors-auth.e2e.test.ts +11 -2
  64. package/src/e2e/subagent-smoke.e2e.test.ts +68 -28
  65. package/src/e2e/subagent.e2e.test.ts +136 -53
  66. package/src/e2e/tool-lifecycle.e2e.test.ts +5 -1
  67. package/src/friday-session.forward-agent.test.ts +44 -12
  68. package/src/friday-session.ts +44 -20
  69. package/src/history/normalize-message.test.ts +35 -8
  70. package/src/history/normalize-message.ts +24 -12
  71. package/src/history/read-transcript.ts +1 -4
  72. package/src/http/handlers/agent-config.test.ts +10 -3
  73. package/src/http/handlers/agent-config.ts +22 -8
  74. package/src/http/handlers/agents-list.test.ts +1 -5
  75. package/src/http/handlers/cancel.test.ts +12 -3
  76. package/src/http/handlers/cancel.ts +4 -2
  77. package/src/http/handlers/device-approve.test.ts +12 -3
  78. package/src/http/handlers/device-approve.ts +33 -21
  79. package/src/http/handlers/files-download.ts +17 -13
  80. package/src/http/handlers/files.test.ts +8 -2
  81. package/src/http/handlers/files.ts +21 -7
  82. package/src/http/handlers/health.test.ts +43 -11
  83. package/src/http/handlers/health.ts +22 -6
  84. package/src/http/handlers/history-messages.test.ts +51 -9
  85. package/src/http/handlers/history-messages.ts +4 -1
  86. package/src/http/handlers/history-sessions.test.ts +46 -9
  87. package/src/http/handlers/history-sessions.ts +5 -3
  88. package/src/http/handlers/history-set-title.test.ts +14 -5
  89. package/src/http/handlers/link-preview.test.ts +57 -16
  90. package/src/http/handlers/link-preview.ts +4 -1
  91. package/src/http/handlers/messages.test.ts +12 -8
  92. package/src/http/handlers/messages.ts +67 -19
  93. package/src/http/handlers/models-list.ts +14 -8
  94. package/src/http/handlers/nodes-approve.test.ts +15 -4
  95. package/src/http/handlers/nodes-approve.ts +38 -40
  96. package/src/http/handlers/plugin-info.ts +5 -6
  97. package/src/http/handlers/plugin-upgrade.ts +4 -1
  98. package/src/http/handlers/sse.ts +3 -1
  99. package/src/http/server.ts +9 -6
  100. package/src/link-preview/og-parse.test.ts +6 -2
  101. package/src/link-preview/og-parse.ts +10 -3
  102. package/src/link-preview/preview-service.ts +4 -1
  103. package/src/link-preview/ssrf-guard.test.ts +72 -15
  104. package/src/link-preview/ssrf-guard.ts +2 -1
  105. package/src/media-fetch.test.ts +7 -2
  106. package/src/media-fetch.ts +1 -2
  107. package/src/openclaw.d.ts +26 -9
  108. package/src/plugin-install-info.ts +20 -9
  109. package/src/run-metadata.ts +2 -1
  110. package/src/session/session-manager.ts +19 -11
  111. package/src/session-usage-snapshot.ts +3 -1
  112. package/src/session-usage-store.ts +3 -1
  113. package/src/skills-discovery.test.ts +14 -10
  114. package/src/skills-discovery.ts +43 -27
  115. package/src/sse/emitter.test.ts +1 -1
  116. package/src/sse/emitter.ts +9 -3
  117. package/src/sse/offline-queue.ts +17 -8
  118. package/src/test-support/app-simulator.ts +17 -3
  119. package/src/test-support/mock-dispatch.ts +17 -4
  120. package/src/thinking-levels.ts +3 -1
  121. package/src/tool-catalog.ts +16 -7
  122. package/src/upgrade-runtime.ts +4 -2
  123. package/src/version.ts +5 -1
  124. package/tsconfig.json +1 -1
@@ -58,12 +58,7 @@ export async function handleNodesApprove(req, res) {
58
58
  if (pendingMatch) {
59
59
  const requestId = pendingMatch.requestId;
60
60
  log.info(`approving nodeId=${normalizedNodeId} requestId=${requestId}`);
61
- const callerScopes = [
62
- "operator.admin",
63
- "operator.pairing",
64
- "operator.read",
65
- "operator.write",
66
- ];
61
+ const callerScopes = ["operator.admin", "operator.pairing", "operator.read", "operator.write"];
67
62
  let approved;
68
63
  try {
69
64
  approved = await approveNodePairing(requestId, { callerScopes });
@@ -1,6 +1,6 @@
1
1
  import { extractBearerToken } from "../middleware/auth.js";
2
2
  import { PLUGIN_VERSION } from "../../version.js";
3
- import { fetchLatestVersion, getInstallSource, semverGreater, } from "../../plugin-install-info.js";
3
+ import { fetchLatestVersion, getInstallSource, semverGreater } from "../../plugin-install-info.js";
4
4
  export async function handlePluginInfo(req, res) {
5
5
  if (req.method !== "GET") {
6
6
  res.statusCode = 405;
@@ -65,7 +65,8 @@ async function handleFridayNextRoute(req, res) {
65
65
  if (req.method === "POST" && pathname === "/friday-next/nodes-approve") {
66
66
  return await handleNodesApprove(req, res);
67
67
  }
68
- if ((req.method === "PUT" || req.method === "GET") && pathname === "/friday-next/sessions/settings") {
68
+ if ((req.method === "PUT" || req.method === "GET") &&
69
+ pathname === "/friday-next/sessions/settings") {
69
70
  return await handleSessionsSettings(req, res);
70
71
  }
71
72
  if (req.method === "GET" && pathname === "/friday-next/models") {
@@ -106,7 +107,8 @@ async function handleFridayNextRoute(req, res) {
106
107
  return await handleHistoryMessages(req, res);
107
108
  }
108
109
  // Route: PUT /friday-next/sessions/title (sync app session name → server displayName)
109
- if ((req.method === "PUT" || req.method === "POST") && pathname === "/friday-next/sessions/title") {
110
+ if ((req.method === "PUT" || req.method === "POST") &&
111
+ pathname === "/friday-next/sessions/title") {
110
112
  return await handleHistorySetTitle(req, res);
111
113
  }
112
114
  // Route: GET /friday-next/link-preview?url=... (Open Graph metadata for preview cards)
@@ -78,7 +78,9 @@ export function parseOpenGraph(html, baseUrl) {
78
78
  let metaDescription = null;
79
79
  for (const match of slice.matchAll(META_TAG_RE)) {
80
80
  const tag = match[0];
81
- const key = (attributeValue(tag, "property") ?? attributeValue(tag, "name"))?.trim().toLowerCase();
81
+ const key = (attributeValue(tag, "property") ?? attributeValue(tag, "name"))
82
+ ?.trim()
83
+ .toLowerCase();
82
84
  if (!key)
83
85
  continue;
84
86
  const content = attributeValue(tag, "content");
@@ -87,7 +87,10 @@ export async function fetchLatestVersion(nowMs) {
87
87
  const controller = new AbortController();
88
88
  const timer = setTimeout(() => controller.abort(), 5000);
89
89
  try {
90
- const res = await fetch(`https://registry.npmjs.org/${PLUGIN_PACKAGE_NAME}/latest`, { signal: controller.signal, headers: { Accept: "application/json" } });
90
+ const res = await fetch(`https://registry.npmjs.org/${PLUGIN_PACKAGE_NAME}/latest`, {
91
+ signal: controller.signal,
92
+ headers: { Accept: "application/json" },
93
+ });
91
94
  if (res.ok) {
92
95
  const body = (await res.json());
93
96
  if (typeof body.version === "string" && body.version)
@@ -118,7 +118,11 @@ export function setSessionSettings(sessionKey, settings, historyDir) {
118
118
  return {};
119
119
  upsertSessionEntry(data, fileKey, sessionKey);
120
120
  const fieldKeys = [
121
- "reasoningLevel", "thinkingLevel", "modelRef", "providerOverride", "modelOverride",
121
+ "reasoningLevel",
122
+ "thinkingLevel",
123
+ "modelRef",
124
+ "providerOverride",
125
+ "modelOverride",
122
126
  ];
123
127
  let updated = false;
124
128
  for (const key of fieldKeys) {
@@ -193,7 +197,7 @@ export function resolveAgentDefaults(sessionKey) {
193
197
  const ocCfg = (forwardRt.getConfig() ?? {});
194
198
  const agents = ocCfg.agents;
195
199
  const targetAgentId = agentIdFromSessionKey(sessionKey);
196
- const agentEntry = agents?.list?.find((a) => agentIdFromSessionKey(`agent:${String(a?.id ?? "")}:x`) === targetAgentId);
200
+ const agentEntry = agents?.list?.find((a) => agentIdFromSessionKey(`agent:${typeof a?.id === "string" ? a.id : typeof a?.id === "number" ? String(a.id) : ""}:x`) === targetAgentId);
197
201
  const agentModel = agentEntry?.model;
198
202
  const perAgentModel = typeof agentModel === "string"
199
203
  ? agentModel
@@ -204,7 +208,9 @@ export function resolveAgentDefaults(sessionKey) {
204
208
  const agentDefaults = agents?.defaults;
205
209
  const model = agentDefaults?.model;
206
210
  const globalModel = typeof model?.primary === "string" ? model.primary : undefined;
207
- const globalThinking = typeof agentDefaults?.thinkingDefault === "string" ? agentDefaults.thinkingDefault : undefined;
211
+ const globalThinking = typeof agentDefaults?.thinkingDefault === "string"
212
+ ? agentDefaults.thinkingDefault
213
+ : undefined;
208
214
  return { model: perAgentModel ?? globalModel, thinking: perAgentThinking ?? globalThinking };
209
215
  }
210
216
  catch {
@@ -24,7 +24,9 @@ export function readSessionUsageSnapshotFromStore(sessionKeyForStore) {
24
24
  const cfg = access.getConfig();
25
25
  const storeConfig = cfg?.session?.store;
26
26
  const canonical = toSessionStoreKey(sessionKeyForStore);
27
- const storePath = access.resolveStorePath(storeConfig, { agentId: agentIdFromSessionKey(canonical) });
27
+ const storePath = access.resolveStorePath(storeConfig, {
28
+ agentId: agentIdFromSessionKey(canonical),
29
+ });
28
30
  const store = access.loadSessionStore(storePath, { skipCache: true });
29
31
  const entry = store[canonical] ?? store[sessionKeyForStore.trim()];
30
32
  if (!entry || typeof entry !== "object")
@@ -12,7 +12,9 @@
12
12
  * marker core's `loadSkillsFromDir` uses).
13
13
  *
14
14
  * Each discovered skill is tagged with a `source` category for the UI:
15
- * - workspace : the agent's own + the shared default-agent workspace `skills/`
15
+ * - workspace : the TARGET agent's own workspace `skills/` only mirroring ControlUI,
16
+ * which scans the single workspace resolved for that agent and never folds
17
+ * in another agent's workspace (main is just another agent, not a shared pool)
16
18
  * - installed : managed skills dir (`<configDir>/skills`, sibling of the workspace)
17
19
  * - built-in : bundled core skills (`<openclaw>/skills`)
18
20
  * - extra : skills from ENABLED extensions (`<openclaw>/dist/extensions/<ext>/skills`,
@@ -49,9 +51,8 @@ export declare function resolveOpenClawRoot(): string | null;
49
51
  export declare function enabledExtensionNames(cfg: unknown): Set<string>;
50
52
  /**
51
53
  * Full set of skills `agentId` can load, sorted by id, each tagged with its source
52
- * category. Aggregates the agent's workspace, the shared root workspace, the managed
53
- * dir, config extra dirs, and bundled core/extension skills. Every source is optional
54
- * and failure-tolerant.
54
+ * category. Aggregates the TARGET agent's own workspace, the managed dir, config extra
55
+ * dirs, and bundled core/extension skills. Every source is optional and failure-tolerant.
55
56
  */
56
57
  export declare function discoverAvailableSkills(cfg: unknown, agentId: string): DiscoveredSkill[];
57
58
  /** Test-only: reset the cached openclaw root. */
@@ -12,7 +12,9 @@
12
12
  * marker core's `loadSkillsFromDir` uses).
13
13
  *
14
14
  * Each discovered skill is tagged with a `source` category for the UI:
15
- * - workspace : the agent's own + the shared default-agent workspace `skills/`
15
+ * - workspace : the TARGET agent's own workspace `skills/` only mirroring ControlUI,
16
+ * which scans the single workspace resolved for that agent and never folds
17
+ * in another agent's workspace (main is just another agent, not a shared pool)
16
18
  * - installed : managed skills dir (`<configDir>/skills`, sibling of the workspace)
17
19
  * - built-in : bundled core skills (`<openclaw>/skills`)
18
20
  * - extra : skills from ENABLED extensions (`<openclaw>/dist/extensions/<ext>/skills`,
@@ -200,34 +202,37 @@ function resolveDefaultAgentId(cfg) {
200
202
  }
201
203
  /**
202
204
  * Full set of skills `agentId` can load, sorted by id, each tagged with its source
203
- * category. Aggregates the agent's workspace, the shared root workspace, the managed
204
- * dir, config extra dirs, and bundled core/extension skills. Every source is optional
205
- * and failure-tolerant.
205
+ * category. Aggregates the TARGET agent's own workspace, the managed dir, config extra
206
+ * dirs, and bundled core/extension skills. Every source is optional and failure-tolerant.
206
207
  */
207
208
  export function discoverAvailableSkills(cfg, agentId) {
208
209
  const c = cfg;
209
210
  const resolveWs = getFridayAgentForwardRuntime()?.resolveAgentWorkspaceDir;
210
211
  const sources = [];
211
212
  if (resolveWs) {
212
- const defaultId = resolveDefaultAgentId(c);
213
- const ids = agentId === defaultId ? [agentId] : [agentId, defaultId];
214
- let defaultWs;
215
- for (const id of ids) {
216
- try {
217
- const ws = resolveWs(cfg, id);
218
- if (ws) {
219
- sources.push({ dir: path.join(ws, "skills"), source: "workspace" });
220
- if (id === defaultId)
221
- defaultWs = ws;
222
- }
223
- }
224
- catch {
225
- // skip unresolvable workspace
226
- }
213
+ // Workspace skills come ONLY from the target agent's own workspace — matching ControlUI's
214
+ // `resolveSkillsAgentWorkspace`→`buildWorkspaceSkillStatus(workspaceDir)`, which scans the
215
+ // single resolved workspace. Folding in the default agent's workspace (the old behavior)
216
+ // leaked main's skills into every other agent's catalog.
217
+ try {
218
+ const ws = resolveWs(cfg, agentId);
219
+ if (ws)
220
+ sources.push({ dir: path.join(ws, "skills"), source: "workspace" });
221
+ }
222
+ catch {
223
+ // skip unresolvable workspace
224
+ }
225
+ // Managed skills dir: `<configDir>/skills`. It is agent-independent; anchor it off the
226
+ // DEFAULT agent's workspace parent (the default workspace lives directly under configDir,
227
+ // whereas non-default workspaces may be nested under it).
228
+ try {
229
+ const defaultWs = resolveWs(cfg, resolveDefaultAgentId(c));
230
+ if (defaultWs)
231
+ sources.push({ dir: path.join(path.dirname(defaultWs), "skills"), source: "installed" });
232
+ }
233
+ catch {
234
+ // skip unresolvable managed dir
227
235
  }
228
- // Managed skills dir: `<configDir>/skills`, the workspace's parent sibling.
229
- if (defaultWs)
230
- sources.push({ dir: path.join(path.dirname(defaultWs), "skills"), source: "installed" });
231
236
  }
232
237
  const extraDirs = c?.skills?.load?.extraDirs;
233
238
  if (Array.isArray(extraDirs)) {
@@ -116,7 +116,10 @@ export class FridaySseOfflineQueue {
116
116
  continue;
117
117
  try {
118
118
  const o = JSON.parse(line);
119
- if (typeof o.id === "number" && typeof o.event === "string" && o.data && typeof o.data === "object") {
119
+ if (typeof o.id === "number" &&
120
+ typeof o.event === "string" &&
121
+ o.data &&
122
+ typeof o.data === "object") {
120
123
  all.push(o);
121
124
  }
122
125
  }
@@ -108,8 +108,7 @@ function readStringArray(value) {
108
108
  }
109
109
  /** Read an agent's `tools` config block from the host config. */
110
110
  function findAgentTools(cfg, agentId) {
111
- const list = cfg?.agents
112
- ?.list;
111
+ const list = cfg?.agents?.list;
113
112
  if (!Array.isArray(list))
114
113
  return undefined;
115
114
  const entry = list.find((a) => a && typeof a === "object" && normalizeAgentId(a.id) === agentId);
@@ -154,7 +153,7 @@ export async function buildAgentToolsCatalog(cfg, agentId) {
154
153
  }
155
154
  }
156
155
  const tools = findAgentTools(cfg, agentId);
157
- const profile = (typeof tools?.profile === "string" && tools.profile.trim()) ? tools.profile.trim() : null;
156
+ const profile = typeof tools?.profile === "string" && tools.profile.trim() ? tools.profile.trim() : null;
158
157
  const allow = new Set(readStringArray(tools?.allow));
159
158
  const alsoAllow = new Set(readStringArray(tools?.alsoAllow));
160
159
  const deny = new Set(readStringArray(tools?.deny));
@@ -30,7 +30,7 @@ export type UpgradeRuntime = {
30
30
  /** Mutate the config file; `afterWrite: { mode: "restart" }` triggers a safe gateway restart. */
31
31
  mutateConfigFile: (params: {
32
32
  afterWrite: ConfigAfterWrite;
33
- mutate: (draft: unknown) => unknown | void;
33
+ mutate: (draft: unknown) => unknown;
34
34
  }) => Promise<unknown>;
35
35
  /**
36
36
  * Filesystem path of THIS loaded plugin (`api.source`). Used to infer the install
@@ -20,7 +20,9 @@ function resolvePluginVersion() {
20
20
  const path = fileURLToPath(new URL(rel, import.meta.url));
21
21
  const raw = readFileSync(path, "utf8");
22
22
  const pkg = JSON.parse(raw);
23
- if (pkg.name === "@syengup/friday-channel-next" && typeof pkg.version === "string" && pkg.version) {
23
+ if (pkg.name === "@syengup/friday-channel-next" &&
24
+ typeof pkg.version === "string" &&
25
+ pkg.version) {
24
26
  return pkg.version;
25
27
  }
26
28
  }
package/index.ts CHANGED
@@ -1,7 +1,10 @@
1
- import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
2
1
  import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
3
2
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
4
- import type { PluginHookBeforeToolCallEvent, PluginHookAfterToolCallEvent, PluginHookToolContext } from "openclaw/plugin-sdk/plugins/types";
3
+ import type {
4
+ PluginHookBeforeToolCallEvent,
5
+ PluginHookAfterToolCallEvent,
6
+ PluginHookToolContext,
7
+ } from "openclaw/plugin-sdk/plugins/types";
5
8
  import { fridayNextChannelPlugin } from "./src/channel.js";
6
9
  import { setFridayNextRuntime } from "./src/runtime.js";
7
10
  import { resolveFridayNextConfig } from "./src/config.js";
@@ -44,7 +47,7 @@ function deviceIdFromToolContext(ctx: PluginHookToolContext): string | null {
44
47
  const sk =
45
48
  typeof ctx.sessionKey === "string" && ctx.sessionKey.trim()
46
49
  ? ctx.sessionKey.trim()
47
- : (ctx.runId ? getOpenClawAgentRunContext(ctx.runId)?.sessionKey?.trim() : undefined) ?? "";
50
+ : ((ctx.runId ? getOpenClawAgentRunContext(ctx.runId)?.sessionKey?.trim() : undefined) ?? "");
48
51
  if (sk) {
49
52
  const d = resolveFridayDeviceIdForSessionKey(sk);
50
53
  if (d) return d;
@@ -83,7 +86,7 @@ export default defineChannelPluginEntry({
83
86
  id: "friday-next",
84
87
  name: "Friday Next",
85
88
  description: "Friday Next Apple 应用通道",
86
- plugin: fridayNextChannelPlugin as ChannelPlugin,
89
+ plugin: fridayNextChannelPlugin,
87
90
  setRuntime: setFridayNextRuntime,
88
91
  registerFull: (api: OpenClawPluginApi) => {
89
92
  setFridayAgentForwardRuntime(api);
@@ -93,7 +96,9 @@ export default defineChannelPluginEntry({
93
96
  lastApiRoutesRegistered = new WeakRef(api);
94
97
  registerFridayNextHttpRoutes(api);
95
98
  } else {
96
- const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(getFridayNextRuntime().config));
99
+ const cfg = resolveFridayNextConfig(
100
+ getHostOpenClawConfigSnapshot(getFridayNextRuntime().config),
101
+ );
97
102
  sseEmitter.setBacklogLimit(cfg.sseBacklogPerDevice);
98
103
  }
99
104
 
@@ -147,36 +152,39 @@ export default defineChannelPluginEntry({
147
152
  };
148
153
  });
149
154
 
150
- api.on("before_tool_call", (event: PluginHookBeforeToolCallEvent, ctx: PluginHookToolContext) => {
151
- if (!shouldForwardToolEventToFriday(ctx)) return;
152
- const deviceId = deviceIdFromToolContext(ctx);
153
- const runId = ctx.runId ?? "(unknown)";
154
-
155
- const logLine = (detail: string) => {
156
- hookLogger.debug(
157
- `[TOOL_CALL] toolName=${event.toolName} runId=${runId} deviceId=${deviceId ?? "(unknown)"} detail=${detail}`,
158
- );
159
- };
160
-
161
- if (!deviceId) {
162
- logLine("SKIP_no_deviceId");
163
- return;
164
- }
165
-
166
- logLine("START");
167
- sseEmitter.broadcastToolEvent(deviceId.toUpperCase(), runId, {
168
- type: "tool-hook",
169
- data: {
170
- when: "before",
171
- runId,
172
- deviceId: deviceId.toUpperCase(),
173
- sessionKey: ctx.sessionKey,
174
- toolName: event.toolName,
175
- params: event.params,
176
- ts: Date.now(),
177
- },
178
- });
179
- });
155
+ api.on(
156
+ "before_tool_call",
157
+ (event: PluginHookBeforeToolCallEvent, ctx: PluginHookToolContext) => {
158
+ if (!shouldForwardToolEventToFriday(ctx)) return;
159
+ const deviceId = deviceIdFromToolContext(ctx);
160
+ const runId = ctx.runId ?? "(unknown)";
161
+
162
+ const logLine = (detail: string) => {
163
+ hookLogger.debug(
164
+ `[TOOL_CALL] toolName=${event.toolName} runId=${runId} deviceId=${deviceId ?? "(unknown)"} detail=${detail}`,
165
+ );
166
+ };
167
+
168
+ if (!deviceId) {
169
+ logLine("SKIP_no_deviceId");
170
+ return;
171
+ }
172
+
173
+ logLine("START");
174
+ sseEmitter.broadcastToolEvent(deviceId.toUpperCase(), runId, {
175
+ type: "tool-hook",
176
+ data: {
177
+ when: "before",
178
+ runId,
179
+ deviceId: deviceId.toUpperCase(),
180
+ sessionKey: ctx.sessionKey,
181
+ toolName: event.toolName,
182
+ params: event.params,
183
+ ts: Date.now(),
184
+ },
185
+ });
186
+ },
187
+ );
180
188
 
181
189
  api.on("after_tool_call", (event: PluginHookAfterToolCallEvent, ctx: PluginHookToolContext) => {
182
190
  if (!shouldForwardToolEventToFriday(ctx)) return;