@kuralle-agents/core 0.13.0 → 0.14.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 (158) 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 +4 -4
  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 +2 -2
  15. package/dist/flow/extraction.js +10 -4
  16. package/dist/flow/reduceTransition.d.ts +2 -2
  17. package/dist/flow/reduceTransition.js +7 -3
  18. package/dist/flow/runFlow.js +34 -12
  19. package/dist/foundation/AgentStateController.d.ts +1 -1
  20. package/dist/foundation/ToolExecutor.d.ts +2 -2
  21. package/dist/foundation/index.d.ts +0 -11
  22. package/dist/foundation/index.js +0 -5
  23. package/dist/index.d.ts +9 -14
  24. package/dist/index.js +5 -6
  25. package/dist/outcomes/streamPart.d.ts +2 -2
  26. package/dist/outcomes/streamPart.js +2 -3
  27. package/dist/prompts/PromptBuilder.d.ts +3 -3
  28. package/dist/prompts/PromptBuilder.js +6 -6
  29. package/dist/prompts/index.d.ts +1 -1
  30. package/dist/prompts/types.d.ts +7 -2
  31. package/dist/runtime/InMemoryRetrievalCache.d.ts +1 -1
  32. package/dist/runtime/KnowledgeProvider.d.ts +3 -2
  33. package/dist/runtime/KnowledgeProvider.js +54 -30
  34. package/dist/runtime/Runtime.d.ts +3 -1
  35. package/dist/runtime/Runtime.js +84 -30
  36. package/dist/runtime/TraceRecorder.d.ts +22 -4
  37. package/dist/runtime/TraceRecorder.js +65 -29
  38. package/dist/runtime/buildAgentToolSurface.js +1 -1
  39. package/dist/runtime/channels/TextDriver.js +12 -9
  40. package/dist/runtime/channels/executeModelTool.d.ts +6 -0
  41. package/dist/runtime/channels/executeModelTool.js +70 -10
  42. package/dist/runtime/channels/extractionTurn.js +1 -1
  43. package/dist/runtime/channels/index.d.ts +0 -3
  44. package/dist/runtime/channels/index.js +0 -6
  45. package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
  46. package/dist/runtime/channels/streaming/speakGated.js +24 -12
  47. package/dist/runtime/citations/index.d.ts +3 -3
  48. package/dist/runtime/closeRun.js +3 -2
  49. package/dist/runtime/controlFlowSignal.d.ts +24 -0
  50. package/dist/runtime/controlFlowSignal.js +29 -0
  51. package/dist/runtime/ctx.d.ts +3 -4
  52. package/dist/runtime/ctx.js +39 -5
  53. package/dist/runtime/durable/RunStore.js +1 -1
  54. package/dist/runtime/grounding/gather.d.ts +1 -1
  55. package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
  56. package/dist/runtime/grounding/knowledge.d.ts +1 -1
  57. package/dist/runtime/hostLoop.js +26 -6
  58. package/dist/runtime/index.d.ts +1 -2
  59. package/dist/runtime/index.js +1 -1
  60. package/dist/runtime/outcomeMarking.d.ts +2 -2
  61. package/dist/runtime/outcomeMarking.js +2 -1
  62. package/dist/runtime/policies/agentTurn.d.ts +1 -1
  63. package/dist/runtime/runHookSafely.d.ts +3 -0
  64. package/dist/runtime/runHookSafely.js +19 -0
  65. package/dist/scheduler/index.d.ts +2 -2
  66. package/dist/scheduler/index.js +1 -1
  67. package/dist/services/TracingService.js +2 -3
  68. package/dist/skills/collectSkills.d.ts +11 -3
  69. package/dist/skills/collectSkills.js +51 -8
  70. package/dist/skills/compositeSkillStore.d.ts +26 -0
  71. package/dist/skills/compositeSkillStore.js +54 -0
  72. package/dist/skills/defineSkill.d.ts +21 -0
  73. package/dist/skills/defineSkill.js +26 -0
  74. package/dist/skills/fsSkillStore.d.ts +3 -0
  75. package/dist/skills/fsSkillStore.js +79 -0
  76. package/dist/skills/index.d.ts +4 -0
  77. package/dist/skills/index.js +4 -0
  78. package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
  79. package/dist/skills/parseSkillFrontmatter.js +183 -0
  80. package/dist/skills/wireAgentSkills.d.ts +2 -1
  81. package/dist/skills/wireAgentSkills.js +2 -2
  82. package/dist/testing/mocks.d.ts +3 -3
  83. package/dist/testing/mocks.js +7 -3
  84. package/dist/tools/controlResults.d.ts +14 -0
  85. package/dist/tools/controlResults.js +15 -0
  86. package/dist/tools/effect/ToolExecutor.d.ts +6 -0
  87. package/dist/tools/effect/ToolExecutor.js +71 -35
  88. package/dist/tools/effect/defineTool.d.ts +9 -0
  89. package/dist/tools/effect/defineTool.js +1 -0
  90. package/dist/tools/effect/errors.d.ts +2 -5
  91. package/dist/tools/effect/errors.js +2 -5
  92. package/dist/tools/index.d.ts +0 -1
  93. package/dist/tools/index.js +0 -1
  94. package/dist/types/effectTool.d.ts +2 -0
  95. package/dist/types/flow.d.ts +1 -1
  96. package/dist/types/guardrails.d.ts +5 -0
  97. package/dist/types/hooks.d.ts +2 -2
  98. package/dist/types/index.d.ts +25 -16
  99. package/dist/types/index.js +8 -13
  100. package/dist/types/knowledge.d.ts +213 -0
  101. package/dist/types/processors.d.ts +0 -58
  102. package/dist/types/run-context.d.ts +3 -7
  103. package/dist/types/runtime.d.ts +3 -74
  104. package/dist/types/session.d.ts +1 -1
  105. package/dist/types/skills.d.ts +15 -1
  106. package/dist/types/stream.d.ts +133 -71
  107. package/dist/types/stream.js +34 -1
  108. package/dist/types/trace.d.ts +2 -0
  109. package/guides/AGENTS.md +3 -3
  110. package/guides/FLOWS.md +1 -1
  111. package/guides/GETTING_STARTED.md +1 -1
  112. package/guides/RUNTIME.md +20 -15
  113. package/guides/TOOLS.md +1 -1
  114. package/package.json +3 -13
  115. package/dist/foundation/AgentDefinition.d.ts +0 -17
  116. package/dist/foundation/ConversationEventLog.d.ts +0 -71
  117. package/dist/foundation/ConversationEventLog.js +0 -1
  118. package/dist/foundation/ConversationState.d.ts +0 -33
  119. package/dist/foundation/ConversationState.js +0 -1
  120. package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
  121. package/dist/foundation/DefaultConversationEventLog.js +0 -205
  122. package/dist/foundation/DefaultConversationState.d.ts +0 -36
  123. package/dist/foundation/DefaultConversationState.js +0 -103
  124. package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
  125. package/dist/foundation/DefaultToolExecutor.js +0 -132
  126. package/dist/foundation/createFoundation.d.ts +0 -32
  127. package/dist/foundation/createFoundation.js +0 -33
  128. package/dist/hooks/HookRunner.d.ts +0 -37
  129. package/dist/hooks/HookRunner.js +0 -111
  130. package/dist/hooks/builtin/logging.d.ts +0 -5
  131. package/dist/hooks/builtin/logging.js +0 -98
  132. package/dist/hooks/builtin/metrics.d.ts +0 -7
  133. package/dist/hooks/builtin/metrics.js +0 -60
  134. package/dist/hooks/builtin/observability.d.ts +0 -20
  135. package/dist/hooks/builtin/observability.js +0 -572
  136. package/dist/hooks/helpers.d.ts +0 -22
  137. package/dist/hooks/helpers.js +0 -183
  138. package/dist/hooks/index.d.ts +0 -8
  139. package/dist/hooks/index.js +0 -6
  140. package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
  141. package/dist/realtime/RealtimeAudioClient.js +0 -14
  142. package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
  143. package/dist/realtime/RealtimeSessionHandle.js +0 -1
  144. package/dist/realtime/index.d.ts +0 -2
  145. package/dist/realtime/index.js +0 -1
  146. package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
  147. package/dist/runtime/channels/VoiceDriver.js +0 -321
  148. package/dist/runtime/channels/voiceTools.d.ts +0 -9
  149. package/dist/runtime/channels/voiceTools.js +0 -51
  150. package/dist/tools/errorHandling.d.ts +0 -35
  151. package/dist/tools/errorHandling.js +0 -145
  152. package/dist/types/voice.d.ts +0 -517
  153. package/dist/types/voice.js +0 -14
  154. package/dist/utils/isRecord.d.ts +0 -1
  155. package/dist/utils/isRecord.js +0 -3
  156. package/dist/utils/messageNormalization.d.ts +0 -2
  157. package/dist/utils/messageNormalization.js +0 -137
  158. /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
