@jangts/agent-core 0.2.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +386 -50
  3. package/development/docs/api/README.en.md +4 -0
  4. package/development/docs/api/README.md +4 -0
  5. package/development/docs/api/decorators.en.md +83 -0
  6. package/development/docs/api/decorators.md +108 -0
  7. package/development/docs/api/observability.en.md +37 -11
  8. package/development/docs/api/observability.md +64 -11
  9. package/development/docs/api/reference.en.md +469 -348
  10. package/development/docs/api/reference.md +225 -137
  11. package/development/docs/api/types.en.md +1 -0
  12. package/development/docs/api/types.md +1 -0
  13. package/development/docs/api/workflow-views.en.md +64 -0
  14. package/development/docs/api/workflow-views.md +78 -0
  15. package/development/docs/api/workflow.en.md +6 -2
  16. package/development/docs/api/workflow.md +6 -2
  17. package/development/skills/agent-core-app/SKILL.md +16 -0
  18. package/dist/cjs/capabilities/invocation.js +21 -1
  19. package/dist/cjs/capabilities/tool-runtime.js +8 -1
  20. package/dist/cjs/capabilities/tools.js +12 -17
  21. package/dist/cjs/diagnostics/index.js +51 -3
  22. package/dist/cjs/diagnostics/native-sub-flow.js +106 -0
  23. package/dist/cjs/diagnostics/sub-flow.js +278 -32
  24. package/dist/cjs/execution/engine.js +3 -2
  25. package/dist/cjs/execution/events.js +5 -5
  26. package/dist/cjs/execution/reporting.js +18 -28
  27. package/dist/cjs/execution/workflow-chain.js +211 -0
  28. package/dist/cjs/execution/workflow-graph.js +72 -0
  29. package/dist/cjs/execution/workflow.js +34 -6
  30. package/dist/cjs/kits/aggregation.js +17 -14
  31. package/dist/cjs/kits/answering/checks.js +2 -1
  32. package/dist/cjs/kits/answering/flow.js +24 -21
  33. package/dist/cjs/kits/answering/tasks.js +25 -20
  34. package/dist/cjs/kits/delivery.js +36 -24
  35. package/dist/cjs/kits/knowledge/catalog.js +33 -30
  36. package/dist/cjs/kits/policies.js +3 -2
  37. package/dist/cjs/kits/presentation/outputs.js +2 -1
  38. package/dist/cjs/kits/retrieval.js +7 -1
  39. package/dist/cjs/kits/tools.js +1 -0
  40. package/dist/cjs/model/decision-retry.js +2 -1
  41. package/dist/cjs/model/runner.js +20 -5
  42. package/dist/cjs/structs/execution/workflow-views.js +2 -0
  43. package/dist/cjs/support/context/nodes.js +48 -45
  44. package/dist/cjs/support/context/processing.js +6 -3
  45. package/dist/cjs/support/decorators/definition.js +42 -0
  46. package/dist/cjs/support/decorators/index.js +63 -0
  47. package/dist/cjs/support/decorators/pool.js +87 -0
  48. package/dist/cjs/support/decorators/types.js +2 -0
  49. package/dist/cjs/utils/invocation.js +1 -0
  50. package/dist/esm/capabilities/invocation.js +21 -1
  51. package/dist/esm/capabilities/tool-runtime.js +8 -1
  52. package/dist/esm/capabilities/tools.js +12 -17
  53. package/dist/esm/diagnostics/index.d.ts +1 -0
  54. package/dist/esm/diagnostics/index.js +48 -2
  55. package/dist/esm/diagnostics/native-sub-flow.d.ts +94 -0
  56. package/dist/esm/diagnostics/native-sub-flow.js +101 -0
  57. package/dist/esm/diagnostics/sub-flow.d.ts +56 -4
  58. package/dist/esm/diagnostics/sub-flow.js +271 -32
  59. package/dist/esm/execution/engine.d.ts +1 -0
  60. package/dist/esm/execution/engine.js +3 -2
  61. package/dist/esm/execution/events.js +5 -5
  62. package/dist/esm/execution/reporting.d.ts +6 -12
  63. package/dist/esm/execution/reporting.js +19 -29
  64. package/dist/esm/execution/workflow-chain.d.ts +3 -0
  65. package/dist/esm/execution/workflow-chain.js +208 -0
  66. package/dist/esm/execution/workflow-graph.d.ts +7 -0
  67. package/dist/esm/execution/workflow-graph.js +68 -0
  68. package/dist/esm/execution/workflow.d.ts +6 -0
  69. package/dist/esm/execution/workflow.js +34 -6
  70. package/dist/esm/kits/aggregation.d.ts +1 -1
  71. package/dist/esm/kits/aggregation.js +17 -14
  72. package/dist/esm/kits/answering/checks.js +2 -1
  73. package/dist/esm/kits/answering/flow.js +24 -21
  74. package/dist/esm/kits/answering/tasks.js +25 -20
  75. package/dist/esm/kits/delivery.d.ts +0 -9
  76. package/dist/esm/kits/delivery.js +36 -24
  77. package/dist/esm/kits/knowledge/catalog.js +33 -30
  78. package/dist/esm/kits/policies.d.ts +1 -1
  79. package/dist/esm/kits/policies.js +3 -2
  80. package/dist/esm/kits/presentation/outputs.js +2 -1
  81. package/dist/esm/kits/retrieval.js +7 -1
  82. package/dist/esm/kits/tools.js +1 -0
  83. package/dist/esm/model/decision-retry.d.ts +2 -0
  84. package/dist/esm/model/decision-retry.js +2 -1
  85. package/dist/esm/model/runner.d.ts +2 -0
  86. package/dist/esm/model/runner.js +20 -5
  87. package/dist/esm/structs/capabilities/invocation.d.ts +2 -0
  88. package/dist/esm/structs/capabilities/tools.d.ts +7 -3
  89. package/dist/esm/structs/common.d.ts +2 -0
  90. package/dist/esm/structs/debug.d.ts +36 -20
  91. package/dist/esm/structs/execution/workflow-views.d.ts +123 -0
  92. package/dist/esm/structs/execution/workflow-views.js +1 -0
  93. package/dist/esm/structs/execution/workflow.d.ts +4 -3
  94. package/dist/esm/structs/index.d.ts +1 -0
  95. package/dist/esm/structs/model/runtime.d.ts +2 -0
  96. package/dist/esm/support/context/nodes.js +48 -45
  97. package/dist/esm/support/context/processing.js +6 -3
  98. package/dist/esm/support/decorators/definition.d.ts +19 -0
  99. package/dist/esm/support/decorators/definition.js +38 -0
  100. package/dist/esm/support/decorators/index.d.ts +39 -0
  101. package/dist/esm/support/decorators/index.js +48 -0
  102. package/dist/esm/support/decorators/pool.d.ts +97 -0
  103. package/dist/esm/support/decorators/pool.js +83 -0
  104. package/dist/esm/support/decorators/types.d.ts +22 -0
  105. package/dist/esm/support/decorators/types.js +1 -0
  106. package/dist/esm/utils/invocation.d.ts +1 -0
  107. package/dist/esm/utils/invocation.js +1 -0
  108. package/package.json +10 -2
  109. package/dist/cjs/utils/debug.js +0 -30
  110. package/dist/esm/utils/debug.d.ts +0 -27
  111. package/dist/esm/utils/debug.js +0 -27
