@jangts/agent-core 0.3.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 (97) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +28 -26
  3. package/development/docs/api/README.en.md +2 -0
  4. package/development/docs/api/README.md +2 -0
  5. package/development/docs/api/observability.en.md +37 -11
  6. package/development/docs/api/observability.md +64 -11
  7. package/development/docs/api/reference.en.md +469 -348
  8. package/development/docs/api/reference.md +193 -137
  9. package/development/docs/api/types.en.md +1 -0
  10. package/development/docs/api/types.md +1 -0
  11. package/development/docs/api/workflow-views.en.md +64 -0
  12. package/development/docs/api/workflow-views.md +78 -0
  13. package/development/docs/api/workflow.en.md +6 -2
  14. package/development/docs/api/workflow.md +6 -2
  15. package/development/skills/agent-core-app/SKILL.md +12 -0
  16. package/dist/cjs/capabilities/invocation.js +21 -1
  17. package/dist/cjs/capabilities/tool-runtime.js +8 -1
  18. package/dist/cjs/capabilities/tools.js +12 -17
  19. package/dist/cjs/diagnostics/index.js +51 -3
  20. package/dist/cjs/diagnostics/native-sub-flow.js +106 -0
  21. package/dist/cjs/diagnostics/sub-flow.js +278 -32
  22. package/dist/cjs/execution/engine.js +3 -2
  23. package/dist/cjs/execution/events.js +5 -5
  24. package/dist/cjs/execution/reporting.js +18 -28
  25. package/dist/cjs/execution/workflow-chain.js +211 -0
  26. package/dist/cjs/execution/workflow-graph.js +72 -0
  27. package/dist/cjs/execution/workflow.js +34 -6
  28. package/dist/cjs/kits/aggregation.js +17 -14
  29. package/dist/cjs/kits/answering/checks.js +2 -1
  30. package/dist/cjs/kits/answering/flow.js +24 -21
  31. package/dist/cjs/kits/answering/tasks.js +25 -20
  32. package/dist/cjs/kits/delivery.js +36 -24
  33. package/dist/cjs/kits/knowledge/catalog.js +33 -30
  34. package/dist/cjs/kits/policies.js +3 -2
  35. package/dist/cjs/kits/presentation/outputs.js +2 -1
  36. package/dist/cjs/kits/retrieval.js +7 -1
  37. package/dist/cjs/kits/tools.js +1 -0
  38. package/dist/cjs/model/decision-retry.js +2 -1
  39. package/dist/cjs/model/runner.js +20 -5
  40. package/dist/cjs/structs/execution/workflow-views.js +2 -0
  41. package/dist/cjs/support/context/nodes.js +48 -45
  42. package/dist/cjs/support/context/processing.js +6 -3
  43. package/dist/cjs/utils/invocation.js +1 -0
  44. package/dist/esm/capabilities/invocation.js +21 -1
  45. package/dist/esm/capabilities/tool-runtime.js +8 -1
  46. package/dist/esm/capabilities/tools.js +12 -17
  47. package/dist/esm/diagnostics/index.d.ts +1 -0
  48. package/dist/esm/diagnostics/index.js +48 -2
  49. package/dist/esm/diagnostics/native-sub-flow.d.ts +94 -0
  50. package/dist/esm/diagnostics/native-sub-flow.js +101 -0
  51. package/dist/esm/diagnostics/sub-flow.d.ts +56 -4
  52. package/dist/esm/diagnostics/sub-flow.js +271 -32
  53. package/dist/esm/execution/engine.d.ts +1 -0
  54. package/dist/esm/execution/engine.js +3 -2
  55. package/dist/esm/execution/events.js +5 -5
  56. package/dist/esm/execution/reporting.d.ts +6 -12
  57. package/dist/esm/execution/reporting.js +19 -29
  58. package/dist/esm/execution/workflow-chain.d.ts +3 -0
  59. package/dist/esm/execution/workflow-chain.js +208 -0
  60. package/dist/esm/execution/workflow-graph.d.ts +7 -0
  61. package/dist/esm/execution/workflow-graph.js +68 -0
  62. package/dist/esm/execution/workflow.d.ts +6 -0
  63. package/dist/esm/execution/workflow.js +34 -6
  64. package/dist/esm/kits/aggregation.d.ts +1 -1
  65. package/dist/esm/kits/aggregation.js +17 -14
  66. package/dist/esm/kits/answering/checks.js +2 -1
  67. package/dist/esm/kits/answering/flow.js +24 -21
  68. package/dist/esm/kits/answering/tasks.js +25 -20
  69. package/dist/esm/kits/delivery.d.ts +0 -9
  70. package/dist/esm/kits/delivery.js +36 -24
  71. package/dist/esm/kits/knowledge/catalog.js +33 -30
  72. package/dist/esm/kits/policies.d.ts +1 -1
  73. package/dist/esm/kits/policies.js +3 -2
  74. package/dist/esm/kits/presentation/outputs.js +2 -1
  75. package/dist/esm/kits/retrieval.js +7 -1
  76. package/dist/esm/kits/tools.js +1 -0
  77. package/dist/esm/model/decision-retry.d.ts +2 -0
  78. package/dist/esm/model/decision-retry.js +2 -1
  79. package/dist/esm/model/runner.d.ts +2 -0
  80. package/dist/esm/model/runner.js +20 -5
  81. package/dist/esm/structs/capabilities/invocation.d.ts +2 -0
  82. package/dist/esm/structs/capabilities/tools.d.ts +7 -3
  83. package/dist/esm/structs/common.d.ts +2 -0
  84. package/dist/esm/structs/debug.d.ts +36 -20
  85. package/dist/esm/structs/execution/workflow-views.d.ts +123 -0
  86. package/dist/esm/structs/execution/workflow-views.js +1 -0
  87. package/dist/esm/structs/execution/workflow.d.ts +4 -3
  88. package/dist/esm/structs/index.d.ts +1 -0
  89. package/dist/esm/structs/model/runtime.d.ts +2 -0
  90. package/dist/esm/support/context/nodes.js +48 -45
  91. package/dist/esm/support/context/processing.js +6 -3
  92. package/dist/esm/utils/invocation.d.ts +1 -0
  93. package/dist/esm/utils/invocation.js +1 -0
  94. package/package.json +1 -1
  95. package/dist/cjs/utils/debug.js +0 -30
  96. package/dist/esm/utils/debug.d.ts +0 -27
  97. package/dist/esm/utils/debug.js +0 -27
