qlogicagent 0.2.1 → 0.3.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.
- package/dist/agent.js +1 -0
- package/dist/cli.js +9 -0
- package/dist/contracts.js +1 -0
- package/dist/index.js +5 -15
- package/dist/orchestration.js +118 -0
- package/package.json +56 -42
- package/dist/agent/agent.js +0 -113
- package/dist/agent/tool-loop.js +0 -575
- package/dist/agent/types.js +0 -14
- package/dist/cli/main.js +0 -23
- package/dist/cli/stdio-server.js +0 -463
- package/dist/config/config.js +0 -21
- package/dist/contracts/hooks.js +0 -7
- package/dist/contracts/index.js +0 -10
- package/dist/contracts/planner.js +0 -2
- package/dist/contracts/skill-candidate.js +0 -195
- package/dist/contracts/todo.js +0 -9
- package/dist/llm/builtin-providers.js +0 -531
- package/dist/llm/index.js +0 -14
- package/dist/llm/llm-client.js +0 -67
- package/dist/llm/model-catalog.js +0 -191
- package/dist/llm/provider-def.js +0 -12
- package/dist/llm/provider-registry.js +0 -147
- package/dist/llm/transport.js +0 -27
- package/dist/llm/transports/anthropic-messages.js +0 -293
- package/dist/llm/transports/openai-chat.js +0 -165
- package/dist/orchestration/agent-registry.js +0 -116
- package/dist/orchestration/approval-aware-tool-plan.js +0 -87
- package/dist/orchestration/context-compression.js +0 -583
- package/dist/orchestration/conversation-repair.js +0 -429
- package/dist/orchestration/curator-scheduler.js +0 -135
- package/dist/orchestration/embedded-failover-policy.js +0 -168
- package/dist/orchestration/error-classification.js +0 -77
- package/dist/orchestration/failover-classification.js +0 -381
- package/dist/orchestration/failover-error.js +0 -198
- package/dist/orchestration/fork-subagent.js +0 -98
- package/dist/orchestration/index.js +0 -267
- package/dist/orchestration/memory-flush-policy.js +0 -85
- package/dist/orchestration/memory-provider.js +0 -2
- package/dist/orchestration/parallel-tool-calls.js +0 -59
- package/dist/orchestration/prompt-cache-strategy.js +0 -228
- package/dist/orchestration/reactive-compact.js +0 -78
- package/dist/orchestration/retry-loop.js +0 -24
- package/dist/orchestration/skill-candidate.js +0 -141
- package/dist/orchestration/skill-consolidation.js +0 -220
- package/dist/orchestration/skill-improvement.js +0 -66
- package/dist/orchestration/skill-similarity.js +0 -131
- package/dist/orchestration/streaming-tool-executor.js +0 -96
- package/dist/orchestration/team-orchestration.js +0 -369
- package/dist/orchestration/team-tool-loop-wiring.js +0 -147
- package/dist/orchestration/tool-choice-policy.js +0 -164
- package/dist/orchestration/tool-loop-state.js +0 -133
- package/dist/orchestration/tool-schema.js +0 -297
- package/dist/orchestration/transcript-repair.js +0 -426
- package/dist/orchestration/turn-loop-guard.js +0 -92
- package/dist/orchestration/web-browser-policy.js +0 -39
- package/dist/runtime/context-compression.js +0 -274
- package/dist/runtime/hook-registry.js +0 -53
- package/dist/runtime/memory-hooks.js +0 -65
- package/dist/runtime/tool-eligibility.js +0 -111
- package/dist/skills/index.js +0 -82
- package/dist/skills/memory-extractor.js +0 -173
- package/dist/skills/memory-query-tool.js +0 -127
- package/dist/skills/memory-store.js +0 -228
- package/dist/skills/memory-tool.js +0 -192
- package/dist/skills/portable-tool.js +0 -14
- package/dist/skills/qmemory-adapter.js +0 -165
- package/dist/skills/skill-frontmatter.js +0 -344
- package/dist/skills/skill-guard.js +0 -229
- package/dist/skills/skill-loader.js +0 -303
- package/dist/skills/skill-source.js +0 -126
- package/dist/skills/skill-types.js +0 -6
- package/dist/skills/think-tool.js +0 -59
- package/dist/skills/todo-tool.js +0 -114
- package/dist/skills/tools/agent-tool.js +0 -142
- package/dist/skills/tools/apply-patch-tool.js +0 -184
- package/dist/skills/tools/ask-user-tool.js +0 -121
- package/dist/skills/tools/brief-tool.js +0 -95
- package/dist/skills/tools/browser-tool.js +0 -155
- package/dist/skills/tools/checkpoint-tool.js +0 -102
- package/dist/skills/tools/config-tool.js +0 -143
- package/dist/skills/tools/cron-tool.js +0 -175
- package/dist/skills/tools/edit-tool.js +0 -70
- package/dist/skills/tools/exec-tool.js +0 -133
- package/dist/skills/tools/image-generate-tool.js +0 -67
- package/dist/skills/tools/instructions-tool.js +0 -187
- package/dist/skills/tools/lsp-tool.js +0 -227
- package/dist/skills/tools/mcp-client-types.js +0 -53
- package/dist/skills/tools/mcp-tool.js +0 -503
- package/dist/skills/tools/memory-tool.js +0 -88
- package/dist/skills/tools/monitor-tool.js +0 -131
- package/dist/skills/tools/music-generate-tool.js +0 -62
- package/dist/skills/tools/notify-tool.js +0 -62
- package/dist/skills/tools/patch-tool.js +0 -505
- package/dist/skills/tools/pdf-tool.js +0 -88
- package/dist/skills/tools/plan-mode-tool.js +0 -122
- package/dist/skills/tools/read-tool.js +0 -84
- package/dist/skills/tools/repl-tool.js +0 -69
- package/dist/skills/tools/search-tool.js +0 -225
- package/dist/skills/tools/send-message-tool.js +0 -76
- package/dist/skills/tools/skill-list-tool.js +0 -54
- package/dist/skills/tools/skill-manage-tool.js +0 -153
- package/dist/skills/tools/skill-view-tool.js +0 -72
- package/dist/skills/tools/sleep-tool.js +0 -81
- package/dist/skills/tools/structured-output-tool.js +0 -176
- package/dist/skills/tools/task-tool.js +0 -161
- package/dist/skills/tools/team-tool.js +0 -105
- package/dist/skills/tools/tool-search-tool.js +0 -110
- package/dist/skills/tools/tts-tool.js +0 -45
- package/dist/skills/tools/video-edit-tool.js +0 -74
- package/dist/skills/tools/video-generate-tool.js +0 -66
- package/dist/skills/tools/video-merge-tool.js +0 -92
- package/dist/skills/tools/video-upscale-tool.js +0 -52
- package/dist/skills/tools/web-fetch-tool.js +0 -92
- package/dist/skills/tools/web-search-tool.js +0 -86
- package/dist/skills/tools/worktree-tool.js +0 -147
- package/dist/skills/tools/write-tool.js +0 -81
- /package/dist/{agent → types/agent}/agent.d.ts +0 -0
- /package/dist/{agent → types/agent}/tool-loop.d.ts +0 -0
- /package/dist/{agent → types/agent}/types.d.ts +0 -0
- /package/dist/{cli → types/cli}/main.d.ts +0 -0
- /package/dist/{cli → types/cli}/stdio-server.d.ts +0 -0
- /package/dist/{config → types/config}/config.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/hooks.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/index.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/planner.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/skill-candidate.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/todo.d.ts +0 -0
- /package/dist/{index.d.ts → types/index.d.ts} +0 -0
- /package/dist/{llm → types/llm}/builtin-providers.d.ts +0 -0
- /package/dist/{llm → types/llm}/index.d.ts +0 -0
- /package/dist/{llm → types/llm}/llm-client.d.ts +0 -0
- /package/dist/{llm → types/llm}/model-catalog.d.ts +0 -0
- /package/dist/{llm → types/llm}/provider-def.d.ts +0 -0
- /package/dist/{llm → types/llm}/provider-registry.d.ts +0 -0
- /package/dist/{llm → types/llm}/transport.d.ts +0 -0
- /package/dist/{llm → types/llm}/transports/anthropic-messages.d.ts +0 -0
- /package/dist/{llm → types/llm}/transports/openai-chat.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/agent-registry.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/approval-aware-tool-plan.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/context-compression.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/conversation-repair.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/curator-scheduler.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/embedded-failover-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/error-classification.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/failover-classification.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/failover-error.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/fork-subagent.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/index.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/memory-flush-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/memory-provider.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/parallel-tool-calls.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/prompt-cache-strategy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/reactive-compact.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/retry-loop.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-candidate.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-consolidation.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-improvement.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-similarity.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/streaming-tool-executor.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/team-orchestration.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/team-tool-loop-wiring.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-choice-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-loop-state.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-schema.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/transcript-repair.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/turn-loop-guard.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/web-browser-policy.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/context-compression.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/hook-registry.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/memory-hooks.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/tool-eligibility.d.ts +0 -0
- /package/dist/{skills → types/skills}/index.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-extractor.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-query-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-store.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/portable-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/qmemory-adapter.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-frontmatter.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-guard.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-loader.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-source.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-types.d.ts +0 -0
- /package/dist/{skills → types/skills}/think-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/todo-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/agent-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/apply-patch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/ask-user-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/brief-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/browser-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/checkpoint-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/config-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/cron-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/edit-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/exec-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/image-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/instructions-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/lsp-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/mcp-client-types.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/mcp-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/memory-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/monitor-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/music-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/notify-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/patch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/pdf-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/plan-mode-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/read-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/repl-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/send-message-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-list-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-manage-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-view-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/sleep-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/structured-output-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/task-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/team-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/tool-search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/tts-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-edit-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-merge-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-upscale-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/web-fetch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/web-search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/worktree-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/write-tool.d.ts +0 -0
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// SKILL.md frontmatter parser — portable, zero external dependencies.
|
|
3
|
-
// Mirrors openclaw/src/markdown/frontmatter.ts + src/agents/skills/frontmatter.ts
|
|
4
|
-
// but without YAML library (uses simple line parser + JSON5-free metadata).
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Block extraction
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
function extractFrontmatterBlock(content) {
|
|
10
|
-
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
11
|
-
if (!normalized.startsWith("---")) {
|
|
12
|
-
return undefined;
|
|
13
|
-
}
|
|
14
|
-
const endIndex = normalized.indexOf("\n---", 3);
|
|
15
|
-
if (endIndex === -1) {
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
return normalized.slice(4, endIndex);
|
|
19
|
-
}
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Line parser (handles both inline and multi-line values)
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
function stripQuotes(value) {
|
|
24
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
25
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
26
|
-
return value.slice(1, -1);
|
|
27
|
-
}
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
function extractMultiLineValue(lines, startIndex) {
|
|
31
|
-
const valueLines = [];
|
|
32
|
-
let i = startIndex + 1;
|
|
33
|
-
while (i < lines.length) {
|
|
34
|
-
const line = lines[i];
|
|
35
|
-
if (line.length > 0 && !line.startsWith(" ") && !line.startsWith("\t")) {
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
valueLines.push(line);
|
|
39
|
-
i += 1;
|
|
40
|
-
}
|
|
41
|
-
return { value: valueLines.join("\n").trim(), linesConsumed: i - startIndex };
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Parse a YAML-like frontmatter block into a flat key→value record.
|
|
45
|
-
* Uses a lightweight line parser to avoid a YAML library dependency.
|
|
46
|
-
*/
|
|
47
|
-
export function parseFrontmatter(content) {
|
|
48
|
-
const block = extractFrontmatterBlock(content);
|
|
49
|
-
if (!block) {
|
|
50
|
-
return {};
|
|
51
|
-
}
|
|
52
|
-
const result = {};
|
|
53
|
-
const lines = block.split("\n");
|
|
54
|
-
let i = 0;
|
|
55
|
-
while (i < lines.length) {
|
|
56
|
-
const line = lines[i];
|
|
57
|
-
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
58
|
-
if (!match) {
|
|
59
|
-
i += 1;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const key = match[1];
|
|
63
|
-
const inlineValue = match[2].trim();
|
|
64
|
-
if (!key) {
|
|
65
|
-
i += 1;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
// Check for multi-line value
|
|
69
|
-
if (!inlineValue && i + 1 < lines.length) {
|
|
70
|
-
const nextLine = lines[i + 1];
|
|
71
|
-
if (nextLine.startsWith(" ") || nextLine.startsWith("\t")) {
|
|
72
|
-
const { value, linesConsumed } = extractMultiLineValue(lines, i);
|
|
73
|
-
if (value) {
|
|
74
|
-
result[key] = value;
|
|
75
|
-
}
|
|
76
|
-
i += linesConsumed;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const value = stripQuotes(inlineValue);
|
|
81
|
-
if (value) {
|
|
82
|
-
result[key] = value;
|
|
83
|
-
}
|
|
84
|
-
i += 1;
|
|
85
|
-
}
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
// Metadata resolver (from `metadata:` JSON block)
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
function parseMetadataJson(raw) {
|
|
92
|
-
// Try direct JSON first: `metadata: {"openclaw": {...}}`
|
|
93
|
-
try {
|
|
94
|
-
const parsed = JSON.parse(raw);
|
|
95
|
-
if (parsed && typeof parsed === "object") {
|
|
96
|
-
const obj = parsed;
|
|
97
|
-
const nested = obj["openclaw"];
|
|
98
|
-
if (nested && typeof nested === "object") {
|
|
99
|
-
return nested;
|
|
100
|
-
}
|
|
101
|
-
return obj;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// Not JSON — fall through to YAML-like parser
|
|
106
|
-
}
|
|
107
|
-
// Handle YAML-like multi-line: `key: value` per line
|
|
108
|
-
return parseYamlLikeMetadata(raw);
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Parse simple YAML-like `key: value` lines where values may be JSON
|
|
112
|
-
* literals (objects, arrays, booleans, numbers) or plain strings.
|
|
113
|
-
*/
|
|
114
|
-
function parseYamlLikeMetadata(raw) {
|
|
115
|
-
const result = {};
|
|
116
|
-
let found = false;
|
|
117
|
-
for (const line of raw.split("\n")) {
|
|
118
|
-
const trimmed = line.trim();
|
|
119
|
-
if (!trimmed)
|
|
120
|
-
continue;
|
|
121
|
-
const m = trimmed.match(/^([\w-]+):\s*(.+)$/);
|
|
122
|
-
if (!m)
|
|
123
|
-
continue;
|
|
124
|
-
const key = m[1];
|
|
125
|
-
const val = m[2].trim();
|
|
126
|
-
found = true;
|
|
127
|
-
// Try JSON parse for objects / arrays / booleans / numbers
|
|
128
|
-
try {
|
|
129
|
-
result[key] = JSON.parse(val);
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
// Plain string — normalise booleans / numbers
|
|
133
|
-
if (val === "true")
|
|
134
|
-
result[key] = true;
|
|
135
|
-
else if (val === "false")
|
|
136
|
-
result[key] = false;
|
|
137
|
-
else if (/^-?\d+(\.\d+)?$/.test(val))
|
|
138
|
-
result[key] = Number(val);
|
|
139
|
-
else
|
|
140
|
-
result[key] = val;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return found ? result : undefined;
|
|
144
|
-
}
|
|
145
|
-
function normalizeStringList(input) {
|
|
146
|
-
if (!input)
|
|
147
|
-
return [];
|
|
148
|
-
if (Array.isArray(input)) {
|
|
149
|
-
return input.map((v) => String(v).trim()).filter(Boolean);
|
|
150
|
-
}
|
|
151
|
-
if (typeof input === "string") {
|
|
152
|
-
return input
|
|
153
|
-
.split(",")
|
|
154
|
-
.map((v) => v.trim())
|
|
155
|
-
.filter(Boolean);
|
|
156
|
-
}
|
|
157
|
-
return [];
|
|
158
|
-
}
|
|
159
|
-
function parseBool(value, fallback) {
|
|
160
|
-
if (value === undefined)
|
|
161
|
-
return fallback;
|
|
162
|
-
const lower = value.toLowerCase().trim();
|
|
163
|
-
if (lower === "true" || lower === "1" || lower === "yes")
|
|
164
|
-
return true;
|
|
165
|
-
if (lower === "false" || lower === "0" || lower === "no")
|
|
166
|
-
return false;
|
|
167
|
-
return fallback;
|
|
168
|
-
}
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Install spec validation (safe input normalization)
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
const BREW_FORMULA_PATTERN = /^[A-Za-z0-9][A-Za-z0-9@+._/-]*$/;
|
|
173
|
-
const GO_MODULE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~+\-/]*(?:@[A-Za-z0-9][A-Za-z0-9._~+\-/]*)?$/;
|
|
174
|
-
const UV_PACKAGE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._\-[\]=<>!~+,]*$/;
|
|
175
|
-
const NPM_SPEC_PATTERN = /^(@[a-z0-9][\w.\-]*\/)?[a-z0-9][\w.\-]*(@[~^]?[\d.*]+[\w.\-+]*)?$/;
|
|
176
|
-
function normalizeSafeBrewFormula(raw) {
|
|
177
|
-
if (typeof raw !== "string")
|
|
178
|
-
return undefined;
|
|
179
|
-
const formula = raw.trim();
|
|
180
|
-
if (!formula || formula.startsWith("-") || formula.includes("\\") || formula.includes("..")) {
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
|
183
|
-
return BREW_FORMULA_PATTERN.test(formula) ? formula : undefined;
|
|
184
|
-
}
|
|
185
|
-
function normalizeSafeNpmSpec(raw) {
|
|
186
|
-
if (typeof raw !== "string")
|
|
187
|
-
return undefined;
|
|
188
|
-
const spec = raw.trim();
|
|
189
|
-
if (!spec || spec.startsWith("-"))
|
|
190
|
-
return undefined;
|
|
191
|
-
return NPM_SPEC_PATTERN.test(spec) ? spec : undefined;
|
|
192
|
-
}
|
|
193
|
-
function normalizeSafeGoModule(raw) {
|
|
194
|
-
if (typeof raw !== "string")
|
|
195
|
-
return undefined;
|
|
196
|
-
const mod = raw.trim();
|
|
197
|
-
if (!mod || mod.startsWith("-") || mod.includes("\\") || mod.includes("://"))
|
|
198
|
-
return undefined;
|
|
199
|
-
return GO_MODULE_PATTERN.test(mod) ? mod : undefined;
|
|
200
|
-
}
|
|
201
|
-
function normalizeSafeUvPackage(raw) {
|
|
202
|
-
if (typeof raw !== "string")
|
|
203
|
-
return undefined;
|
|
204
|
-
const pkg = raw.trim();
|
|
205
|
-
if (!pkg || pkg.startsWith("-") || pkg.includes("\\") || pkg.includes("://"))
|
|
206
|
-
return undefined;
|
|
207
|
-
return UV_PACKAGE_PATTERN.test(pkg) ? pkg : undefined;
|
|
208
|
-
}
|
|
209
|
-
function normalizeSafeDownloadUrl(raw) {
|
|
210
|
-
if (typeof raw !== "string")
|
|
211
|
-
return undefined;
|
|
212
|
-
const value = raw.trim();
|
|
213
|
-
if (!value || /\s/.test(value))
|
|
214
|
-
return undefined;
|
|
215
|
-
try {
|
|
216
|
-
const parsed = new URL(value);
|
|
217
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
218
|
-
return undefined;
|
|
219
|
-
return parsed.toString();
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return undefined;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
function parseInstallSpec(input) {
|
|
226
|
-
if (!input || typeof input !== "object")
|
|
227
|
-
return undefined;
|
|
228
|
-
const raw = input;
|
|
229
|
-
const kindRaw = typeof raw.kind === "string" ? raw.kind : typeof raw.type === "string" ? raw.type : "";
|
|
230
|
-
const kind = kindRaw.trim().toLowerCase();
|
|
231
|
-
const ALLOWED_KINDS = ["brew", "node", "go", "uv", "download"];
|
|
232
|
-
if (!ALLOWED_KINDS.includes(kind))
|
|
233
|
-
return undefined;
|
|
234
|
-
const spec = { kind: kind };
|
|
235
|
-
if (typeof raw.id === "string")
|
|
236
|
-
spec.id = raw.id;
|
|
237
|
-
if (typeof raw.label === "string")
|
|
238
|
-
spec.label = raw.label;
|
|
239
|
-
const bins = normalizeStringList(raw.bins);
|
|
240
|
-
if (bins.length > 0)
|
|
241
|
-
spec.bins = bins;
|
|
242
|
-
const osList = normalizeStringList(raw.os);
|
|
243
|
-
if (osList.length > 0)
|
|
244
|
-
spec.os = osList;
|
|
245
|
-
// Kind-specific fields
|
|
246
|
-
const formula = normalizeSafeBrewFormula(raw.formula) ?? normalizeSafeBrewFormula(raw.cask);
|
|
247
|
-
if (formula)
|
|
248
|
-
spec.formula = formula;
|
|
249
|
-
if (kind === "node") {
|
|
250
|
-
const pkg = normalizeSafeNpmSpec(raw.package);
|
|
251
|
-
if (pkg)
|
|
252
|
-
spec.package = pkg;
|
|
253
|
-
}
|
|
254
|
-
else if (kind === "uv") {
|
|
255
|
-
const pkg = normalizeSafeUvPackage(raw.package);
|
|
256
|
-
if (pkg)
|
|
257
|
-
spec.package = pkg;
|
|
258
|
-
}
|
|
259
|
-
const mod = normalizeSafeGoModule(raw.module);
|
|
260
|
-
if (mod)
|
|
261
|
-
spec.module = mod;
|
|
262
|
-
const url = normalizeSafeDownloadUrl(raw.url);
|
|
263
|
-
if (url)
|
|
264
|
-
spec.url = url;
|
|
265
|
-
if (typeof raw.archive === "string")
|
|
266
|
-
spec.archive = raw.archive;
|
|
267
|
-
if (typeof raw.extract === "boolean")
|
|
268
|
-
spec.extract = raw.extract;
|
|
269
|
-
if (typeof raw.stripComponents === "number")
|
|
270
|
-
spec.stripComponents = raw.stripComponents;
|
|
271
|
-
if (typeof raw.targetDir === "string")
|
|
272
|
-
spec.targetDir = raw.targetDir;
|
|
273
|
-
// Validate required field per kind
|
|
274
|
-
if (kind === "brew" && !spec.formula)
|
|
275
|
-
return undefined;
|
|
276
|
-
if (kind === "node" && !spec.package)
|
|
277
|
-
return undefined;
|
|
278
|
-
if (kind === "go" && !spec.module)
|
|
279
|
-
return undefined;
|
|
280
|
-
if (kind === "uv" && !spec.package)
|
|
281
|
-
return undefined;
|
|
282
|
-
if (kind === "download" && !spec.url)
|
|
283
|
-
return undefined;
|
|
284
|
-
return spec;
|
|
285
|
-
}
|
|
286
|
-
// ---------------------------------------------------------------------------
|
|
287
|
-
// Public API
|
|
288
|
-
// ---------------------------------------------------------------------------
|
|
289
|
-
/**
|
|
290
|
-
* Resolve structured metadata from the `metadata:` frontmatter field.
|
|
291
|
-
* Expects `metadata: {"openclaw": {...}}` (JSON format).
|
|
292
|
-
*/
|
|
293
|
-
export function resolveSkillMetadata(frontmatter) {
|
|
294
|
-
const raw = frontmatter["metadata"];
|
|
295
|
-
if (!raw)
|
|
296
|
-
return undefined;
|
|
297
|
-
const metadataObj = parseMetadataJson(raw);
|
|
298
|
-
if (!metadataObj)
|
|
299
|
-
return undefined;
|
|
300
|
-
const requiresRaw = typeof metadataObj.requires === "object" && metadataObj.requires !== null
|
|
301
|
-
? metadataObj.requires
|
|
302
|
-
: undefined;
|
|
303
|
-
const requires = requiresRaw
|
|
304
|
-
? {
|
|
305
|
-
bins: normalizeStringList(requiresRaw.bins),
|
|
306
|
-
anyBins: normalizeStringList(requiresRaw.anyBins),
|
|
307
|
-
env: normalizeStringList(requiresRaw.env),
|
|
308
|
-
config: normalizeStringList(requiresRaw.config),
|
|
309
|
-
}
|
|
310
|
-
: undefined;
|
|
311
|
-
const installRaw = Array.isArray(metadataObj.install)
|
|
312
|
-
? metadataObj.install
|
|
313
|
-
: [];
|
|
314
|
-
const install = installRaw
|
|
315
|
-
.map((entry) => parseInstallSpec(entry))
|
|
316
|
-
.filter((entry) => Boolean(entry));
|
|
317
|
-
const osRaw = normalizeStringList(metadataObj.os);
|
|
318
|
-
return {
|
|
319
|
-
always: typeof metadataObj.always === "boolean" ? metadataObj.always : undefined,
|
|
320
|
-
emoji: typeof metadataObj.emoji === "string" ? metadataObj.emoji : undefined,
|
|
321
|
-
homepage: typeof metadataObj.homepage === "string" ? metadataObj.homepage : undefined,
|
|
322
|
-
skillKey: typeof metadataObj.skillKey === "string" ? metadataObj.skillKey : undefined,
|
|
323
|
-
primaryEnv: typeof metadataObj.primaryEnv === "string" ? metadataObj.primaryEnv : undefined,
|
|
324
|
-
os: osRaw.length > 0 ? osRaw : undefined,
|
|
325
|
-
category: typeof metadataObj.category === "string" ? metadataObj.category : undefined,
|
|
326
|
-
requires,
|
|
327
|
-
install: install.length > 0 ? install : undefined,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Resolve invocation policy from frontmatter fields.
|
|
332
|
-
*/
|
|
333
|
-
export function resolveSkillInvocationPolicy(frontmatter) {
|
|
334
|
-
return {
|
|
335
|
-
userInvocable: parseBool(frontmatter["user-invocable"], true),
|
|
336
|
-
disableModelInvocation: parseBool(frontmatter["disable-model-invocation"], false),
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Get the canonical key for a skill (metadata.skillKey or skill.name).
|
|
341
|
-
*/
|
|
342
|
-
export function resolveSkillKey(skillName, metadata) {
|
|
343
|
-
return metadata?.skillKey ?? skillName;
|
|
344
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Skill security scanner — portable, zero external dependencies.
|
|
3
|
-
// Mirrors openclaw/src/security/skill-scanner.ts rules but uses DI deps
|
|
4
|
-
// instead of Node.js fs/path directly.
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
const LINE_RULES = [
|
|
7
|
-
{
|
|
8
|
-
ruleId: "dangerous-exec",
|
|
9
|
-
severity: "critical",
|
|
10
|
-
message: "Shell command execution detected (child_process)",
|
|
11
|
-
pattern: /\b(exec|execSync|spawn|spawnSync|execFile|execFileSync)\s*\(/,
|
|
12
|
-
requiresContext: /child_process/,
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
ruleId: "dynamic-code-execution",
|
|
16
|
-
severity: "critical",
|
|
17
|
-
message: "Dynamic code execution detected",
|
|
18
|
-
pattern: /\beval\s*\(|new\s+Function\s*\(/,
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
ruleId: "crypto-mining",
|
|
22
|
-
severity: "critical",
|
|
23
|
-
message: "Possible crypto-mining reference detected",
|
|
24
|
-
pattern: /stratum\+tcp|stratum\+ssl|coinhive|cryptonight|xmrig/i,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
ruleId: "suspicious-network",
|
|
28
|
-
severity: "warn",
|
|
29
|
-
message: "WebSocket connection to non-standard port",
|
|
30
|
-
pattern: /new\s+WebSocket\s*\(\s*["']wss?:\/\/[^"']*:(\d+)/,
|
|
31
|
-
},
|
|
32
|
-
];
|
|
33
|
-
const STANDARD_PORTS = new Set([80, 443, 8080, 8443, 3000]);
|
|
34
|
-
const SOURCE_RULES = [
|
|
35
|
-
{
|
|
36
|
-
ruleId: "potential-exfiltration",
|
|
37
|
-
severity: "warn",
|
|
38
|
-
message: "File read combined with network send — possible data exfiltration",
|
|
39
|
-
pattern: /readFileSync|readFile/,
|
|
40
|
-
requiresContext: /\bfetch\b|\bpost\b|http\.request/i,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
ruleId: "obfuscated-code",
|
|
44
|
-
severity: "warn",
|
|
45
|
-
message: "Hex-encoded string sequence detected (possible obfuscation)",
|
|
46
|
-
pattern: /(\\x[0-9a-fA-F]{2}){6,}/,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
ruleId: "obfuscated-code",
|
|
50
|
-
severity: "warn",
|
|
51
|
-
message: "Large base64 payload with decode call detected (possible obfuscation)",
|
|
52
|
-
pattern: /(?:atob|Buffer\.from)\s*\(\s*["'][A-Za-z0-9+/=]{200,}["']/,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
ruleId: "env-harvesting",
|
|
56
|
-
severity: "critical",
|
|
57
|
-
message: "Environment variable access combined with network send — possible credential harvesting",
|
|
58
|
-
pattern: /process\.env/,
|
|
59
|
-
requiresContext: /\bfetch\b|\bpost\b|http\.request/i,
|
|
60
|
-
},
|
|
61
|
-
];
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// Scannable extensions
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
const SCANNABLE_EXTENSIONS = new Set([
|
|
66
|
-
".js", ".ts", ".mjs", ".cjs", ".mts", ".cts", ".jsx", ".tsx",
|
|
67
|
-
]);
|
|
68
|
-
const DEFAULT_MAX_SCAN_FILES = 500;
|
|
69
|
-
const DEFAULT_MAX_FILE_BYTES = 1024 * 1024;
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
// Core scanner (operates on source text — no FS dependency)
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
function truncateEvidence(evidence, maxLen = 120) {
|
|
74
|
-
return evidence.length <= maxLen ? evidence : `${evidence.slice(0, maxLen)}…`;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Scan a source string for security findings.
|
|
78
|
-
* This is a pure function — no file I/O.
|
|
79
|
-
*/
|
|
80
|
-
export function scanSource(source, filePath) {
|
|
81
|
-
const findings = [];
|
|
82
|
-
const lines = source.split("\n");
|
|
83
|
-
const matchedLineRules = new Set();
|
|
84
|
-
// Line rules
|
|
85
|
-
for (const rule of LINE_RULES) {
|
|
86
|
-
if (matchedLineRules.has(rule.ruleId))
|
|
87
|
-
continue;
|
|
88
|
-
if (rule.requiresContext && !rule.requiresContext.test(source))
|
|
89
|
-
continue;
|
|
90
|
-
for (let i = 0; i < lines.length; i++) {
|
|
91
|
-
const line = lines[i];
|
|
92
|
-
const match = rule.pattern.exec(line);
|
|
93
|
-
if (!match)
|
|
94
|
-
continue;
|
|
95
|
-
// Special: suspicious-network checks port number
|
|
96
|
-
if (rule.ruleId === "suspicious-network") {
|
|
97
|
-
const port = parseInt(match[1], 10);
|
|
98
|
-
if (STANDARD_PORTS.has(port))
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
findings.push({
|
|
102
|
-
ruleId: rule.ruleId,
|
|
103
|
-
severity: rule.severity,
|
|
104
|
-
file: filePath,
|
|
105
|
-
line: i + 1,
|
|
106
|
-
message: rule.message,
|
|
107
|
-
evidence: truncateEvidence(line.trim()),
|
|
108
|
-
});
|
|
109
|
-
matchedLineRules.add(rule.ruleId);
|
|
110
|
-
break; // one finding per line-rule per file
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// Source rules
|
|
114
|
-
const matchedSourceRules = new Set();
|
|
115
|
-
for (const rule of SOURCE_RULES) {
|
|
116
|
-
const ruleKey = `${rule.ruleId}::${rule.message}`;
|
|
117
|
-
if (matchedSourceRules.has(ruleKey))
|
|
118
|
-
continue;
|
|
119
|
-
if (!rule.pattern.test(source))
|
|
120
|
-
continue;
|
|
121
|
-
if (rule.requiresContext && !rule.requiresContext.test(source))
|
|
122
|
-
continue;
|
|
123
|
-
let matchLine = 0;
|
|
124
|
-
let matchEvidence = "";
|
|
125
|
-
for (let i = 0; i < lines.length; i++) {
|
|
126
|
-
if (rule.pattern.test(lines[i])) {
|
|
127
|
-
matchLine = i + 1;
|
|
128
|
-
matchEvidence = lines[i].trim();
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (matchLine === 0) {
|
|
133
|
-
matchLine = 1;
|
|
134
|
-
matchEvidence = source.slice(0, 120);
|
|
135
|
-
}
|
|
136
|
-
findings.push({
|
|
137
|
-
ruleId: rule.ruleId,
|
|
138
|
-
severity: rule.severity,
|
|
139
|
-
file: filePath,
|
|
140
|
-
line: matchLine,
|
|
141
|
-
message: rule.message,
|
|
142
|
-
evidence: truncateEvidence(matchEvidence),
|
|
143
|
-
});
|
|
144
|
-
matchedSourceRules.add(ruleKey);
|
|
145
|
-
}
|
|
146
|
-
return findings;
|
|
147
|
-
}
|
|
148
|
-
async function walkDirWithLimit(deps, dirPath, maxFiles) {
|
|
149
|
-
const files = [];
|
|
150
|
-
const stack = [dirPath];
|
|
151
|
-
while (stack.length > 0 && files.length < maxFiles) {
|
|
152
|
-
const currentDir = stack.pop();
|
|
153
|
-
if (!currentDir)
|
|
154
|
-
break;
|
|
155
|
-
let entries;
|
|
156
|
-
try {
|
|
157
|
-
entries = deps.fs.readdirSync(currentDir, { withFileTypes: true });
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
for (const entry of entries) {
|
|
163
|
-
if (files.length >= maxFiles)
|
|
164
|
-
break;
|
|
165
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
166
|
-
continue;
|
|
167
|
-
const fullPath = deps.path.join(currentDir, entry.name);
|
|
168
|
-
if (entry.isDirectory()) {
|
|
169
|
-
stack.push(fullPath);
|
|
170
|
-
}
|
|
171
|
-
else if (SCANNABLE_EXTENSIONS.has(deps.path.extname(entry.name).toLowerCase())) {
|
|
172
|
-
files.push(fullPath);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return files;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Scan an entire skill directory for security issues.
|
|
180
|
-
* Returns a summary with per-file findings.
|
|
181
|
-
*/
|
|
182
|
-
export async function scanSkillDirectory(deps, skillDir, opts) {
|
|
183
|
-
const maxFiles = Math.max(1, opts?.maxFiles ?? DEFAULT_MAX_SCAN_FILES);
|
|
184
|
-
const maxFileBytes = Math.max(1, opts?.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES);
|
|
185
|
-
const files = opts?.includeFiles && opts.includeFiles.length > 0
|
|
186
|
-
? opts.includeFiles
|
|
187
|
-
: await walkDirWithLimit(deps, skillDir, maxFiles);
|
|
188
|
-
const allFindings = [];
|
|
189
|
-
let scannedFiles = 0;
|
|
190
|
-
for (const filePath of files) {
|
|
191
|
-
try {
|
|
192
|
-
const stat = deps.fs.statSync(filePath);
|
|
193
|
-
if (stat.size > maxFileBytes)
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
let source;
|
|
200
|
-
try {
|
|
201
|
-
source = await deps.fs.readFile(filePath, "utf-8");
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
scannedFiles += 1;
|
|
207
|
-
const findings = scanSource(source, filePath);
|
|
208
|
-
allFindings.push(...findings);
|
|
209
|
-
}
|
|
210
|
-
return {
|
|
211
|
-
scannedFiles,
|
|
212
|
-
critical: allFindings.filter((f) => f.severity === "critical").length,
|
|
213
|
-
warn: allFindings.filter((f) => f.severity === "warn").length,
|
|
214
|
-
info: allFindings.filter((f) => f.severity === "info").length,
|
|
215
|
-
findings: allFindings,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Quick check: does this source have any critical findings?
|
|
220
|
-
*/
|
|
221
|
-
export function hasCriticalFindings(source, filePath) {
|
|
222
|
-
return scanSource(source, filePath).some((f) => f.severity === "critical");
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Check if a file extension is scannable by the guard.
|
|
226
|
-
*/
|
|
227
|
-
export function isScannable(filePath, pathDeps) {
|
|
228
|
-
return SCANNABLE_EXTENSIONS.has(pathDeps.extname(filePath).toLowerCase());
|
|
229
|
-
}
|