@kuralle-agents/core 0.13.0 → 0.15.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 (160) hide show
  1. package/README.md +4 -4
  2. package/dist/ai-sdk/uiMessageStream.d.ts +2 -2
  3. package/dist/ai-sdk/uiMessageStream.js +42 -29
  4. package/dist/capabilities/LivePromptAssembler.d.ts +1 -1
  5. package/dist/capabilities/LivePromptAssembler.js +1 -1
  6. package/dist/capabilities/index.d.ts +1 -1
  7. package/dist/eval/EvalRunner.js +4 -4
  8. package/dist/eval/simulation.js +4 -3
  9. package/dist/events/TurnHandle.d.ts +3 -3
  10. package/dist/flow/collectUntilComplete.js +30 -7
  11. package/dist/flow/degrade.js +8 -4
  12. package/dist/flow/emitInteractive.d.ts +2 -2
  13. package/dist/flow/emitInteractive.js +6 -3
  14. package/dist/flow/extraction.d.ts +21 -2
  15. package/dist/flow/extraction.js +62 -7
  16. package/dist/flow/reduceTransition.d.ts +2 -2
  17. package/dist/flow/reduceTransition.js +7 -3
  18. package/dist/flow/runFlow.d.ts +11 -0
  19. package/dist/flow/runFlow.js +68 -12
  20. package/dist/foundation/AgentStateController.d.ts +1 -1
  21. package/dist/foundation/ToolExecutor.d.ts +2 -2
  22. package/dist/foundation/index.d.ts +0 -11
  23. package/dist/foundation/index.js +0 -5
  24. package/dist/index.d.ts +9 -14
  25. package/dist/index.js +5 -6
  26. package/dist/outcomes/streamPart.d.ts +2 -2
  27. package/dist/outcomes/streamPart.js +2 -3
  28. package/dist/prompts/PromptBuilder.d.ts +3 -3
  29. package/dist/prompts/PromptBuilder.js +6 -6
  30. package/dist/prompts/index.d.ts +1 -1
  31. package/dist/prompts/types.d.ts +7 -2
  32. package/dist/runtime/InMemoryRetrievalCache.d.ts +1 -1
  33. package/dist/runtime/KnowledgeProvider.d.ts +3 -2
  34. package/dist/runtime/KnowledgeProvider.js +54 -30
  35. package/dist/runtime/Runtime.d.ts +9 -1
  36. package/dist/runtime/Runtime.js +123 -30
  37. package/dist/runtime/TraceRecorder.d.ts +22 -4
  38. package/dist/runtime/TraceRecorder.js +65 -29
  39. package/dist/runtime/buildAgentToolSurface.js +1 -1
  40. package/dist/runtime/channels/TextDriver.js +12 -9
  41. package/dist/runtime/channels/executeModelTool.d.ts +6 -0
  42. package/dist/runtime/channels/executeModelTool.js +70 -10
  43. package/dist/runtime/channels/extractionTurn.js +1 -1
  44. package/dist/runtime/channels/index.d.ts +0 -3
  45. package/dist/runtime/channels/index.js +0 -6
  46. package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
  47. package/dist/runtime/channels/streaming/speakGated.js +33 -12
  48. package/dist/runtime/citations/index.d.ts +3 -3
  49. package/dist/runtime/closeRun.js +3 -2
  50. package/dist/runtime/controlFlowSignal.d.ts +24 -0
  51. package/dist/runtime/controlFlowSignal.js +29 -0
  52. package/dist/runtime/ctx.d.ts +3 -4
  53. package/dist/runtime/ctx.js +39 -5
  54. package/dist/runtime/durable/RunStore.js +1 -1
  55. package/dist/runtime/grounding/gather.d.ts +1 -1
  56. package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
  57. package/dist/runtime/grounding/knowledge.d.ts +1 -1
  58. package/dist/runtime/hostLoop.js +39 -10
  59. package/dist/runtime/index.d.ts +1 -2
  60. package/dist/runtime/index.js +1 -1
  61. package/dist/runtime/outcomeMarking.d.ts +2 -2
  62. package/dist/runtime/outcomeMarking.js +2 -1
  63. package/dist/runtime/policies/agentTurn.d.ts +1 -1
  64. package/dist/runtime/runHookSafely.d.ts +3 -0
  65. package/dist/runtime/runHookSafely.js +19 -0
  66. package/dist/scheduler/index.d.ts +2 -2
  67. package/dist/scheduler/index.js +1 -1
  68. package/dist/services/TracingService.js +2 -3
  69. package/dist/skills/collectSkills.d.ts +11 -3
  70. package/dist/skills/collectSkills.js +51 -8
  71. package/dist/skills/compositeSkillStore.d.ts +26 -0
  72. package/dist/skills/compositeSkillStore.js +54 -0
  73. package/dist/skills/defineSkill.d.ts +21 -0
  74. package/dist/skills/defineSkill.js +26 -0
  75. package/dist/skills/fsSkillStore.d.ts +3 -0
  76. package/dist/skills/fsSkillStore.js +79 -0
  77. package/dist/skills/index.d.ts +4 -0
  78. package/dist/skills/index.js +4 -0
  79. package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
  80. package/dist/skills/parseSkillFrontmatter.js +183 -0
  81. package/dist/skills/wireAgentSkills.d.ts +2 -1
  82. package/dist/skills/wireAgentSkills.js +2 -2
  83. package/dist/testing/mocks.d.ts +3 -3
  84. package/dist/testing/mocks.js +7 -3
  85. package/dist/tools/controlResults.d.ts +14 -0
  86. package/dist/tools/controlResults.js +15 -0
  87. package/dist/tools/effect/ToolExecutor.d.ts +6 -0
  88. package/dist/tools/effect/ToolExecutor.js +71 -35
  89. package/dist/tools/effect/defineTool.d.ts +9 -0
  90. package/dist/tools/effect/defineTool.js +1 -0
  91. package/dist/tools/effect/errors.d.ts +2 -5
  92. package/dist/tools/effect/errors.js +2 -5
  93. package/dist/tools/fs/createFsTool.js +2 -1
  94. package/dist/tools/index.d.ts +0 -1
  95. package/dist/tools/index.js +0 -1
  96. package/dist/types/effectTool.d.ts +2 -0
  97. package/dist/types/flow.d.ts +1 -1
  98. package/dist/types/guardrails.d.ts +5 -0
  99. package/dist/types/hooks.d.ts +2 -2
  100. package/dist/types/index.d.ts +25 -16
  101. package/dist/types/index.js +8 -13
  102. package/dist/types/knowledge.d.ts +213 -0
  103. package/dist/types/processors.d.ts +0 -58
  104. package/dist/types/run-context.d.ts +3 -7
  105. package/dist/types/runtime.d.ts +3 -74
  106. package/dist/types/session.d.ts +1 -1
  107. package/dist/types/skills.d.ts +15 -1
  108. package/dist/types/stream.d.ts +133 -71
  109. package/dist/types/stream.js +34 -1
  110. package/dist/types/trace.d.ts +2 -0
  111. package/guides/AGENTS.md +3 -3
  112. package/guides/FLOWS.md +1 -1
  113. package/guides/GETTING_STARTED.md +1 -1
  114. package/guides/RUNTIME.md +20 -15
  115. package/guides/TOOLS.md +1 -1
  116. package/package.json +3 -13
  117. package/dist/foundation/AgentDefinition.d.ts +0 -17
  118. package/dist/foundation/ConversationEventLog.d.ts +0 -71
  119. package/dist/foundation/ConversationEventLog.js +0 -1
  120. package/dist/foundation/ConversationState.d.ts +0 -33
  121. package/dist/foundation/ConversationState.js +0 -1
  122. package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
  123. package/dist/foundation/DefaultConversationEventLog.js +0 -205
  124. package/dist/foundation/DefaultConversationState.d.ts +0 -36
  125. package/dist/foundation/DefaultConversationState.js +0 -103
  126. package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
  127. package/dist/foundation/DefaultToolExecutor.js +0 -132
  128. package/dist/foundation/createFoundation.d.ts +0 -32
  129. package/dist/foundation/createFoundation.js +0 -33
  130. package/dist/hooks/HookRunner.d.ts +0 -37
  131. package/dist/hooks/HookRunner.js +0 -111
  132. package/dist/hooks/builtin/logging.d.ts +0 -5
  133. package/dist/hooks/builtin/logging.js +0 -98
  134. package/dist/hooks/builtin/metrics.d.ts +0 -7
  135. package/dist/hooks/builtin/metrics.js +0 -60
  136. package/dist/hooks/builtin/observability.d.ts +0 -20
  137. package/dist/hooks/builtin/observability.js +0 -572
  138. package/dist/hooks/helpers.d.ts +0 -22
  139. package/dist/hooks/helpers.js +0 -183
  140. package/dist/hooks/index.d.ts +0 -8
  141. package/dist/hooks/index.js +0 -6
  142. package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
  143. package/dist/realtime/RealtimeAudioClient.js +0 -14
  144. package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
  145. package/dist/realtime/RealtimeSessionHandle.js +0 -1
  146. package/dist/realtime/index.d.ts +0 -2
  147. package/dist/realtime/index.js +0 -1
  148. package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
  149. package/dist/runtime/channels/VoiceDriver.js +0 -321
  150. package/dist/runtime/channels/voiceTools.d.ts +0 -9
  151. package/dist/runtime/channels/voiceTools.js +0 -51
  152. package/dist/tools/errorHandling.d.ts +0 -35
  153. package/dist/tools/errorHandling.js +0 -145
  154. package/dist/types/voice.d.ts +0 -517
  155. package/dist/types/voice.js +0 -14
  156. package/dist/utils/isRecord.d.ts +0 -1
  157. package/dist/utils/isRecord.js +0 -3
  158. package/dist/utils/messageNormalization.d.ts +0 -2
  159. package/dist/utils/messageNormalization.js +0 -137
  160. /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  import type { TurnControl } from '../../types/channel.js';