@@ -2,6 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Delivery = void 0;
4
4
  exports.defineDelivery = defineDelivery;
5
+ /**
6
+ * Delivery Kit: run an ordered, synchronous stage pipeline that ends with one result.
7
+ *
8
+ * Place in the package: an optional App-side delivery pipeline (for example formatting and
9
+ * sending one reply). The Kit owns ordering, cancellation checks and outcome validation; the
10
+ * stages, their state and the actual side effects belong to the App.
11
+ *
12
+ * Exports: `defineDelivery`, `Delivery`, and the delivery option/outcome types.
13
+ */
14
+ const native_sub_flow_js_1 = require("../diagnostics/native-sub-flow.js");
5
15
  const synchronous_js_1 = require("../utils/synchronous.js");
6
16
  /**
7
17
  * Compile a delivery pipeline from its declared stages.
@@ -42,33 +52,35 @@ function defineDelivery(options) {
42
52
  * handler results.
43
53
  */
44
54
  deliver(input, execution) {
45
- execution.signal.throwIfAborted();
46
- const state = (0, synchronous_js_1.synchronous)(createState(input, execution), 'Delivery state creation must be synchronous');
47
- const context = {
48
- ...execution,
49
- signal: execution.signal,
50
- diagnostics: execution.diagnostics,
51
- app: input,
52
- };
53
- for (const stage of stages) {
55
+ return (0, native_sub_flow_js_1.nativeSubFlowSync)(execution, 'delivery', 'delivery', () => {
54
56
  execution.signal.throwIfAborted();
55
- (0, synchronous_js_1.synchronous)(beforeStage?.({ stageId: stage.id, state }, context), 'Delivery hooks must be synchronous');
56
- execution.signal.throwIfAborted();
57
- if (!stage.handle)
58
- continue;
59
- const outcome = (0, synchronous_js_1.synchronous)(stage.handle(state, context), 'Delivery handlers must be synchronous');
60
- execution.signal.throwIfAborted();
61
- if (!outcome ||
62
- !['continue', 'complete'].includes(outcome.type) ||
63
- (outcome.type === 'complete' && !('value' in outcome)))
64
- throw new TypeError('Delivery handlers require explicit continue/complete');
65
- if (outcome.type === 'complete') {
66
- const value = outcome.value;
57
+ const state = (0, synchronous_js_1.synchronous)(createState(input, execution), 'Delivery state creation must be synchronous');
58
+ const context = {
59
+ ...execution,
60
+ signal: execution.signal,
61
+ diagnostics: execution.diagnostics,
62
+ app: input,
63
+ };
64
+ for (const stage of stages) {
65
+ execution.signal.throwIfAborted();
66
+ (0, synchronous_js_1.synchronous)(beforeStage?.({ stageId: stage.id, state }, context), 'Delivery hooks must be synchronous');
67
+ execution.signal.throwIfAborted();
68
+ if (!stage.handle)
69
+ continue;
70
+ const outcome = (0, synchronous_js_1.synchronous)(stage.handle(state, context), 'Delivery handlers must be synchronous');
67
71
  execution.signal.throwIfAborted();
68
- return value;
72
+ if (!outcome ||
73
+ !['continue', 'complete'].includes(outcome.type) ||
74
+ (outcome.type === 'complete' && !('value' in outcome)))
75
+ throw new TypeError('Delivery handlers require explicit continue/complete');
76
+ if (outcome.type === 'complete') {
77
+ const value = outcome.value;
78
+ execution.signal.throwIfAborted();
79
+ return value;
80
+ }
69
81
  }
70
- }
71
- throw (0, synchronous_js_1.synchronous)(missingResult(), 'Delivery errors must be synchronous');
82
+ throw (0, synchronous_js_1.synchronous)(missingResult(), 'Delivery errors must be synchronous');
83
+ });
72
84
  },
73
85
  });
