@dotsetlabs/dotclaw 1.9.0 → 2.1.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 (171) hide show
  1. package/.env.example +6 -0
  2. package/README.md +13 -8
  3. package/config-examples/groups/global/CLAUDE.md +6 -14
  4. package/config-examples/groups/main/CLAUDE.md +8 -39
  5. package/config-examples/runtime.json +16 -122
  6. package/config-examples/tool-policy.json +2 -15
  7. package/container/agent-runner/package-lock.json +258 -0
  8. package/container/agent-runner/package.json +2 -1
  9. package/container/agent-runner/src/agent-config.ts +62 -47
  10. package/container/agent-runner/src/browser.ts +180 -0
  11. package/container/agent-runner/src/container-protocol.ts +4 -9
  12. package/container/agent-runner/src/id.ts +3 -2
  13. package/container/agent-runner/src/index.ts +331 -846
  14. package/container/agent-runner/src/ipc.ts +3 -33
  15. package/container/agent-runner/src/mcp-client.ts +222 -0
  16. package/container/agent-runner/src/mcp-registry.ts +163 -0
  17. package/container/agent-runner/src/skill-loader.ts +375 -0
  18. package/container/agent-runner/src/tools.ts +154 -184
  19. package/container/agent-runner/src/tts.ts +61 -0
  20. package/dist/admin-commands.d.ts.map +1 -1
  21. package/dist/admin-commands.js +12 -0
  22. package/dist/admin-commands.js.map +1 -1
  23. package/dist/agent-execution.d.ts +5 -9
  24. package/dist/agent-execution.d.ts.map +1 -1
  25. package/dist/agent-execution.js +32 -20
  26. package/dist/agent-execution.js.map +1 -1
  27. package/dist/cli.js +61 -16
  28. package/dist/cli.js.map +1 -1
  29. package/dist/config.d.ts +1 -4
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +2 -5
  32. package/dist/config.js.map +1 -1
  33. package/dist/container-protocol.d.ts +4 -9
  34. package/dist/container-protocol.d.ts.map +1 -1
  35. package/dist/container-runner.d.ts.map +1 -1
  36. package/dist/container-runner.js +3 -8
  37. package/dist/container-runner.js.map +1 -1
  38. package/dist/dashboard.d.ts +5 -6
  39. package/dist/dashboard.d.ts.map +1 -1
  40. package/dist/dashboard.js +12 -60
  41. package/dist/dashboard.js.map +1 -1
  42. package/dist/db.d.ts +1 -59
  43. package/dist/db.d.ts.map +1 -1
  44. package/dist/db.js +41 -262
  45. package/dist/db.js.map +1 -1
  46. package/dist/error-messages.d.ts.map +1 -1
  47. package/dist/error-messages.js +5 -1
  48. package/dist/error-messages.js.map +1 -1
  49. package/dist/hooks.d.ts +7 -0
  50. package/dist/hooks.d.ts.map +1 -0
  51. package/dist/hooks.js +93 -0
  52. package/dist/hooks.js.map +1 -0
  53. package/dist/id.d.ts.map +1 -1
  54. package/dist/id.js +2 -1
  55. package/dist/id.js.map +1 -1
  56. package/dist/index.js +673 -2790
  57. package/dist/index.js.map +1 -1
  58. package/dist/ipc-dispatcher.d.ts +26 -0
  59. package/dist/ipc-dispatcher.d.ts.map +1 -0
  60. package/dist/ipc-dispatcher.js +861 -0
  61. package/dist/ipc-dispatcher.js.map +1 -0
  62. package/dist/local-embeddings.d.ts +7 -0
  63. package/dist/local-embeddings.d.ts.map +1 -0
  64. package/dist/local-embeddings.js +60 -0
  65. package/dist/local-embeddings.js.map +1 -0
  66. package/dist/maintenance.d.ts.map +1 -1
  67. package/dist/maintenance.js +3 -7
  68. package/dist/maintenance.js.map +1 -1
  69. package/dist/memory-embeddings.d.ts +1 -1
  70. package/dist/memory-embeddings.d.ts.map +1 -1
  71. package/dist/memory-embeddings.js +59 -31
  72. package/dist/memory-embeddings.js.map +1 -1
  73. package/dist/memory-store.d.ts +0 -10
  74. package/dist/memory-store.d.ts.map +1 -1
  75. package/dist/memory-store.js +11 -27
  76. package/dist/memory-store.js.map +1 -1
  77. package/dist/message-pipeline.d.ts +47 -0
  78. package/dist/message-pipeline.d.ts.map +1 -0
  79. package/dist/message-pipeline.js +652 -0
  80. package/dist/message-pipeline.js.map +1 -0
  81. package/dist/metrics.d.ts +7 -10
  82. package/dist/metrics.d.ts.map +1 -1
  83. package/dist/metrics.js +2 -33
  84. package/dist/metrics.js.map +1 -1
  85. package/dist/model-registry.d.ts +0 -14
  86. package/dist/model-registry.d.ts.map +1 -1
  87. package/dist/model-registry.js +0 -36
  88. package/dist/model-registry.js.map +1 -1
  89. package/dist/paths.d.ts.map +1 -1
  90. package/dist/paths.js +2 -0
  91. package/dist/paths.js.map +1 -1
  92. package/dist/providers/discord/discord-format.d.ts +16 -0
  93. package/dist/providers/discord/discord-format.d.ts.map +1 -0
  94. package/dist/providers/discord/discord-format.js +153 -0
  95. package/dist/providers/discord/discord-format.js.map +1 -0
  96. package/dist/providers/discord/discord-provider.d.ts +50 -0
  97. package/dist/providers/discord/discord-provider.d.ts.map +1 -0
  98. package/dist/providers/discord/discord-provider.js +607 -0
  99. package/dist/providers/discord/discord-provider.js.map +1 -0
  100. package/dist/providers/discord/index.d.ts +4 -0
  101. package/dist/providers/discord/index.d.ts.map +1 -0
  102. package/dist/providers/discord/index.js +3 -0
  103. package/dist/providers/discord/index.js.map +1 -0
  104. package/dist/providers/registry.d.ts +14 -0
  105. package/dist/providers/registry.d.ts.map +1 -0
  106. package/dist/providers/registry.js +49 -0
  107. package/dist/providers/registry.js.map +1 -0
  108. package/dist/providers/telegram/index.d.ts +4 -0
  109. package/dist/providers/telegram/index.d.ts.map +1 -0
  110. package/dist/providers/telegram/index.js +3 -0
  111. package/dist/providers/telegram/index.js.map +1 -0
  112. package/dist/providers/telegram/telegram-format.d.ts +3 -0
  113. package/dist/providers/telegram/telegram-format.d.ts.map +1 -0
  114. package/dist/providers/telegram/telegram-format.js +215 -0
  115. package/dist/providers/telegram/telegram-format.js.map +1 -0
  116. package/dist/providers/telegram/telegram-provider.d.ts +51 -0
  117. package/dist/providers/telegram/telegram-provider.d.ts.map +1 -0
  118. package/dist/providers/telegram/telegram-provider.js +824 -0
  119. package/dist/providers/telegram/telegram-provider.js.map +1 -0
  120. package/dist/providers/types.d.ts +107 -0
  121. package/dist/providers/types.d.ts.map +1 -0
  122. package/dist/providers/types.js +2 -0
  123. package/dist/providers/types.js.map +1 -0
  124. package/dist/request-router.d.ts +9 -31
  125. package/dist/request-router.d.ts.map +1 -1
  126. package/dist/request-router.js +12 -142
  127. package/dist/request-router.js.map +1 -1
  128. package/dist/runtime-config.d.ts +79 -101
  129. package/dist/runtime-config.d.ts.map +1 -1
  130. package/dist/runtime-config.js +140 -208
  131. package/dist/runtime-config.js.map +1 -1
  132. package/dist/skill-manager.d.ts +39 -0
  133. package/dist/skill-manager.d.ts.map +1 -0
  134. package/dist/skill-manager.js +286 -0
  135. package/dist/skill-manager.js.map +1 -0
  136. package/dist/streaming.d.ts +58 -0
  137. package/dist/streaming.d.ts.map +1 -0
  138. package/dist/streaming.js +196 -0
  139. package/dist/streaming.js.map +1 -0
  140. package/dist/task-scheduler.d.ts.map +1 -1
  141. package/dist/task-scheduler.js +11 -45
  142. package/dist/task-scheduler.js.map +1 -1
  143. package/dist/tool-policy.d.ts.map +1 -1
  144. package/dist/tool-policy.js +13 -5
  145. package/dist/tool-policy.js.map +1 -1
  146. package/dist/transcription.d.ts +8 -0
  147. package/dist/transcription.d.ts.map +1 -0
  148. package/dist/transcription.js +174 -0
  149. package/dist/transcription.js.map +1 -0
  150. package/dist/types.d.ts +2 -50
  151. package/dist/types.d.ts.map +1 -1
  152. package/package.json +15 -4
  153. package/scripts/bootstrap.js +40 -4
  154. package/scripts/configure.js +129 -7
  155. package/scripts/doctor.js +30 -4
  156. package/scripts/init.js +13 -6
  157. package/scripts/install.sh +1 -1
  158. package/config-examples/plugin-http.json +0 -18
  159. package/container/skills/agent-browser.md +0 -159
  160. package/dist/background-job-classifier.d.ts +0 -20
  161. package/dist/background-job-classifier.d.ts.map +0 -1
  162. package/dist/background-job-classifier.js +0 -145
  163. package/dist/background-job-classifier.js.map +0 -1
  164. package/dist/background-jobs.d.ts +0 -56
  165. package/dist/background-jobs.d.ts.map +0 -1
  166. package/dist/background-jobs.js +0 -550
  167. package/dist/background-jobs.js.map +0 -1
  168. package/dist/planner-probe.d.ts +0 -14
  169. package/dist/planner-probe.d.ts.map +0 -1
  170. package/dist/planner-probe.js +0 -97
  171. package/dist/planner-probe.js.map +0 -1
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Skill Loader — parses SKILL.md frontmatter, builds a catalog of
3
+ * name+description summaries for the system prompt.
4
+ *
5
+ * Skills use YAML frontmatter in SKILL.md files:
6
+ * skills/<name>/SKILL.md (directory form)
7
+ * skills/<name>.md (single-file form, with frontmatter)
8
+ *
9
+ * Plain .md files without frontmatter are ignored.
10
+ */
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+
15
+ // ── Types ────────────────────────────────────────────────────────────
16
+
17
+ export type SkillFrontmatter = {
18
+ name: string;
19
+ description: string;
20
+ license?: string;
21
+ compatibility?: string;
22
+ metadata?: { author?: string; version?: string; tags?: string[] };
23
+ plugins?: string[];
24
+ };
25
+
26
+ export type SkillEntry = {
27
+ scope: 'group' | 'global';
28
+ name: string;
29
+ description: string;
30
+ directory: string;
31
+ skillMdPath: string;
32
+ frontmatter: SkillFrontmatter;
33
+ };
34
+
35
+ export type SkillCatalog = {
36
+ entries: SkillEntry[];
37
+ };
38
+
39
+ // ── Frontmatter parser ───────────────────────────────────────────────
40
+
41
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
42
+
43
+ /**
44
+ * Minimal YAML-subset parser for SKILL.md frontmatter.
45
+ * Handles simple key: value, multiline `>`, and `- item` arrays.
46
+ * Returns null if the content has no frontmatter block.
47
+ */
48
+ export function parseFrontmatter(content: string): SkillFrontmatter | null {
49
+ const match = content.match(FRONTMATTER_RE);
50
+ if (!match) return null;
51
+
52
+ const yaml = match[1];
53
+ const result: Record<string, unknown> = {};
54
+ let currentKey = '';
55
+ let currentIndent = 0;
56
+ let collectingMultiline = false;
57
+ let multilineValue = '';
58
+ let collectingArray = false;
59
+ let arrayItems: string[] = [];
60
+ let nestedObject: Record<string, unknown> = {};
61
+ let collectingNested = false;
62
+ let nestedKey = '';
63
+
64
+ const flushMultiline = () => {
65
+ if (collectingMultiline && currentKey) {
66
+ result[currentKey] = multilineValue.trim();
67
+ collectingMultiline = false;
68
+ multilineValue = '';
69
+ }
70
+ };
71
+
72
+ const flushArray = () => {
73
+ if (collectingArray && currentKey) {
74
+ result[currentKey] = arrayItems;
75
+ collectingArray = false;
76
+ arrayItems = [];
77
+ }
78
+ };
79
+
80
+ const flushNested = () => {
81
+ if (collectingNested && nestedKey) {
82
+ result[nestedKey] = { ...nestedObject };
83
+ collectingNested = false;
84
+ nestedObject = {};
85
+ nestedKey = '';
86
+ }
87
+ };
88
+
89
+ for (const rawLine of yaml.split('\n')) {
90
+ const line = rawLine.replace(/\r$/, '');
91
+ const trimmed = line.trimStart();
92
+ const indent = line.length - trimmed.length;
93
+
94
+ // Blank line in multiline block
95
+ if (!trimmed && collectingMultiline) {
96
+ multilineValue += '\n';
97
+ continue;
98
+ }
99
+
100
+ // Array item continuation
101
+ if (collectingArray && indent > currentIndent && trimmed.startsWith('- ')) {
102
+ // First array item disambiguates: this is an array, not a nested object
103
+ if (collectingNested) {
104
+ collectingNested = false;
105
+ nestedObject = {};
106
+ nestedKey = '';
107
+ }
108
+ const item = trimmed.slice(2).trim();
109
+ const unquoted = item.replace(/^["']|["']$/g, '');
110
+ arrayItems.push(unquoted);
111
+ continue;
112
+ }
113
+
114
+ // Nested object key: value
115
+ if (collectingNested && indent > currentIndent && trimmed.includes(':') && !trimmed.startsWith('- ')) {
116
+ // First nested key disambiguates: this is a nested object, not an array
117
+ if (collectingArray) {
118
+ collectingArray = false;
119
+ arrayItems = [];
120
+ }
121
+ const colonIdx = trimmed.indexOf(':');
122
+ const nk = trimmed.slice(0, colonIdx).trim();
123
+ let nv: unknown = trimmed.slice(colonIdx + 1).trim();
124
+ // Handle inline arrays: ["tag1", "tag2"]
125
+ if (typeof nv === 'string' && (nv as string).startsWith('[')) {
126
+ try {
127
+ nv = JSON.parse(nv as string);
128
+ } catch {
129
+ // leave as string
130
+ }
131
+ }
132
+ // Unquote strings
133
+ if (typeof nv === 'string') {
134
+ nv = (nv as string).replace(/^["']|["']$/g, '');
135
+ }
136
+ nestedObject[nk] = nv;
137
+ continue;
138
+ }
139
+
140
+ // Multiline continuation
141
+ if (collectingMultiline && indent > currentIndent) {
142
+ multilineValue += (multilineValue ? ' ' : '') + trimmed;
143
+ continue;
144
+ }
145
+
146
+ // End of any multiline/array/nested collection
147
+ flushMultiline();
148
+ flushArray();
149
+ flushNested();
150
+
151
+ // Skip empty or comment lines
152
+ if (!trimmed || trimmed.startsWith('#')) continue;
153
+
154
+ // Parse "key: value" line
155
+ const colonIdx = trimmed.indexOf(':');
156
+ if (colonIdx === -1) continue;
157
+
158
+ const key = trimmed.slice(0, colonIdx).trim();
159
+ const rawValue = trimmed.slice(colonIdx + 1).trim();
160
+ currentKey = key;
161
+ currentIndent = indent;
162
+
163
+ if (rawValue === '>' || rawValue === '|') {
164
+ collectingMultiline = true;
165
+ multilineValue = '';
166
+ continue;
167
+ }
168
+
169
+ if (rawValue === '') {
170
+ // Could be a nested object or array — peek at next line
171
+ // We set up for both; the first continuation line determines which
172
+ collectingNested = true;
173
+ nestedKey = key;
174
+ nestedObject = {};
175
+ collectingArray = true;
176
+ // Array items also collected, we'll pick whichever gets data
177
+ continue;
178
+ }
179
+
180
+ // Inline array: [a, b, c]
181
+ if (rawValue.startsWith('[')) {
182
+ try {
183
+ result[key] = JSON.parse(rawValue);
184
+ } catch {
185
+ result[key] = rawValue;
186
+ }
187
+ continue;
188
+ }
189
+
190
+ // Unquote
191
+ result[key] = rawValue.replace(/^["']|["']$/g, '');
192
+ }
193
+
194
+ flushMultiline();
195
+ flushArray();
196
+ flushNested();
197
+
198
+ // Validate required fields
199
+ const name = typeof result.name === 'string' ? result.name.trim() : '';
200
+ const description = typeof result.description === 'string' ? result.description.trim() : '';
201
+ if (!name || !description) return null;
202
+
203
+ // Build typed result
204
+ const fm: SkillFrontmatter = { name, description };
205
+ if (typeof result.license === 'string') fm.license = result.license;
206
+ if (typeof result.compatibility === 'string') fm.compatibility = result.compatibility;
207
+
208
+ // Metadata
209
+ if (result.metadata && typeof result.metadata === 'object' && !Array.isArray(result.metadata)) {
210
+ const m = result.metadata as Record<string, unknown>;
211
+ fm.metadata = {};
212
+ if (typeof m.author === 'string') fm.metadata.author = m.author;
213
+ if (typeof m.version === 'string') fm.metadata.version = m.version;
214
+ if (Array.isArray(m.tags)) fm.metadata.tags = m.tags.filter((t): t is string => typeof t === 'string');
215
+ }
216
+
217
+ // Plugins
218
+ if (Array.isArray(result.plugins)) {
219
+ fm.plugins = result.plugins.filter((p): p is string => typeof p === 'string');
220
+ }
221
+
222
+ return fm;
223
+ }
224
+
225
+ // ── Filesystem helpers ───────────────────────────────────────────────
226
+
227
+ function readdirSafe(dir: string): string[] {
228
+ try {
229
+ return fs.readdirSync(dir);
230
+ } catch {
231
+ return [];
232
+ }
233
+ }
234
+
235
+ function isDirectorySafe(p: string): boolean {
236
+ try {
237
+ return fs.statSync(p).isDirectory();
238
+ } catch {
239
+ return false;
240
+ }
241
+ }
242
+
243
+ function isFileSafe(p: string): boolean {
244
+ try {
245
+ const stat = fs.lstatSync(p);
246
+ return stat.isFile() && !stat.isSymbolicLink();
247
+ } catch {
248
+ return false;
249
+ }
250
+ }
251
+
252
+ function readFileSafe(p: string, maxChars: number): string | null {
253
+ try {
254
+ const content = fs.readFileSync(p, 'utf-8');
255
+ return content.length > maxChars ? content.slice(0, maxChars) : content;
256
+ } catch {
257
+ return null;
258
+ }
259
+ }
260
+
261
+ // ── Catalog builder ──────────────────────────────────────────────────
262
+
263
+ const DEFAULT_MAX_SKILLS = 32;
264
+
265
+ export function buildSkillCatalog(params: {
266
+ groupDir: string;
267
+ globalDir: string;
268
+ maxSkills?: number;
269
+ }): SkillCatalog {
270
+ const maxSkills = params.maxSkills ?? DEFAULT_MAX_SKILLS;
271
+ const entries: SkillEntry[] = [];
272
+
273
+ const scanScope = (scope: 'group' | 'global', rootDir: string) => {
274
+ const skillsDir = path.join(rootDir, 'skills');
275
+ if (!isDirectorySafe(skillsDir)) return;
276
+
277
+ const items = readdirSafe(skillsDir).sort();
278
+ for (const item of items) {
279
+ if (entries.length >= maxSkills) break;
280
+
281
+ const itemPath = path.join(skillsDir, item);
282
+
283
+ // Directory form: skills/<name>/SKILL.md
284
+ if (isDirectorySafe(itemPath)) {
285
+ const skillMdPath = path.join(itemPath, 'SKILL.md');
286
+ if (!isFileSafe(skillMdPath)) continue;
287
+
288
+ const content = readFileSafe(skillMdPath, 8000);
289
+ if (!content) continue;
290
+
291
+ const fm = parseFrontmatter(content);
292
+ if (fm) {
293
+ entries.push({
294
+ scope,
295
+ name: fm.name,
296
+ description: fm.description,
297
+ directory: itemPath,
298
+ skillMdPath,
299
+ frontmatter: fm,
300
+ });
301
+ }
302
+ continue;
303
+ }
304
+
305
+ // Single-file form: skills/<name>.md (must have frontmatter)
306
+ if (item.endsWith('.md') && isFileSafe(itemPath)) {
307
+ const content = readFileSafe(itemPath, 8000);
308
+ if (!content) continue;
309
+
310
+ const fm = parseFrontmatter(content);
311
+ if (fm) {
312
+ entries.push({
313
+ scope,
314
+ name: fm.name,
315
+ description: fm.description,
316
+ directory: path.dirname(itemPath),
317
+ skillMdPath: itemPath,
318
+ frontmatter: fm,
319
+ });
320
+ }
321
+ }
322
+ }
323
+ };
324
+
325
+ scanScope('group', params.groupDir);
326
+ scanScope('global', params.globalDir);
327
+
328
+ return { entries };
329
+ }
330
+
331
+ // ── Formatters ───────────────────────────────────────────────────────
332
+
333
+ export function formatSkillCatalog(catalog: SkillCatalog): string {
334
+ if (catalog.entries.length === 0) return '';
335
+
336
+ const lines = [
337
+ 'Skills available (use Read tool to load full instructions when needed):',
338
+ ];
339
+ for (const entry of catalog.entries) {
340
+ const scopePrefix = entry.scope === 'global' ? '/workspace/global' : '/workspace/group';
341
+ const dir = path.basename(entry.directory);
342
+ const readPath = entry.skillMdPath.startsWith('/')
343
+ ? entry.skillMdPath // already absolute container path
344
+ : `${scopePrefix}/skills/${dir}/SKILL.md`;
345
+ lines.push(`- ${entry.name}: ${entry.description} (${readPath})`);
346
+ }
347
+ lines.push('');
348
+ lines.push('When a task matches a skill, read its SKILL.md for full instructions.');
349
+ return lines.join('\n');
350
+ }
351
+
352
+ // ── Skill plugin directory collector ─────────────────────────────────
353
+
354
+ export function collectSkillPluginDirs(params: {
355
+ groupDir: string;
356
+ globalDir: string;
357
+ }): string[] {
358
+ const dirs: string[] = [];
359
+
360
+ const scan = (rootDir: string) => {
361
+ const skillsDir = path.join(rootDir, 'skills');
362
+ if (!isDirectorySafe(skillsDir)) return;
363
+
364
+ for (const entry of readdirSafe(skillsDir)) {
365
+ const pluginDir = path.join(skillsDir, entry, 'plugins');
366
+ if (isDirectorySafe(pluginDir)) {
367
+ dirs.push(pluginDir);
368
+ }
369
+ }
370
+ };
371
+
372
+ scan(params.groupDir);
373
+ scan(params.globalDir);
374
+ return dirs;
375
+ }