@kuralle-agents/core 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +202 -0
  2. package/dist/debug.d.ts +3 -0
  3. package/dist/debug.js +7 -0
  4. package/dist/flow/emitInteractive.d.ts +3 -0
  5. package/dist/flow/emitInteractive.js +43 -0
  6. package/dist/flow/extraction.d.ts +0 -6
  7. package/dist/flow/extraction.js +6 -6
  8. package/dist/flow/nodeBuilders.d.ts +0 -2
  9. package/dist/flow/nodeBuilders.js +1 -1
  10. package/dist/flow/normalizeTransition.d.ts +0 -1
  11. package/dist/flow/normalizeTransition.js +1 -1
  12. package/dist/flow/reduceTransition.js +2 -0
  13. package/dist/flow/runFlow.d.ts +1 -3
  14. package/dist/flow/runFlow.js +4 -2
  15. package/dist/flows/extraction.d.ts +0 -7
  16. package/dist/flows/extraction.js +0 -16
  17. package/dist/flows/template.d.ts +2 -3
  18. package/dist/flows/template.js +0 -3
  19. package/dist/flows/transitions.d.ts +4 -32
  20. package/dist/flows/transitions.js +0 -20
  21. package/dist/guards/ToolEnforcer.js +3 -2
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.js +1 -1
  24. package/dist/runtime/Runtime.d.ts +4 -0
  25. package/dist/runtime/Runtime.js +9 -0
  26. package/dist/runtime/channels/TextDriver.js +14 -10
  27. package/dist/runtime/channels/VoiceDriver.js +14 -10
  28. package/dist/runtime/channels/index.d.ts +1 -1
  29. package/dist/runtime/channels/index.js +1 -1
  30. package/dist/runtime/channels/voiceTools.d.ts +0 -1
  31. package/dist/runtime/channels/voiceTools.js +0 -10
  32. package/dist/runtime/citations/index.d.ts +1 -12
  33. package/dist/runtime/citations/index.js +0 -36
  34. package/dist/runtime/contextOverflow.d.ts +0 -11
  35. package/dist/runtime/contextOverflow.js +0 -23
  36. package/dist/runtime/ctx.d.ts +2 -2
  37. package/dist/runtime/ctx.js +20 -1
  38. package/dist/runtime/durable/idempotency.d.ts +0 -1
  39. package/dist/runtime/durable/idempotency.js +1 -1
  40. package/dist/runtime/durable/replay.d.ts +0 -1
  41. package/dist/runtime/durable/replay.js +1 -1
  42. package/dist/runtime/durable/types.d.ts +2 -1
  43. package/dist/runtime/openRun.d.ts +2 -0
  44. package/dist/runtime/openRun.js +9 -3
  45. package/dist/runtime/policies/agentTurn.d.ts +0 -5
  46. package/dist/runtime/policies/agentTurn.js +0 -6
  47. package/dist/runtime/policies/limits.d.ts +0 -1
  48. package/dist/runtime/policies/limits.js +1 -1
  49. package/dist/runtime/policies/resolvePolicies.d.ts +0 -3
  50. package/dist/runtime/policies/resolvePolicies.js +0 -13
  51. package/dist/runtime/promptCache.d.ts +0 -4
  52. package/dist/tools/Tool.d.ts +0 -6
  53. package/dist/tools/Tool.js +0 -8
  54. package/dist/tools/effect/ToolExecutor.d.ts +2 -0
  55. package/dist/tools/effect/ToolExecutor.js +6 -1
  56. package/dist/tools/effect/errors.d.ts +10 -0
  57. package/dist/tools/effect/errors.js +15 -0
  58. package/dist/tools/effect/index.d.ts +1 -0
  59. package/dist/tools/effect/index.js +1 -0
  60. package/dist/tools/errorHandling.d.ts +0 -1
  61. package/dist/types/channel.d.ts +2 -1
  62. package/dist/types/flow.d.ts +3 -0
  63. package/dist/types/index.d.ts +1 -0
  64. package/dist/types/index.js +1 -0
  65. package/dist/types/run-context.d.ts +7 -0
  66. package/dist/types/selection.d.ts +18 -0
  67. package/dist/types/selection.js +1 -0
  68. package/dist/types/stream.d.ts +11 -0
  69. package/package.json +14 -10
  70. package/dist/events/index.d.ts +0 -2
  71. package/dist/events/index.js +0 -1
  72. package/dist/flow/index.d.ts +0 -10
  73. package/dist/flow/index.js +0 -10
  74. package/dist/runtime/SessionEventManager.d.ts +0 -16
  75. package/dist/runtime/SessionEventManager.js +0 -148
  76. package/dist/runtime/durable/index.d.ts +0 -5
  77. package/dist/runtime/durable/index.js +0 -5
  78. package/dist/runtime/policies/index.d.ts +0 -3
  79. package/dist/runtime/policies/index.js +0 -3
  80. package/dist/runtime/tokenSessionUtils.d.ts +0 -5
  81. package/dist/runtime/tokenSessionUtils.js +0 -24
  82. package/dist/tools/transferToTriage.d.ts +0 -15
  83. package/dist/tools/transferToTriage.js +0 -48
  84. package/dist/utils/aiStream.d.ts +0 -6
  85. package/dist/utils/aiStream.js +0 -48
  86. package/dist/utils/streamChunk.d.ts +0 -4
  87. package/dist/utils/streamChunk.js +0 -49