74
86
  }
@@ -10,6 +10,7 @@ exports.defineKnowledgeCatalog = defineKnowledgeCatalog;
10
10
  *
11
11
  * Exports: `defineKnowledgeCatalog`, and the knowledge document/read/snapshot types.
12
12
  */
13
+ const native_sub_flow_js_1 = require("../../diagnostics/native-sub-flow.js");
13
14
  const catalog_js_1 = require("../../utils/catalog.js");
14
15
  const sections_js_1 = require("./sections.js");
15
16
  const observer_js_1 = require("../../utils/observer.js");
@@ -68,37 +69,39 @@ function defineKnowledgeCatalog(documents, options = {}) {
68
69
  ...(0, catalog_js_1.readonlyCatalog)([...catalog.values()].map((entry) => entry.document), (id) => new TypeError(`Unknown knowledge document: ${id}`)),
69
70
  sectionIds,
70
71
  assertSelection,
71
- read(input) {
72
- const { read, entry } = resolve(input), raw = read.selection;
73
- const selection = raw.mode === 'document'
74
- ? Object.freeze({ mode: 'document' })
75
- : Object.freeze({ mode: raw.mode, sectionIds: Object.freeze([...raw.sectionIds]) });
76
- const content = (selection.mode === 'document'
77
- ? (0, sections_js_1.knowledgeText)(entry.document.parts)
78
- : selection.sectionIds
79
- .map((id) => (0, sections_js_1.knowledgeText)(entry.sections.get(id).parts, selection.mode === 'subtree'))
80
- .join('\n\n')).trim();
81
- if (maxChars !== undefined && content.length > maxChars)
82
- throw new RangeError('Knowledge selection exceeds its budget');
83
- // Preserve the existing opaque snapshot identity format.
84
- const identitySelection = selection.mode === 'document'
85
- ? selection
86
- : { mode: selection.mode, sections: selection.sectionIds };
87
- const snapshot = Object.freeze({
88
- documentId: read.documentId,
89
- selection,
90
- revision: entry.document.revision,
91
- locale: entry.document.locale,
92
- content,
93
- id: JSON.stringify([
94
- read.documentId,
95
- entry.document.revision,
96
- entry.document.locale,
97
- identitySelection,
98
- ]),
72
+ read(input, context = {}) {
73
+ return (0, native_sub_flow_js_1.nativeSubFlowSync)(context, 'knowledge', input.documentId, () => {
74
+ const { read, entry } = resolve(input), raw = read.selection;
75
+ const selection = raw.mode === 'document'
76
+ ? Object.freeze({ mode: 'document' })
77
+ : Object.freeze({ mode: raw.mode, sectionIds: Object.freeze([...raw.sectionIds]) });
78
+ const content = (selection.mode === 'document'
79
+ ? (0, sections_js_1.knowledgeText)(entry.document.parts)
80
+ : selection.sectionIds
81
+ .map((id) => (0, sections_js_1.knowledgeText)(entry.sections.get(id).parts, selection.mode === 'subtree'))
82
+ .join('\n\n')).trim();
83
+ if (maxChars !== undefined && content.length > maxChars)
84
+ throw new RangeError('Knowledge selection exceeds its budget');
85
+ // Preserve the existing opaque snapshot identity format.
86
+ const identitySelection = selection.mode === 'document'
87
+ ? selection
88
+ : { mode: selection.mode, sections: selection.sectionIds };
89
+ const snapshot = Object.freeze({
90
+ documentId: read.documentId,
91
+ selection,
92
+ revision: entry.document.revision,
93
+ locale: entry.document.locale,
94
+ content,
95
+ id: JSON.stringify([
96
+ read.documentId,
97
+ entry.document.revision,
98
+ entry.document.locale,
99
+ identitySelection,
100
+ ]),
101
+ });
102
+ (0, observer_js_1.observeSafely)(observe, snapshot);
103
+ return snapshot;
99
104
  });
100
- (0, observer_js_1.observeSafely)(observe, snapshot);
101
- return snapshot;
102
105
  },
103
106
  });
104
107
  }
@@ -12,6 +12,7 @@ exports.defineRecoveryChain = defineRecoveryChain;
12
12
  *
13
13
  * Exports: `definePolicySet`, `defineRecoveryChain`, `Policies`, and the policy types.
14
14
  */
15
+ const native_sub_flow_js_1 = require("../diagnostics/native-sub-flow.js");
15
16
  const catalog_js_1 = require("../utils/catalog.js");
16
17
  const synchronous_js_1 = require("../utils/synchronous.js");
17
18
  const rules_js_1 = require("../utils/rules.js");
@@ -70,9 +71,9 @@ function defineRecoveryChain(definitions) {
70
71
  },
71
72
  });
