@hybridaione/hybridclaw 0.2.2 → 0.2.6

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 (277) hide show
  1. package/.github/workflows/ci.yml +70 -0
  2. package/.husky/pre-commit +1 -0
  3. package/CHANGELOG.md +85 -0
  4. package/CONTRIBUTING.md +33 -0
  5. package/README.md +41 -16
  6. package/SECURITY.md +17 -0
  7. package/biome.json +35 -0
  8. package/config.example.json +71 -8
  9. package/container/package-lock.json +2 -2
  10. package/container/package.json +1 -1
  11. package/container/src/approval-policy.ts +1303 -0
  12. package/container/src/browser-tools.ts +431 -136
  13. package/container/src/extensions.ts +36 -12
  14. package/container/src/hybridai-client.ts +34 -13
  15. package/container/src/index.ts +451 -109
  16. package/container/src/ipc.ts +5 -3
  17. package/container/src/token-usage.ts +20 -10
  18. package/container/src/tools.ts +599 -225
  19. package/container/src/types.ts +32 -2
  20. package/container/src/web-fetch.ts +89 -32
  21. package/dist/agent.d.ts.map +1 -1
  22. package/dist/agent.js +10 -2
  23. package/dist/agent.js.map +1 -1
  24. package/dist/audit-cli.d.ts.map +1 -1
  25. package/dist/audit-cli.js +4 -2
  26. package/dist/audit-cli.js.map +1 -1
  27. package/dist/audit-events.d.ts.map +1 -1
  28. package/dist/audit-events.js +53 -3
  29. package/dist/audit-events.js.map +1 -1
  30. package/dist/audit-trail.d.ts.map +1 -1
  31. package/dist/audit-trail.js +17 -8
  32. package/dist/audit-trail.js.map +1 -1
  33. package/dist/channels/discord/attachments.d.ts.map +1 -1
  34. package/dist/channels/discord/attachments.js +14 -7
  35. package/dist/channels/discord/attachments.js.map +1 -1
  36. package/dist/channels/discord/debounce.d.ts +9 -0
  37. package/dist/channels/discord/debounce.d.ts.map +1 -0
  38. package/dist/channels/discord/debounce.js +20 -0
  39. package/dist/channels/discord/debounce.js.map +1 -0
  40. package/dist/channels/discord/delivery.d.ts +4 -1
  41. package/dist/channels/discord/delivery.d.ts.map +1 -1
  42. package/dist/channels/discord/delivery.js +19 -3
  43. package/dist/channels/discord/delivery.js.map +1 -1
  44. package/dist/channels/discord/human-delay.d.ts +16 -0
  45. package/dist/channels/discord/human-delay.d.ts.map +1 -0
  46. package/dist/channels/discord/human-delay.js +29 -0
  47. package/dist/channels/discord/human-delay.js.map +1 -0
  48. package/dist/channels/discord/inbound.d.ts +4 -0
  49. package/dist/channels/discord/inbound.d.ts.map +1 -1
  50. package/dist/channels/discord/inbound.js +45 -4
  51. package/dist/channels/discord/inbound.js.map +1 -1
  52. package/dist/channels/discord/mentions.d.ts.map +1 -1
  53. package/dist/channels/discord/mentions.js +16 -4
  54. package/dist/channels/discord/mentions.js.map +1 -1
  55. package/dist/channels/discord/presence.d.ts +33 -0
  56. package/dist/channels/discord/presence.d.ts.map +1 -0
  57. package/dist/channels/discord/presence.js +111 -0
  58. package/dist/channels/discord/presence.js.map +1 -0
  59. package/dist/channels/discord/rate-limiter.d.ts +14 -0
  60. package/dist/channels/discord/rate-limiter.d.ts.map +1 -0
  61. package/dist/channels/discord/rate-limiter.js +49 -0
  62. package/dist/channels/discord/rate-limiter.js.map +1 -0
  63. package/dist/channels/discord/reactions.d.ts +38 -0
  64. package/dist/channels/discord/reactions.d.ts.map +1 -0
  65. package/dist/channels/discord/reactions.js +151 -0
  66. package/dist/channels/discord/reactions.js.map +1 -0
  67. package/dist/channels/discord/runtime.d.ts +6 -3
  68. package/dist/channels/discord/runtime.d.ts.map +1 -1
  69. package/dist/channels/discord/runtime.js +621 -125
  70. package/dist/channels/discord/runtime.js.map +1 -1
  71. package/dist/channels/discord/stream.d.ts +4 -1
  72. package/dist/channels/discord/stream.d.ts.map +1 -1
  73. package/dist/channels/discord/stream.js +16 -8
  74. package/dist/channels/discord/stream.js.map +1 -1
  75. package/dist/channels/discord/tool-actions.d.ts.map +1 -1
  76. package/dist/channels/discord/tool-actions.js +24 -12
  77. package/dist/channels/discord/tool-actions.js.map +1 -1
  78. package/dist/channels/discord/typing.d.ts +15 -0
  79. package/dist/channels/discord/typing.d.ts.map +1 -0
  80. package/dist/channels/discord/typing.js +106 -0
  81. package/dist/channels/discord/typing.js.map +1 -0
  82. package/dist/chunk.d.ts.map +1 -1
  83. package/dist/chunk.js +4 -2
  84. package/dist/chunk.js.map +1 -1
  85. package/dist/cli.js +47 -22
  86. package/dist/cli.js.map +1 -1
  87. package/dist/config.d.ts +19 -0
  88. package/dist/config.d.ts.map +1 -1
  89. package/dist/config.js +103 -18
  90. package/dist/config.js.map +1 -1
  91. package/dist/container-runner.d.ts.map +1 -1
  92. package/dist/container-runner.js +58 -26
  93. package/dist/container-runner.js.map +1 -1
  94. package/dist/container-setup.d.ts.map +1 -1
  95. package/dist/container-setup.js +10 -9
  96. package/dist/container-setup.js.map +1 -1
  97. package/dist/conversation.d.ts +2 -2
  98. package/dist/conversation.d.ts.map +1 -1
  99. package/dist/conversation.js +1 -1
  100. package/dist/conversation.js.map +1 -1
  101. package/dist/db.d.ts +118 -2
  102. package/dist/db.d.ts.map +1 -1
  103. package/dist/db.js +1568 -50
  104. package/dist/db.js.map +1 -1
  105. package/dist/delegation-manager.d.ts.map +1 -1
  106. package/dist/delegation-manager.js +3 -2
  107. package/dist/delegation-manager.js.map +1 -1
  108. package/dist/gateway-client.d.ts +2 -2
  109. package/dist/gateway-client.d.ts.map +1 -1
  110. package/dist/gateway-client.js +10 -4
  111. package/dist/gateway-client.js.map +1 -1
  112. package/dist/gateway-service.d.ts +3 -3
  113. package/dist/gateway-service.d.ts.map +1 -1
  114. package/dist/gateway-service.js +563 -73
  115. package/dist/gateway-service.js.map +1 -1
  116. package/dist/gateway-types.d.ts +24 -0
  117. package/dist/gateway-types.d.ts.map +1 -1
  118. package/dist/gateway-types.js.map +1 -1
  119. package/dist/gateway.js +179 -24
  120. package/dist/gateway.js.map +1 -1
  121. package/dist/health.d.ts.map +1 -1
  122. package/dist/health.js +20 -10
  123. package/dist/health.js.map +1 -1
  124. package/dist/heartbeat.d.ts +4 -0
  125. package/dist/heartbeat.d.ts.map +1 -1
  126. package/dist/heartbeat.js +48 -20
  127. package/dist/heartbeat.js.map +1 -1
  128. package/dist/hybridai-bots.d.ts.map +1 -1
  129. package/dist/hybridai-bots.js +4 -2
  130. package/dist/hybridai-bots.js.map +1 -1
  131. package/dist/instruction-approval-audit.d.ts.map +1 -1
  132. package/dist/instruction-approval-audit.js.map +1 -1
  133. package/dist/instruction-integrity.d.ts.map +1 -1
  134. package/dist/instruction-integrity.js +8 -2
  135. package/dist/instruction-integrity.js.map +1 -1
  136. package/dist/ipc.d.ts.map +1 -1
  137. package/dist/ipc.js +6 -1
  138. package/dist/ipc.js.map +1 -1
  139. package/dist/logger.js.map +1 -1
  140. package/dist/memory-consolidation.d.ts +17 -0
  141. package/dist/memory-consolidation.d.ts.map +1 -0
  142. package/dist/memory-consolidation.js +25 -0
  143. package/dist/memory-consolidation.js.map +1 -0
  144. package/dist/memory-service.d.ts +200 -0
  145. package/dist/memory-service.d.ts.map +1 -0
  146. package/dist/memory-service.js +294 -0
  147. package/dist/memory-service.js.map +1 -0
  148. package/dist/mount-security.d.ts.map +1 -1
  149. package/dist/mount-security.js +31 -7
  150. package/dist/mount-security.js.map +1 -1
  151. package/dist/observability-ingest.d.ts.map +1 -1
  152. package/dist/observability-ingest.js +32 -11
  153. package/dist/observability-ingest.js.map +1 -1
  154. package/dist/onboarding.d.ts.map +1 -1
  155. package/dist/onboarding.js +32 -9
  156. package/dist/onboarding.js.map +1 -1
  157. package/dist/proactive-policy.d.ts.map +1 -1
  158. package/dist/proactive-policy.js +2 -1
  159. package/dist/proactive-policy.js.map +1 -1
  160. package/dist/prompt-hooks.d.ts.map +1 -1
  161. package/dist/prompt-hooks.js +9 -7
  162. package/dist/prompt-hooks.js.map +1 -1
  163. package/dist/runtime-config.d.ts +98 -1
  164. package/dist/runtime-config.d.ts.map +1 -1
  165. package/dist/runtime-config.js +477 -23
  166. package/dist/runtime-config.js.map +1 -1
  167. package/dist/scheduled-task-runner.d.ts +1 -0
  168. package/dist/scheduled-task-runner.d.ts.map +1 -1
  169. package/dist/scheduled-task-runner.js +29 -10
  170. package/dist/scheduled-task-runner.js.map +1 -1
  171. package/dist/scheduler.d.ts +43 -4
  172. package/dist/scheduler.d.ts.map +1 -1
  173. package/dist/scheduler.js +530 -56
  174. package/dist/scheduler.js.map +1 -1
  175. package/dist/session-export.d.ts +26 -0
  176. package/dist/session-export.d.ts.map +1 -0
  177. package/dist/session-export.js +149 -0
  178. package/dist/session-export.js.map +1 -0
  179. package/dist/session-maintenance.d.ts.map +1 -1
  180. package/dist/session-maintenance.js +75 -13
  181. package/dist/session-maintenance.js.map +1 -1
  182. package/dist/session-transcripts.d.ts.map +1 -1
  183. package/dist/session-transcripts.js.map +1 -1
  184. package/dist/side-effects.d.ts.map +1 -1
  185. package/dist/side-effects.js +14 -2
  186. package/dist/side-effects.js.map +1 -1
  187. package/dist/skills-guard.d.ts.map +1 -1
  188. package/dist/skills-guard.js +893 -130
  189. package/dist/skills-guard.js.map +1 -1
  190. package/dist/skills.d.ts +5 -0
  191. package/dist/skills.d.ts.map +1 -1
  192. package/dist/skills.js +29 -15
  193. package/dist/skills.js.map +1 -1
  194. package/dist/token-efficiency.d.ts.map +1 -1
  195. package/dist/token-efficiency.js.map +1 -1
  196. package/dist/tui.js +92 -11
  197. package/dist/tui.js.map +1 -1
  198. package/dist/types.d.ts +146 -0
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +24 -1
  201. package/dist/types.js.map +1 -1
  202. package/dist/update.d.ts.map +1 -1
  203. package/dist/update.js +42 -14
  204. package/dist/update.js.map +1 -1
  205. package/dist/workspace.d.ts.map +1 -1
  206. package/dist/workspace.js +49 -9
  207. package/dist/workspace.js.map +1 -1
  208. package/docs/chat.html +9 -3
  209. package/docs/index.html +37 -13
  210. package/package.json +8 -2
  211. package/src/agent.ts +16 -3
  212. package/src/audit-cli.ts +44 -16
  213. package/src/audit-events.ts +69 -5
  214. package/src/audit-trail.ts +41 -15
  215. package/src/channels/discord/attachments.ts +81 -27
  216. package/src/channels/discord/debounce.ts +25 -0
  217. package/src/channels/discord/delivery.ts +57 -13
  218. package/src/channels/discord/human-delay.ts +48 -0
  219. package/src/channels/discord/inbound.ts +66 -7
  220. package/src/channels/discord/mentions.ts +42 -18
  221. package/src/channels/discord/presence.ts +148 -0
  222. package/src/channels/discord/rate-limiter.ts +58 -0
  223. package/src/channels/discord/reactions.ts +211 -0
  224. package/src/channels/discord/runtime.ts +1048 -182
  225. package/src/channels/discord/stream.ts +73 -27
  226. package/src/channels/discord/tool-actions.ts +78 -37
  227. package/src/channels/discord/typing.ts +140 -0
  228. package/src/chunk.ts +12 -4
  229. package/src/cli.ts +141 -56
  230. package/src/config.ts +192 -34
  231. package/src/container-runner.ts +132 -42
  232. package/src/container-setup.ts +57 -22
  233. package/src/conversation.ts +9 -7
  234. package/src/db.ts +2217 -84
  235. package/src/delegation-manager.ts +6 -2
  236. package/src/gateway-client.ts +41 -17
  237. package/src/gateway-service.ts +1019 -201
  238. package/src/gateway-types.ts +33 -0
  239. package/src/gateway.ts +321 -48
  240. package/src/health.ts +66 -26
  241. package/src/heartbeat.ts +84 -22
  242. package/src/hybridai-bots.ts +14 -5
  243. package/src/instruction-approval-audit.ts +4 -1
  244. package/src/instruction-integrity.ts +30 -9
  245. package/src/ipc.ts +23 -5
  246. package/src/logger.ts +4 -1
  247. package/src/memory-consolidation.ts +41 -0
  248. package/src/memory-service.ts +606 -0
  249. package/src/mount-security.ts +58 -13
  250. package/src/observability-ingest.ts +134 -35
  251. package/src/onboarding.ts +126 -35
  252. package/src/proactive-policy.ts +3 -1
  253. package/src/prompt-hooks.ts +40 -17
  254. package/src/runtime-config.ts +1114 -99
  255. package/src/scheduled-task-runner.ts +63 -11
  256. package/src/scheduler.ts +683 -60
  257. package/src/session-export.ts +196 -0
  258. package/src/session-maintenance.ts +125 -22
  259. package/src/session-transcripts.ts +12 -3
  260. package/src/side-effects.ts +28 -5
  261. package/src/skills-guard.ts +1067 -219
  262. package/src/skills.ts +163 -65
  263. package/src/token-efficiency.ts +31 -9
  264. package/src/tui.ts +166 -25
  265. package/src/types.ts +195 -2
  266. package/src/update.ts +79 -23
  267. package/src/workspace.ts +63 -11
  268. package/tests/approval-policy.test.ts +224 -0
  269. package/tests/discord.basic.test.ts +82 -2
  270. package/tests/discord.human-presence.test.ts +85 -0
  271. package/tests/gateway-service.media-routing.test.ts +8 -2
  272. package/tests/memory-service.test.ts +1114 -0
  273. package/tests/token-efficiency.basic.test.ts +8 -2
  274. package/vitest.e2e.config.ts +3 -1
  275. package/vitest.integration.config.ts +3 -1
  276. package/vitest.live.config.ts +3 -1
  277. package/vitest.unit.config.ts +9 -0
