@js-eyes/protocol 2.7.0 → 2.8.2
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/index.js +12 -14
- package/package.json +1 -1
- package/skill-registry.js +58 -5
- package/skills.js +20 -1
package/index.js
CHANGED
|
@@ -24,13 +24,12 @@ const FORWARDABLE_ACTIONS = [
|
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
const SENSITIVE_TOOL_NAMES = Object.freeze([
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'js_eyes_install_skill',
|
|
27
|
+
'browser/execute-script',
|
|
28
|
+
'browser/get-cookies',
|
|
29
|
+
'browser/get-cookies-by-domain',
|
|
30
|
+
'browser/inject-css',
|
|
31
|
+
'browser/upload-file',
|
|
32
|
+
'skills/plan-install',
|
|
34
33
|
]);
|
|
35
34
|
|
|
36
35
|
const LOOPBACK_HOSTS = Object.freeze(['localhost', '127.0.0.1', '::1', '::ffff:127.0.0.1', '0:0:0:0:0:0:0:1']);
|
|
@@ -79,13 +78,12 @@ const DEFAULT_SECURITY_CONFIG = Object.freeze({
|
|
|
79
78
|
taint: { ...DEFAULT_TAINT_CONFIG },
|
|
80
79
|
profile: { ...DEFAULT_PROFILE_CONFIG },
|
|
81
80
|
toolPolicies: {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
js_eyes_install_skill: 'confirm',
|
|
81
|
+
'browser/execute-script': 'confirm',
|
|
82
|
+
'browser/get-cookies': 'confirm',
|
|
83
|
+
'browser/get-cookies-by-domain': 'confirm',
|
|
84
|
+
'browser/inject-css': 'confirm',
|
|
85
|
+
'browser/upload-file': 'confirm',
|
|
86
|
+
'skills/plan-install': 'confirm',
|
|
89
87
|
},
|
|
90
88
|
sensitiveCookieDomains: [
|
|
91
89
|
'bank',
|
package/package.json
CHANGED
package/skill-registry.js
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* SkillRegistry — js-eyes 技能运行时注册表
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 通过两种绑定模式实现零重启热加载:
|
|
7
|
+
* - routerMode=true(OpenClaw 单工具模式):只维护 actionBindings,由 `js-eyes`
|
|
8
|
+
* 总线工具按 `skill/<skillId>/<action>` 委派执行,不向宿主注册子技能工具;
|
|
9
|
+
* - routerMode=false(兼容/测试模式):保留工具级 dispatcher 间接层。
|
|
10
|
+
*
|
|
11
|
+
* 兼容 dispatcher 模式:
|
|
7
12
|
* - 插件启动时 api.registerTool(name, dispatcher) 仅对每个 tool name 注册一次稳定闭包;
|
|
8
13
|
* - 每个 dispatcher 在调用时从 toolBindings.get(name) 查当前实现并委派;
|
|
9
14
|
* - 热加载只改 toolBindings 映射,不触碰宿主注册表。
|
|
@@ -15,6 +20,8 @@
|
|
|
15
20
|
* 让 OpenClaw / LLM 看到正确的入参约束(required / properties 等)。热加载时若 contract
|
|
16
21
|
* 改了 schema,会按**引用** mutate 已注册的 dispatcher 对象;若某些 OpenClaw 宿主对 tool
|
|
17
22
|
* 对象做了深拷贝/快照,则 mutate 不会生效,但首次注册的 schema 仍然是正确的。
|
|
23
|
+
* routerMode 下 OpenClaw 只看到 `js-eyes` 的总线 schema,子技能 schema 作为内部 definition
|
|
24
|
+
* 保留给后续 introspection / 文档输出使用。
|
|
18
25
|
*/
|
|
19
26
|
|
|
20
27
|
const fs = require('fs');
|
|
@@ -30,6 +37,7 @@ function isSkillEnabled(...args) { return skillsApi.isSkillEnabled(...args); }
|
|
|
30
37
|
function loadSkillContract(...args) { return skillsApi.loadSkillContract(...args); }
|
|
31
38
|
function readSkillIntegrity(...args) { return skillsApi.readSkillIntegrity(...args); }
|
|
32
39
|
function resolveSkillSources(...args) { return skillsApi.resolveSkillSources(...args); }
|
|
40
|
+
function skillToolActionName(...args) { return skillsApi.skillToolActionName(...args); }
|
|
33
41
|
function verifySkillIntegrity(...args) { return skillsApi.verifySkillIntegrity(...args); }
|
|
34
42
|
|
|
35
43
|
const { verifyExtraDir: verifyExtraSkillDir } = require('./extra-integrity');
|
|
@@ -129,6 +137,7 @@ function createSkillRegistry(options = {}) {
|
|
|
129
137
|
pluginConfig = {},
|
|
130
138
|
wrapSensitiveTool = null,
|
|
131
139
|
builtinToolNames = [],
|
|
140
|
+
routerMode = false,
|
|
132
141
|
// When true (default when a watcher is active), we set suppressNextReload
|
|
133
142
|
// after our own setConfigValue writes so the chokidar echo doesn't cause a
|
|
134
143
|
// duplicate reload. Leave this false in test harnesses that drive reload()
|
|
@@ -136,11 +145,11 @@ function createSkillRegistry(options = {}) {
|
|
|
136
145
|
suppressSelfWrites = true,
|
|
137
146
|
} = options;
|
|
138
147
|
|
|
139
|
-
if (!api || typeof api.registerTool !== 'function') {
|
|
148
|
+
if (!routerMode && (!api || typeof api.registerTool !== 'function')) {
|
|
140
149
|
throw new Error('createSkillRegistry: api.registerTool is required');
|
|
141
150
|
}
|
|
142
151
|
|
|
143
|
-
const logger = makeLogger(options.logger || api.logger);
|
|
152
|
+
const logger = makeLogger(options.logger || (api && api.logger));
|
|
144
153
|
const configLoader = typeof options.configLoader === 'function'
|
|
145
154
|
? options.configLoader
|
|
146
155
|
: () => ({});
|
|
@@ -159,6 +168,8 @@ function createSkillRegistry(options = {}) {
|
|
|
159
168
|
const skills = new Map();
|
|
160
169
|
// toolName -> { skillId, definition, optional }
|
|
161
170
|
const toolBindings = new Map();
|
|
171
|
+
// action -> { skillId, toolName, definition, optional }
|
|
172
|
+
const actionBindings = new Map();
|
|
162
173
|
// toolName -> dispatcher object (registered once per name; kept by reference so we can
|
|
163
174
|
// mutate `description`/`parameters`/`label` on hot-reload to keep OpenClaw-visible schema in sync)
|
|
164
175
|
const dispatchers = new Map();
|
|
@@ -266,6 +277,11 @@ function createSkillRegistry(options = {}) {
|
|
|
266
277
|
removed.push(name);
|
|
267
278
|
}
|
|
268
279
|
}
|
|
280
|
+
for (const [action, binding] of actionBindings) {
|
|
281
|
+
if (binding.skillId === skillId) {
|
|
282
|
+
actionBindings.delete(action);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
269
285
|
return removed;
|
|
270
286
|
}
|
|
271
287
|
|
|
@@ -347,6 +363,7 @@ function createSkillRegistry(options = {}) {
|
|
|
347
363
|
contract,
|
|
348
364
|
adapter,
|
|
349
365
|
toolNames: toolDefs.map((t) => t.toolName),
|
|
366
|
+
actionNames: toolDefs.map((t) => skillToolActionName(skill.id, t.toolName)),
|
|
350
367
|
// runtime.dispose is used by hot-unload to drain WS, clear intervals, etc.
|
|
351
368
|
dispose: async () => {
|
|
352
369
|
const runtime = adapter && adapter.runtime;
|
|
@@ -365,17 +382,35 @@ function createSkillRegistry(options = {}) {
|
|
|
365
382
|
|
|
366
383
|
function applyBindings(state) {
|
|
367
384
|
const failedDispatchers = [];
|
|
385
|
+
const localActions = new Set();
|
|
368
386
|
for (const entry of state.toolDefs) {
|
|
369
|
-
|
|
370
|
-
|
|
387
|
+
if (!routerMode) {
|
|
388
|
+
const dispatched = ensureDispatcher(entry.toolName, entry.optional, entry.definition);
|
|
389
|
+
if (!dispatched.ok) {
|
|
390
|
+
failedDispatchers.push(entry.toolName);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const actionName = skillToolActionName(state.id, entry.toolName);
|
|
395
|
+
if (localActions.has(actionName)) {
|
|
396
|
+
logger.warn(
|
|
397
|
+
`[js-eyes] Skill "${state.id}" skipped tool "${entry.toolName}" because action "${actionName}" duplicates another tool after slug normalization`,
|
|
398
|
+
);
|
|
371
399
|
failedDispatchers.push(entry.toolName);
|
|
372
400
|
continue;
|
|
373
401
|
}
|
|
402
|
+
localActions.add(actionName);
|
|
374
403
|
toolBindings.set(entry.toolName, {
|
|
375
404
|
skillId: state.id,
|
|
376
405
|
definition: entry.definition,
|
|
377
406
|
optional: entry.optional,
|
|
378
407
|
});
|
|
408
|
+
actionBindings.set(actionName, {
|
|
409
|
+
skillId: state.id,
|
|
410
|
+
toolName: entry.toolName,
|
|
411
|
+
definition: entry.definition,
|
|
412
|
+
optional: entry.optional,
|
|
413
|
+
});
|
|
379
414
|
}
|
|
380
415
|
return { failedDispatchers };
|
|
381
416
|
}
|
|
@@ -663,12 +698,27 @@ function createSkillRegistry(options = {}) {
|
|
|
663
698
|
sourcePath: s.sourcePath,
|
|
664
699
|
skillDir: s.skillDir,
|
|
665
700
|
tools: s.toolNames.slice(),
|
|
701
|
+
actions: s.actionNames.slice(),
|
|
666
702
|
})),
|
|
667
703
|
toolBindings: Array.from(toolBindings.keys()),
|
|
704
|
+
actionBindings: Array.from(actionBindings.keys()),
|
|
668
705
|
dispatchersRegistered: Array.from(dispatchers.keys()),
|
|
669
706
|
};
|
|
670
707
|
}
|
|
671
708
|
|
|
709
|
+
async function executeAction(action, toolCallId, params) {
|
|
710
|
+
const binding = actionBindings.get(action);
|
|
711
|
+
if (!binding) {
|
|
712
|
+
return { content: [{ type: 'text', text: DEFAULT_UNAVAILABLE_MESSAGE(action) }] };
|
|
713
|
+
}
|
|
714
|
+
return binding.definition.execute(toolCallId, params);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function getActionDefinition(action) {
|
|
718
|
+
const binding = actionBindings.get(action);
|
|
719
|
+
return binding ? binding.definition : null;
|
|
720
|
+
}
|
|
721
|
+
|
|
672
722
|
return {
|
|
673
723
|
init,
|
|
674
724
|
reload,
|
|
@@ -676,9 +726,12 @@ function createSkillRegistry(options = {}) {
|
|
|
676
726
|
disposeAll,
|
|
677
727
|
snapshot,
|
|
678
728
|
getState,
|
|
729
|
+
executeAction,
|
|
730
|
+
getActionDefinition,
|
|
679
731
|
// Testing / plugin integration helpers
|
|
680
732
|
_internals: {
|
|
681
733
|
toolBindings,
|
|
734
|
+
actionBindings,
|
|
682
735
|
skills,
|
|
683
736
|
dispatchers,
|
|
684
737
|
setSuppressNextReload(v) { suppressNextReload = Boolean(v); },
|
package/skills.js
CHANGED
|
@@ -13,6 +13,20 @@ const SKILL_CONTRACT_FILE = 'skill.contract.js';
|
|
|
13
13
|
const INTEGRITY_FILE = '.integrity.json';
|
|
14
14
|
const INSTALL_MANIFEST_FILE = 'skills-install.json';
|
|
15
15
|
|
|
16
|
+
function toolNameToActionSegment(name) {
|
|
17
|
+
return String(name || '')
|
|
18
|
+
.trim()
|
|
19
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
20
|
+
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
21
|
+
.replace(/^-+|-+$/g, '')
|
|
22
|
+
.toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function skillToolActionName(skillId, toolName) {
|
|
26
|
+
const action = toolNameToActionSegment(toolName);
|
|
27
|
+
return `skill/${skillId}/${action || 'run'}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
function loadSkillContract(skillDir) {
|
|
17
31
|
const contractPath = path.resolve(skillDir, SKILL_CONTRACT_FILE);
|
|
18
32
|
if (!fs.existsSync(contractPath)) return null;
|
|
@@ -142,8 +156,10 @@ function normalizeSkillMetadata(skillDir) {
|
|
|
142
156
|
? cli.commands.map((command) => command.name)
|
|
143
157
|
: [];
|
|
144
158
|
|
|
159
|
+
const id = contract?.id || pkg.name || path.basename(skillDir);
|
|
160
|
+
|
|
145
161
|
return {
|
|
146
|
-
id
|
|
162
|
+
id,
|
|
147
163
|
name: contract?.name || pkg.name || path.basename(skillDir),
|
|
148
164
|
version: contract?.version || pkg.version || '1.0.0',
|
|
149
165
|
description: contract?.description || pkg.description || '',
|
|
@@ -151,6 +167,7 @@ function normalizeSkillMetadata(skillDir) {
|
|
|
151
167
|
cliEntry: cli.entry ? path.resolve(skillDir, cli.entry) : path.join(skillDir, 'index.js'),
|
|
152
168
|
commands,
|
|
153
169
|
tools,
|
|
170
|
+
actions: tools.map((tool) => skillToolActionName(id, tool)),
|
|
154
171
|
runtime: contract?.runtime || {},
|
|
155
172
|
contract,
|
|
156
173
|
};
|
|
@@ -728,6 +745,8 @@ Object.assign(module.exports, {
|
|
|
728
745
|
resolveSkillsDir,
|
|
729
746
|
resolveOpenClawPluginEntry,
|
|
730
747
|
runSkillCli,
|
|
748
|
+
skillToolActionName,
|
|
749
|
+
toolNameToActionSegment,
|
|
731
750
|
verifySkillIntegrity,
|
|
732
751
|
writeIntegrityManifest,
|
|
733
752
|
});
|