72
73
  });
73
- const tryRecover = (input) => {
74
+ const tryRecover = (input, execution = {}) => {
74
75
  for (const policy of catalog) {
75
- const result = policy.tryRecover(input);
76
+ const result = (0, native_sub_flow_js_1.nativeSubFlowSync)(execution, 'recovery', policy.id, () => policy.tryRecover(input));
76
77
  if (result.handled)
77
78
  return result;
78
79
  }
@@ -10,6 +10,7 @@ exports.defineOutputCatalog = defineOutputCatalog;
10
10
  *
11
11
  * Exports: `defineOutputCatalog`, and the output definition/field/provider/result types.
12
12
  */
13
+ const native_sub_flow_js_1 = require("../../diagnostics/native-sub-flow.js");
13
14
  const schema_adapter_js_1 = require("../../utils/schema-adapter.js");
14
15
  const catalog_js_1 = require("../../utils/catalog.js");
15
16
  const synchronous_js_1 = require("../../utils/synchronous.js");
@@ -324,7 +325,7 @@ function defineOutputCatalog(definitions, options = {}) {
324
325
  throw new TypeError('Use a named output request');
325
326
  if (!context?.signal)
326
327
  throw new TypeError('Output composition requires OperationContext');
327
- return composeInternal(request.outputId, request.input, context);
328
+ return (0, native_sub_flow_js_1.nativeSubFlowSync)(context, 'output', request.outputId, () => composeInternal(request.outputId, request.input, context));
328
329
  },
329
330
  });
330
331
  }
@@ -12,6 +12,7 @@ exports.defineRetrieval = defineRetrieval;
12
12
  *
13
13
  * Exports: `defineRetrieval`, `retrievalQueryKey`, `Retrieval`, and the retrieval port types.
14
14
  */
15
+ const native_sub_flow_js_1 = require("../diagnostics/native-sub-flow.js");
15
16
  const synchronous_js_1 = require("../utils/synchronous.js");
16
17
  const query_key_js_1 = require("../utils/query-key.js");
17
18
  const rules_js_1 = require("../utils/rules.js");
@@ -148,7 +149,12 @@ function defineRetrieval(definition) {
148
149
  }
149
150
  return receipt;
150
151
  }
151
- return Object.freeze({ id, description, effects, run });
152
+ return Object.freeze({
153
+ id,
154
+ description,
155
+ effects,
156
+ run: (input, context) => (0, native_sub_flow_js_1.nativeSubFlowSync)(context, 'retrieval', id, () => run(input, context)),
157
+ });
152
158
  }
153
159
  function reason(value, query, context) {
154
160
  return () => (typeof value === 'function' ? value(query, context) : value);
@@ -92,6 +92,7 @@ class ToolLifecycleRegistry {
92
92
  });
93
93
  this.diagnostics = this.options.execution?.diagnostics;