2
2
  import type { ToolCallRecord } from '../../types/session.js';
3
- import type { SourceRef } from '../../types/voice.js';
3
+ import type { SourceRef } from '../../types/knowledge.js';
4
4
  import type { RunContext } from '../../types/run-context.js';
5
5
  export interface PreTurnResult {
6
6
  proceed: boolean;
@@ -0,0 +1,3 @@
1
+ type HookCallback = () => void | Promise<void>;
2
+ export declare function runHookSafely(name: string, callback: HookCallback | undefined): Promise<void>;
3
+ export {};
@@ -0,0 +1,19 @@
1
+ export function runHookSafely(name, callback) {
2
+ if (!callback)
3
+ return Promise.resolve();
4
+ try {
5
+ return Promise.resolve(callback()).catch((error) => reportHookError(name, error));
6
+ }
7
+ catch (error) {
8
+ reportHookError(name, error);
9
+ return Promise.resolve();
10
+ }
11
+ }
12
+ function reportHookError(name, error) {
13
+ try {
14
+ console.error(`Hook ${name} failed`, error);
15
+ }
16
+ catch {
17
+ // Hook failure reporting must not affect run correctness.
18
+ }
19
+ }
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import type { Tool } from '../types/effectTool.js';
3
- import type { HarnessStreamPart, TurnHandle } from '../types/stream.js';
3
+ import type { StreamPart, TurnHandle } from '../types/stream.js';
4
4
  /**
5
5
  * Deferred-work scheduling for proactive (agent-initiated) turns.
6
6
  *
@@ -51,7 +51,7 @@ export interface WakeDelivery {
51
51
  reason: string;
52
52
  payload?: Record<string, unknown>;
53
53
  /** Full stream of the wake turn (text, tool events, interactive parts…). */
54
- parts: HarnessStreamPart[];
54
+ parts: StreamPart[];
55
55
  /** Concatenated assistant text of the wake turn. */
56
56
  text: string;
57
57
  }