@@ -0,0 +1,29 @@
1
+ import { SuspendError } from './durable/RunStore.js';
2
+ import { ToolApprovalDeniedError } from '../tools/effect/errors.js';
3
+ /**
4
+ * The run is not finished: stop the turn, keep the journal, resume when a signal arrives.
5
+ * It unwinds the stack like an error but is not a failure, so it must never be reported to
6
+ * the model as a tool error, shown to the user as an error, or journaled as one.
7
+ *
8
+ * Only `SuspendError` qualifies. An approval *denial* is deliberately not a signal — see
9
+ * `isApprovalDenial`.
10
+ */
11
+ export function isControlFlowSignal(error) {
12
+ return error instanceof SuspendError;
13
+ }
14
+ /**
15
+ * A human answered "no". Neither a failure nor a suspend — the opposite of a suspend, in
16
+ * fact: a suspend defers the decision, a denial resolves it.
17
+ *
18
+ * Two rules follow, and they differ by who is driving the call:
19
+ *
20
+ * - It must never be degraded into "something went wrong on my side". Nothing went wrong;
21
+ * saying so would be a lie to the user.
22
+ * - On the **model** path it becomes a tool result, because there is no author code to
23
+ * catch it and the agent needs to be able to tell the user the request was declined.
24
+ * On the **flow** path it keeps propagating, because an `action` node's author chose to
25
+ * call the tool and can catch it or branch on `ctx.approve()` instead.
26
+ */
27
+ export function isApprovalDenial(error) {
28
+ return error instanceof ToolApprovalDeniedError;
29
+ }
@@ -1,7 +1,7 @@
1
1
  import type { LanguageModel } from 'ai';
