@syengup/friday-channel-next 0.1.30 → 0.1.36
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/README.md +8 -4
- package/dist/src/agent/abort-run.d.ts +12 -1
- package/dist/src/agent/abort-run.js +24 -9
- package/dist/src/agent/media-bridge.d.ts +8 -1
- package/dist/src/agent/media-bridge.js +23 -2
- package/dist/src/agent-forward-runtime.d.ts +15 -0
- package/dist/src/agent-forward-runtime.js +2 -0
- package/dist/src/agent-id.d.ts +8 -0
- package/dist/src/agent-id.js +21 -0
- package/dist/src/channel-actions.js +45 -14
- package/dist/src/channel.js +22 -1
- package/dist/src/http/handlers/agent-config.d.ts +27 -0
- package/dist/src/http/handlers/agent-config.js +182 -0
- package/dist/src/http/handlers/agent-files.d.ts +21 -0
- package/dist/src/http/handlers/agent-files.js +137 -0
- package/dist/src/http/handlers/agent-tools-catalog.d.ts +10 -0
- package/dist/src/http/handlers/agent-tools-catalog.js +33 -0
- package/dist/src/http/handlers/agents-list.js +1 -19
- package/dist/src/http/handlers/cancel.js +12 -6
- package/dist/src/http/handlers/files.d.ts +16 -0
- package/dist/src/http/handlers/files.js +80 -12
- package/dist/src/http/handlers/messages.js +8 -3
- package/dist/src/http/handlers/models-list.d.ts +5 -0
- package/dist/src/http/handlers/models-list.js +8 -0
- package/dist/src/http/handlers/sessions-settings.js +15 -10
- package/dist/src/http/server.js +23 -0
- package/dist/src/link-preview/ssrf-guard.js +6 -2
- package/dist/src/media-fetch.js +4 -1
- package/dist/src/skills-discovery.d.ts +58 -0
- package/dist/src/skills-discovery.js +247 -0
- package/dist/src/thinking-levels.d.ts +21 -0
- package/dist/src/thinking-levels.js +48 -0
- package/dist/src/tool-catalog.d.ts +53 -0
- package/dist/src/tool-catalog.js +192 -0
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
- package/src/agent/abort-run.ts +24 -8
- package/src/agent/media-bridge.test.ts +71 -0
- package/src/agent/media-bridge.ts +23 -1
- package/src/agent-forward-runtime.ts +11 -0
- package/src/agent-id.ts +24 -0
- package/src/channel-actions.test.ts +47 -0
- package/src/channel-actions.ts +38 -14
- package/src/channel.lifecycle.test.ts +41 -0
- package/src/channel.ts +23 -1
- package/src/http/handlers/agent-config.test.ts +205 -0
- package/src/http/handlers/agent-config.ts +218 -0
- package/src/http/handlers/agent-files.test.ts +136 -0
- package/src/http/handlers/agent-files.ts +149 -0
- package/src/http/handlers/agent-tools-catalog.ts +42 -0
- package/src/http/handlers/agents-list.ts +1 -22
- package/src/http/handlers/cancel.test.ts +12 -2
- package/src/http/handlers/cancel.ts +12 -6
- package/src/http/handlers/files.test.ts +114 -0
- package/src/http/handlers/files.ts +97 -13
- package/src/http/handlers/messages.ts +7 -2
- package/src/http/handlers/models-list.test.ts +114 -0
- package/src/http/handlers/models-list.ts +12 -0
- package/src/http/handlers/sessions-settings.ts +16 -11
- package/src/http/server.ts +24 -0
- package/src/link-preview/ssrf-guard.test.ts +7 -2
- package/src/link-preview/ssrf-guard.ts +5 -1
- package/src/media-fetch.test.ts +1 -1
- package/src/media-fetch.ts +4 -1
- package/src/openclaw.d.ts +25 -1
- package/src/skills-discovery.test.ts +148 -0
- package/src/skills-discovery.ts +248 -0
- package/src/thinking-levels.test.ts +143 -0
- package/src/thinking-levels.ts +68 -0
- package/src/tool-catalog.ts +252 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an agent's tool-permission catalog for the app's toolbox editor — the same
|
|
3
|
+
* tools/categories/descriptions/profiles ControlUI shows.
|
|
4
|
+
*
|
|
5
|
+
* The catalog (core + plugin tools, grouped, with descriptions and per-tool
|
|
6
|
+
* `defaultProfiles`) is produced by core's `buildToolsCatalogResult({cfg, agentId})`.
|
|
7
|
+
* That builder lives only in a hash-named dist chunk (no stable plugin-sdk export) and
|
|
8
|
+
* the catalog is CODE, not scannable data — so unlike skill discovery we can't avoid
|
|
9
|
+
* importing it. We locate the chunk RESILIENTLY (scan `<openclaw>/dist/*.js` for the one
|
|
10
|
+
* defining `buildToolsCatalogResult`, then dynamic-import it — Node returns the gateway's
|
|
11
|
+
* already-loaded module instance, so no side effects), cache it, and degrade gracefully
|
|
12
|
+
* (null) if the layout changes. Per-tool `enabled`/`inProfile` are then resolved here from
|
|
13
|
+
* the agent's `tools` config so the app can render simple toggles.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { getFridayAgentForwardRuntime } from "./agent-forward-runtime.js";
|
|
19
|
+
import { resolveOpenClawRoot } from "./skills-discovery.js";
|
|
20
|
+
import { normalizeAgentId } from "./agent-id.js";
|
|
21
|
+
|
|
22
|
+
interface CoreCatalogTool {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
description: string;
|
|
26
|
+
source: string;
|
|
27
|
+
defaultProfiles: string[];
|
|
28
|
+
}
|
|
29
|
+
interface CoreCatalogGroup {
|
|
30
|
+
id: string;
|
|
31
|
+
label: string;
|
|
32
|
+
source: string;
|
|
33
|
+
pluginId?: string;
|
|
34
|
+
tools: CoreCatalogTool[];
|
|
35
|
+
}
|
|
36
|
+
interface CoreCatalogResult {
|
|
37
|
+
agentId: string;
|
|
38
|
+
profiles: Array<{ id: string; label: string }>;
|
|
39
|
+
groups: CoreCatalogGroup[];
|
|
40
|
+
}
|
|
41
|
+
type BuildFn = (params: { cfg: unknown; agentId?: string; includePlugins?: boolean }) => CoreCatalogResult;
|
|
42
|
+
|
|
43
|
+
let cachedBuildFn: BuildFn | null | undefined;
|
|
44
|
+
|
|
45
|
+
async function loadBuildFn(): Promise<BuildFn | null> {
|
|
46
|
+
if (cachedBuildFn !== undefined) return cachedBuildFn;
|
|
47
|
+
cachedBuildFn = await locateBuildFn();
|
|
48
|
+
return cachedBuildFn;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Core's catalog builder enumerates plugin tools via an internal
|
|
53
|
+
* `ensureStandaloneRuntimePluginRegistryLoaded({ surface: "channel" })`, which
|
|
54
|
+
* `pinActivePluginChannelRegistry()`s a tool-scoped registry that does NOT carry the
|
|
55
|
+
* friday-next channel registration. Because friday-next is an external channel (not in
|
|
56
|
+
* core's static CHANNEL_IDS), that re-pin drops it from the deliverable-channel set for
|
|
57
|
+
* the WHOLE gateway until the next full reload/restart — so every agent `message` send
|
|
58
|
+
* then fails with `Unknown channel: friday-next`. We snapshot the channel registry before
|
|
59
|
+
* the build and pin it back after, neutralizing the side effect. Resilient-import the
|
|
60
|
+
* runtime chunk like the catalog builder (gateway singleton; state lives on globalThis).
|
|
61
|
+
*/
|
|
62
|
+
interface ChannelRegistryFns {
|
|
63
|
+
get: () => unknown;
|
|
64
|
+
pin: (registry: unknown) => void;
|
|
65
|
+
}
|
|
66
|
+
let cachedChannelRegistryFns: ChannelRegistryFns | null | undefined;
|
|
67
|
+
|
|
68
|
+
async function loadChannelRegistryFns(): Promise<ChannelRegistryFns | null> {
|
|
69
|
+
if (cachedChannelRegistryFns !== undefined) return cachedChannelRegistryFns;
|
|
70
|
+
cachedChannelRegistryFns = await locateChannelRegistryFns();
|
|
71
|
+
return cachedChannelRegistryFns;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function locateChannelRegistryFns(): Promise<ChannelRegistryFns | null> {
|
|
75
|
+
const root = resolveOpenClawRoot();
|
|
76
|
+
if (!root) return null;
|
|
77
|
+
const distDir = path.join(root, "dist");
|
|
78
|
+
let files: string[];
|
|
79
|
+
try {
|
|
80
|
+
files = fs.readdirSync(distDir).filter((f) => f.endsWith(".js"));
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
let content: string;
|
|
86
|
+
try {
|
|
87
|
+
content = fs.readFileSync(path.join(distDir, file), "utf8");
|
|
88
|
+
} catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// Only the chunk that re-exports both helpers by their real names is usable.
|
|
92
|
+
if (!content.includes("pinActivePluginChannelRegistry")) continue;
|
|
93
|
+
try {
|
|
94
|
+
const mod = (await import(path.join(distDir, file))) as Record<string, unknown>;
|
|
95
|
+
const pin = mod.pinActivePluginChannelRegistry;
|
|
96
|
+
const get = mod.getActivePluginChannelRegistry;
|
|
97
|
+
if (typeof pin === "function" && typeof get === "function") {
|
|
98
|
+
return {
|
|
99
|
+
get: get as () => unknown,
|
|
100
|
+
pin: pin as (registry: unknown) => void,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// unreadable/non-importable candidate → keep scanning
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function locateBuildFn(): Promise<BuildFn | null> {
|
|
111
|
+
const root = resolveOpenClawRoot();
|
|
112
|
+
if (!root) return null;
|
|
113
|
+
const distDir = path.join(root, "dist");
|
|
114
|
+
let files: string[];
|
|
115
|
+
try {
|
|
116
|
+
files = fs.readdirSync(distDir).filter((f) => f.endsWith(".js"));
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
let content: string;
|
|
122
|
+
try {
|
|
123
|
+
content = fs.readFileSync(path.join(distDir, file), "utf8");
|
|
124
|
+
} catch {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!content.includes("function buildToolsCatalogResult")) continue;
|
|
128
|
+
try {
|
|
129
|
+
const mod = (await import(path.join(distDir, file))) as Record<string, unknown>;
|
|
130
|
+
if (typeof mod.buildToolsCatalogResult === "function") return mod.buildToolsCatalogResult as BuildFn;
|
|
131
|
+
} catch {
|
|
132
|
+
// unreadable/non-importable candidate → keep scanning
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface AgentToolsConfigShape {
|
|
139
|
+
profile?: string;
|
|
140
|
+
allow?: string[];
|
|
141
|
+
alsoAllow?: string[];
|
|
142
|
+
deny?: string[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface AgentCatalogTool {
|
|
146
|
+
id: string;
|
|
147
|
+
label: string;
|
|
148
|
+
description: string;
|
|
149
|
+
source: string;
|
|
150
|
+
/** Effective state under the agent's current tools config. */
|
|
151
|
+
enabled: boolean;
|
|
152
|
+
/** Whether the active profile grants this tool (drives the app's allow/deny delta). */
|
|
153
|
+
inProfile: boolean;
|
|
154
|
+
}
|
|
155
|
+
export interface AgentCatalogGroup {
|
|
156
|
+
id: string;
|
|
157
|
+
label: string;
|
|
158
|
+
source: string;
|
|
159
|
+
pluginId?: string;
|
|
160
|
+
tools: AgentCatalogTool[];
|
|
161
|
+
}
|
|
162
|
+
export interface AgentToolsCatalog {
|
|
163
|
+
/** The agent's configured profile (null when unset). */
|
|
164
|
+
profile: string | null;
|
|
165
|
+
profiles: Array<{ id: string; label: string }>;
|
|
166
|
+
groups: AgentCatalogGroup[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function readStringArray(value: unknown): string[] {
|
|
170
|
+
return Array.isArray(value) ? value.filter((v): v is string => typeof v === "string") : [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Read an agent's `tools` config block from the host config. */
|
|
174
|
+
function findAgentTools(cfg: unknown, agentId: string): AgentToolsConfigShape | undefined {
|
|
175
|
+
const list = ((cfg as Record<string, unknown> | undefined)?.agents as Record<string, unknown> | undefined)
|
|
176
|
+
?.list as Array<Record<string, unknown>> | undefined;
|
|
177
|
+
if (!Array.isArray(list)) return undefined;
|
|
178
|
+
const entry = list.find((a) => a && typeof a === "object" && normalizeAgentId(a.id) === agentId);
|
|
179
|
+
return entry?.tools as AgentToolsConfigShape | undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The agent's full tool catalog with per-tool effective state, or null if the core
|
|
184
|
+
* catalog builder can't be located.
|
|
185
|
+
*/
|
|
186
|
+
export async function buildAgentToolsCatalog(cfg: unknown, agentId: string): Promise<AgentToolsCatalog | null> {
|
|
187
|
+
const build = await loadBuildFn();
|
|
188
|
+
if (!build) return null;
|
|
189
|
+
// Snapshot the channel registry so we can undo the build's `surface:"channel"` re-pin
|
|
190
|
+
// (which would otherwise drop friday-next from the gateway's deliverable channels).
|
|
191
|
+
const channelFns = await loadChannelRegistryFns();
|
|
192
|
+
const channelRegistryBefore = (() => {
|
|
193
|
+
try {
|
|
194
|
+
return channelFns?.get() ?? null;
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
})();
|
|
199
|
+
let core: CoreCatalogResult;
|
|
200
|
+
try {
|
|
201
|
+
core = build({ cfg, agentId, includePlugins: true });
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
} finally {
|
|
205
|
+
// Pin the original channel registry back. Idempotent when the build didn't clobber it
|
|
206
|
+
// (core returns early when the surface already points at this registry).
|
|
207
|
+
if (channelFns && channelRegistryBefore) {
|
|
208
|
+
try {
|
|
209
|
+
channelFns.pin(channelRegistryBefore);
|
|
210
|
+
} catch {
|
|
211
|
+
// best effort — never fail the catalog request over the restore
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const tools = findAgentTools(cfg, agentId);
|
|
217
|
+
const profile = (typeof tools?.profile === "string" && tools.profile.trim()) ? tools.profile.trim() : null;
|
|
218
|
+
const allow = new Set(readStringArray(tools?.allow));
|
|
219
|
+
const alsoAllow = new Set(readStringArray(tools?.alsoAllow));
|
|
220
|
+
const deny = new Set(readStringArray(tools?.deny));
|
|
221
|
+
// No profile + no explicit allow == core's "allow all (except deny)".
|
|
222
|
+
const allowAll = profile === "full" || allow.has("*") || (!profile && allow.size === 0);
|
|
223
|
+
|
|
224
|
+
const groups: AgentCatalogGroup[] = core.groups.map((g) => ({
|
|
225
|
+
id: g.id,
|
|
226
|
+
label: g.label,
|
|
227
|
+
source: g.source,
|
|
228
|
+
pluginId: g.pluginId,
|
|
229
|
+
tools: g.tools.map((t) => {
|
|
230
|
+
const inProfile = allowAll ? true : profile ? t.defaultProfiles.includes(profile) : false;
|
|
231
|
+
let enabled: boolean;
|
|
232
|
+
if (deny.has(t.id)) enabled = false;
|
|
233
|
+
else if (allowAll) enabled = true;
|
|
234
|
+
else enabled = inProfile || allow.has(t.id) || alsoAllow.has(t.id);
|
|
235
|
+
return {
|
|
236
|
+
id: t.id,
|
|
237
|
+
label: t.label,
|
|
238
|
+
description: t.description,
|
|
239
|
+
source: t.source,
|
|
240
|
+
enabled,
|
|
241
|
+
inProfile,
|
|
242
|
+
};
|
|
243
|
+
}),
|
|
244
|
+
}));
|
|
245
|
+
|
|
246
|
+
return { profile, profiles: core.profiles, groups };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Test-only: reset the cached catalog builder. */
|
|
250
|
+
export function resetToolCatalogCacheForTest(): void {
|
|
251
|
+
cachedBuildFn = undefined;
|
|
252
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { readFileSync } from "node:fs";
|
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
|
|
13
13
|
/** Keep in sync with package.json "version" as a last-resort fallback. */
|
|
14
|
-
const FALLBACK_VERSION = "0.1.
|
|
14
|
+
const FALLBACK_VERSION = "0.1.36";
|
|
15
15
|
|
|
16
16
|
function resolvePluginVersion(): string {
|
|
17
17
|
// dist layout: <root>/dist/src/version.js → ../../package.json = <root>/package.json
|