@@ -60,7 +60,7 @@ export function createWakeJobRunner(runtime, opts) {
60
60
  for await (const part of handle.events) {
61
61
  parts.push(part);
62
62
  if (part.type === 'text-delta') {
63
- text += part.delta;
63
+ text += part.payload.delta;
64
64
  }
65
65
  }
66
66
  const result = await handle;
@@ -11,9 +11,8 @@ export class TracingService {
11
11
  }
12
12
  startSpan(name, attributes, parentSpanId) {
13
13
  if (!this.config) {
14
- // Fallback or throw? Helpers threw.
15
- // We will throw to maintain behavior, but maybe warn in future.
16
- throw new Error('Tracing not initialized. Call initTracing() first.');
14
+ throw new Error('TracingService not initialized. Construct it with a config — ' +
15
+ 'new TracingService(config) or call .init(config) before startSpan().');
17
16
  }
18
17
  const span = {
19
18
  id: this.generateSpanId(),
@@ -1,5 +1,6 @@
1
1
  import type { AnyTool } from '../types/effectTool.js';
2
- import type { SkillLike, SkillSource, SkillStoreLike } from '../types/skills.js';
2
+ import type { FileSystem } from '../types/filesystem.js';
3
+ import type { SkillEntry, SkillLike, SkillSource, SkillStoreLike } from '../types/skills.js';
3
4
  export interface SkillWireAgent {
4
5
  skills?: SkillSource;
5
6
  tools?: Record<string, AnyTool>;
@@ -8,10 +9,17 @@ export interface SkillWireAgent {
8
9
  name: string;
9
10
  }>;
10
11
  }
11
- export declare function isSkillStore(value: SkillSource): value is SkillStoreLike;
12
+ export declare function isSkillStore(value: SkillEntry | SkillSource): value is SkillStoreLike;
12
13
  export declare function collectRegisteredNames(agent: SkillWireAgent): Set<string>;
13
14
  export declare function validateSkillAllowedTools(skills: SkillLike[], registered: Set<string>): void;
14
- export declare function prepareSkillStore(source: SkillSource): Promise<{
15
+ /**
16
+ * Turns whatever the author wrote in `AgentConfig.skills` into one store.
17
+ *
18
+ * `fs` is the agent's workspace filesystem, needed only to resolve `string` entries. A path
19
+ * without a workspace is an authoring mistake, so it throws rather than silently yielding no
20
+ * skills — a missing skill is invisible at runtime and looks like the model ignoring it.
21
+ */
22
+ export declare function prepareSkillStore(source: SkillSource, fs?: FileSystem): Promise<{
15
23
  store: SkillStoreLike;
16
24
  skills: SkillLike[];
17
25
  }>;
@@ -1,4 +1,6 @@
1
1
  import { InlineSkillStore } from './inlineSkillStore.js';
2
+ import { CompositeSkillStore } from './compositeSkillStore.js';
3
+ import { fsSkillStore } from './fsSkillStore.js';
2
4
  export function isSkillStore(value) {
3
5
  return (typeof value === 'object' &&
4
6
  value !== null &&
@@ -7,9 +9,6 @@ export function isSkillStore(value) {
7
9
  typeof value.list === 'function');
8
10
  }
9
11
  async function collectSkillsFromSource(source) {
10
- if (!isSkillStore(source)) {
11
- return Array.isArray(source) ? source : [source];
12
- }
13
12
  if (typeof source.getAllSkills === 'function') {
14
13
  const all = source.getAllSkills();
15
14
  return Array.isArray(all) ? all : await all;
@@ -47,10 +46,54 @@ export function validateSkillAllowedTools(skills, registered) {
47
46
  }
48
47
  }
49
48
  }
50
- export async function prepareSkillStore(source) {
51
- if (isSkillStore(source)) {
52
- return { store: source, skills: await collectSkillsFromSource(source) };
49
+ /**
50
+ * Turns whatever the author wrote in `AgentConfig.skills` into one store.
51
+ *
52
+ * `fs` is the agent's workspace filesystem, needed only to resolve `string` entries. A path
53
+ * without a workspace is an authoring mistake, so it throws rather than silently yielding no
54
+ * skills — a missing skill is invisible at runtime and looks like the model ignoring it.
55
+ */
56
+ export async function prepareSkillStore(source, fs) {
57
+ const entries = Array.isArray(source)
58
+ ? [...source]
59
+ : [source];
60
+ // A lone store stays itself: wrapping one store in a composite would add a layer with
61
+ // nothing to compose, and lose any extra capability the store exposes.
62
+ if (entries.length === 1 && isSkillStore(entries[0])) {
63
+ const only = entries[0];
64
+ return { store: only, skills: await collectSkillsFromSource(only) };
65
+ }
66
+ const stores = [];
67
+ let inline = [];
68
+ const flushInline = () => {
69
+ if (inline.length > 0) {
70
+ stores.push(new InlineSkillStore(inline));
71
+ inline = [];
72
+ }
73
+ };
74
+ for (const entry of entries) {
75
+ if (typeof entry === 'string') {
76
+ if (!fs) {
77
+ throw new Error(`[skills] "${entry}" is a workspace path, but this agent has no \`workspace\` filesystem. ` +
78
+ 'Set `workspace` on the agent, or pass skills inline with defineSkill().');
79
+ }
80
+ flushInline();
81
+ stores.push(fsSkillStore(fs, [entry]));
82
+ continue;
83
+ }
84
+ if (isSkillStore(entry)) {
85
+ flushInline();
86
+ stores.push(entry);
87
+ continue;
88
+ }
89
+ // Consecutive inline skills share one store so ordering within a run is preserved.
90
+ inline.push(entry);
91
+ }
92
+ flushInline();
93
+ if (stores.length === 1) {
94
+ const only = stores[0];
95
+ return { store: only, skills: await collectSkillsFromSource(only) };
53
96
  }
54
- const skills = Array.isArray(source) ? source : [source];
55
- return { store: new InlineSkillStore(skills), skills };
97
+ const store = new CompositeSkillStore(stores);
98
+ return { store, skills: await collectSkillsFromSource(store) };
56
99
  }
@@ -0,0 +1,26 @@
1
+ import type { SkillMeta, SkillStoreLike } from '../types/skills.js';
2
+ /**
3
+ * Presents several skill stores as one, with **later stores winning** a name collision.
4
+ *
5
+ * Delegates rather than flattening on purpose: flattening would force every body and
6
+ * resource to load up front, which is exactly what progressive disclosure exists to avoid.
7
+ * Only `list()` (Level 1 — names and descriptions) is eager; bodies and resources stay
8
+ * behind their own calls.
9
+ */
10
+ export declare class CompositeSkillStore implements SkillStoreLike {
11
+ private readonly stores;
12
+ /** Name → the store that owns it. Built on first `list()`, reused after. */
13
+ private owners?;
14
+ /** @param stores In precedence order, lowest first. */
15
+ constructor(stores: readonly SkillStoreLike[]);
16
+ list(): Promise<SkillMeta[]>;
17
+ loadBody(name: string): Promise<string>;
18
+ loadResource(name: string, path: string): Promise<string | Uint8Array>;
19
+ loadAllSkills(): Promise<{
20
+ name: string;
21
+ description: string;
22
+ body: string;
23
+ }[]>;
24
+ private ownerOf;
25
+ private resolveOwners;
26
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Presents several skill stores as one, with **later stores winning** a name collision.
3
+ *
4
+ * Delegates rather than flattening on purpose: flattening would force every body and
5
+ * resource to load up front, which is exactly what progressive disclosure exists to avoid.
6
+ * Only `list()` (Level 1 — names and descriptions) is eager; bodies and resources stay
7
+ * behind their own calls.
8
+ */
9
+ export class CompositeSkillStore {
10
+ stores;
11
+ /** Name → the store that owns it. Built on first `list()`, reused after. */
12
+ owners;
13
+ /** @param stores In precedence order, lowest first. */
14
+ constructor(stores) {
15
+ this.stores = stores;
16
+ }
17
+ async list() {
18
+ const merged = new Map();
19
+ const owners = new Map();
20
+ for (const store of this.stores) {
21
+ for (const meta of await store.list()) {
22
+ merged.set(meta.name, meta);
23
+ owners.set(meta.name, store);
24
+ }
25
+ }
26
+ this.owners = owners;
27
+ return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
28
+ }
29
+ async loadBody(name) {
30
+ return (await this.ownerOf(name)).loadBody(name);
31
+ }
32
+ async loadResource(name, path) {
33
+ return (await this.ownerOf(name)).loadResource(name, path);
34
+ }
35
+ async loadAllSkills() {
36
+ const owners = await this.resolveOwners();
37
+ const out = [];
38
+ for (const [name, store] of owners) {
39
+ out.push({ name, description: '', body: await store.loadBody(name) });
40
+ }
41
+ return out;
42
+ }
43
+ async ownerOf(name) {
44
+ const owner = (await this.resolveOwners()).get(name);
45
+ if (!owner)
46
+ throw new Error(`[skills] Skill "${name}" not found.`);
47
+ return owner;
48
+ }
49
+ async resolveOwners() {
50
+ if (!this.owners)
51
+ await this.list();
52
+ return this.owners;
53
+ }
54
+ }
@@ -0,0 +1,21 @@
1
+ import type { SkillLike } from '../types/skills.js';
2
+ export interface DefineSkillConfig {
3
+ /** Lowercase, hyphen-separated, ≤64 chars — the Agent Skills naming rule. */
4
+ name: string;
5
+ /** When the model should reach for this skill. Disclosed up front, so keep it specific. ≤1024 chars. */
6
+ description: string;
7
+ /** The skill body — Level 2, loaded only when the model calls `load_skill`. */
8
+ instructions: string;
9
+ /** Level 3: fetched individually via `read_skill_resource`, never loaded up front. */
10
+ resources?: Record<string, string | Uint8Array>;
11
+ /** Restricts this skill to a subset of the agent's tools. Unknown names fail at wiring time. */
12
+ allowedTools?: string[];
13
+ }
14
+ /**
15
+ * Authors a skill inline, with no filesystem involved.
16
+ *
17
+ * Validated against the same rules as a `SKILL.md` on disk — an inline skill that would be
18
+ * rejected as a file is rejected here too, so moving one to `agents/<id>/skills/` later
19
+ * cannot start failing.
20
+ */
21
+ export declare function defineSkill(config: DefineSkillConfig): SkillLike;
@@ -0,0 +1,26 @@
1
+ import { validateSkillDescription, validateSkillName } from './parseSkillFrontmatter.js';
2
+ /**
3
+ * Authors a skill inline, with no filesystem involved.
4
+ *
5
+ * Validated against the same rules as a `SKILL.md` on disk — an inline skill that would be
6
+ * rejected as a file is rejected here too, so moving one to `agents/<id>/skills/` later
7
+ * cannot start failing.
8
+ */
9
+ export function defineSkill(config) {
10
+ const name = config.name?.trim();
11
+ const description = config.description?.trim();
12
+ if (!name)
13
+ throw new Error('[skills] defineSkill requires a non-empty name.');
14
+ if (!description) {
15
+ throw new Error(`[skills] Skill "${name}" requires a non-empty description.`);
16
+ }
17
+ validateSkillName(name, name);
18
+ validateSkillDescription(description, name);
19
+ return {
20
+ name,
21
+ description,
22
+ body: config.instructions,
23
+ resources: config.resources,
24
+ allowedTools: config.allowedTools,
25
+ };
26
+ }
@@ -0,0 +1,3 @@
1
+ import type { FileSystem } from '../types/filesystem.js';
2
+ import type { SkillStoreLike } from '../types/skills.js';
3
+ export declare function fsSkillStore(fs: FileSystem, orderedRoots?: readonly string[]): SkillStoreLike;
@@ -0,0 +1,79 @@
1
+ import { parseSkillFrontmatter } from './parseSkillFrontmatter.js';
2
+ const DEFAULT_ROOT = '/skills';
3
+ export function fsSkillStore(fs, orderedRoots = [DEFAULT_ROOT]) {
4
+ const roots = [...orderedRoots];
5
+ return {
6
+ async list() {
7
+ const skills = await discoverSkills(fs, roots, true);
8
+ return [...skills.values()]
9
+ .map((skill) => skill.meta)
10
+ .sort((a, b) => a.name.localeCompare(b.name));
11
+ },
12
+ async loadBody(name) {
13
+ const skill = (await discoverSkills(fs, roots, false)).get(name);
14
+ if (!skill) {
15
+ throw new Error(`[skills] Skill "${name}" not found.`);
16
+ }
17
+ return skill.body;
18
+ },
19
+ async loadResource(name, path) {
20
+ const skill = (await discoverSkills(fs, roots, false)).get(name);
21
+ if (!skill) {
22
+ throw new Error(`[skills] Skill "${name}" not found.`);
23
+ }
24
+ const normalized = path.trim().replace(/^\.?\//, '');
25
+ if (normalized.includes('..') || normalized.startsWith('/')) {
26
+ throw new Error(`[skills] Invalid resource path "${path}".`);
27
+ }
28
+ const resourcePath = fs.resolvePath(skill.root, `${skill.folder}/${normalized}`);
29
+ if (!(await fs.exists(resourcePath))) {
30
+ const err = new Error(`ENOENT: [skills] Resource "${normalized}" not found for skill "${name}".`);
31
+ throw err;
32
+ }
33
+ return fs.readFile(resourcePath);
34
+ },
35
+ };
36
+ }
37
+ async function discoverSkills(fs, roots, warnInvalid) {
38
+ const skills = new Map();
39
+ for (const root of roots) {
40
+ let entries;
41
+ try {
42
+ entries = await fs.readdir(root);
43
+ }
44
+ catch {
45
+ continue;
46
+ }
47
+ for (const entry of entries.sort()) {
48
+ const entryPath = fs.resolvePath(root, entry);
49
+ let stat;
50
+ try {
51
+ stat = await fs.stat(entryPath);
52
+ }
53
+ catch {
54
+ continue;
55
+ }
56
+ if (stat.type !== 'directory')
57
+ continue;
58
+ const skillPath = fs.resolvePath(root, `${entry}/SKILL.md`);
59
+ if (!(await fs.exists(skillPath)))
60
+ continue;
61
+ try {
62
+ const content = await fs.readFile(skillPath);
63
+ const parsed = parseSkillFrontmatter(content, { path: skillPath });
64
+ skills.set(parsed.name, {
65
+ root,
66
+ folder: entry,
67
+ meta: { name: parsed.name, description: parsed.description },
68
+ body: parsed.body,
69
+ });
70
+ }
71
+ catch (err) {
72
+ if (warnInvalid) {
73
+ console.warn(`[skills] Skipping ${skillPath}: ${err instanceof Error ? err.message : String(err)}`);
74
+ }
75
+ }
76
+ }
77
+ }
78
+ return skills;
79
+ }
@@ -3,3 +3,7 @@ export { wireAgentSkills } from './wireAgentSkills.js';
3
3
  export type { WiredAgentSkills } from './wireAgentSkills.js';
4
4
  export { collectRegisteredNames, validateSkillAllowedTools, prepareSkillStore, isSkillStore, type SkillWireAgent, } from './collectSkills.js';
5
5
  export { InlineSkillStore } from './inlineSkillStore.js';
6
+ export { CompositeSkillStore } from './compositeSkillStore.js';
7
+ export { fsSkillStore } from './fsSkillStore.js';
8
+ export { defineSkill, type DefineSkillConfig } from './defineSkill.js';
9
+ export { parseSkillFrontmatter, validateSkillName, validateSkillDescription, type ParsedSkill, } from './parseSkillFrontmatter.js';
@@ -2,3 +2,7 @@ export { SkillsCapability } from './SkillsCapability.js';
2
2
  export { wireAgentSkills } from './wireAgentSkills.js';
3
3
  export { collectRegisteredNames, validateSkillAllowedTools, prepareSkillStore, isSkillStore, } from './collectSkills.js';
4
4
  export { InlineSkillStore } from './inlineSkillStore.js';
5
+ export { CompositeSkillStore } from './compositeSkillStore.js';
6
+ export { fsSkillStore } from './fsSkillStore.js';
7
+ export { defineSkill } from './defineSkill.js';
8
+ export { parseSkillFrontmatter, validateSkillName, validateSkillDescription, } from './parseSkillFrontmatter.js';
@@ -0,0 +1,14 @@
1
+ export interface ParsedSkill {
2
+ name: string;
3
+ description: string;
4
+ body: string;
5
+ license?: string;
6
+ compatibility?: string;
7
+ allowedTools?: string[];
8
+ metadata?: Record<string, string>;
9
+ }
10
+ export declare function parseSkillFrontmatter(content: string, ctx: {
11
+ path: string;
12
+ }): ParsedSkill;
13
+ export declare function validateSkillName(name: string, path: string): void;
14
+ export declare function validateSkillDescription(description: string, path: string): void;
@@ -0,0 +1,183 @@
1
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)([\s\S]*)$/;
2
+ const NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3
+ export function parseSkillFrontmatter(content, ctx) {
4
+ const stripped = content.replace(/^\uFEFF/, '');
5
+ const match = stripped.match(FRONTMATTER_RE);
6
+ if (!match) {
7
+ throw new Error(`[skills] Skill ${ctx.path} is missing YAML frontmatter. Start SKILL.md with "---", ` +
8
+ 'include "name" and "description", then close the block with "---".');
9
+ }
10
+ const raw = parseFlatYaml(match[1] ?? '', ctx.path);
11
+ const name = requireField(raw, 'name', ctx.path);
12
+ const description = requireField(raw, 'description', ctx.path);
13
+ validateSkillName(name, ctx.path);
14
+ validateSkillDescription(description, ctx.path);
15
+ const compatibility = optionalString(raw.compatibility);
16
+ if (compatibility !== undefined && codePointLength(compatibility) > 500) {
17
+ throw new Error(`[skills] Skill ${ctx.path} field "compatibility" exceeds 500 characters.`);
18
+ }
19
+ let body = match[2] ?? '';
20
+ if (body.startsWith('\n')) {
21
+ body = body.slice(1);
22
+ }
23
+ const result = { name, description, body };
24
+ const license = optionalString(raw.license);
25
+ if (license !== undefined)
26
+ result.license = license;
27
+ if (compatibility !== undefined)
28
+ result.compatibility = compatibility;
29
+ const allowedTools = parseAllowedTools(raw['allowed-tools']);
30
+ if (allowedTools !== undefined)
31
+ result.allowedTools = allowedTools;
32
+ const metadata = parseMetadata(raw.metadata);
33
+ if (metadata !== undefined)
34
+ result.metadata = metadata;
35
+ return result;
36
+ }
37
+ function parseFlatYaml(yaml, path) {
38
+ const result = {};
39
+ const lines = yaml.split(/\r?\n/);
40
+ let i = 0;
41
+ while (i < lines.length) {
42
+ const line = lines[i];
43
+ if (line.trim() === '') {
44
+ i += 1;
45
+ continue;
46
+ }
47
+ const keyMatch = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
48
+ if (!keyMatch) {
49
+ throw new Error(`[skills] Skill ${path} has invalid YAML frontmatter near line: ${line}`);
50
+ }
51
+ const key = keyMatch[1];
52
+ const rest = (keyMatch[2] ?? '').trim();
53
+ if (rest === '') {
54
+ i += 1;
55
+ if (i >= lines.length)
56
+ break;
57
+ const next = lines[i];
58
+ if (next.match(/^\s+-\s+/)) {
59
+ const items = [];
60
+ while (i < lines.length) {
61
+ const listLine = lines[i];
62
+ const itemMatch = listLine.match(/^\s+-\s+(.*)$/);
63
+ if (!itemMatch)
64
+ break;
65
+ items.push(parseScalar(itemMatch[1] ?? ''));
66
+ i += 1;
67
+ }
68
+ result[key] = items;
69
+ continue;
70
+ }
71
+ if (key === 'metadata') {
72
+ const meta = {};
73
+ while (i < lines.length) {
74
+ const metaLine = lines[i];
75
+ const metaMatch = metaLine.match(/^\s{2}([A-Za-z0-9_-]+):\s*(.*)$/);
76
+ if (!metaMatch)
77
+ break;
78
+ meta[metaMatch[1]] = parseScalar((metaMatch[2] ?? '').trim());
79
+ i += 1;
80
+ }
81
+ result[key] = meta;
82
+ continue;
83
+ }
84
+ result[key] = '';
85
+ continue;
86
+ }
87
+ if (rest.startsWith('[') && rest.endsWith(']')) {
88
+ const inner = rest.slice(1, -1).trim();
89
+ result[key] =
90
+ inner === ''
91
+ ? []
92
+ : inner.split(',').map((s) => parseScalar(s.trim()));
93
+ i += 1;
94
+ continue;
95
+ }
96
+ result[key] = parseScalar(rest);
97
+ i += 1;
98
+ }
99
+ return result;
100
+ }
101
+ function parseScalar(value) {
102
+ const trimmed = value.trim();
103
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
104
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
105
+ return trimmed.slice(1, -1);
106
+ }
107
+ return trimmed;
108
+ }
109
+ function requireField(raw, field, path) {
110
+ const value = raw[field];
111
+ if (typeof value !== 'string' || value.trim().length === 0) {
112
+ throw new Error(`[skills] Skill ${path} must define frontmatter ${field} as a non-empty string.`);
113
+ }
114
+ return value.trim();
115
+ }
116
+ function optionalString(value) {
117
+ if (typeof value !== 'string')
118
+ return undefined;
119
+ const trimmed = value.trim();
120
+ return trimmed.length > 0 ? trimmed : undefined;
121
+ }
122
+ /** Agent Skills forbids reserved vendor words in a skill name. */
123
+ const RESERVED_NAME_WORDS = ['anthropic', 'claude'];
124
+ /** Frontmatter is injected into the system prompt, so markup in it is a prompt-injection seam. */
125
+ const XML_TAG_PATTERN = /<\/?[a-zA-Z][^>]*>/;
126
+ export function validateSkillName(name, path) {
127
+ if (name.length > 64) {
128
+ throw new Error(`[skills] Skill ${path} field "name" must be at most 64 characters.`);
129
+ }
130
+ if (XML_TAG_PATTERN.test(name)) {
131
+ throw new Error(`[skills] Skill ${path} field "name" must not contain XML tags.`);
132
+ }
133
+ if (!NAME_PATTERN.test(name)) {
134
+ throw new Error(`[skills] Skill ${path} field "name" "${name}" must match /^[a-z0-9]+(?:-[a-z0-9]+)*$/.`);
135
+ }
136
+ const reserved = RESERVED_NAME_WORDS.find((word) => name.includes(word));
137
+ if (reserved) {
138
+ throw new Error(`[skills] Skill ${path} field "name" must not contain the reserved word "${reserved}".`);
139
+ }
140
+ }
141
+ export function validateSkillDescription(description, path) {
142
+ if (codePointLength(description) > 1024) {
143
+ throw new Error(`[skills] Skill ${path} field "description" exceeds 1024 characters.`);
144
+ }
145
+ if (XML_TAG_PATTERN.test(description)) {
146
+ throw new Error(`[skills] Skill ${path} field "description" must not contain XML tags.`);
147
+ }
148
+ }
149
+ function codePointLength(value) {
150
+ return [...value].length;
151
+ }
152
+ function parseAllowedTools(value) {
153
+ if (value === undefined || value === null)
154
+ return undefined;
155
+ if (Array.isArray(value)) {
156
+ const tools = value.map((v) => String(v).trim()).filter(Boolean);
157
+ return tools.length > 0 ? tools : undefined;
158
+ }
159
+ if (typeof value === 'string') {
160
+ const trimmed = value.trim();
161
+ if (!trimmed)
162
+ return undefined;
163
+ const tools = trimmed.includes(',')
164
+ ? trimmed.split(',').map((s) => s.trim()).filter(Boolean)
165
+ : trimmed.split(/\s+/).filter(Boolean);
166
+ return tools.length > 0 ? tools : undefined;
167
+ }
168
+ return undefined;
169
+ }
170
+ function parseMetadata(value) {
171
+ if (value === undefined || value === null)
172
+ return undefined;
173
+ if (typeof value !== 'object' || Array.isArray(value))
174
+ return undefined;
175
+ const entries = Object.entries(value);
176
+ if (entries.length === 0)
177
+ return undefined;
178
+ const meta = {};
179
+ for (const [k, v] of entries) {
180
+ meta[k] = String(v);
181
+ }
182
+ return meta;
183
+ }
@@ -1,3 +1,4 @@
1
+ import type { FileSystem } from '../types/filesystem.js';
1
2
  import { type AnyTool } from '../types/effectTool.js';
2
3
  import type { PromptSection } from '../capabilities/index.js';
3
4
  import { type SkillWireAgent } from './collectSkills.js';
@@ -7,4 +8,4 @@ export interface WiredAgentSkills {
7
8
  tools: Record<string, AnyTool>;
8
9
  promptSections: PromptSection[];
9
10
  }
10
- export declare function wireAgentSkills(agent: SkillWireAgent): Promise<WiredAgentSkills | undefined>;
11
+ export declare function wireAgentSkills(agent: SkillWireAgent, fs?: FileSystem): Promise<WiredAgentSkills | undefined>;
@@ -1,10 +1,10 @@
1
1
  import { defineTool } from '../types/effectTool.js';
2
2
  import { collectRegisteredNames, prepareSkillStore, validateSkillAllowedTools, } from './collectSkills.js';
3
3
  import { SkillsCapability } from './SkillsCapability.js';
4
- export async function wireAgentSkills(agent) {
4
+ export async function wireAgentSkills(agent, fs) {
5
5
  if (!agent.skills)
6
6
  return undefined;
7
- const { store, skills } = await prepareSkillStore(agent.skills);
7
+ const { store, skills } = await prepareSkillStore(agent.skills, fs);
8
8
  validateSkillAllowedTools(skills, collectRegisteredNames(agent));
9
9
  const metas = await store.list();
10
10
  const capability = new SkillsCapability(store, metas);
@@ -1,4 +1,4 @@
1
- import type { HarnessStreamPart, TurnHandle } from '../types/stream.js';
1
+ import type { StreamPart, TurnHandle } from '../types/stream.js';
2
2
  import type { TurnResult } from '../types/channel.js';
3
3
  import type { Session } from '../types/session.js';
4
4
  import type { UserInputContent } from '../runtime/userInput.js';
@@ -13,8 +13,8 @@ export interface CreateMockRuntimeOptions {
13
13
  sessions?: Map<string, Session>;
14
14
  onRun?: (call: MockRuntimeRunCall) => void;
15
15
  }
16
- export declare function createMockTurnHandle(events: AsyncIterable<HarnessStreamPart>, settled?: TurnResult): TurnHandle;
16
+ export declare function createMockTurnHandle(events: AsyncIterable<StreamPart>, settled?: TurnResult): TurnHandle;
17
17
  export declare function createMockSession(partial?: Partial<Session>): Session;
18
- type MockRuntimeEvents = HarnessStreamPart[] | AsyncIterable<HarnessStreamPart> | (() => never);
18
+ type MockRuntimeEvents = StreamPart[] | AsyncIterable<StreamPart> | (() => never);
19
19
  export declare function createMockRuntime(parts: MockRuntimeEvents, options?: CreateMockRuntimeOptions): RuntimeLike;
20
20
  export {};