2
2
  import type { Session } from '../types/session.js';
3
- import type { EffectToolExecutor, HookRunner, MemoryService, AutoRetrieveProvider, RunContext } from '../types/run-context.js';
4
- import type { HarnessStreamPart } from '../types/stream.js';
3
+ import type { EffectToolExecutor, MemoryService, AutoRetrieveProvider, RunContext } from '../types/run-context.js';
4
+ import type { StreamPart } from '../types/stream.js';
5
5
  import type { RefinementCapability } from '../capabilities/RefinementCapability.js';
6
6
  import type { ValidationCapability } from '../capabilities/ValidationCapability.js';
7
7
  import type { InputProcessor, OutputProcessor } from '../types/processors.js';
@@ -19,7 +19,6 @@ export interface CtxDeps {
19
19
  runStore: RunStore;
20
20
  steps: StepRecord[];
21
21
  toolExecutor: EffectToolExecutor;
22
- hookRunner?: HookRunner;
23
22
  model: LanguageModel;
24
23
  controlModel?: LanguageModel;
25
24
  outOfBandControl?: boolean;
@@ -34,7 +33,7 @@ export interface CtxDeps {
34
33
  bargeIn?: AbortSignal;
35
34
  abortSignal?: AbortSignal;
36
35
  clock?: EffectClock;
37
- emit?: (part: HarnessStreamPart) => void;
36
+ emit?: (part: StreamPart) => void;
38
37
  }
39
38
  export declare function createRunContext(deps: CtxDeps): Promise<RunContext>;
40
39
  export {};
@@ -14,6 +14,22 @@ function makeCtx(deps) {
14
14
  const emit = deps.emit ?? (() => { });
15
15
  // Mutable holder: handoff reassigns runCtx.toolExecutor; ctx.tool must see the swap.
16
16
  const toolExecutorHolder = { executor: deps.toolExecutor };
17
+ let pendingAppendTail = Promise.resolve();
18
+ const serializePendingAppend = (operation) => {
19
+ const previous = pendingAppendTail;
20
+ let release;
21
+ pendingAppendTail = new Promise((resolve) => {
22
+ release = resolve;
23
+ });
24
+ return previous.then(async () => {
25
+ try {
26
+ return await operation();
27
+ }
28
+ finally {
29
+ release();
30
+ }
31
+ });
32
+ };
17
33
  const consumeCallsite = () => {
18
34
  const site = String(effectOrdinal);
19
35
  effectOrdinal += 1;
@@ -85,9 +101,28 @@ function makeCtx(deps) {
85
101
  }
86
102
  }
87
103
  const isRetry = hit?.status === 'running';
88
- const stepIndex = options?.index ?? hit?.index ?? steps.length;
104
+ const stepIndex = options?.index ?? hit?.index;
89
105
  if (!isRetry) {
90
- await appendPendingStep(key, kind, name, stepIndex);
106
+ const append = async () => {
107
+ let index = stepIndex;
108
+ if (index === undefined) {
109
+ if (deps.runStore.reserveSteps) {
110
+ index = (await deps.runStore.reserveSteps(deps.runState.runId, 1))[0];
111
+ }
112
+ else {
113
+ // ctx.tool owns ordinal assignment: use the store's atomic reservation when available,
114
+ // and serialize legacy stores that do not expose reserveSteps within this context.
115
+ index = steps.length;
116
+ }
117
+ }
118
+ await appendPendingStep(key, kind, name, index);
119
+ };
120
+ if (stepIndex === undefined && !deps.runStore.reserveSteps) {
121
+ await serializePendingAppend(append);
122
+ }
123
+ else {
124
+ await append();
125
+ }
91
126
  }
92
127
  let result;
93
128
  try {
@@ -131,7 +166,7 @@ function makeCtx(deps) {
131
166
  deps.runState.status = 'paused';
132
167
  deps.runState.updatedAt = Date.now();
133
168
  await deps.runStore.putRunState(deps.runState);
134
- emit({ type: 'paused', waitingFor: signalName });
169
+ emit({ channel: 'internal', type: 'paused', payload: { waitingFor: signalName } });
135
170
  throw new SuspendError(signalName);
136
171
  };
137
172
  const pauseEffect = async (signalName, meta) => {
@@ -160,7 +195,6 @@ function makeCtx(deps) {
160
195
  set toolExecutor(executor) {
161
196
  toolExecutorHolder.executor = executor;
162
197
  },
163
- hookRunner: deps.hookRunner ?? {},
164
198
  model: deps.model,
165
199
  controlModel: deps.controlModel ?? deps.model,
166
200
  outOfBandControl: deps.outOfBandControl ?? false,
@@ -223,7 +257,7 @@ function makeCtx(deps) {
223
257
  toolCtx: options?.toolCtx,
224
258
  });
225
259
  if (def?.replay === false) {
226
- const auditKey = `${key}:${steps.length}`;
260
+ const auditKey = `${key}:${steps.length}:${options?.index ?? callsite}`;
227
261
  return replayOrExecute(auditKey, 'tool', name, executeTool, { index: options?.index });
228
262
  }
229
263
  return replayOrExecute(key, 'tool', name, executeTool, { index: options?.index });
@@ -3,7 +3,7 @@ export class LogConflictError extends Error {
3
3
  expectedIndex;
4
4
  actualIndex;
5
5
  constructor(runId, expectedIndex, actualIndex) {
6
- super(`Log conflict for run ${runId}: expected append at index ${expectedIndex}, current length is ${actualIndex}`);
6
+ super(`Log conflict for run ${runId}: expected append at index ${expectedIndex}, current length is ${actualIndex}. For parallel durable effects, use ctx.tool directly or reserve callsites with ctx.reserveCallsites(count) before supplying explicit indices.`);
7
7
  this.name = 'LogConflictError';
8
8
  this.runId = runId;
9
9
  this.expectedIndex = expectedIndex;
@@ -1,4 +1,4 @@
1
- import type { SourceRef } from '../../types/voice.js';
1
+ import type { SourceRef } from '../../types/knowledge.js';
2
2
  import type { GatherScope, RunContext } from '../../types/run-context.js';
3
3
  export type { GatherScope } from '../../types/run-context.js';
4
4
  export interface GatherResult {
@@ -1,4 +1,4 @@
1
- import type { KnowledgeProviderConfig, KnowledgeRetrieverAdapter } from '../../types/voice.js';
1
+ import type { KnowledgeProviderConfig, KnowledgeRetrieverAdapter } from '../../types/knowledge.js';
2
2
  export interface InMemoryKnowledgeDocument {
3
3
  id?: string;
4
4
  text: string;
@@ -1,7 +1,7 @@
1
1
  import type { AgentConfig } from '../../types/agentConfig.js';
2
2
  import type { AutoRetrieveProvider } from '../../types/run-context.js';
3
3
  import type { AnyTool } from '../../types/effectTool.js';
4
- import type { KnowledgeProviderConfig } from '../../types/voice.js';
4
+ import type { KnowledgeProviderConfig } from '../../types/knowledge.js';
5
5
  import { KnowledgeProvider } from '../KnowledgeProvider.js';
6
6
  export declare function buildKnowledgeProvider(config: KnowledgeProviderConfig): KnowledgeProvider;
7
7
  export declare function buildAutoRetrieveProvider(provider: KnowledgeProvider, agent: AgentConfig): AutoRetrieveProvider | undefined;
@@ -36,7 +36,7 @@ export async function hostLoop(options) {
36
36
  return { kind: 'paused' };
37
37
  }
38
38
  if (error instanceof LimitsExceededError) {
39
- ctx.emit({ type: 'error', error: error.message });
39
+ ctx.emit({ channel: 'client', type: 'error', payload: { error: error.message } });
40
40
  return { kind: 'ended', reason: error.message };
41
41
  }
42
42
  throw error;
@@ -160,7 +160,11 @@ function guardVerdictToTelemetryVerdict(verdict) {
160
160
  return 'keep';
161
161
  }
162
162
  function emitHostGuardTelemetry(ctx, data) {
163
- ctx.emit({ type: 'custom', name: 'host-guard', data });
163
+ ctx.emit({
164
+ channel: 'internal',
165
+ type: 'custom',
166
+ payload: { name: 'host-guard', data },
167
+ });
164
168
  }
165
169
  function guardVerdictToControl(verdict, agent) {
166
170
  const selection = verdictToSelection(verdict, agent);
@@ -174,7 +178,11 @@ function guardVerdictToControl(verdict, agent) {
174
178
  }
175
179
  async function executeHostControl(agent, run, driver, ctx, control) {
176
180
  if (!control) {
177
- ctx.emit({ type: 'error', error: 'No valid host control target resolved' });
181
+ ctx.emit({
182
+ channel: 'client',
183
+ type: 'error',
184
+ payload: { error: 'No valid host control target resolved' },
185
+ });
178
186
  return { kind: 'ended', reason: 'dispatch_failed' };
179
187
  }
180
188
  if (control.type === 'enterFlow') {
@@ -182,18 +190,30 @@ async function executeHostControl(agent, run, driver, ctx, control) {
182
190
  if (flow) {
183
191
  return await runActiveFlow(flow, run, driver, ctx, agent);
184
192
  }
185
- ctx.emit({ type: 'error', error: `Flow not found: ${control.flowName}` });
193
+ ctx.emit({
194
+ channel: 'client',
195
+ type: 'error',
196
+ payload: { error: `Flow not found: ${control.flowName}` },
197
+ });
186
198
  return { kind: 'ended', reason: 'flow_not_found' };
187
199
  }
188
200
  if (control.type === 'handoff') {
189
- ctx.emit({ type: 'handoff', targetAgent: control.target, reason: control.reason });
201
+ ctx.emit({
202
+ channel: 'internal',
203
+ type: 'handoff',
204
+ payload: { targetAgent: control.target, reason: control.reason },
205
+ });
190
206
  return { kind: 'handoff', to: control.target, reason: control.reason };
191
207
  }
192
208
  if (control.type === 'end') {
193
209
  return { kind: 'ended', reason: control.reason };
194
210
  }
195
211
  if (control.type === 'escalate') {
196
- ctx.emit({ type: 'handoff', targetAgent: 'human', reason: control.reason });
212
+ // No handoff part is emitted here. Runtime owns it: it emits one for any
213
+ // target in `terminalHandoffTargets` (default ['human']) on this exact
214
+ // return value. Emitting here too produced two handoff spans per escalation
215
+ // and a meaningless `human -> human` self-edge, which then mis-attributed
216
+ // every later span in the turn to `human`.
197
217
  return { kind: 'handoff', to: 'human', reason: control.reason, category: control.category };
198
218
  }
199
219
  if (control.type === 'recover') {
@@ -3,8 +3,7 @@ export type { RuntimeLike } from './RuntimeLike.js';
3
3
  export { TraceRecorder, runOnce, type TraceRecorderOptions } from './TraceRecorder.js';
4
4
  export type { AgentSpan, AgentTrace, SpanKind } from '../types/trace.js';
5
5
  export { SessionWorkingMemory } from './WorkingMemory.js';
6
- export { TextDriver, VoiceDriver } from './channels/index.js';
7
- export type { VoiceDriverConfig } from './channels/index.js';
6
+ export { TextDriver } from './channels/index.js';
8
7
  export { setPendingUserInput, consumePendingUserInput, consumeAllPendingUserInput, peekPendingUserInput, hasPendingUserInput, } from './channels/index.js';
9
8
  export { userInputToText, mergeUserInputContents, hasMediaParts, transcribeAudioParts, type UserInputContent, } from './userInput.js';
10
9
  export { isAbortSignal, type InterruptionEvent, type AbortOptions, type CancellationReason, } from '../types/index.js';
@@ -1,7 +1,7 @@
1
1
  export { createRuntime, Runtime, } from './Runtime.js';
2
2
  export { TraceRecorder, runOnce } from './TraceRecorder.js';
3
3
  export { SessionWorkingMemory } from './WorkingMemory.js';
4
- export { TextDriver, VoiceDriver } from './channels/index.js';
4
+ export { TextDriver } from './channels/index.js';
5
5
  // Pending-input buffer helpers — required by custom ChannelDriver authors to
6
6
  // implement awaitUser the same FIFO-aware way the built-in drivers do (the
7
7
  // buffer is an ordered queue since 0.3.13/H3, not a single slot).
@@ -1,9 +1,9 @@
1
1
  import type { Session } from '../types/session.js';
2
2
  import type { SessionStore } from '../session/SessionStore.js';
3
3
  import type { ConversationOutcome, ConversationOutcomeMarkedBy, ConversationOutcomeRecord } from '../outcomes/types.js';
4
- import type { HarnessStreamPart } from '../types/stream.js';
4
+ import type { StreamPart } from '../types/stream.js';
5
5
  export declare function isTerminalOutcome(outcome: ConversationOutcome): boolean;
6
6
  export declare function markSessionOutcome(sessionStore: SessionStore, session: Session, outcome: ConversationOutcome, opts?: {
7
7
  reason?: string;
8
8
  markedBy?: ConversationOutcomeMarkedBy;
9
- }, emit?: (part: HarnessStreamPart) => void): Promise<ConversationOutcomeRecord>;
9
+ }, emit?: (part: StreamPart) => void): Promise<ConversationOutcomeRecord>;
@@ -22,8 +22,9 @@ export async function markSessionOutcome(sessionStore, session, outcome, opts =
22
22
  await sessionStore.save(session);
23
23
  if (emit) {
24
24
  emit({
25
+ channel: 'client',
25
26
  type: 'conversation-outcome',
26
- outcome: record.outcome,
27
+ payload: { outcome: record.outcome },
27
28
  });
28
29
  }
29
30
  return record;
@@ -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;