@@ -0,0 +1,208 @@
1
+ /** Read-side projection of captured workflow facts; never dispatches or reconstructs topology. */
2
+ import { normalizeDiagnosticEvent } from '../diagnostics/index.js';
3
+ import { freezeView } from './workflow-graph.js';
4
+ const lifecycleTypes = new Set(['enter', 'transition', 'complete', 'error']);
5
+ /** Aggregate main visits by span and keep raw subflow records in their own append order. */
6
+ export function projectWorkflowChain(workflowId, runId, input) {
7
+ const coverage = structuredClone(input.coverage);
8
+ const issues = [...coverage.issues];
9
+ const issue = (code, spanId, eventId) => issues.push({ code, spanId, eventId });
10
+ const events = input.events
11
+ .map(normalizeDiagnosticEvent)
12
+ .filter((e) => e.runId === runId)
13
+ .sort((a, b) => a.producerSequence - b.producerSequence);
14
+ const visits = new Map();
15
+ const ledgers = new Map();
16
+ const transitions = [];
17
+ const seen = new Map();
18
+ for (const raw of events) {
19
+ const payload = raw.payload && typeof raw.payload === 'object'
20
+ ? raw.payload
21
+ : {};
22
+ const candidate = payload.event;
23
+ const fact = raw.type === 'workflow.event' && candidate && typeof candidate === 'object'
24
+ ? candidate
25
+ : undefined;
26
+ const belongs = fact?.workflowId === workflowId ||
27
+ (raw.nodeRole === 'workflow-node' && raw.definitionId === workflowId);
28
+ if (!belongs || (raw.nodeRole !== undefined && raw.nodeRole !== 'workflow-node'))
29
+ continue;
30
+ if (raw.type === 'workflow.event' && !fact) {
31
+ issue('invalid-workflow-event', raw.spanId, raw.eventId);
32
+ continue;
33
+ }
34
+ const identity = JSON.stringify(raw);
35
+ if (seen.has(raw.eventId)) {
36
+ if (seen.get(raw.eventId) !== identity)
37
+ issue('conflicting-event', raw.spanId, raw.eventId);
38
+ continue;
39
+ }
40
+ seen.set(raw.eventId, identity);
41
+ if (fact &&
42
+ (!lifecycleTypes.has(fact.type) ||
43
+ fact.eventId !== raw.eventId ||
44
+ fact.workflowId !== workflowId ||
45
+ (raw.workflowInvocationId !== undefined &&
46
+ fact.workflowInvocationId !== raw.workflowInvocationId) ||
47
+ [
48
+ 'targetSpanId',
49
+ 'targetNodeId',
50
+ 'routeId',
51
+ 'conditionId',
52
+ 'errorCode',
53
+ 'errorEventId',
54
+ ].some((key) => {
55
+ const value = fact[key];
56
+ return value !== undefined && typeof value !== 'string';
57
+ }) ||
58
+ (fact.route !== undefined && (!Number.isSafeInteger(fact.route) || fact.route < 0)) ||
59
+ (fact.durationMs !== undefined &&
60
+ (!Number.isFinite(fact.durationMs) || fact.durationMs < 0)) ||
61
+ !Number.isSafeInteger(fact.roundIndex) ||
62
+ typeof fact.workflowInvocationId !== 'string' ||
63
+ fact.nodeId !== raw.nodeId ||
64
+ (fact.spanId !== undefined && fact.spanId !== raw.spanId) ||
65
+ (fact.runId !== undefined && fact.runId !== runId))) {
66
+ issue('invalid-workflow-event', raw.spanId, raw.eventId);
67
+ continue;
68
+ }
69
+ if (!raw.nodeId) {
70
+ issue('missing-node-id', raw.spanId, raw.eventId);
71
+ continue;
72
+ }
73
+ let visit = visits.get(raw.spanId);
74
+ if (!visit) {
75
+ visit = {
76
+ spanId: raw.spanId,
77
+ nodeId: raw.nodeId,
78
+ workflowInvocationId: raw.workflowInvocationId ?? fact?.workflowInvocationId,
79
+ definitionRevision: raw.definitionRevision,
80
+ name: raw.nodeName,
81
+ description: raw.nodeDescription,
82
+ kind: raw.nodeKind,
83
+ roundIndex: fact?.roundIndex,
84
+ firstEventSequence: raw.producerSequence,
85
+ lastEventSequence: raw.producerSequence,
86
+ status: 'unknown',
87
+ events: [],
88
+ subflow: [],
89
+ };
90
+ visits.set(raw.spanId, visit);
91
+ ledgers.set(raw.spanId, new Map());
92
+ }
93
+ if (visit.nodeId !== raw.nodeId ||
94
+ visit.workflowInvocationId !== (raw.workflowInvocationId ?? fact?.workflowInvocationId)) {
95
+ issue('conflicting-span', raw.spanId, raw.eventId);
96
+ continue;
97
+ }
98
+ visit.lastEventSequence = raw.producerSequence;
99
+ if (raw.usedModel !== undefined)
100
+ visit.usedModel = raw.usedModel;
101
+ if (raw.subStepsDropped || raw.subStepsTruncated)
102
+ issue('subflow-truncated', raw.spanId, raw.eventId);
103
+ const ledger = ledgers.get(raw.spanId);
104
+ for (const step of raw.subSteps ?? []) {
105
+ const previous = ledger.get(step.stepId);
106
+ if (!previous)
107
+ ledger.set(step.stepId, structuredClone(step));
108
+ else if (JSON.stringify({ ...previous, sourceEventId: undefined, producerSequence: undefined }) !==
109
+ JSON.stringify(step))
110
+ issue('conflicting-subflow', raw.spanId, raw.eventId);
111
+ }
112
+ const progress = payload.subStep;
113
+ if (raw.type === 'node.progress' && progress) {
114
+ const record = progress.stepId ? ledger.get(progress.stepId) : undefined;
115
+ if (record) {
116
+ ledger.set(record.stepId, {
117
+ ...record,
118
+ sourceEventId: raw.eventId,
119
+ producerSequence: raw.producerSequence,
120
+ });
121
+ }
122
+ else if (typeof progress.stepId === 'string' && Number.isSafeInteger(progress.sequence)) {
123
+ ledger.set(progress.stepId, {
124
+ ...structuredClone(progress),
125
+ origin: 'reported',
126
+ sourceEventId: raw.eventId,
127
+ producerSequence: raw.producerSequence,
128
+ });
129
+ }
130
+ else
131
+ issue('invalid-subflow-progress', raw.spanId, raw.eventId);
132
+ continue;
133
+ }
134
+ if (fact || raw.type === 'node.progress') {
135
+ visit.events.push({
136
+ eventId: raw.eventId,
137
+ producerSequence: raw.producerSequence,
138
+ occurredAtMs: raw.occurredAtMs,
139
+ type: fact?.type ?? 'progress',
140
+ phase: fact?.phase,
141
+ routeId: fact?.routeId,
142
+ conditionId: fact?.conditionId,
143
+ errorCode: fact?.errorCode,
144
+ errorEventId: fact?.errorEventId,
145
+ payload: payload.payload,
146
+ });
147
+ }
148
+ if (!fact)
149
+ continue;
150
+ visit.roundIndex ??= fact.roundIndex;
151
+ if (fact.type === 'enter') {
152
+ visit.enteredAtMs = raw.occurredAtMs;
153
+ visit.status = 'running';
154
+ }
155
+ if (fact.type === 'error')
156
+ visit.status =
157
+ fact.errorCode === 'CANCELLED' || fact.errorCode === 'TIMEOUT' ? 'cancelled' : 'error';
158
+ if (fact.type !== 'enter') {
159
+ visit.endedAtMs = raw.occurredAtMs;
160
+ visit.durationMs = fact.durationMs;
161
+ if (fact.type !== 'error' && visit.status !== 'error' && visit.status !== 'cancelled')
162
+ visit.status = 'success';
163
+ }
164
+ if (fact.type === 'transition' ||
165
+ fact.type === 'complete' ||
166
+ (fact.type === 'error' && fact.targetNodeId !== undefined))
167
+ transitions.push({
168
+ eventId: raw.eventId,
169
+ producerSequence: raw.producerSequence,
170
+ occurredAtMs: raw.occurredAtMs,
171
+ type: fact.type === 'complete' ? 'complete' : 'transition',
172
+ sourceSpanId: raw.spanId,
173
+ targetSpanId: fact.targetSpanId,
174
+ targetNodeId: fact.targetNodeId,
175
+ routeOrder: fact.route,
176
+ routeId: fact.routeId,
177
+ conditionId: fact.conditionId,
178
+ errorEventId: fact.type === 'error' ? raw.eventId : fact.errorEventId,
179
+ });
180
+ }
181
+ if (!visits.size)
182
+ throw new RangeError(`No captured workflow visits: ${workflowId} / ${runId}`);
183
+ for (const visit of visits.values()) {
184
+ visit.subflow = [...ledgers.get(visit.spanId).values()].sort((a, b) => a.sequence - b.sequence);
185
+ if (visit.enteredAtMs === undefined)
186
+ issue('missing-enter', visit.spanId);
187
+ if (visit.endedAtMs === undefined && coverage.complete === true) {
188
+ visit.status = 'unknown';
189
+ issue('missing-exit', visit.spanId);
190
+ }
191
+ for (const step of visit.subflow)
192
+ if (step.operation === undefined ||
193
+ step.revision === undefined ||
194
+ step.createdAtMs === undefined)
195
+ issue('historical-subflow-fields', visit.spanId);
196
+ }
197
+ for (const edge of transitions)
198
+ if (edge.targetSpanId && !visits.has(edge.targetSpanId))
199
+ issue('missing-target-enter', edge.targetSpanId, edge.eventId);
200
+ return freezeView({
201
+ schemaVersion: 1,
202
+ workflowId,
203
+ runId,
204
+ nodes: [...visits.values()],
205
+ transitions,
206
+ coverage: { ...coverage, complete: issues.length ? false : coverage.complete, issues },
207
+ });
208
+ }
@@ -0,0 +1,7 @@
1
+ /** Project the validated catalog to pure topology without evaluating route conditions. */
2
+ import type { WorkflowNode } from '../structs/execution/workflow.js';
3
+ import type { WorkflowGraph } from '../structs/execution/workflow-views.js';
4
+ /** Edge identity depends on topology, never on a route label or callback. */
5
+ export declare function projectWorkflowGraph<S, R, C>(workflowId: string, entryNodeId: string, revision: string | undefined, definitions: readonly WorkflowNode<S, R, C>[]): WorkflowGraph;
6
+ /** Freeze detached JSON data recursively so callers cannot alter another consumer's view. */
7
+ export declare function freezeView<T>(value: T): T;
@@ -0,0 +1,68 @@
1
+ /** Edge identity depends on topology, never on a route label or callback. */
2
+ export function projectWorkflowGraph(workflowId, entryNodeId, revision, definitions) {
3
+ const edges = new Map();
4
+ const nodes = definitions.map((node, order) => {
5
+ const edgeFor = (kind, targetNodeId) => {
6
+ const id = JSON.stringify([node.id, kind, targetNodeId ?? null]);
7
+ if (!edges.has(id))
8
+ edges.set(id, {
9
+ id,
10
+ kind,
11
+ sourceNodeId: node.id,
12
+ targetNodeId,
13
+ routeOrders: [],
14
+ ...(kind === 'error' ? { errorRouteId: node.onErrorRouteId } : {}),
15
+ });
16
+ return edges.get(id);
17
+ };
18
+ const routes = node.routes.map((route, order) => {
19
+ const edge = edgeFor(route.complete ? 'complete' : 'transition', route.targetNodeId);
20
+ edge.routeOrders.push(order);
21
+ return {
22
+ order,
23
+ routeId: route.id,
24
+ description: route.description,
25
+ conditional: !!route.when,
26
+ conditionId: route.conditionId,
27
+ edgeId: edge.id,
28
+ };
29
+ });
30
+ if (node.onErrorNodeId)
31
+ edgeFor('error', node.onErrorNodeId);
32
+ return {
33
+ id: node.id,
34
+ name: node.name ?? node.id,
35
+ description: node.description,
36
+ order,
37
+ startsRound: !!node.startsRound,
38
+ requires: [...(node.requires ?? [])],
39
+ provides: [...(node.provides ?? [])],
40
+ routes,
41
+ incomingEdgeIds: [],
42
+ outgoingEdgeIds: [],
43
+ };
44
+ });
45
+ const byId = new Map(nodes.map((node) => [node.id, node]));
46
+ for (const edge of edges.values()) {
47
+ byId.get(edge.sourceNodeId).outgoingEdgeIds.push(edge.id);
48
+ if (edge.targetNodeId)
49
+ byId.get(edge.targetNodeId).incomingEdgeIds.push(edge.id);
50
+ }
51
+ return freezeView({
52
+ schemaVersion: 1,
53
+ workflowId,
54
+ revision,
55
+ entryNodeId,
56
+ nodes,
57
+ edges: [...edges.values()],
58
+ });
59
+ }
60
+ /** Freeze detached JSON data recursively so callers cannot alter another consumer's view. */
61
+ export function freezeView(value) {
62
+ if (value && typeof value === 'object') {
63
+ for (const child of Object.values(value))
64
+ freezeView(child);
65
+ Object.freeze(value);
66
+ }
67
+ return value;
68
+ }
@@ -1,3 +1,4 @@
1
+ import type { WorkflowGraph, WorkflowChain, WorkflowChainSource } from '../structs/execution/workflow-views.js';
1
2
  import type { AgentWorkflowDefinition, WorkflowExtension, WorkflowNode } from '../structs/execution/workflow.js';
