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.
Files changed (229) hide show
  1. package/dist/agent.js +1 -0
  2. package/dist/cli.js +9 -0
  3. package/dist/contracts.js +1 -0
  4. package/dist/index.js +5 -15
  5. package/dist/orchestration.js +118 -0
  6. package/package.json +56 -42
  7. package/dist/agent/agent.js +0 -113
  8. package/dist/agent/tool-loop.js +0 -575
  9. package/dist/agent/types.js +0 -14
  10. package/dist/cli/main.js +0 -23
  11. package/dist/cli/stdio-server.js +0 -463
  12. package/dist/config/config.js +0 -21
  13. package/dist/contracts/hooks.js +0 -7
  14. package/dist/contracts/index.js +0 -10
  15. package/dist/contracts/planner.js +0 -2
  16. package/dist/contracts/skill-candidate.js +0 -195
  17. package/dist/contracts/todo.js +0 -9
  18. package/dist/llm/builtin-providers.js +0 -531
  19. package/dist/llm/index.js +0 -14
  20. package/dist/llm/llm-client.js +0 -67
  21. package/dist/llm/model-catalog.js +0 -191
  22. package/dist/llm/provider-def.js +0 -12
  23. package/dist/llm/provider-registry.js +0 -147
  24. package/dist/llm/transport.js +0 -27
  25. package/dist/llm/transports/anthropic-messages.js +0 -293
  26. package/dist/llm/transports/openai-chat.js +0 -165
  27. package/dist/orchestration/agent-registry.js +0 -116
  28. package/dist/orchestration/approval-aware-tool-plan.js +0 -87
  29. package/dist/orchestration/context-compression.js +0 -583
  30. package/dist/orchestration/conversation-repair.js +0 -429
  31. package/dist/orchestration/curator-scheduler.js +0 -135
  32. package/dist/orchestration/embedded-failover-policy.js +0 -168
  33. package/dist/orchestration/error-classification.js +0 -77
  34. package/dist/orchestration/failover-classification.js +0 -381
  35. package/dist/orchestration/failover-error.js +0 -198
  36. package/dist/orchestration/fork-subagent.js +0 -98
  37. package/dist/orchestration/index.js +0 -267
  38. package/dist/orchestration/memory-flush-policy.js +0 -85
  39. package/dist/orchestration/memory-provider.js +0 -2
  40. package/dist/orchestration/parallel-tool-calls.js +0 -59
  41. package/dist/orchestration/prompt-cache-strategy.js +0 -228
  42. package/dist/orchestration/reactive-compact.js +0 -78
  43. package/dist/orchestration/retry-loop.js +0 -24
  44. package/dist/orchestration/skill-candidate.js +0 -141
  45. package/dist/orchestration/skill-consolidation.js +0 -220
  46. package/dist/orchestration/skill-improvement.js +0 -66
  47. package/dist/orchestration/skill-similarity.js +0 -131
  48. package/dist/orchestration/streaming-tool-executor.js +0 -96
  49. package/dist/orchestration/team-orchestration.js +0 -369
  50. package/dist/orchestration/team-tool-loop-wiring.js +0 -147
  51. package/dist/orchestration/tool-choice-policy.js +0 -164
  52. package/dist/orchestration/tool-loop-state.js +0 -133
  53. package/dist/orchestration/tool-schema.js +0 -297
  54. package/dist/orchestration/transcript-repair.js +0 -426
  55. package/dist/orchestration/turn-loop-guard.js +0 -92
  56. package/dist/orchestration/web-browser-policy.js +0 -39
  57. package/dist/runtime/context-compression.js +0 -274
  58. package/dist/runtime/hook-registry.js +0 -53
  59. package/dist/runtime/memory-hooks.js +0 -65
  60. package/dist/runtime/tool-eligibility.js +0 -111
  61. package/dist/skills/index.js +0 -82
  62. package/dist/skills/memory-extractor.js +0 -173
  63. package/dist/skills/memory-query-tool.js +0 -127
  64. package/dist/skills/memory-store.js +0 -228
  65. package/dist/skills/memory-tool.js +0 -192
  66. package/dist/skills/portable-tool.js +0 -14
  67. package/dist/skills/qmemory-adapter.js +0 -165
  68. package/dist/skills/skill-frontmatter.js +0 -344
  69. package/dist/skills/skill-guard.js +0 -229
  70. package/dist/skills/skill-loader.js +0 -303
  71. package/dist/skills/skill-source.js +0 -126
  72. package/dist/skills/skill-types.js +0 -6
  73. package/dist/skills/think-tool.js +0 -59
  74. package/dist/skills/todo-tool.js +0 -114
  75. package/dist/skills/tools/agent-tool.js +0 -142
  76. package/dist/skills/tools/apply-patch-tool.js +0 -184
  77. package/dist/skills/tools/ask-user-tool.js +0 -121
  78. package/dist/skills/tools/brief-tool.js +0 -95
  79. package/dist/skills/tools/browser-tool.js +0 -155
  80. package/dist/skills/tools/checkpoint-tool.js +0 -102
  81. package/dist/skills/tools/config-tool.js +0 -143
  82. package/dist/skills/tools/cron-tool.js +0 -175
  83. package/dist/skills/tools/edit-tool.js +0 -70
  84. package/dist/skills/tools/exec-tool.js +0 -133
  85. package/dist/skills/tools/image-generate-tool.js +0 -67
  86. package/dist/skills/tools/instructions-tool.js +0 -187
  87. package/dist/skills/tools/lsp-tool.js +0 -227
  88. package/dist/skills/tools/mcp-client-types.js +0 -53
  89. package/dist/skills/tools/mcp-tool.js +0 -503
  90. package/dist/skills/tools/memory-tool.js +0 -88
  91. package/dist/skills/tools/monitor-tool.js +0 -131
  92. package/dist/skills/tools/music-generate-tool.js +0 -62
  93. package/dist/skills/tools/notify-tool.js +0 -62
  94. package/dist/skills/tools/patch-tool.js +0 -505
  95. package/dist/skills/tools/pdf-tool.js +0 -88
  96. package/dist/skills/tools/plan-mode-tool.js +0 -122
  97. package/dist/skills/tools/read-tool.js +0 -84
  98. package/dist/skills/tools/repl-tool.js +0 -69
  99. package/dist/skills/tools/search-tool.js +0 -225
  100. package/dist/skills/tools/send-message-tool.js +0 -76
  101. package/dist/skills/tools/skill-list-tool.js +0 -54
  102. package/dist/skills/tools/skill-manage-tool.js +0 -153
  103. package/dist/skills/tools/skill-view-tool.js +0 -72
  104. package/dist/skills/tools/sleep-tool.js +0 -81
  105. package/dist/skills/tools/structured-output-tool.js +0 -176
  106. package/dist/skills/tools/task-tool.js +0 -161
  107. package/dist/skills/tools/team-tool.js +0 -105
  108. package/dist/skills/tools/tool-search-tool.js +0 -110
  109. package/dist/skills/tools/tts-tool.js +0 -45
  110. package/dist/skills/tools/video-edit-tool.js +0 -74
  111. package/dist/skills/tools/video-generate-tool.js +0 -66
  112. package/dist/skills/tools/video-merge-tool.js +0 -92
  113. package/dist/skills/tools/video-upscale-tool.js +0 -52
  114. package/dist/skills/tools/web-fetch-tool.js +0 -92
  115. package/dist/skills/tools/web-search-tool.js +0 -86
  116. package/dist/skills/tools/worktree-tool.js +0 -147
  117. package/dist/skills/tools/write-tool.js +0 -81
  118. /package/dist/{agent → types/agent}/agent.d.ts +0 -0
  119. /package/dist/{agent → types/agent}/tool-loop.d.ts +0 -0
  120. /package/dist/{agent → types/agent}/types.d.ts +0 -0
  121. /package/dist/{cli → types/cli}/main.d.ts +0 -0
  122. /package/dist/{cli → types/cli}/stdio-server.d.ts +0 -0
  123. /package/dist/{config → types/config}/config.d.ts +0 -0
  124. /package/dist/{contracts → types/contracts}/hooks.d.ts +0 -0
  125. /package/dist/{contracts → types/contracts}/index.d.ts +0 -0
  126. /package/dist/{contracts → types/contracts}/planner.d.ts +0 -0
  127. /package/dist/{contracts → types/contracts}/skill-candidate.d.ts +0 -0
  128. /package/dist/{contracts → types/contracts}/todo.d.ts +0 -0
  129. /package/dist/{index.d.ts → types/index.d.ts} +0 -0
  130. /package/dist/{llm → types/llm}/builtin-providers.d.ts +0 -0
  131. /package/dist/{llm → types/llm}/index.d.ts +0 -0
  132. /package/dist/{llm → types/llm}/llm-client.d.ts +0 -0
  133. /package/dist/{llm → types/llm}/model-catalog.d.ts +0 -0
  134. /package/dist/{llm → types/llm}/provider-def.d.ts +0 -0
  135. /package/dist/{llm → types/llm}/provider-registry.d.ts +0 -0
  136. /package/dist/{llm → types/llm}/transport.d.ts +0 -0
  137. /package/dist/{llm → types/llm}/transports/anthropic-messages.d.ts +0 -0
  138. /package/dist/{llm → types/llm}/transports/openai-chat.d.ts +0 -0
  139. /package/dist/{orchestration → types/orchestration}/agent-registry.d.ts +0 -0
  140. /package/dist/{orchestration → types/orchestration}/approval-aware-tool-plan.d.ts +0 -0
  141. /package/dist/{orchestration → types/orchestration}/context-compression.d.ts +0 -0
  142. /package/dist/{orchestration → types/orchestration}/conversation-repair.d.ts +0 -0
  143. /package/dist/{orchestration → types/orchestration}/curator-scheduler.d.ts +0 -0
  144. /package/dist/{orchestration → types/orchestration}/embedded-failover-policy.d.ts +0 -0
  145. /package/dist/{orchestration → types/orchestration}/error-classification.d.ts +0 -0
  146. /package/dist/{orchestration → types/orchestration}/failover-classification.d.ts +0 -0
  147. /package/dist/{orchestration → types/orchestration}/failover-error.d.ts +0 -0
  148. /package/dist/{orchestration → types/orchestration}/fork-subagent.d.ts +0 -0
  149. /package/dist/{orchestration → types/orchestration}/index.d.ts +0 -0
  150. /package/dist/{orchestration → types/orchestration}/memory-flush-policy.d.ts +0 -0
  151. /package/dist/{orchestration → types/orchestration}/memory-provider.d.ts +0 -0
  152. /package/dist/{orchestration → types/orchestration}/parallel-tool-calls.d.ts +0 -0
  153. /package/dist/{orchestration → types/orchestration}/prompt-cache-strategy.d.ts +0 -0
  154. /package/dist/{orchestration → types/orchestration}/reactive-compact.d.ts +0 -0
  155. /package/dist/{orchestration → types/orchestration}/retry-loop.d.ts +0 -0
  156. /package/dist/{orchestration → types/orchestration}/skill-candidate.d.ts +0 -0
  157. /package/dist/{orchestration → types/orchestration}/skill-consolidation.d.ts +0 -0
  158. /package/dist/{orchestration → types/orchestration}/skill-improvement.d.ts +0 -0
  159. /package/dist/{orchestration → types/orchestration}/skill-similarity.d.ts +0 -0
  160. /package/dist/{orchestration → types/orchestration}/streaming-tool-executor.d.ts +0 -0
  161. /package/dist/{orchestration → types/orchestration}/team-orchestration.d.ts +0 -0
  162. /package/dist/{orchestration → types/orchestration}/team-tool-loop-wiring.d.ts +0 -0
  163. /package/dist/{orchestration → types/orchestration}/tool-choice-policy.d.ts +0 -0
  164. /package/dist/{orchestration → types/orchestration}/tool-loop-state.d.ts +0 -0
  165. /package/dist/{orchestration → types/orchestration}/tool-schema.d.ts +0 -0
  166. /package/dist/{orchestration → types/orchestration}/transcript-repair.d.ts +0 -0
  167. /package/dist/{orchestration → types/orchestration}/turn-loop-guard.d.ts +0 -0
  168. /package/dist/{orchestration → types/orchestration}/web-browser-policy.d.ts +0 -0
  169. /package/dist/{runtime → types/runtime}/context-compression.d.ts +0 -0
  170. /package/dist/{runtime → types/runtime}/hook-registry.d.ts +0 -0
  171. /package/dist/{runtime → types/runtime}/memory-hooks.d.ts +0 -0
  172. /package/dist/{runtime → types/runtime}/tool-eligibility.d.ts +0 -0
  173. /package/dist/{skills → types/skills}/index.d.ts +0 -0
  174. /package/dist/{skills → types/skills}/memory-extractor.d.ts +0 -0
  175. /package/dist/{skills → types/skills}/memory-query-tool.d.ts +0 -0
  176. /package/dist/{skills → types/skills}/memory-store.d.ts +0 -0
  177. /package/dist/{skills → types/skills}/memory-tool.d.ts +0 -0
  178. /package/dist/{skills → types/skills}/portable-tool.d.ts +0 -0
  179. /package/dist/{skills → types/skills}/qmemory-adapter.d.ts +0 -0
  180. /package/dist/{skills → types/skills}/skill-frontmatter.d.ts +0 -0
  181. /package/dist/{skills → types/skills}/skill-guard.d.ts +0 -0
  182. /package/dist/{skills → types/skills}/skill-loader.d.ts +0 -0
  183. /package/dist/{skills → types/skills}/skill-source.d.ts +0 -0
  184. /package/dist/{skills → types/skills}/skill-types.d.ts +0 -0
  185. /package/dist/{skills → types/skills}/think-tool.d.ts +0 -0
  186. /package/dist/{skills → types/skills}/todo-tool.d.ts +0 -0
  187. /package/dist/{skills → types/skills}/tools/agent-tool.d.ts +0 -0
  188. /package/dist/{skills → types/skills}/tools/apply-patch-tool.d.ts +0 -0
  189. /package/dist/{skills → types/skills}/tools/ask-user-tool.d.ts +0 -0
  190. /package/dist/{skills → types/skills}/tools/brief-tool.d.ts +0 -0
  191. /package/dist/{skills → types/skills}/tools/browser-tool.d.ts +0 -0
  192. /package/dist/{skills → types/skills}/tools/checkpoint-tool.d.ts +0 -0
  193. /package/dist/{skills → types/skills}/tools/config-tool.d.ts +0 -0
  194. /package/dist/{skills → types/skills}/tools/cron-tool.d.ts +0 -0
  195. /package/dist/{skills → types/skills}/tools/edit-tool.d.ts +0 -0
  196. /package/dist/{skills → types/skills}/tools/exec-tool.d.ts +0 -0
  197. /package/dist/{skills → types/skills}/tools/image-generate-tool.d.ts +0 -0
  198. /package/dist/{skills → types/skills}/tools/instructions-tool.d.ts +0 -0
  199. /package/dist/{skills → types/skills}/tools/lsp-tool.d.ts +0 -0
  200. /package/dist/{skills → types/skills}/tools/mcp-client-types.d.ts +0 -0
  201. /package/dist/{skills → types/skills}/tools/mcp-tool.d.ts +0 -0
  202. /package/dist/{skills → types/skills}/tools/memory-tool.d.ts +0 -0
  203. /package/dist/{skills → types/skills}/tools/monitor-tool.d.ts +0 -0
  204. /package/dist/{skills → types/skills}/tools/music-generate-tool.d.ts +0 -0
  205. /package/dist/{skills → types/skills}/tools/notify-tool.d.ts +0 -0
  206. /package/dist/{skills → types/skills}/tools/patch-tool.d.ts +0 -0
  207. /package/dist/{skills → types/skills}/tools/pdf-tool.d.ts +0 -0
  208. /package/dist/{skills → types/skills}/tools/plan-mode-tool.d.ts +0 -0
  209. /package/dist/{skills → types/skills}/tools/read-tool.d.ts +0 -0
  210. /package/dist/{skills → types/skills}/tools/repl-tool.d.ts +0 -0
  211. /package/dist/{skills → types/skills}/tools/search-tool.d.ts +0 -0
  212. /package/dist/{skills → types/skills}/tools/send-message-tool.d.ts +0 -0
  213. /package/dist/{skills → types/skills}/tools/skill-list-tool.d.ts +0 -0
  214. /package/dist/{skills → types/skills}/tools/skill-manage-tool.d.ts +0 -0
  215. /package/dist/{skills → types/skills}/tools/skill-view-tool.d.ts +0 -0
  216. /package/dist/{skills → types/skills}/tools/sleep-tool.d.ts +0 -0
  217. /package/dist/{skills → types/skills}/tools/structured-output-tool.d.ts +0 -0
  218. /package/dist/{skills → types/skills}/tools/task-tool.d.ts +0 -0
  219. /package/dist/{skills → types/skills}/tools/team-tool.d.ts +0 -0
  220. /package/dist/{skills → types/skills}/tools/tool-search-tool.d.ts +0 -0
  221. /package/dist/{skills → types/skills}/tools/tts-tool.d.ts +0 -0
  222. /package/dist/{skills → types/skills}/tools/video-edit-tool.d.ts +0 -0
  223. /package/dist/{skills → types/skills}/tools/video-generate-tool.d.ts +0 -0
  224. /package/dist/{skills → types/skills}/tools/video-merge-tool.d.ts +0 -0
  225. /package/dist/{skills → types/skills}/tools/video-upscale-tool.d.ts +0 -0
  226. /package/dist/{skills → types/skills}/tools/web-fetch-tool.d.ts +0 -0
  227. /package/dist/{skills → types/skills}/tools/web-search-tool.d.ts +0 -0
  228. /package/dist/{skills → types/skills}/tools/worktree-tool.d.ts +0 -0
  229. /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
- }