package/src/skills.ts CHANGED
@@ -3,10 +3,11 @@
3
3
  * The system prompt includes skill metadata + location, and inlines full
4
4
  * bodies for skills marked `always: true`.
5
5
  */
6
+
7
+ import { createHash } from 'crypto';
6
8
  import fs from 'fs';
7
9
  import os from 'os';
8
10
  import path from 'path';
9
- import { createHash } from 'crypto';
10
11
  import { fileURLToPath } from 'url';
11
12
 
12
13
  import { agentWorkspaceDir } from './ipc.js';
@@ -205,7 +206,9 @@ function parseInlineStringList(raw: string): string[] {
205
206
  function normalizeStringList(raw: unknown): string[] {
206
207
  if (Array.isArray(raw)) {
207
208
  return raw
208
- .map((item) => (typeof item === 'string' ? item.trim() : String(item ?? '').trim()))
209
+ .map((item) =>
210
+ typeof item === 'string' ? item.trim() : String(item ?? '').trim(),
211
+ )
209
212
  .filter(Boolean);
210
213
  }
211
214
  if (typeof raw === 'string') {
@@ -221,7 +224,8 @@ function normalizeStringList(raw: unknown): string[] {
221
224
 
222
225
  function tryParseJsonObject(raw: string): Record<string, unknown> | null {
223
226
  const trimmed = stripQuotes(raw.trim());
224
- if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('['))) return null;
227
+ if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('[')))
228
+ return null;
225
229
  try {
226
230
  const parsed = JSON.parse(trimmed) as unknown;
227
231
  if (isRecord(parsed)) return parsed;
@@ -231,7 +235,10 @@ function tryParseJsonObject(raw: string): Record<string, unknown> | null {
231
235
  return null;
232
236
  }
233
237
 
234
- function extractTopLevelSection(block: string, key: string): FrontmatterSection | null {
238
+ function extractTopLevelSection(
239
+ block: string,
240
+ key: string,
241
+ ): FrontmatterSection | null {
235
242
  const lines = block.split('\n');
236
243
  for (let i = 0; i < lines.length; i += 1) {
237
244
  const line = lines[i] || '';
@@ -264,9 +271,11 @@ function extractTopLevelSection(block: string, key: string): FrontmatterSection
264
271
  return null;
265
272
  }
266
273
 
267
- function parseSectionChildren(children: string[]): Map<string, FrontmatterSection> {
274
+ function parseSectionChildren(
275
+ children: string[],
276
+ ): Map<string, FrontmatterSection> {
268
277
  const parsed = new Map<string, FrontmatterSection>();
269
- for (let i = 0; i < children.length;) {
278
+ for (let i = 0; i < children.length; ) {
270
279
  const line = children[i] || '';
271
280
  const trimmed = line.trim();
272
281
  if (!trimmed) {
@@ -305,7 +314,9 @@ function parseSectionChildren(children: string[]): Map<string, FrontmatterSectio
305
314
  return parsed;
306
315
  }
307
316
 
308
- function parseSectionStringList(section: FrontmatterSection | undefined): string[] {
317
+ function parseSectionStringList(
318
+ section: FrontmatterSection | undefined,
319
+ ): string[] {
309
320
  if (!section) return [];
310
321
  const inline = parseInlineStringList(section.inline);
311
322
  if (inline.length > 0 || section.inline.trim() === '[]') return inline;
@@ -324,7 +335,9 @@ function parseRequiresFromFrontmatter(frontmatter: FrontmatterParseResult): {
324
335
  bins: string[];
325
336
  env: string[];
326
337
  } {
327
- const fromInlineJson = frontmatter.meta.requires ? tryParseJsonObject(frontmatter.meta.requires) : null;
338
+ const fromInlineJson = frontmatter.meta.requires
339
+ ? tryParseJsonObject(frontmatter.meta.requires)
340
+ : null;
328
341
  if (fromInlineJson) {
329
342
  return {
330
343
  bins: normalizeStringList(fromInlineJson.bins),
@@ -354,15 +367,21 @@ function parseHybridClawMetadata(frontmatter: FrontmatterParseResult): {
354
367
  tags: string[];
355
368
  relatedSkills: string[];
356
369
  } {
357
- const normalizeMetadata = (raw: Record<string, unknown>): { tags: string[]; relatedSkills: string[] } => {
370
+ const normalizeMetadata = (
371
+ raw: Record<string, unknown>,
372
+ ): { tags: string[]; relatedSkills: string[] } => {
358
373
  const hybridRaw = isRecord(raw.hybridclaw) ? raw.hybridclaw : raw;
359
374
  return {
360
375
  tags: normalizeStringList(hybridRaw.tags),
361
- relatedSkills: normalizeStringList(hybridRaw.related_skills ?? hybridRaw.relatedSkills),
376
+ relatedSkills: normalizeStringList(
377
+ hybridRaw.related_skills ?? hybridRaw.relatedSkills,
378
+ ),
362
379
  };
363
380
  };
364
381
 
365
- const fromInlineJson = frontmatter.meta.metadata ? tryParseJsonObject(frontmatter.meta.metadata) : null;
382
+ const fromInlineJson = frontmatter.meta.metadata
383
+ ? tryParseJsonObject(frontmatter.meta.metadata)
384
+ : null;
366
385
  if (fromInlineJson) return normalizeMetadata(fromInlineJson);
367
386
 
368
387
  const metadataSection = extractTopLevelSection(frontmatter.block, 'metadata');
@@ -394,7 +413,8 @@ function hasBinary(binName: string): boolean {
394
413
  if (!bin) return false;
395
414
 
396
415
  const currentPath = process.env.PATH || '';
397
- const currentPathExt = process.platform === 'win32' ? (process.env.PATHEXT || '') : '';
416
+ const currentPathExt =
417
+ process.platform === 'win32' ? process.env.PATHEXT || '' : '';
398
418
  if (cachedPathEnv !== currentPath || cachedPathExt !== currentPathExt) {
399
419
  cachedPathEnv = currentPath;
400
420
  cachedPathExt = currentPathExt;
@@ -404,9 +424,16 @@ function hasBinary(binName: string): boolean {
404
424
  const cached = hasBinaryCache.get(bin);
405
425
  if (cached != null) return cached;
406
426
 
407
- const exts = process.platform === 'win32'
408
- ? ['', ...currentPathExt.split(';').map((ext) => ext.trim()).filter(Boolean)]
409
- : [''];
427
+ const exts =
428
+ process.platform === 'win32'
429
+ ? [
430
+ '',
431
+ ...currentPathExt
432
+ .split(';')
433
+ .map((ext) => ext.trim())
434
+ .filter(Boolean),
435
+ ]
436
+ : [''];
410
437
  for (const part of currentPath.split(path.delimiter).filter(Boolean)) {
411
438
  for (const ext of exts) {
412
439
  const candidate = path.join(part, `${bin}${ext}`);
@@ -448,7 +475,10 @@ function pathWithin(root: string, target: string): boolean {
448
475
  return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
449
476
  }
450
477
 
451
- function asContainerPath(workspaceDir: string, absolutePath: string): string | null {
478
+ function asContainerPath(
479
+ workspaceDir: string,
480
+ absolutePath: string,
481
+ ): string | null {
452
482
  if (!pathWithin(workspaceDir, absolutePath)) return null;
453
483
  const rel = toPosixPath(path.relative(workspaceDir, absolutePath));
454
484
  return rel ? `/workspace/${rel}` : '/workspace';
@@ -515,7 +545,10 @@ function scanSkillsDir(dir: string, source: SkillSource): SkillCandidate[] {
515
545
  name,
516
546
  description: (meta.description || '').trim(),
517
547
  userInvocable: parseBool(meta['user-invocable'], true),
518
- disableModelInvocation: parseBool(meta['disable-model-invocation'], false),
548
+ disableModelInvocation: parseBool(
549
+ meta['disable-model-invocation'],
550
+ false,
551
+ ),
519
552
  always,
520
553
  requires,
521
554
  metadata: {
@@ -599,8 +632,14 @@ function resolveSyncedSkillTarget(
599
632
  };
600
633
  }
601
634
 
602
- function syncSkillIntoWorkspace(skill: SkillCandidate, workspaceDir: string): string {
603
- const { rootDir, targetDir, targetSkillFile } = resolveSyncedSkillTarget(skill, workspaceDir);
635
+ function syncSkillIntoWorkspace(
636
+ skill: SkillCandidate,
637
+ workspaceDir: string,
638
+ ): string {
639
+ const { rootDir, targetDir, targetSkillFile } = resolveSyncedSkillTarget(
640
+ skill,
641
+ workspaceDir,
642
+ );
604
643
  fs.mkdirSync(rootDir, { recursive: true });
605
644
 
606
645
  if (!pathWithin(rootDir, targetDir)) {
@@ -641,8 +680,14 @@ function sanitizeCommandName(name: string): string {
641
680
  .slice(0, MAX_SKILL_COMMAND_NAME_LENGTH);
642
681
  }
643
682
 
644
- function resolveUniqueCommandName(baseName: string, usedNames: Set<string>): string | null {
645
- const normalizedBase = (baseName || 'skill').slice(0, MAX_SKILL_COMMAND_NAME_LENGTH);
683
+ function resolveUniqueCommandName(
684
+ baseName: string,
685
+ usedNames: Set<string>,
686
+ ): string | null {
687
+ const normalizedBase = (baseName || 'skill').slice(
688
+ 0,
689
+ MAX_SKILL_COMMAND_NAME_LENGTH,
690
+ );
646
691
  if (!usedNames.has(normalizedBase)) {
647
692
  usedNames.add(normalizedBase);
648
693
  return normalizedBase;
@@ -650,7 +695,10 @@ function resolveUniqueCommandName(baseName: string, usedNames: Set<string>): str
650
695
 
651
696
  for (let index = 2; index < 10_000; index += 1) {
652
697
  const suffix = `-${index}`;
653
- const prefixLen = Math.max(1, MAX_SKILL_COMMAND_NAME_LENGTH - suffix.length);
698
+ const prefixLen = Math.max(
699
+ 1,
700
+ MAX_SKILL_COMMAND_NAME_LENGTH - suffix.length,
701
+ );
654
702
  const candidate = `${normalizedBase.slice(0, prefixLen)}${suffix}`;
655
703
  if (usedNames.has(candidate)) continue;
656
704
  usedNames.add(candidate);
@@ -660,7 +708,9 @@ function resolveUniqueCommandName(baseName: string, usedNames: Set<string>): str
660
708
  }
661
709
 
662
710
  function buildSkillCommandSpecs(skills: Skill[]): SkillCommandSpec[] {
663
- const used = new Set<string>(Array.from(RESERVED_SKILL_COMMAND_NAMES.values()));
711
+ const used = new Set<string>(
712
+ Array.from(RESERVED_SKILL_COMMAND_NAMES.values()),
713
+ );
664
714
  const specs: SkillCommandSpec[] = [];
665
715
 
666
716
  for (const skill of skills) {
@@ -678,29 +728,39 @@ function buildSkillCommandSpecs(skills: Skill[]): SkillCommandSpec[] {
678
728
  return specs;
679
729
  }
680
730
 
681
- function findSkillCommand(skillCommands: SkillCommandSpec[], rawName: string): SkillCommandSpec | null {
731
+ function findSkillCommand(
732
+ skillCommands: SkillCommandSpec[],
733
+ rawName: string,
734
+ ): SkillCommandSpec | null {
682
735
  const lowered = rawName.trim().toLowerCase();
683
736
  if (!lowered) return null;
684
737
  const sanitized = sanitizeCommandName(rawName);
685
- return skillCommands.find((entry) => (
686
- entry.name === lowered ||
687
- (sanitized && entry.name === sanitized)
688
- )) || null;
738
+ return (
739
+ skillCommands.find(
740
+ (entry) =>
741
+ entry.name === lowered || (sanitized && entry.name === sanitized),
742
+ ) || null
743
+ );
689
744
  }
690
745
 
691
746
  function findInvocableSkill(skills: Skill[], rawName: string): Skill | null {
692
747
  const target = rawName.trim().toLowerCase();
693
748
  if (!target) return null;
694
749
  const normalizedTarget = normalizeSkillLookup(rawName);
695
- return skills.find((skill) => {
696
- if (!skill.userInvocable) return false;
697
- const name = skill.name.toLowerCase();
698
- if (name === target) return true;
699
- return normalizeSkillLookup(skill.name) === normalizedTarget;
700
- }) || null;
750
+ return (
751
+ skills.find((skill) => {
752
+ if (!skill.userInvocable) return false;
753
+ const name = skill.name.toLowerCase();
754
+ if (name === target) return true;
755
+ return normalizeSkillLookup(skill.name) === normalizedTarget;
756
+ }) || null
757
+ );
701
758
  }
702
759
 
703
- function parseSkillInvocation(content: string, skills: Skill[]): { skill: Skill; args: string } | null {
760
+ function parseSkillInvocation(
761
+ content: string,
762
+ skills: Skill[],
763
+ ): { skill: Skill; args: string } | null {
704
764
  const trimmed = content.trim();
705
765
  if (!trimmed.startsWith('/')) return null;
706
766
  const skillCommands = buildSkillCommandSpecs(skills);
@@ -719,7 +779,10 @@ function parseSkillInvocation(content: string, skills: Skill[]): { skill: Skill;
719
779
  if (!skillMatch) return null;
720
780
  const explicitName = (skillMatch[1] || '').trim();
721
781
  const explicitSkill = findInvocableSkill(skills, explicitName);
722
- const skill = explicitSkill || findSkillCommand(skillCommands, explicitName)?.skill || null;
782
+ const skill =
783
+ explicitSkill ||
784
+ findSkillCommand(skillCommands, explicitName)?.skill ||
785
+ null;
723
786
  if (!skill) return null;
724
787
  return { skill, args: (skillMatch[2] || '').trim() };
725
788
  }
@@ -728,7 +791,10 @@ function parseSkillInvocation(content: string, skills: Skill[]): { skill: Skill;
728
791
  const skillName = commandName.slice('skill:'.length).trim();
729
792
  if (!skillName) return null;
730
793
  const explicitSkill = findInvocableSkill(skills, skillName);
731
- const skill = explicitSkill || findSkillCommand(skillCommands, skillName)?.skill || null;
794
+ const skill =
795
+ explicitSkill ||
796
+ findSkillCommand(skillCommands, skillName)?.skill ||
797
+ null;
732
798
  if (!skill) return null;
733
799
  return { skill, args: remainder };
734
800
  }
@@ -745,7 +811,10 @@ function loadSkillBody(skill: Skill, maxChars: number): string {
745
811
  if (body.length <= maxChars) return body;
746
812
  return `${body.slice(0, maxChars)}\n\n[truncated]`;
747
813
  } catch (err) {
748
- logger.warn({ skill: skill.name, path: skill.filePath, err }, 'Failed to load SKILL.md body');
814
+ logger.warn(
815
+ { skill: skill.name, path: skill.filePath, err },
816
+ 'Failed to load SKILL.md body',
817
+ );
749
818
  return '';
750
819
  }
751
820
  }
@@ -757,7 +826,10 @@ function loadSkillBody(skill: Skill, maxChars: number): string {
757
826
  * - /skill:<name> [input]
758
827
  * - /<name> [input] (user-invocable skills)
759
828
  */
760
- export function expandSkillInvocation(content: string, skills: Skill[]): string {
829
+ export function expandSkillInvocation(
830
+ content: string,
831
+ skills: Skill[],
832
+ ): string {
761
833
  const invocation = parseSkillInvocation(content, skills);
762
834
  if (!invocation) return content;
763
835
 
@@ -771,12 +843,7 @@ export function expandSkillInvocation(content: string, skills: Skill[]): string
771
843
  ];
772
844
 
773
845
  if (body) {
774
- lines.push(
775
- '',
776
- '<skill_instructions>',
777
- body,
778
- '</skill_instructions>',
779
- );
846
+ lines.push('', '<skill_instructions>', body, '</skill_instructions>');
780
847
  } else {
781
848
  lines.push('Read the skill file with the `read` tool and follow it.');
782
849
  }
@@ -804,11 +871,19 @@ export function loadSkills(agentId: string): Skill[] {
804
871
  const agentsPersonalSkillsDir = path.join(os.homedir(), '.agents', 'skills');
805
872
 
806
873
  const extraSkills = extraDirs.flatMap((dir) => scanSkillsDir(dir, 'extra'));
807
- const bundledSkills = bundledSkillsDir ? scanSkillsDir(bundledSkillsDir, 'bundled') : [];
874
+ const bundledSkills = bundledSkillsDir
875
+ ? scanSkillsDir(bundledSkillsDir, 'bundled')
876
+ : [];
808
877
  const codexSkills = codexDirs.flatMap((dir) => scanSkillsDir(dir, 'codex'));
809
878
  const claudeSkills = scanSkillsDir(claudeSkillsDir, 'claude');
810
- const agentsPersonalSkills = scanSkillsDir(agentsPersonalSkillsDir, 'agents-personal');
811
- const projectAgentsSkills = scanSkillsDir(PROJECT_AGENTS_SKILLS_DIR, 'agents-project');
879
+ const agentsPersonalSkills = scanSkillsDir(
880
+ agentsPersonalSkillsDir,
881
+ 'agents-personal',
882
+ );
883
+ const projectAgentsSkills = scanSkillsDir(
884
+ PROJECT_AGENTS_SKILLS_DIR,
885
+ 'agents-project',
886
+ );
812
887
  const workspaceSkills = scanSkillsDir(WORKSPACE_SKILLS_DIR, 'workspace');
813
888
 
814
889
  const byName = new Map<string, SkillCandidate>();
@@ -822,8 +897,9 @@ export function loadSkills(agentId: string): Skill[] {
822
897
  for (const skill of projectAgentsSkills) byName.set(skill.name, skill);
823
898
  for (const skill of workspaceSkills) byName.set(skill.name, skill);
824
899
 
825
- const eligible = Array.from(byName.values())
826
- .filter((skill) => checkEligibility(skill).available);
900
+ const eligible = Array.from(byName.values()).filter(
901
+ (skill) => checkEligibility(skill).available,
902
+ );
827
903
  const guarded = eligible.filter((skill) => {
828
904
  const decision = guardSkillDirectory({
829
905
  skillName: skill.name,
@@ -835,14 +911,17 @@ export function loadSkills(agentId: string): Skill[] {
835
911
  const fingerprint = `${path.resolve(skill.baseDir)}:${decision.result.verdict}:${decision.result.findings.length}`;
836
912
  if (!warnedBlockedSkills.has(fingerprint)) {
837
913
  warnedBlockedSkills.add(fingerprint);
838
- logger.warn({
839
- skill: skill.name,
840
- source: skill.source,
841
- trustLevel: decision.result.trustLevel,
842
- verdict: decision.result.verdict,
843
- findings: decision.result.findings.length,
844
- reason: decision.reason,
845
- }, 'Blocked skill by security scanner');
914
+ logger.warn(
915
+ {
916
+ skill: skill.name,
917
+ source: skill.source,
918
+ trustLevel: decision.result.trustLevel,
919
+ verdict: decision.result.verdict,
920
+ findings: decision.result.findings.length,
921
+ reason: decision.reason,
922
+ },
923
+ 'Blocked skill by security scanner',
924
+ );
846
925
  }
847
926
  return false;
848
927
  });
@@ -850,13 +929,22 @@ export function loadSkills(agentId: string): Skill[] {
850
929
  const resolved: Skill[] = [];
851
930
  for (const skill of guarded) {
852
931
  try {
853
- let containerSkillPath = asContainerPath(workspaceDir, path.resolve(skill.filePath));
932
+ let containerSkillPath = asContainerPath(
933
+ workspaceDir,
934
+ path.resolve(skill.filePath),
935
+ );
854
936
  if (!containerSkillPath) {
855
937
  const syncedSkillFile = syncSkillIntoWorkspace(skill, workspaceDir);
856
- containerSkillPath = asContainerPath(workspaceDir, path.resolve(syncedSkillFile));
938
+ containerSkillPath = asContainerPath(
939
+ workspaceDir,
940
+ path.resolve(syncedSkillFile),
941
+ );
857
942
  }
858
943
  if (!containerSkillPath) {
859
- logger.warn({ skill: skill.name, path: skill.filePath }, 'Could not resolve container-readable skill path');
944
+ logger.warn(
945
+ { skill: skill.name, path: skill.filePath },
946
+ 'Could not resolve container-readable skill path',
947
+ );
860
948
  continue;
861
949
  }
862
950
 
@@ -865,7 +953,10 @@ export function loadSkills(agentId: string): Skill[] {
865
953
  location: containerSkillPath,
866
954
  });
867
955
  } catch (err) {
868
- logger.warn({ skill: skill.name, err }, 'Failed to resolve skill location');
956
+ logger.warn(
957
+ { skill: skill.name, err },
958
+ 'Failed to resolve skill location',
959
+ );
869
960
  }
870
961
  }
871
962
 
@@ -886,7 +977,9 @@ export function buildSkillsPrompt(skills: Skill[]): string {
886
977
  const demotedAlways: Skill[] = [];
887
978
 
888
979
  let alwaysChars = 0;
889
- for (const skill of promptCandidates.filter((candidate) => candidate.always)) {
980
+ for (const skill of promptCandidates.filter(
981
+ (candidate) => candidate.always,
982
+ )) {
890
983
  const body = loadSkillBody(skill, Number.MAX_SAFE_INTEGER);
891
984
  if (!body) {
892
985
  demotedAlways.push(skill);
@@ -909,10 +1002,15 @@ export function buildSkillsPrompt(skills: Skill[]): string {
909
1002
 
910
1003
  if (demotedAlways.length > 0) {
911
1004
  const demotedNames = demotedAlways.map((skill) => skill.name).join(', ');
912
- lines.push(`⚠️ maxAlwaysChars=${MAX_ALWAYS_CHARS} exceeded; demoted to summary: ${demotedNames}`, '');
1005
+ lines.push(
1006
+ `⚠️ maxAlwaysChars=${MAX_ALWAYS_CHARS} exceeded; demoted to summary: ${demotedNames}`,
1007
+ '',
1008
+ );
913
1009
  }
914
1010
 
915
- const summaryCandidates = promptCandidates.filter((skill) => !embeddedAlways.has(skill.name));
1011
+ const summaryCandidates = promptCandidates.filter(
1012
+ (skill) => !embeddedAlways.has(skill.name),
1013
+ );
916
1014
  if (summaryCandidates.length > 0) {
917
1015
  lines.push('<available_skills>');
918
1016
 
@@ -67,13 +67,17 @@ function trimToRecentWithinBudget(
67
67
  return kept.reverse();
68
68
  }
69
69
 
70
- export function estimateTokenCountFromText(text: string | null | undefined): number {
70
+ export function estimateTokenCountFromText(
71
+ text: string | null | undefined,
72
+ ): number {
71
73
  const normalized = typeof text === 'string' ? text : '';
72
74
  if (!normalized) return 0;
73
75
  return Math.max(1, Math.ceil(normalized.length / DEFAULT_CHARS_PER_TOKEN));
74
76
  }
75
77
 
76
- function normalizeMessageContentToText(content: ChatMessage['content']): string {
78
+ function normalizeMessageContentToText(
79
+ content: ChatMessage['content'],
80
+ ): string {
77
81
  if (typeof content === 'string') return content;
78
82
  if (!Array.isArray(content)) return '';
79
83
  const chunks: string[] = [];
@@ -98,16 +102,24 @@ export function estimateTokenCountFromMessages(
98
102
  for (const message of messages) {
99
103
  total += 4; // Approximate per-message framing overhead.
100
104
  total += estimateTokenCountFromText(message.role);
101
- total += estimateTokenCountFromText(normalizeMessageContentToText(message.content));
105
+ total += estimateTokenCountFromText(
106
+ normalizeMessageContentToText(message.content),
107
+ );
102
108
  }
103
109
  return total;
104
110
  }
105
111
 
106
- export function truncateMessageContent(content: string, maxChars: number): string {
112
+ export function truncateMessageContent(
113
+ content: string,
114
+ maxChars: number,
115
+ ): string {
107
116
  if (!Number.isFinite(maxChars) || maxChars <= 0) return '';
108
117
  if (content.length <= maxChars) return content;
109
118
 
110
- const bodyMax = Math.max(0, Math.floor(maxChars) - MESSAGE_TRUNCATED_MARKER.length);
119
+ const bodyMax = Math.max(
120
+ 0,
121
+ Math.floor(maxChars) - MESSAGE_TRUNCATED_MARKER.length,
122
+ );
111
123
  if (bodyMax <= 0) {
112
124
  return content.slice(0, Math.floor(maxChars));
113
125
  }
@@ -164,15 +176,22 @@ export function optimizeHistoryMessagesForPrompt(
164
176
  );
165
177
  const protectHeadMessages = Math.max(
166
178
  0,
167
- Math.floor(options?.protectHeadMessages ?? DEFAULT_HISTORY_PROTECT_HEAD_MESSAGES),
179
+ Math.floor(
180
+ options?.protectHeadMessages ?? DEFAULT_HISTORY_PROTECT_HEAD_MESSAGES,
181
+ ),
168
182
  );
169
183
  const protectTailMessages = Math.max(
170
184
  0,
171
- Math.floor(options?.protectTailMessages ?? DEFAULT_HISTORY_PROTECT_TAIL_MESSAGES),
185
+ Math.floor(
186
+ options?.protectTailMessages ?? DEFAULT_HISTORY_PROTECT_TAIL_MESSAGES,
187
+ ),
172
188
  );
173
189
 
174
190
  const originalCount = messages.length;
175
- const originalChars = messages.reduce((total, message) => total + message.content.length, 0);
191
+ const originalChars = messages.reduce(
192
+ (total, message) => total + message.content.length,
193
+ 0,
194
+ );
176
195
  let perMessageTruncatedCount = 0;
177
196
 
178
197
  const normalized = messages.map((message) => {
@@ -191,7 +210,10 @@ export function optimizeHistoryMessagesForPrompt(
191
210
  if (preBudgetChars > maxTotalChars) {
192
211
  middleCompressionApplied = true;
193
212
  const headCount = Math.min(protectHeadMessages, normalized.length);
194
- const tailCount = Math.min(protectTailMessages, Math.max(0, normalized.length - headCount));
213
+ const tailCount = Math.min(
214
+ protectTailMessages,
215
+ Math.max(0, normalized.length - headCount),
216
+ );
195
217
  const middleStart = headCount;
196
218
  const middleEnd = normalized.length - tailCount;
197
219
  const head = normalized.slice(0, headCount);