2
3
  /**
3
4
  * Executable catalog: validate references and bounded cycles before any handler runs.
@@ -14,6 +15,7 @@ export declare class AgentWorkflow<State, Result, Runtime = undefined> {
14
15
  readonly revision?: string;
15
16
  /** Optional result guard; when present it must return exactly true. */
16
17
  readonly isResult?: (result: Result) => boolean;
18
+ private readonly chainSource?;
17
19
  private readonly nodes;
18
20
  /**
19
21
  * Build and validate a workflow.
@@ -38,6 +40,10 @@ export declare class AgentWorkflow<State, Result, Runtime = undefined> {
38
40
  * @throws TypeError When an override names an unknown node or a node is invalid.
39
41
  */
40
42
  add(extension: WorkflowExtension<State, Result, Runtime> | AgentWorkflow<State, Result, Runtime>, routes?: WorkflowExtension<State, Result, Runtime>['routes']): AgentWorkflow<State, Result, Runtime>;
43
+ /** Export topology from the registered snapshot without executing callbacks. */
44
+ toGraph(): WorkflowGraph;
45
+ /** Read an authorized run through the bound App source, or an explicit per-call source. */
46
+ toChain(runId: string, source?: WorkflowChainSource | undefined): Promise<WorkflowChain>;
41
47
  /**
42
48
  * Look up a node that must exist.
43
49
  *
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Validated workflow catalog and round accounting.
3
+ *
4
+ * A workflow is declared as data (nodes plus routes) and validated before any handler
5
+ * runs: references must resolve, every node must be reachable from the entry, and every
6
+ * cycle must cross a round boundary so the engine's budget stays authoritative. This
7
+ * module owns the catalog; `engine.ts` owns traversal and execution.
8
+ *
9
+ * Exports: `AgentWorkflow`, `defineWorkflow`, `AttemptBudget`, the re-exported
10
+ * `requireWorkflowValue` and `route`, and the workflow contract types.
11
+ */
12
+ import { projectWorkflowGraph } from './workflow-graph.js';
13
+ import { projectWorkflowChain } from './workflow-chain.js';
1
14
  /**
2
15
  * Derive unique route ids for one node.
3
16
  *
@@ -51,6 +64,7 @@ export class AgentWorkflow {
51
64
  revision;
52
65
  /** Optional result guard; when present it must return exactly true. */