@@ -19,6 +19,7 @@ export class Runtime {
19
19
  sessionStore;
20
20
  defaultModel;
21
21
  maxHandoffs;
22
+ terminalHandoffTargets;
22
23
  hooks;
23
24
  activeTurnAborts = new Map();
24
25
  constructor(config) {
@@ -27,6 +28,7 @@ export class Runtime {
27
28
  this.sessionStore = config.sessionStore ?? new MemoryStore();
28
29
  this.defaultModel = config.defaultModel;
29
30
  this.maxHandoffs = config.maxHandoffs ?? 5;
31
+ this.terminalHandoffTargets = new Set(config.terminalHandoffTargets ?? ['human']);
30
32
  this.hooks = config.hooks;
31
33
  }
32
34
  run(opts) {
@@ -47,6 +49,7 @@ export class Runtime {
47
49
  sessionId,
48
50
  userId: opts.userId,
49
51
  input: opts.input,
52
+ selection: opts.selection,
50
53
  agentId: opts.agentId,
51
54
  seedMessages: opts.seedMessages,
52
55
  historyDelta: opts.historyDelta,
@@ -110,6 +113,12 @@ export class Runtime {
110
113
  select: this.config.hostSelect,
111
114
  });
112
115
  if (loopResult.kind === 'handoff') {
116
+ if (this.terminalHandoffTargets.has(loopResult.to)) {
117
+ emit({ type: 'handoff', targetAgent: loopResult.to, reason: loopResult.reason });
118
+ runCtx.runState.status = 'paused';
119
+ await runCtx.runStore.putRunState(runCtx.runState);
120
+ break;
121
+ }
113
122
  handoffCount += 1;
114
123
  if (handoffCount > this.maxHandoffs) {
115
124
  throw new Error(`maxHandoffs exceeded (${this.maxHandoffs})`);
@@ -69,16 +69,20 @@ export class TextDriver {
69
69
  toolCallId: call.toolCallId,
70
70
  });
71
71
  const localTool = node.localTools?.[call.toolName];
72
- const toolResult = localTool
73
- ? await localTool.execute(call.input, {
74
- session: ctx.session,
75
- runState: ctx.runState,
76
- tool: ctx.tool.bind(ctx),
77
- now: ctx.now.bind(ctx),
78
- uuid: ctx.uuid.bind(ctx),
79
- emit: ctx.emit.bind(ctx),
80
- })
81
- : await ctx.tool(call.toolName, call.input);
72
+ const toolResult = await ctx.tool(call.toolName, call.input, {
73
+ toolCallId: call.toolCallId,
74
+ ...(localTool && {
75
+ def: localTool,
76
+ toolCtx: {
77
+ session: ctx.session,
78
+ runState: ctx.runState,
79
+ tool: ctx.tool.bind(ctx),
80
+ now: ctx.now.bind(ctx),
81
+ uuid: ctx.uuid.bind(ctx),
82
+ emit: ctx.emit.bind(ctx),
83
+ },
84
+ }),
85
+ });
82
86
  out.toolResults.push({
83
87
  name: call.toolName,
84
88
  args: call.input,
@@ -121,16 +121,20 @@ export class VoiceDriver {
121
121
  try {
122
122
  ctx.emit({ type: 'tool-call', toolName: name, args, toolCallId: id });
123
123
  const localTool = localTools?.[name];
124
- const toolResult = localTool
125
- ? await localTool.execute(args, {
126
- session: ctx.session,
127
- runState: ctx.runState,
128
- tool: ctx.tool.bind(ctx),
129
- now: ctx.now.bind(ctx),
130
- uuid: ctx.uuid.bind(ctx),
131
- emit: ctx.emit.bind(ctx),
132
- })
133
- : await ctx.tool(name, args, { toolCallId: id });
124
+ const toolResult = await ctx.tool(name, args, {
125
+ toolCallId: id,
126
+ ...(localTool && {
127
+ def: localTool,
128
+ toolCtx: {
129
+ session: ctx.session,
130
+ runState: ctx.runState,
131
+ tool: ctx.tool.bind(ctx),
132
+ now: ctx.now.bind(ctx),
133
+ uuid: ctx.uuid.bind(ctx),
134
+ emit: ctx.emit.bind(ctx),
135
+ },
136
+ }),
137
+ });
134
138
  out.toolResults.push({ name, args, result: toolResult, toolCallId: id });
135
139
  toolCallsMade.push({
136
140
  toolCallId: id,
@@ -1,7 +1,7 @@
1
1
  export type { ChannelDriver } from '../../types/channel.js';
2
2
  export { TextDriver, buildNodePrompt } from './TextDriver.js';
3
3
  export type { TextDriverConfig } from './TextDriver.js';
4
- export { VoiceDriver, buildNodePrompt as buildVoiceNodePrompt } from './VoiceDriver.js';
4
+ export { VoiceDriver } from './VoiceDriver.js';
5
5
  export type { VoiceDriverConfig } from './VoiceDriver.js';
6
6
  export { resolveVoiceGeminiTools, v2ToolsToGemini } from './voiceTools.js';
7
7
  export { setPendingUserInput, consumePendingUserInput, peekPendingUserInput } from './inputBuffer.js';
@@ -1,4 +1,4 @@
1
1
  export { TextDriver, buildNodePrompt } from './TextDriver.js';
2
- export { VoiceDriver, buildNodePrompt as buildVoiceNodePrompt } from './VoiceDriver.js';
2
+ export { VoiceDriver } from './VoiceDriver.js';
3
3
  export { resolveVoiceGeminiTools, v2ToolsToGemini } from './voiceTools.js';
4
4
  export { setPendingUserInput, consumePendingUserInput, peekPendingUserInput } from './inputBuffer.js';
@@ -4,5 +4,4 @@ import type { AnyTool } from '../../types/effectTool.js';
4
4
  import { buildToolSet } from '../../tools/effect/defineTool.js';
5
5
  export declare function v2ToolsToGemini(tools: Record<string, AnyTool>): GeminiFunctionDeclaration[];
6
6
  export declare function resolveVoiceGeminiTools(resolved: ResolvedNode, toolDefs: Record<string, AnyTool>): GeminiFunctionDeclaration[];
7
- export declare function mergeVoiceToolDefs(resolved: ResolvedNode, toolDefs: Record<string, AnyTool>): Record<string, AnyTool>;
8
7
  export { buildToolSet };
@@ -36,14 +36,4 @@ function toolSetToEffectTools(tools) {
36
36
  }
37
37
  return out;
38
38
  }
39
- export function mergeVoiceToolDefs(resolved, toolDefs) {
40
- const merged = { ...toolDefs, ...(resolved.localTools ?? {}) };
41
- const fromNode = toolSetToEffectTools(resolved.tools);
42
- for (const [name, tool] of Object.entries(fromNode)) {
43
- if (!merged[name]) {
44
- merged[name] = tool;
45
- }
46
- }
47
- return merged;
48
- }
49
39
  export { buildToolSet };
@@ -1,5 +1,4 @@
1
- import type { AgentKnowledgeOverrides, HarnessStreamPart, KnowledgeRetrievalResult, RetrievalCacheAdapter, Session, SourceRef } from '../../types/index.js';
2
- export declare const PENDING_CITATIONS_KEY = "__pendingCitations";
1
+ import type { AgentKnowledgeOverrides, HarnessStreamPart, KnowledgeRetrievalResult, RetrievalCacheAdapter, SourceRef } from '../../types/index.js';
3
2
  export interface CitationRetrievalProvider {
4
3
  retrieve(query: string, cache: RetrievalCacheAdapter | undefined, agentOverrides?: AgentKnowledgeOverrides, isVoice?: boolean): Promise<{
5
4
  results: KnowledgeRetrievalResult[];
@@ -14,13 +13,3 @@ export interface CitationRetrievalResult {
14
13
  }
15
14
  export declare function retrieveWithCitations(provider: CitationRetrievalProvider, query: string, cache: RetrievalCacheAdapter | undefined, agentOverrides?: AgentKnowledgeOverrides, isVoice?: boolean): Promise<CitationRetrievalResult>;
16
15
  export declare function normalizeCitations(results: KnowledgeRetrievalResult[], nativeCitations?: readonly SourceRef[]): SourceRef[];
17
- export declare function getPendingCitations(session: Session): SourceRef[];
18
- export declare function popPendingCitations(session: Session): SourceRef[];
19
- export declare function withCitationMetadata<T extends {
20
- role?: unknown;
21
- metadata?: unknown;
22
- }>(message: T, citations: readonly SourceRef[]): T;
23
- export declare function withCitationsOnLastAssistantMessage<T extends {
24
- role?: unknown;
25
- metadata?: unknown;
26
- }>(messages: readonly T[], citations: readonly SourceRef[]): T[];
@@ -1,5 +1,4 @@
1
1
  import { createHash } from 'node:crypto';
2
- export const PENDING_CITATIONS_KEY = '__pendingCitations';
3
2
  export async function retrieveWithCitations(provider, query, cache, agentOverrides, isVoice = false) {
4
3
  const raw = await provider.retrieve(query, cache, agentOverrides, isVoice);
5
4
  return {
@@ -20,41 +19,6 @@ export function normalizeCitations(results, nativeCitations) {
20
19
  }
21
20
  return Array.from(byId.values()).sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
22
21
  }
23
- export function getPendingCitations(session) {
24
- const pending = session.workingMemory[PENDING_CITATIONS_KEY];
25
- return Array.isArray(pending) ? pending : [];
26
- }
27
- export function popPendingCitations(session) {
28
- const citations = getPendingCitations(session);
29
- delete session.workingMemory[PENDING_CITATIONS_KEY];
30
- return citations;
31
- }
32
- export function withCitationMetadata(message, citations) {
33
- if (message.role !== 'assistant' || citations.length === 0)
34
- return message;
35
- const metadata = typeof message.metadata === 'object' && message.metadata !== null
36
- ? { ...message.metadata }
37
- : {};
38
- return {
39
- ...message,
40
- metadata: {
41
- ...metadata,
42
- citations,
43
- },
44
- };
45
- }
46
- export function withCitationsOnLastAssistantMessage(messages, citations) {
47
- if (citations.length === 0)
48
- return [...messages];
49
- const next = [...messages];
50
- for (let index = next.length - 1; index >= 0; index--) {
51
- if (next[index].role === 'assistant') {
52
- next[index] = withCitationMetadata(next[index], citations);
53
- break;
54
- }
55
- }
56
- return next;
57
- }
58
22
  function sourceRefFromResult(result) {
59
23
  const sourceId = result.sourceId || `synthetic-${hashShort(result.text)}`;
60
24
  return {
@@ -30,11 +30,6 @@
30
30
  * cost of a false negative is a fatal turn loss. Bias OR.
31
31
  */
32
32
  import type { Session } from '../types/index.js';
33
- /** Error subclass surfaced to TurnPipeline when overflow recovery fails twice. */
34
- export declare class ContextOverflowUnrecoverableError extends Error {
35
- readonly cause?: unknown;
36
- constructor(cause?: unknown);
37
- }
38
33
  /**
39
34
  * Returns true if the error is a provider context-overflow / "prompt too
40
35
  * long" condition. Matches on AI SDK APICallError + raw provider errors +
@@ -44,12 +39,6 @@ export declare class ContextOverflowUnrecoverableError extends Error {
44
39
  * (fatal turn loss).
45
40
  */
46
41
  export declare function isContextOverflowError(error: unknown): boolean;
47
- /**
48
- * APICallError-aware check that also recognizes the AI SDK v6 type guard.
49
- * Exported separately so callers can compose with other classifiers without
50
- * paying the message-regex cost when the type tells them everything.
51
- */
52
- export declare function isApiCallContextOverflow(error: unknown): boolean;
53
42
  export interface OverflowRecoveryResult {
54
43
  /** Whether at least one message was stripped from session.messages. */
55
44
  stripped: boolean;
@@ -1,16 +1,3 @@
1
- // AI SDK v6 ships APICallError both directly via 'ai' and re-exports the
2
- // '@ai-sdk/provider' definition. We import the type only — runtime check
3
- // uses `APICallError.isInstance` via the imported class.
4
- import { APICallError } from 'ai';
5
- /** Error subclass surfaced to TurnPipeline when overflow recovery fails twice. */
6
- export class ContextOverflowUnrecoverableError extends Error {
7
- cause;
8
- constructor(cause) {
9
- super('Context overflow could not be recovered after retry');
10
- this.name = 'ContextOverflowUnrecoverableError';
11
- this.cause = cause;
12
- }
13
- }
14
1
  // Pre-compiled message patterns — keep tight and audited; loose patterns
15
2
  // inflate false positives across providers.
16
3
  const OVERFLOW_MESSAGE_PATTERNS = [
@@ -109,16 +96,6 @@ export function isContextOverflowError(error) {
109
96
  }
110
97
  return false;
111
98
  }
112
- /**
113
- * APICallError-aware check that also recognizes the AI SDK v6 type guard.
114
- * Exported separately so callers can compose with other classifiers without
115
- * paying the message-regex cost when the type tells them everything.
116
- */
117
- export function isApiCallContextOverflow(error) {
118
- if (!APICallError.isInstance(error))
119
- return false;
120
- return isContextOverflowError(error);
121
- }
122
99
  /**
123
100
  * Strip partial assistant + tool messages that followed the most recent
124
101
  * user message — these are in-flight work from the turn that overflowed.
@@ -8,7 +8,7 @@ import type { InputProcessor, OutputProcessor } from '../types/processors.js';
8
8
  import type { Limits } from '../types/guardrails.js';
9
9
  import type { RunState, StepRecord } from './durable/types.js';
10
10
  import type { RunStore } from './durable/RunStore.js';
11
- export interface EffectClock {
11
+ interface EffectClock {
12
12
  now(): number;
13
13
  uuid(): string;
14
14
  }
@@ -32,5 +32,5 @@ export interface CtxDeps {
32
32
  clock?: EffectClock;
33
33
  emit?: (part: HarnessStreamPart) => void;
34
34
  }
35
- export declare function makeCtx(deps: CtxDeps): RunContext;
36
35
  export declare function createRunContext(deps: CtxDeps): Promise<RunContext>;
36
+ export {};
@@ -2,8 +2,9 @@ import { randomUUID } from 'node:crypto';
2
2
  import { SuspendError } from './durable/RunStore.js';
3
3
  import { clockEffectKey, pauseEffectKey, toolEffectKey, } from './durable/idempotency.js';
4
4
  import { findStepByKey } from './durable/replay.js';
5
+ import { ToolApprovalDeniedError } from '../tools/effect/errors.js';
5
6
  const APPROVAL_SIGNAL = '__approval';
6
- export function makeCtx(deps) {
7
+ function makeCtx(deps) {
7
8
  let effectOrdinal = 0;
8
9
  const steps = deps.steps;
9
10
  const clock = deps.clock ?? {
@@ -108,6 +109,22 @@ export function makeCtx(deps) {
108
109
  bargeIn: deps.bargeIn,
109
110
  abortSignal: deps.abortSignal,
110
111
  tool: async (name, args, options) => {
112
+ // needsApproval gate: a tool flagged `needsApproval` must be approved by a human
113
+ // before it runs. Approval is a durable pause (the `__approval` signal); on resume
114
+ // the recorded decision is replayed, then the tool effect runs exactly once. The
115
+ // approval pause consumes its own callsite ordinal before the tool effect, so the
116
+ // ordering is deterministic across replays. NOTE: the surrounding agent turn is not
117
+ // itself a replayable effect — this is fully deterministic for flow `action` tools;
118
+ // for model-issued tool calls, resume re-enters the agent turn.
119
+ const def = options?.def ?? deps.toolExecutor.getTool?.(name);
120
+ if (def?.needsApproval) {
121
+ const decision = (await pauseEffect(APPROVAL_SIGNAL, {
122
+ approval: { title: `Approve tool: ${name}` },
123
+ }));
124
+ if (!decision.approved) {
125
+ throw new ToolApprovalDeniedError(name, decision.by);
126
+ }
127
+ }
111
128
  const callsite = consumeCallsite();
112
129
  const key = toolEffectKey(deps.runState.runId, callsite, name, args);
113
130
  return replayOrExecute(key, 'tool', name, () => deps.toolExecutor.execute({
@@ -116,6 +133,8 @@ export function makeCtx(deps) {
116
133
  session: deps.session,
117
134
  toolCallId: options?.toolCallId,
118
135
  abortSignal: deps.bargeIn ?? deps.abortSignal,
136
+ def: options?.def,
137
+ toolCtx: options?.toolCtx,
119
138
  }));
120
139
  },
121
140
  approve: async (req) => {
@@ -1,4 +1,3 @@
1
- export declare function stableStringify(value: unknown): string;
2
1
  export declare function idempotencyKey(runId: string, callsite: string, payload: unknown): string;
3
2
  export declare function toolEffectKey(runId: string, callsite: string, name: string, args: unknown): string;
4
3
  export declare function pauseEffectKey(runId: string, callsite: string, name: string): string;
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- export function stableStringify(value) {
2
+ function stableStringify(value) {
3
3
  return JSON.stringify(value, (_key, val) => {
4
4
  if (val && typeof val === 'object' && !Array.isArray(val)) {
5
5
  const record = val;
@@ -1,6 +1,5 @@
1
1
  import type { RunState, SignalDelivery, StepRecord } from './types.js';
2
2
  import type { RunStore } from './RunStore.js';
3
3
  export declare function findStepByKey(steps: StepRecord[], key: string): StepRecord | undefined;
4
- export declare function findStepBySignalId(steps: StepRecord[], signalId: string): StepRecord | undefined;
5
4
  export declare function recordSignalDelivery(runStore: RunStore, runState: RunState, delivery: SignalDelivery): Promise<boolean>;
6
5
  export declare function loadRecordedSteps(runStore: RunStore, runId: string): Promise<StepRecord[]>;
@@ -2,7 +2,7 @@ import { pauseEffectKey } from './idempotency.js';
2
2
  export function findStepByKey(steps, key) {
3
3
  return steps.find((step) => step.key === key);
4
4
  }
5
- export function findStepBySignalId(steps, signalId) {
5
+ function findStepBySignalId(steps, signalId) {
6
6
  return steps.find((step) => step.signalId === signalId);
7
7
  }
8
8
  export async function recordSignalDelivery(runStore, runState, delivery) {
@@ -14,7 +14,7 @@ export interface StepRecord {
14
14
  startedAt: number;
15
15
  finishedAt?: number;
16
16
  }
17
- export interface WaitingFor {
17
+ interface WaitingFor {
18
18
  signalName: string;
19
19
  callsite: string;
20
20
  deadline?: number;
@@ -48,3 +48,4 @@ export interface PersistedRun {
48
48
  }
49
49
  export declare const DURABLE_RUNS_KEY: "durableRuns";
50
50
  export type SessionDurableRuns = Record<string, PersistedRun>;
51
+ export {};
@@ -5,10 +5,12 @@ import type { AgentConfig } from '../types/agentConfig.js';
5
5
  import type { SignalDelivery } from './durable/types.js';
6
6
  import { SessionRunStore } from './durable/SessionRunStore.js';
7
7
  import type { RunState } from './durable/types.js';
8
+ import type { ResolvedSelection } from '../types/selection.js';
8
9
  export interface OpenRunOptions {
9
10
  sessionId: string;
10
11
  userId?: string;
11
12
  input?: string;
13
+ selection?: ResolvedSelection;
12
14
  agentId?: string;
13
15
  seedMessages?: ModelMessage[];
14
16
  historyDelta?: ModelMessage[];
@@ -40,16 +40,22 @@ export async function openRun(agentsById, options) {
40
40
  await recordSignalDelivery(runStore, runState, options.signalDelivery);
41
41
  runState = (await runStore.getRunState(runId)) ?? runState;
42
42
  }
43
- if (options.input) {
43
+ if (options.selection?.formData) {
44
+ runState.state = { ...runState.state, ...options.selection.formData };
45
+ runState.updatedAt = Date.now();
46
+ await runStore.putRunState(runState);
47
+ }
48
+ const effectiveInput = options.selection?.id ?? options.input;
49
+ if (effectiveInput) {
44
50
  runState.updatedAt = Date.now();
45
51
  if (runState.activeFlow) {
46
52
  await runStore.putRunState(runState);
47
53
  const sessionAfterPersist = (await options.sessionStore.get(options.sessionId)) ?? session;
48
- setPendingUserInput(sessionAfterPersist, options.input);
54
+ setPendingUserInput(sessionAfterPersist, effectiveInput);
49
55
  await options.sessionStore.save(sessionAfterPersist);
50
56
  }
51
57
  else {
52
- const userMessage = { role: 'user', content: options.input };
58
+ const userMessage = { role: 'user', content: effectiveInput };
53
59
  runState.messages = [...runState.messages, userMessage];
54
60
  runState.updatedAt = Date.now();
55
61
  await runStore.putRunState(runState);
@@ -1,8 +1,5 @@
1
- import type { InputProcessor, OutputProcessor } from '../../types/processors.js';
2
1
  import type { ToolCallRecord } from '../../types/session.js';
3
2
  import type { RunContext } from '../../types/run-context.js';
4
- import type { RefinementCapability } from '../../capabilities/RefinementCapability.js';
5
- import type { ValidationCapability } from '../../capabilities/ValidationCapability.js';
6
3
  export interface PreTurnResult {
7
4
  proceed: boolean;
8
5
  userMessage: string;
@@ -15,5 +12,3 @@ export interface PostTurnResult {
15
12
  }
16
13
  export declare function applyPreTurnPolicies(ctx: RunContext): Promise<PreTurnResult>;
17
14
  export declare function applyPostTurnPolicies(ctx: RunContext, assistantOutput: string, toolCallsMade?: ToolCallRecord[]): Promise<PostTurnResult>;
18
- export declare function buildValidationPolicies(outputProcessors: OutputProcessor[], moderators: ValidationCapability[]): ValidationCapability[];
19
- export declare function buildRefinementPolicies(inputProcessors: InputProcessor[], refinements: RefinementCapability[]): RefinementCapability[];
@@ -129,9 +129,3 @@ function patchLatestUserMessage(messages, next) {
129
129
  }
130
130
  messages.push({ role: 'user', content: next });
131
131
  }
132
- export function buildValidationPolicies(outputProcessors, moderators) {
133
- return [...moderators];
134
- }
135
- export function buildRefinementPolicies(inputProcessors, refinements) {
136
- return [...refinements];
137
- }
@@ -3,7 +3,6 @@ import type { RunState } from '../durable/types.js';
3
3
  export declare class LimitsExceededError extends Error {
4
4
  constructor(message: string);
5
5
  }
6
- export declare function readTurnCount(run: RunState): number;
7
6
  export declare function incrementTurnCount(run: RunState): number;
8
7
  export declare function assertWithinTurnLimit(run: RunState, limits?: Limits): void;
9
8
  export declare function resolveMaxSteps(limits: Limits | undefined, fallback: number): number;
@@ -5,7 +5,7 @@ export class LimitsExceededError extends Error {
5
5
  this.name = 'LimitsExceededError';
6
6
  }
7
7
  }
8
- export function readTurnCount(run) {
8
+ function readTurnCount(run) {
9
9
  const value = run.state[TURN_COUNT_KEY];
10
10
  return typeof value === 'number' ? value : 0;
11
11
  }
@@ -2,7 +2,6 @@ import type { AgentConfig } from '../../types/agentConfig.js';
2
2
  import type { RefinementCapability } from '../../capabilities/RefinementCapability.js';
3
3
  import type { ValidationCapability } from '../../capabilities/ValidationCapability.js';
4
4
  import type { InputProcessor, OutputProcessor } from '../../types/processors.js';
5
- import type { EnforcementRule } from '../../types/tool.js';
6
5
  import type { Limits } from '../../types/guardrails.js';
7
6
  import { type ToolEnforcer } from '../../guards/ToolEnforcer.js';
8
7
  export interface ResolvedAgentPolicies {
@@ -14,5 +13,3 @@ export interface ResolvedAgentPolicies {
14
13
  enforcer?: ToolEnforcer;
15
14
  }
16
15
  export declare function resolveAgentPolicies(agent: AgentConfig): ResolvedAgentPolicies;
17
- export declare function withValidationPolicies(base: ResolvedAgentPolicies, validationPolicies: ValidationCapability[]): ResolvedAgentPolicies;
18
- export declare function withEnforcementRules(base: ResolvedAgentPolicies, rules: EnforcementRule[]): ResolvedAgentPolicies;
@@ -11,16 +11,3 @@ export function resolveAgentPolicies(agent) {
11
11
  enforcer: enforcementRules.length > 0 ? createToolEnforcer(enforcementRules) : undefined,
12
12
  };
13
13
  }
14
- export function withValidationPolicies(base, validationPolicies) {
15
- return {
16
- ...base,
17
- validationPolicies: [...base.validationPolicies, ...validationPolicies],
18
- };
19
- }
20
- export function withEnforcementRules(base, rules) {
21
- const merged = [...(base.enforcer?.getRules() ?? []), ...rules];
22
- return {
23
- ...base,
24
- enforcer: merged.length > 0 ? createToolEnforcer(merged) : undefined,
25
- };
26
- }
@@ -25,10 +25,6 @@
25
25
  */
26
26
  import type { ModelMessage } from 'ai';
27
27
  export type AnthropicCacheTtl = '5m' | '1h';
28
- export interface AnthropicCacheControl {
29
- type: 'ephemeral';
30
- ttl?: '1h';
31
- }
32
28
  /**
33
29
  * Returns a SHALLOW-COPIED message list with cache_control markers
34
30
  * applied to the system message (if present) + the last 3 non-system
@@ -1,5 +1,4 @@
1
1
  import { type Tool as AiTool, type ToolSet as AiToolSet, type ToolExecutionOptions as AiToolExecutionOptions, type ToolExecuteFunction } from 'ai';
2
- import type { Runtime } from '../runtime/Runtime.js';
3
2
  import type { ZodTypeAny, z } from 'zod';
4
3
  export type Tool<TInput = unknown, TResult = unknown> = AiTool<TInput, TResult>;
5
4
  export type ToolSet = AiToolSet;
@@ -16,11 +15,6 @@ export interface ToolExecutionContext {
16
15
  [key: string]: unknown;
17
16
  }
18
17
  export type ToolExecutionOptions = AiToolExecutionOptions;
19
- export type ToolExecutionContextWithRuntime = ToolExecutionContext & {
20
- runtime: Runtime;
21
- };
22
- export declare function isAgentContext(value: unknown): value is ToolExecutionContextWithRuntime;
23
- export declare function getRuntimeFromContext(context: ToolExecutionContext | undefined): Runtime | undefined;
24
18
  export interface ToolDefinition<TInput = unknown, TResult = unknown> {
25
19
  description: string;
26
20
  inputSchema: ZodTypeAny;
@@ -1,12 +1,4 @@
1
1
  import { tool as aiTool, zodSchema, } from 'ai';
2
- export function isAgentContext(value) {
3
- return (typeof value === 'object' &&
4
- value !== null &&
5
- 'runtime' in value);
6
- }
7
- export function getRuntimeFromContext(context) {
8
- return context?.runtime;
9
- }
10
2
  export function createTool(definition) {
11
3
  const { description, inputSchema, execute } = definition;
12
4
  // AI SDK tool() overload resolution fails when INPUT/OUTPUT are deferred wrapper generics.
@@ -17,6 +17,8 @@ export interface CoreExecuteArgs {
17
17
  abortSignal?: AbortSignal;
18
18
  toolCallId?: string;
19
19
  toolCtx?: ToolContext;
20
+ /** Flow-local tool def passed by the driver; wins over the registry when both exist. */
21
+ def?: AnyTool;
20
22
  }
21
23
  export declare class CoreToolExecutor implements EffectToolExecutor {
22
24
  private readonly tools;
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { debug } from '../../debug.js';
2
3
  import { cancelledPlaceholder, inProgressPlaceholder, PairingTracker, } from './pairing.js';
3
4
  import { ToolValidationError, validateAndSanitize, validateOutput } from './schema.js';
4
5
  export class CoreToolExecutor {
@@ -45,7 +46,11 @@ export class CoreToolExecutor {
45
46
  }
46
47
  async executeInner(args) {
47
48
  const { name, session, abortSignal, toolCallId, toolCtx } = args;
48
- const def = this.tools.get(name);
49
+ const registryDef = this.tools.get(name);
50
+ if (args.def && registryDef) {
51
+ debug(`[ToolExecutor] flow-local tool "${name}" shadows a same-named registry tool`);
52
+ }
53
+ const def = args.def ?? registryDef;
49
54
  if (!def) {
50
55
  throw new Error(`Unknown tool: ${name}`);
51
56
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Thrown by `ctx.tool(...)` when a tool declared `needsApproval: true` is denied
3
+ * by a human (the `__approval` signal resolves with `approved: false`). Catch it
4
+ * inside the calling flow `action` node to route gracefully (e.g. escalate or end).
5
+ */
6
+ export declare class ToolApprovalDeniedError extends Error {
7
+ readonly toolName: string;
8
+ readonly by?: string;
9
+ constructor(toolName: string, by?: string);
10
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Thrown by `ctx.tool(...)` when a tool declared `needsApproval: true` is denied
3
+ * by a human (the `__approval` signal resolves with `approved: false`). Catch it
4
+ * inside the calling flow `action` node to route gracefully (e.g. escalate or end).
5
+ */
6
+ export class ToolApprovalDeniedError extends Error {
7
+ toolName;
8
+ by;
9
+ constructor(toolName, by) {
10
+ super(`Tool "${toolName}" was denied approval${by ? ` by ${by}` : ''}`);
11
+ this.name = 'ToolApprovalDeniedError';
12
+ this.toolName = toolName;
13
+ this.by = by;
14
+ }
15
+ }
@@ -4,3 +4,4 @@ export type { CoreToolExecutorConfig, CoreExecuteArgs } from './ToolExecutor.js'
4
4
  export { PairingTracker, cancelledPlaceholder, inProgressPlaceholder, } from './pairing.js';
5
5
  export type { ToolCallPair, ToolPairStatus, ToolRequestRecord, ToolResponseRecord, CancelledToolResult, InProgressToolResult, } from './pairing.js';
6
6
  export { validateAndSanitize, validateOutput, ToolValidationError } from './schema.js';
7
+ export { ToolApprovalDeniedError } from './errors.js';
@@ -2,3 +2,4 @@ export { defineTool, toolToAiSdk, buildToolSet } from './defineTool.js';
2
2
  export { CoreToolExecutor } from './ToolExecutor.js';
3
3
  export { PairingTracker, cancelledPlaceholder, inProgressPlaceholder, } from './pairing.js';
4
4
  export { validateAndSanitize, validateOutput, ToolValidationError } from './schema.js';
5
+ export { ToolApprovalDeniedError } from './errors.js';
@@ -20,7 +20,6 @@ export declare function executeWithRetry<T>(fn: () => Promise<T>, config?: {
20
20
  maxDelayMs?: number;
21
21
  onRetry?: (error: Error, attempt: number) => void;
22
22
  }): Promise<T>;
23
- export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
24
23
  export declare function createCircuitBreaker<T, Args extends unknown[]>(fn: (...args: Args) => Promise<T>, config?: {
25
24
  failureThreshold?: number;
26
25
  resetTimeoutMs?: number;
@@ -27,7 +27,7 @@ export type UserSignal = {
27
27
  type: 'message';
28
28
  input: string;
29
29
  };
30
- export interface ToolResultRecord {
30
+ interface ToolResultRecord {
31
31
  name: string;
32
32
  args: unknown;
33
33
  result: unknown;
@@ -41,3 +41,4 @@ export type TurnControl = {
41
41
  type: 'end';
42
42
  reason: string;
43
43
  };
44
+ export {};