94
94
  this.invocationPorts = Object.freeze({
95
+ logger: this.options.logger,
95
96
  diagnostics: this.options.diagnostics ?? (() => this.diagnostics),
96
97
  signal: this.options.signal,
97
98
  journal: this.options.journal,
@@ -10,6 +10,7 @@ exports.retryMultipleModelCalls = retryMultipleModelCalls;
10
10
  *
11
11
  * Exports: `retryMultipleModelCalls`.
12
12
  */
13
+ const native_sub_flow_js_1 = require("../diagnostics/native-sub-flow.js");
13
14
  const errors_js_1 = require("../errors.js");
14
15
  /**
15
16
  * Retry once when the failure is a rejected multi-call proposal.
@@ -28,6 +29,6 @@ async function retryMultipleModelCalls(request) {
28
29
  catch (error) {
29
30
  if (!(0, errors_js_1.getErrorInfo)(error).codes.includes('MODEL_MULTIPLE_CALLS'))
30
31
  throw error;
31
- return request.retry(error);
32
+ return (0, native_sub_flow_js_1.nativeSubFlowAsync)(request, 'retry', 'MODEL_MULTIPLE_CALLS', () => request.retry(error));
32
33
  }
33
34
  }
@@ -13,6 +13,8 @@ exports.ModelRunner = exports.BudgetError = void 0;
13
13
  * Exports: `ModelRunner`, the re-exported `BudgetError`, and the runtime/config type
14
14
  * re-exports at the bottom of the file.
15
15
  */
16
+ const sub_flow_js_1 = require("../diagnostics/sub-flow.js");
17
+ const native_sub_flow_js_1 = require("../diagnostics/native-sub-flow.js");
16
18
  const events_js_1 = require("../execution/events.js");
17
19
  const call_budget_js_1 = require("./call-budget.js");
18
20
  const synchronous_js_1 = require("../utils/synchronous.js");
@@ -321,6 +323,19 @@ class ModelRunner {
321
323
  return snapshot;
322
324
  }
323
325
  async invoke(request) {
326
+ const context = typeof request === 'string' ? { logger: undefined, signal: undefined } : request;
327
+ (0, sub_flow_js_1.requireSubFlowLogger)(context.logger);
328
+ const callId = (0, invocation_js_1.invocationId)();
329
+ return (0, native_sub_flow_js_1.nativeSubFlowAsync)({
330
+ logger: context.logger,
331
+ signal: (0, cancellation_js_1.combineSignals)(this.signal, context.signal),
332
+ invocationId: callId,
333
+ }, 'model', typeof request === 'string'
334
+ ? defaults_js_1.modelRunnerDefaults.stage
335
+ : (request.stageId ?? request.prepared?.stageId ?? defaults_js_1.modelRunnerDefaults.stage), () => this.invokeRequest(request, callId));
336
+ }
337
+ /** Validate and execute a single request; optional recording is owned by invoke. */
338
+ async invokeRequest(request, callId) {
324
339
  const call = typeof request === 'string' ? { input: request } : request;
325
340
  const state = call.prepared === undefined ? undefined : this.preparedCalls.get(call.prepared);
326
341
  if (call.prepared !== undefined && (!state || state.used))
@@ -384,7 +399,7 @@ class ModelRunner {
384
399
  throw invalid('Prepared model request settings cannot be replaced');
385
400
  if (state)
386
401
  state.used = true;
387
- return this.invokePrepared(stage, input, tokens, transportId, call.diagnostics, prepared, call.signal);
402
+ return this.invokePrepared(stage, input, tokens, transportId, call.diagnostics, prepared, call.signal, callId);
388
403
  }
389
404
  /**
390
405
  * Acquire the per-runner call slot and execute one call.
@@ -403,7 +418,7 @@ class ModelRunner {
403
418
  * @returns The parsed result with invocation metadata.
404
419
  * @throws Error When a call is already in flight or the request capacity is exhausted.
405
420
  */
406
- async invokePrepared(stage, input, tokens, transportId, diagnostics = this.ports.diagnostics, prepared, requestSignal) {
421
+ async invokePrepared(stage, input, tokens, transportId, diagnostics = this.ports.diagnostics, prepared, requestSignal, callId = (0, invocation_js_1.invocationId)()) {
407
422
  const configuredRun = this.ports.diagnostics?.identity?.runId, selectedRun = diagnostics?.identity?.runId;
408
423
  if (configuredRun && selectedRun && configuredRun !== selectedRun)
409
424
  throw (0, errors_js_1.recordErrorContext)(new TypeError('Model observation parent belongs to a different run'), { stageId: stage, codes: ['MODEL_CONFIG'], isRetryable: false });
@@ -413,7 +428,7 @@ class ModelRunner {
413
428
  this.requestGate.active++;
414
429
  let failure;
415
430
  try {
416
- return await this.execute(stage, input, tokens, transportId, (0, cancellation_js_1.combineSignals)(this.signal, requestSignal), diagnostics, prepared);
431
+ return await this.execute(stage, input, tokens, transportId, (0, cancellation_js_1.combineSignals)(this.signal, requestSignal), diagnostics, prepared, callId);
417
432
  }
418
433
  catch (error) {
419
434
  failure = (0, errors_js_1.recordErrorContext)(error, { stageId: stage });
@@ -444,8 +459,8 @@ class ModelRunner {
444
459
  * @returns The parsed result with invocation metadata.
445
460
  * @throws Error or ValidationError from any phase, decorated with stage context.
446
461
  */
447
- async execute(stage, input, tokens, transportId, signal, parentDiagnostics = this.ports.diagnostics, prepared) {
448
- const id = (0, invocation_js_1.invocationId)(), startedAtMs = Date.now();
462
+ async execute(stage, input, tokens, transportId, signal, parentDiagnostics = this.ports.diagnostics, prepared, callId = (0, invocation_js_1.invocationId)()) {
463
+ const id = callId, startedAtMs = Date.now();
449
464
  this.activeCall = undefined;
450
465
  const invocationDiagnostics = parentDiagnostics?.child(id, {
451
466
  invocationId: id,
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -14,6 +14,7 @@ exports.createContextReductionNode = createContextReductionNode;
14
14
  * Main exports: `createContextProcessingNode`, `createContextSelectionNode`,
15
15
  * `createContextReductionNode` and their result/definition types.
16
16
  */
17
+ const native_sub_flow_js_1 = require("../../diagnostics/native-sub-flow.js");
17
18
  const processing_js_1 = require("./processing.js");
18
19
  const defaults_js_1 = require("../../model/defaults.js");
19
20
  const synchronous_js_1 = require("../../utils/synchronous.js");
@@ -129,53 +130,55 @@ function contextNode(definition, execute) {
129
130
  onErrorRouteId: definition.onErrorRouteId,
130
131
  routes: definition.routes,
131
132
  async run(state, context) {
132
- // The node timeout is an additional bound; parent cancellation still wins.
133
- const signal = (0, cancellation_js_1.combineSignals)(context.signal, AbortSignal.timeout(timeoutMs));
134
- const prepared = selectPrepared
135
- ? selectPrepared(state, context)
136
- : context.model.prepare(typeof request === 'function' ? request(state, context) : request);
137
- const input = (0, synchronous_js_1.synchronous)(plan(state, context), 'Context plans must be synchronous');
138
- // An empty measurement reports the allowance left after transport, instruction and schema
139
- // overhead, so processing must fit inside whichever limit is tighter.
140
- const overhead = prepared.measure([]);
141
- const fullLimit = Math.min(input.maxInputChars, overhead.maxInputChars);
142
- const bodyLimit = fullLimit;
143
- (0, rules_js_1.breakWhile)(bodyLimit < 1, 'No input capacity for context');
144
- const work = (0, processing_js_1.beginContextProcessing)({
145
- fragments: input.fragments,
146
- additionalMessages: input.additionalMessages,
147
- originalFragments: input.originalFragments,
148
- mutableFragmentIds: input.mutableFragmentIds,
149
- maxInputChars: bodyLimit,
150
- }, policy, {
151
- stageId: prepared.stageId,
152
- signal,
153
- diagnostics: context.diagnostics,
154
- compressors,
155
- selectors,
156
- // Read lazily so handlers see the live budget rather than the value at preparation.
157
- remainingCalls: () => prepared.remainingCalls,
158
- callModel: (options) => context.model.invoke({
159
- ...options,
160
- modelId: options.modelId ?? prepared.modelId,
133
+ return (0, native_sub_flow_js_1.nativeSubFlowAsync)(context, 'context', definition.id, async () => {
134
+ // The node timeout is an additional bound; parent cancellation still wins.
135
+ const signal = (0, cancellation_js_1.combineSignals)(context.signal, AbortSignal.timeout(timeoutMs));
136
+ const prepared = selectPrepared
137
+ ? selectPrepared(state, context)
138
+ : context.model.prepare(typeof request === 'function' ? request(state, context) : request);
139
+ const input = (0, synchronous_js_1.synchronous)(plan(state, context), 'Context plans must be synchronous');
140
+ // An empty measurement reports the allowance left after transport, instruction and schema
141
+ // overhead, so processing must fit inside whichever limit is tighter.
142
+ const overhead = prepared.measure([]);
143
+ const fullLimit = Math.min(input.maxInputChars, overhead.maxInputChars);
144
+ const bodyLimit = fullLimit;
145
+ (0, rules_js_1.breakWhile)(bodyLimit < 1, 'No input capacity for context');
146
+ const work = (0, processing_js_1.beginContextProcessing)({
147
+ fragments: input.fragments,
148
+ additionalMessages: input.additionalMessages,
149
+ originalFragments: input.originalFragments,
150
+ mutableFragmentIds: input.mutableFragmentIds,
151
+ maxInputChars: bodyLimit,
152
+ }, policy, {
153
+ stageId: prepared.stageId,
161
154
  signal,
162
- }),
163
- ...(overhead.counterId
164
- ? {
165
- tokenBudget: {
166
- counterId: overhead.counterId,
167
- inputLimit: overhead.inputTokenLimit,
168
- measure: (messages) => prepared.measure(messages).inputTokens,
169
- },
170
- }
171
- : {}),
155
+ diagnostics: context.diagnostics,
156
+ compressors,
157
+ selectors,
158
+ // Read lazily so handlers see the live budget rather than the value at preparation.
159
+ remainingCalls: () => prepared.remainingCalls,
160
+ callModel: (options) => context.model.invoke({
161
+ ...options,
162
+ modelId: options.modelId ?? prepared.modelId,
163
+ signal,
164
+ }),
165
+ ...(overhead.counterId
166
+ ? {
167
+ tokenBudget: {
168
+ counterId: overhead.counterId,
169
+ inputLimit: overhead.inputTokenLimit,
170
+ measure: (messages) => prepared.measure(messages).inputTokens,
171
+ },
172
+ }
173
+ : {}),
174
+ });
175
+ context.report({ payload: { phase: 'processing', fragmentCount: input.fragments.length } });
176
+ const result = await execute(work, prepared, fullLimit, state, context);
177
+ // Never report or commit a result produced after cancellation.
178
+ signal.throwIfAborted();
179
+ context.report({ payload: { phase: 'processed', report: work.report } });
180
+ return (0, synchronous_js_1.synchronous)(update(result, state), 'Context node updates must be synchronous');
172
181
  });
173
- context.report({ payload: { phase: 'processing', fragmentCount: input.fragments.length } });
174
- const result = await execute(work, prepared, fullLimit, state, context);
175
- // Never report or commit a result produced after cancellation.
176
- signal.throwIfAborted();
177
- context.report({ payload: { phase: 'processed', report: work.report } });
178
- return (0, synchronous_js_1.synchronous)(update(result, state), 'Context node updates must be synchronous');
179
182
  },
180
183
  };
181
184
  }
@@ -26,6 +26,7 @@ exports.processContext = processContext;
26
26
  * `finishContextProcessing` (staged runs), the policy/reduction catalogs and the individual
27
27
  * validation, measurement and reduction helpers used by workflow nodes.
28
28
  */
29
+ const native_sub_flow_js_1 = require("../../diagnostics/native-sub-flow.js");
29
30
  const snapshot_js_1 = require("./snapshot.js");
30
31
  const audit_js_1 = require("../../context/audit.js");
31
32
  const history_js_1 = require("./history.js");
@@ -610,7 +611,9 @@ function finishContextProcessing(state) {
610
611
  * @throws {BudgetError} When a handler exhausts its call allowance.
611
612
  */
612
613
  async function processContext(plan, policy, ports) {
613
- const state = beginContextProcessing(plan, policy, ports);
614
- await exports.contextPolicyCatalog[policy.mode].run(state);
615
- return finishContextProcessing(state);
614
+ return (0, native_sub_flow_js_1.nativeSubFlowAsync)(ports, 'context', policy.mode, async () => {
615
+ const state = beginContextProcessing(plan, policy, ports);
616
+ await exports.contextPolicyCatalog[policy.mode].run(state);
617
+ return finishContextProcessing(state);
618
+ });
616
619
  }
@@ -46,6 +46,7 @@ function toolInvocation(request, arity) {
46
46
  throw new TypeError('Use one tool request with toolId and input');
47
47
  }
48
48
  return {
49
+ logger: request.logger,
49
50
  toolId: request.toolId,
50
51
  input: request.input,
51
52
  diagnostics: request.diagnostics,
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * Exports: `executeInvocation` and the invocation contracts re-exported at the bottom.
10
10
  */
11
+ import { nativeSubFlowSync } from '../diagnostics/native-sub-flow.js';
11
12
  import { invocationId } from '../utils/invocation.js';
12
13
  import { synchronous } from '../utils/synchronous.js';
13
14
  import { diagnosticError } from '../diagnostics/index.js';
@@ -28,12 +29,30 @@ import { NodeReports } from '../execution/reporting.js';
28
29
  * signal checks.
29
30
  */
30
31
  export function executeInvocation(invocation, input, context, ports = {}) {
32
+ let logger;
33
+ try {
34
+ logger = ports.logger?.(context);
35
+ }
36
+ catch {
37
+ /* Optional recording cannot block execution. */
38
+ }
39
+ const callId = invocationId();
40
+ return nativeSubFlowSync({
41
+ logger,
42
+ invocationId: callId,
43
+ get signal() {
44
+ return ports.signal?.(context);
45
+ },
46
+ }, 'invocation', invocation.id, (scopedLogger) => runInvocation(invocation, input, context, ports, scopedLogger, callId));
47
+ }
48
+ /** Execute the existing synchronous lifecycle without changing journal or error precedence. */
49
+ function runInvocation(invocation, input, context, ports, logger, executionId = invocationId()) {
31
50
  const toolId = invocation.id;
32
51
  // Identify the call before touching the ports: journal and diagnostics records cannot be
33
52
  // reconciled with an anonymous handler.
34
53
  if (!toolId?.trim())
35
54
  throw new TypeError('Invalid invocation handler identity');
36
- const journal = ports.journal?.(context), id = invocationId(), startedAtMs = Date.now();
55
+ const journal = ports.journal?.(context), id = executionId, startedAtMs = Date.now();
37
56
  const diagnostics = ports.diagnostics?.(context)?.child(id, {
38
57
  invocationId: id,
39
58
  nodeRole: 'invocation',
@@ -64,6 +83,7 @@ export function executeInvocation(invocation, input, context, ports = {}) {
64
83
  // The helper rejects a promise and drains its rejection: a handler that turns async must
65
84
  // fail here instead of escaping as an unhandled rejection.
66
85
  synchronous(invocation.call(input, context, {
86
+ logger,
67
87
  mode: 'sync',
68
88
  invocationId: id,
69
89
  signal: signal ?? new AbortController().signal,
@@ -27,7 +27,14 @@ function execute(definition, input, context, execution) {
27
27
  signal.throwIfAborted();
28
28
  return value;
29
29
  };
30
- const operation = { app: context, signal, diagnostics, report, createSubFlowLogger };
30
+ const operation = {
31
+ app: context,
32
+ signal,
33
+ diagnostics,
34
+ logger: execution.logger,
35
+ report,
36
+ createSubFlowLogger,
37
+ };
31
38
  const authorized = (result) => {
32
39
  assertion(result, 'Tool authorization must return void');
33
40
  signal.throwIfAborted();
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * Exports: `ToolRegistry`, `executeInvocation`, and the tool/invocation contracts.
13
13
  */
14
+ import { nativeSubFlowSync, nativeSubFlowAsync } from '../diagnostics/native-sub-flow.js';
14
15
  import { executionEvents } from '../execution/events.js';
15
16
  import { invocationId as nextInvocationId, invocationResult, toolInvocation, } from '../utils/invocation.js';
16
17
  import { synchronous } from '../utils/synchronous.js';
@@ -116,7 +117,8 @@ export class ToolRegistry {
116
117
  */
117
118
  invoke(request) {
118
119
  const call = toolInvocation(request, arguments.length);
119
- return this.execute(call.toolId, call.input, false, call.diagnostics, call.signal);
120
+ const callId = nextInvocationId();
121
+ return nativeSubFlowSync({ ...call, signal: combineSignals(this.ports.signal, call.signal), invocationId: callId }, 'tool', call.toolId, (scopedLogger) => this.execute(call.toolId, call.input, false, call.diagnostics, call.signal, scopedLogger, callId));
120
122
  }
121
123
  /**
122
124
  * Invoke a registered tool and await its result.
@@ -128,7 +130,8 @@ export class ToolRegistry {
128
130
  */
129
131
  async invokeAsync(request) {
130
132
  const call = toolInvocation(request, arguments.length);
131
- return (await this.execute(call.toolId, call.input, true, call.diagnostics, call.signal));
133
+ const callId = nextInvocationId();
134
+ return nativeSubFlowAsync({ ...call, signal: combineSignals(this.ports.signal, call.signal), invocationId: callId }, 'tool', call.toolId, (scopedLogger) => this.execute(call.toolId, call.input, true, call.diagnostics, call.signal, scopedLogger, callId));
132
135
  }
133
136
  /**
134
137
  * Run one call: validate, parse input, execute, parse output, then record the outcome.
@@ -140,7 +143,7 @@ export class ToolRegistry {
140
143
  * @param requestSignal Per-call signal, combined with the request-lifetime signal.
141
144
  * @returns The frozen result on success; every failure path throws.
142
145
  */
143
- execute(name, input, asynchronous, parentDiagnostics = this.diagnostics, requestSignal) {
146
+ execute(name, input, asynchronous, parentDiagnostics = this.diagnostics, requestSignal, logger, callId = nextInvocationId()) {
144
147
  // A tool call stays inside the run that invoked it: mixing two diagnostic trees would
145
148
  // produce records with two parents and break run-scoped correlation.
146
149
  if (this.diagnostics?.identity?.runId &&
@@ -152,7 +155,7 @@ export class ToolRegistry {
152
155
  throw this.ports.unavailable?.(name) ?? new Error(`Tool is not registered: ${name}`);
153
156
  // Either signal may cancel the call; combining them keeps both lifetimes effective.
154
157
  const signal = combineSignals(this.ports.signal, requestSignal);
155
- const invocationId = nextInvocationId();
158
+ const invocationId = callId;
156
159
  const diagnostics = parentDiagnostics?.child(invocationId, {
157
160
  invocationId,
158
161
  nodeRole: 'invocation',
@@ -190,24 +193,15 @@ export class ToolRegistry {
190
193
  };
191
194
  const succeed = (result) => {
192
195
  signal.throwIfAborted();
193
- let debug = reports.debug();
194
196
  if (tool.debugSteps && diagnostics?.enabled('tool.result', 'content')) {
195
197
  try {
196
- debug = reports.debug({
197
- subs: synchronous(tool.debugSteps(result, reporting), 'Tool debug reports must be synchronous'),
198
- });
198
+ synchronous(tool.debugSteps(result, reporting), 'Tool debug reports must be synchronous');
199
199
  }
200
- catch (error) {
201
- // Debug material is optional evidence: it must not turn a successful tool call into
202
- // a failure. Defer the error until a consumer actually asks for the sub-steps.
203
- debug = {
204
- usedModel: reports.debug().usedModel,
205
- subSteps: () => {
206
- throw error;
207
- },
208
- };
200
+ catch {
201
+ diagnostics?.emit('node.progress', () => ({ reportError: 'INVALID_NODE_REPORT' }));
209
202
  }
210
203
  }
204
+ const debug = reports.debug();
211
205
  signal.throwIfAborted();
212
206
  reports.close();
213
207
  diagnostics?.emit('tool.result', () => ({
@@ -277,6 +271,7 @@ export class ToolRegistry {
277
271
  signal.throwIfAborted();
278
272
  phase = 'execute';
279
273
  const value = tool.execute(parsed, {
274
+ logger,
280
275
  mode: asynchronous ? 'async' : 'sync',
281
276
  invocationId,
282
277
  signal,
@@ -1,6 +1,7 @@
1
1
  import type { DiagnosticContext, CreateDiagnosticsOptions } from '../structs/diagnostics.js';
2
2
  export * from '../structs/diagnostics.js';
3
3
  export * from './catalog.js';
4
+ export { SubFlowLogger, ScopedSubFlowLogger } from './sub-flow.js';
4
5
  export type * from '../structs/debug.js';
5
6
  export { reportedStepStatusCatalog, reportedStepStatuses } from '../structs/debug.js';
6
7
  /**