53
66
  isResult;
67
+ chainSource;
54
68
  nodes = new Map();
55
69
  /**
56
70
  * Build and validate a workflow.
@@ -68,6 +82,7 @@ export class AgentWorkflow {
68
82
  if (!definition.id?.trim() || !entry?.trim() || !definition.nodes.length) {
69
83
  throw new TypeError('Invalid workflow');
70
84
  }
85
+ this.chainSource = definition.chainSource?.bind(definition);
71
86
  this.id = definition.id;
72
87
  this.entryNodeId = entry;
73
88
  this.revision = definition.revision;
@@ -104,6 +119,7 @@ export class AgentWorkflow {
104
119
  return new AgentWorkflow({
105
120
  id: this.id,
106
121
  revision: patch.revision,
122
+ chainSource: this.chainSource,
107
123
  entryNodeId: this.entryNodeId,
108
124
  isResult: this.isResult,
109
125
  nodes: nodes.map((node) => ({
@@ -121,6 +137,18 @@ export class AgentWorkflow {
121
137
  })),
122
138
  });
123
139
  }
140
+ /** Export topology from the registered snapshot without executing callbacks. */
141
+ toGraph() {
142
+ return projectWorkflowGraph(this.id, this.entryNodeId, this.revision, this.definitions);
143
+ }
144
+ /** Read an authorized run through the bound App source, or an explicit per-call source. */
145
+ async toChain(runId, source = this.chainSource) {
146
+ if (!runId.trim())
147
+ throw new TypeError('A runId is required');
148
+ if (!source)
149
+ throw new TypeError('Workflow chainSource is not configured');
150
+ return projectWorkflowChain(this.id, runId, await source(runId));
151
+ }
124
152
  /**
125
153
  * Look up a node that must exist.
126
154
  *
@@ -203,25 +231,26 @@ export class AgentWorkflow {
203
231
  * @throws TypeError When the node, a route or a state contract is invalid.
204
232
  */
205
233
  register(node) {
234
+ const declaredRoutes = node.routes;
206
235
  if (!node.id?.trim() ||
207
236
  !node.description?.trim() ||
208
237
  this.nodes.has(node.id) ||
209
- !node.routes.length ||
238
+ !declaredRoutes.length ||
210
239
  (node.run !== undefined && typeof node.run !== 'function')) {
211
240
  throw new TypeError('Invalid or duplicate workflow node');
212
241
  }
213
242
  validateNodeFields(node);
214
243
  if (node.onErrorRouteId !== undefined && (!node.onErrorRouteId.trim() || !node.onErrorNodeId))
215
244
  throw new TypeError(`Invalid workflow error route identity: ${node.id}`);
216
- const ids = assignRouteIds(node.routes);
217
- for (const [index, route] of node.routes.entries()) {
245
+ const ids = assignRouteIds(declaredRoutes);
246
+ for (const [index, route] of declaredRoutes.entries()) {
218
247
  if ('to' in route ||
219
248
  (route.id !== undefined && !route.id.trim()) ||
220
249
  (route.conditionId !== undefined && (!route.conditionId.trim() || !route.when)))
221
250
  throw new TypeError(`Invalid workflow route identity: ${node.id}`);
222
251
  const hasTarget = typeof route.targetNodeId === 'string';
223
252
  const hasCompletion = typeof route.complete === 'function';
224
- const shadowsFollowingRoutes = !route.when && index !== node.routes.length - 1;
253
+ const shadowsFollowingRoutes = !route.when && index !== declaredRoutes.length - 1;
225
254
  if (hasTarget === hasCompletion ||
226
255
  shadowsFollowingRoutes ||
227
256
  (route.when !== undefined && typeof route.when !== 'function')) {
@@ -235,7 +264,7 @@ export class AgentWorkflow {
235
264
  throw new TypeError(`Invalid workflow state contract: ${node.id}`);
236
265
  }
237
266
  }
238
- const routes = Object.freeze(node.routes.map((route, index) => {
267
+ const routes = Object.freeze(declaredRoutes.map((route, index) => {
239
268
  const id = ids[index];
240
269
  const conditionId = route.conditionId?.replace(/\{(nid|rid)\}/g, (_, key) => {
241
270
  if (key === 'nid')
@@ -258,7 +287,6 @@ export class AgentWorkflow {
258
287
  // only when it declares it explicitly.
259
288
  const startsRound = node.startsRound ?? (node.id === this.entryNodeId ? true : undefined);
260
289
  this.nodes.set(node.id, Object.freeze({
261
- ...node,
262
290
  id: node.id,
263
291
  description: node.description,
264
292
  name: node.name,
@@ -34,7 +34,7 @@ export declare function defineQueryAggregation<Input, Item, Context>(options: Qu
34
34
  * @param context - Host context forwarded unchanged to `execute`, `key` and `merge`.
35
35
  * @returns The merged items and their count.
36
36
  */
37
- collect(inputs: readonly Input[], context: Context): {
37
+ collect(inputs: readonly Input[], context: Context, execution?: Partial<import("../structs/common.js").ExecutionContext>): {
38
38
  total: number;
39
39
  items: Item[];
40
40
  };
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * Exports: `mergeByKey`, `defineQueryAggregation`, `Aggregation`.
8
8
  */
9
+ import { nativeSubFlowSync } from '../diagnostics/native-sub-flow.js';
9
10
  import { synchronous } from '../utils/synchronous.js';
10
11
  /**
11
12
  * Merge duplicates without moving their first position; selection policy remains host-owned.
@@ -51,21 +52,23 @@ export function defineQueryAggregation(options) {
51
52
  * @param context - Host context forwarded unchanged to `execute`, `key` and `merge`.
52
53
  * @returns The merged items and their count.
53
54
  */
54
- collect(inputs, context) {
55
- const rows = [], merged = new Map();
56
- for (const input of inputs)
57
- for (const item of synchronous(execute(input, context), 'Query aggregation must be synchronous')) {
58
- if (!keyFor) {
59
- rows.push(item);
60
- continue;
55
+ collect(inputs, context, execution = {}) {
56
+ return nativeSubFlowSync(execution, 'aggregation', 'query-aggregation', () => {
57
+ const rows = [], merged = new Map();
58
+ for (const input of inputs)
59
+ for (const item of synchronous(execute(input, context), 'Query aggregation must be synchronous')) {
60
+ if (!keyFor) {
61
+ rows.push(item);
62
+ continue;
63
+ }
64
+ const key = synchronous(keyFor(item, input, context), 'Aggregation keys must be synchronous');
65
+ merged.set(key, merge
66
+ ? synchronous(merge(item, merged.get(key), input, context), 'Aggregation merge must be synchronous')
67
+ : item);
61
68
  }
62
- const key = synchronous(keyFor(item, input, context), 'Aggregation keys must be synchronous');
63
- merged.set(key, merge
64
- ? synchronous(merge(item, merged.get(key), input, context), 'Aggregation merge must be synchronous')
65
- : item);
66
- }
67
- const items = keyFor ? [...merged.values()] : rows;
68
- return { total: items.length, items };
69
+ const items = keyFor ? [...merged.values()] : rows;
70
+ return { total: items.length, items };
71
+ });
69
72
  },
70
73
  });
71
74
  }
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * Exports: `defineAnswerCheckCatalog`, and the answer checker types.
8
8
  */
9
+ import { nativeSubFlowSync } from '../../diagnostics/native-sub-flow.js';
9
10
  import { snapshotAnswerCheck } from './check-result.js';
10
11
  import { readonlyCatalog } from '../../utils/catalog.js';
11
12
  import { synchronous } from '../../utils/synchronous.js';
@@ -37,7 +38,7 @@ export function defineAnswerCheckCatalog(definitions) {
37
38
  throw new TypeError('Duplicate selected check id');
38
39
  for (const id of request.checkIds) {
39
40
  context.signal.throwIfAborted();
40
- const result = snapshotAnswerCheck(synchronous(checks.require(id).check(value, context), 'Answer checkers must be synchronous'), id);
41
+ const result = nativeSubFlowSync(context, 'check', id, () => snapshotAnswerCheck(synchronous(checks.require(id).check(value, context), 'Answer checkers must be synchronous'), id));
41
42
  context.signal.throwIfAborted();
42
43
  if (Object.hasOwn(result, 'value'))
43
44
  value = result.value;
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * Exports: `defineAnswerFlow`, and the answer flow/item/result/check types.
9
9
  */
10
+ import { nativeSubFlowAsync } from '../../diagnostics/native-sub-flow.js';
10
11
  import { booleanResult, synchronous } from '../../utils/synchronous.js';
11
12
  import { awaitWithSignal } from '../../utils/cancellation.js';
12
13
  import { AgentCoreEngine } from '../../execution/engine.js';
@@ -279,27 +280,29 @@ export function defineAnswerFlow(definition) {
279
280
  * exhausted without a final result.
280
281
  */
281
282
  async run(request, context) {
282
- context.signal.throwIfAborted();
283
- validateAnswerBatch(request.inputIds, request.items, maxItems);
284
- const cells = request.items.map((item) => ({
285
- item: Object.freeze({
286
- id: item.id,
287
- data: item.data,
288
- sourceStatus: item.sourceStatus,
289
- inputIds: Object.freeze([...item.inputIds]),
290
- }),
291
- hasValue: false,
292
- local: { status: 'not-run', issues: [] },
293
- review: { status: 'not-run', issues: [] },
294
- knownReviewFailure: false,
295
- }));
296
- return new AgentCoreEngine({
297
- maxRounds: maxRepairs + 1,
298
- errors: { exhausted: () => new TypeError('Answer flow exhausted') },
299
- execution: { signal: context.signal, diagnostics: context.diagnostics },
300
- })
301
- .bindWorkflow(workflow, context.app)
302
- .run({ cells, pending: [], repairBudget: new AttemptBudget(maxRepairs) });
283
+ return nativeSubFlowAsync(context, 'answer', 'answer-flow', async () => {
284
+ context.signal.throwIfAborted();
285
+ validateAnswerBatch(request.inputIds, request.items, maxItems);
286
+ const cells = request.items.map((item) => ({
287
+ item: Object.freeze({
288
+ id: item.id,
289
+ data: item.data,
290
+ sourceStatus: item.sourceStatus,
291
+ inputIds: Object.freeze([...item.inputIds]),
292
+ }),
293
+ hasValue: false,
294
+ local: { status: 'not-run', issues: [] },
295
+ review: { status: 'not-run', issues: [] },
296
+ knownReviewFailure: false,
297
+ }));
298
+ return new AgentCoreEngine({
299
+ maxRounds: maxRepairs + 1,
300
+ errors: { exhausted: () => new TypeError('Answer flow exhausted') },
301
+ execution: { signal: context.signal, diagnostics: context.diagnostics },
302
+ })
303
+ .bindWorkflow(workflow, context.app)
304
+ .run({ cells, pending: [], repairBudget: new AttemptBudget(maxRepairs) });
305
+ });
303
306
  },
304
307
  });
305
308
  }
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * Exports: `defineAnswerReaderCatalog`, `defineAnswerTaskCatalog`, and the task/reader types.
9
9
  */
10
+ import { nativeSubFlowSync, nativeSubFlowAsync } from '../../diagnostics/native-sub-flow.js';
10
11
  import { readonlyCatalog } from '../../utils/catalog.js';
11
12
  import { assertion, synchronous } from '../../utils/synchronous.js';
12
13
  import { freezeSchema } from '../../utils/object.js';
@@ -96,16 +97,18 @@ export function defineAnswerTaskCatalog(definitions, ports) {
96
97
  * @throws TypeError For an unknown task, a parse failure, or a reader returning a Promise.
97
98
  */
98
99
  read(request, context) {
99
- const { input, task, reader } = prepare(request, context);
100
- const value = synchronous(reader.read(input, {
101
- ...context,
102
- app: context.app,
103
- signal: context.signal,
104
- diagnostics: context.diagnostics,
105
- task,
106
- }), 'Synchronous answer readers must not return a Promise');
107
- context.signal.throwIfAborted();
108
- return value;
100
+ return nativeSubFlowSync(context, 'task', request.taskId, () => {
101
+ const { input, task, reader } = prepare(request, context);
102
+ const value = synchronous(reader.read(input, {
103
+ ...context,
104
+ app: context.app,
105
+ signal: context.signal,
106
+ diagnostics: context.diagnostics,
107
+ task,
108
+ }), 'Synchronous answer readers must not return a Promise');
109
+ context.signal.throwIfAborted();
110
+ return value;
111
+ });
109
112
  },
110
113
  /**
111
114
  * Read evidence through a task's registered reader, awaiting an async reader.
@@ -113,16 +116,18 @@ export function defineAnswerTaskCatalog(definitions, ports) {
113
116
  * @throws TypeError For an unknown task or a parse failure; aborts with the caller's signal.
114
117
  */
115
118
  async readAsync(request, context) {
116
- const { input, task, reader } = prepare(request, context);
117
- const value = await awaitWithSignal(reader.read(input, {
118
- ...context,
119
- app: context.app,
120
- signal: context.signal,
121
- diagnostics: context.diagnostics,
122
- task,
123
- }), context.signal);
124
- context.signal.throwIfAborted();
125
- return value;
119
+ return nativeSubFlowAsync(context, 'task', request.taskId, async () => {
120
+ const { input, task, reader } = prepare(request, context);
121
+ const value = await awaitWithSignal(reader.read(input, {
122
+ ...context,
123
+ app: context.app,
124
+ signal: context.signal,
125
+ diagnostics: context.diagnostics,
126
+ task,
127
+ }), context.signal);
128
+ context.signal.throwIfAborted();
129
+ return value;
130
+ });
126
131
  },
127
132
  });
128
133
  }
@@ -1,12 +1,3 @@
1
- /**
2
- * Delivery Kit: run an ordered, synchronous stage pipeline that ends with one result.
3
- *
4
- * Place in the package: an optional App-side delivery pipeline (for example formatting and
5
- * sending one reply). The Kit owns ordering, cancellation checks and outcome validation; the
6
- * stages, their state and the actual side effects belong to the App.
7
- *
8
- * Exports: `defineDelivery`, `Delivery`, and the delivery option/outcome types.
9
- */
10
1
  import type { DeliveryOptions } from '../structs/kits/delivery.js';
11
2
  import type { ExecutionContext } from '../structs/common.js';
12
3
  /**