@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
@@ -1,3 +1,13 @@
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
+ import { nativeSubFlowSync } from '../diagnostics/native-sub-flow.js';
1
11
  import { synchronous } from '../utils/synchronous.js';
2
12
  /**
3
13
  * Compile a delivery pipeline from its declared stages.
@@ -38,33 +48,35 @@ export function defineDelivery(options) {
38
48
  * handler results.
39
49
  */
40
50
  deliver(input, execution) {
41
- execution.signal.throwIfAborted();
42
- const state = synchronous(createState(input, execution), 'Delivery state creation must be synchronous');
43
- const context = {
44
- ...execution,
45
- signal: execution.signal,
46
- diagnostics: execution.diagnostics,
47
- app: input,
48
- };
49
- for (const stage of stages) {
51
+ return nativeSubFlowSync(execution, 'delivery', 'delivery', () => {
50
52
  execution.signal.throwIfAborted();
51
- synchronous(beforeStage?.({ stageId: stage.id, state }, context), 'Delivery hooks must be synchronous');
52
- execution.signal.throwIfAborted();
53
- if (!stage.handle)
54
- continue;
55
- const outcome = synchronous(stage.handle(state, context), 'Delivery handlers must be synchronous');
56
- execution.signal.throwIfAborted();
57
- if (!outcome ||
58
- !['continue', 'complete'].includes(outcome.type) ||
59
- (outcome.type === 'complete' && !('value' in outcome)))
60
- throw new TypeError('Delivery handlers require explicit continue/complete');
61
- if (outcome.type === 'complete') {
62
- const value = outcome.value;
53
+ const state = synchronous(createState(input, execution), 'Delivery state creation must be synchronous');
54
+ const context = {
55
+ ...execution,
56
+ signal: execution.signal,
57
+ diagnostics: execution.diagnostics,
58
+ app: input,
59
+ };
60
+ for (const stage of stages) {
61
+ execution.signal.throwIfAborted();
62
+ synchronous(beforeStage?.({ stageId: stage.id, state }, context), 'Delivery hooks must be synchronous');
63
+ execution.signal.throwIfAborted();
64
+ if (!stage.handle)
65
+ continue;
66
+ const outcome = synchronous(stage.handle(state, context), 'Delivery handlers must be synchronous');
63
67
  execution.signal.throwIfAborted();
64
- return value;
68
+ if (!outcome ||
69
+ !['continue', 'complete'].includes(outcome.type) ||
70
+ (outcome.type === 'complete' && !('value' in outcome)))
71
+ throw new TypeError('Delivery handlers require explicit continue/complete');
72
+ if (outcome.type === 'complete') {
73
+ const value = outcome.value;
74
+ execution.signal.throwIfAborted();
75
+ return value;
76
+ }
65
77
  }
66
- }
67
- throw synchronous(missingResult(), 'Delivery errors must be synchronous');
78
+ throw synchronous(missingResult(), 'Delivery errors must be synchronous');
79
+ });
68
80
  },
69
81
  });
70
82
  }
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * Exports: `defineKnowledgeCatalog`, and the knowledge document/read/snapshot types.
9
9
  */
10
+ import { nativeSubFlowSync } from '../../diagnostics/native-sub-flow.js';
10
11
  import { readonlyCatalog } from '../../utils/catalog.js';
11
12
  import { compileKnowledgeDocument, knowledgeText } from './sections.js';
12
13
  import { observeSafely } from '../../utils/observer.js';
@@ -65,37 +66,39 @@ export function defineKnowledgeCatalog(documents, options = {}) {
65
66
  ...readonlyCatalog([...catalog.values()].map((entry) => entry.document), (id) => new TypeError(`Unknown knowledge document: ${id}`)),
66
67
  sectionIds,
67
68
  assertSelection,
68
- read(input) {
69
- const { read, entry } = resolve(input), raw = read.selection;
70
- const selection = raw.mode === 'document'
71
- ? Object.freeze({ mode: 'document' })
72
- : Object.freeze({ mode: raw.mode, sectionIds: Object.freeze([...raw.sectionIds]) });
73
- const content = (selection.mode === 'document'
74
- ? knowledgeText(entry.document.parts)
75
- : selection.sectionIds
76
- .map((id) => knowledgeText(entry.sections.get(id).parts, selection.mode === 'subtree'))
77
- .join('\n\n')).trim();
78
- if (maxChars !== undefined && content.length > maxChars)
79
- throw new RangeError('Knowledge selection exceeds its budget');
80
- // Preserve the existing opaque snapshot identity format.
81
- const identitySelection = selection.mode === 'document'
82
- ? selection
83
- : { mode: selection.mode, sections: selection.sectionIds };
84
- const snapshot = Object.freeze({
85
- documentId: read.documentId,
86
- selection,
87
- revision: entry.document.revision,
88
- locale: entry.document.locale,
89
- content,
90
- id: JSON.stringify([
91
- read.documentId,
92
- entry.document.revision,
93
- entry.document.locale,
94
- identitySelection,
95
- ]),
69
+ read(input, context = {}) {
70
+ return nativeSubFlowSync(context, 'knowledge', input.documentId, () => {
71
+ const { read, entry } = resolve(input), raw = read.selection;
72
+ const selection = raw.mode === 'document'
73
+ ? Object.freeze({ mode: 'document' })
74
+ : Object.freeze({ mode: raw.mode, sectionIds: Object.freeze([...raw.sectionIds]) });
75
+ const content = (selection.mode === 'document'
76
+ ? knowledgeText(entry.document.parts)
77
+ : selection.sectionIds
78
+ .map((id) => knowledgeText(entry.sections.get(id).parts, selection.mode === 'subtree'))
79
+ .join('\n\n')).trim();
80
+ if (maxChars !== undefined && content.length > maxChars)
81
+ throw new RangeError('Knowledge selection exceeds its budget');
82
+ // Preserve the existing opaque snapshot identity format.
83
+ const identitySelection = selection.mode === 'document'
84
+ ? selection
85
+ : { mode: selection.mode, sections: selection.sectionIds };
86
+ const snapshot = Object.freeze({
87
+ documentId: read.documentId,
88
+ selection,
89
+ revision: entry.document.revision,
90
+ locale: entry.document.locale,
91
+ content,
92
+ id: JSON.stringify([
93
+ read.documentId,
94
+ entry.document.revision,
95
+ entry.document.locale,
96
+ identitySelection,
97
+ ]),
98
+ });
99
+ observeSafely(observe, snapshot);
100
+ return snapshot;
96
101
  });
97
- observeSafely(observe, snapshot);
98
- return snapshot;
99
102
  },
100
103
  });
101
104
  }
@@ -38,7 +38,7 @@ export declare function definePolicySet<Input, Kind extends string>(definitions:
38
38
  * boolean `handled` or omits `value` while claiming to have handled the input.
39
39
  */
40
40
  export declare function defineRecoveryChain<Input, Result>(definitions: readonly RecoveryDefinition<Input, Result>[]): Readonly<{
41
- tryRecover: (input: Input) => RecoveryResult<Result>;
41
+ tryRecover: (input: Input, execution?: Partial<import("../structs/common.js").ExecutionContext>) => RecoveryResult<Result>;
42
42
  definitions: readonly Readonly<{
43
43
  id: string;
44
44
  description: string;
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * Exports: `definePolicySet`, `defineRecoveryChain`, `Policies`, and the policy types.
9
9
  */
10
+ import { nativeSubFlowSync } from '../diagnostics/native-sub-flow.js';
10
11
  import { readonlyCatalog } from '../utils/catalog.js';
11
12
  import { synchronous, booleanResult } from '../utils/synchronous.js';
12
13
  import { breakWhile } from '../utils/rules.js';
@@ -65,9 +66,9 @@ export function defineRecoveryChain(definitions) {
65
66
  },
66
67
  });
67
68
  });
68
- const tryRecover = (input) => {
69
+ const tryRecover = (input, execution = {}) => {
69
70
  for (const policy of catalog) {
70
- const result = policy.tryRecover(input);
71
+ const result = nativeSubFlowSync(execution, 'recovery', policy.id, () => policy.tryRecover(input));
71
72
  if (result.handled)
72
73
  return result;
73
74
  }
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * Exports: `defineOutputCatalog`, and the output definition/field/provider/result types.
9
9
  */
10
+ import { nativeSubFlowSync } from '../../diagnostics/native-sub-flow.js';
10
11
  import { snapshotSchemaAdapter } from '../../utils/schema-adapter.js';
11
12
  import { readonlyCatalog } from '../../utils/catalog.js';
12
13
  import { synchronous } from '../../utils/synchronous.js';
@@ -321,7 +322,7 @@ export function defineOutputCatalog(definitions, options = {}) {
321
322
  throw new TypeError('Use a named output request');
322
323
  if (!context?.signal)
323
324
  throw new TypeError('Output composition requires OperationContext');
324
- return composeInternal(request.outputId, request.input, context);
325
+ return nativeSubFlowSync(context, 'output', request.outputId, () => composeInternal(request.outputId, request.input, context));
325
326
  },
326
327
  });
327
328
  }
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * Exports: `defineRetrieval`, `retrievalQueryKey`, `Retrieval`, and the retrieval port types.
10
10
  */
11
+ import { nativeSubFlowSync } from '../diagnostics/native-sub-flow.js';
11
12
  import { assertion, synchronous } from '../utils/synchronous.js';
12
13
  import { retrievalQueryKey } from '../utils/query-key.js';
13
14
  import { breakWhile } from '../utils/rules.js';
@@ -144,7 +145,12 @@ export function defineRetrieval(definition) {
144
145
  }
145
146
  return receipt;
146
147
  }
147
- return Object.freeze({ id, description, effects, run });
148
+ return Object.freeze({
149
+ id,
150
+ description,
151
+ effects,
152
+ run: (input, context) => nativeSubFlowSync(context, 'retrieval', id, () => run(input, context)),
153
+ });
148
154
  }
149
155
  function reason(value, query, context) {
150
156
  return () => (typeof value === 'function' ? value(query, context) : value);
@@ -89,6 +89,7 @@ export class ToolLifecycleRegistry {
89
89
  });
90
90
  this.diagnostics = this.options.execution?.diagnostics;
91
91
  this.invocationPorts = Object.freeze({
92
+ logger: this.options.logger,
92
93
  diagnostics: this.options.diagnostics ?? (() => this.diagnostics),
93
94
  signal: this.options.signal,
94
95
  journal: this.options.journal,
@@ -9,6 +9,8 @@
9
9
  * @throws The original error when it is not a multi-call rejection, or the fallback error.
10
10
  */
11
11
  export declare function retryMultipleModelCalls<Value>(request: {
12
+ readonly logger?: import('../structs/debug.js').SubFlowLogger;
13
+ readonly signal?: AbortSignal;
12
14
  readonly invoke: () => Promise<Value>;
13
15
  readonly retry: (error: unknown) => Promise<Value>;
14
16
  }): Promise<Value>;
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * Exports: `retryMultipleModelCalls`.
9
9
  */
10
+ import { nativeSubFlowAsync } from '../diagnostics/native-sub-flow.js';
10
11
  import { getErrorInfo } from '../errors.js';
11
12
  /**
12
13
  * Retry once when the failure is a rejected multi-call proposal.
@@ -25,6 +26,6 @@ export async function retryMultipleModelCalls(request) {
25
26
  catch (error) {
26
27
  if (!getErrorInfo(error).codes.includes('MODEL_MULTIPLE_CALLS'))
27
28
  throw error;
28
- return request.retry(error);
29
+ return nativeSubFlowAsync(request, 'retry', 'MODEL_MULTIPLE_CALLS', () => request.retry(error));
29
30
  }
30
31
  }
@@ -145,6 +145,8 @@ export declare class ModelRunner<TransportId extends string = string, const Tran
145
145
  }): Promise<ModelResult<import('../structs/model/runtime.js').RegisteredTransportOutput<Transports, Id>>>;
146
146
  /** Invoke without narrowing the transport type. */
147
147
  invoke(request: ModelCallRequest<TransportId | ModelTransportDefinition>): Promise<ModelResult<unknown>>;
148
+ /** Validate and execute a single request; optional recording is owned by invoke. */
149
+ private invokeRequest;
148
150
  /**
149
151
  * Acquire the per-runner call slot and execute one call.
150
152
  *
@@ -10,6 +10,8 @@
10
10
  * Exports: `ModelRunner`, the re-exported `BudgetError`, and the runtime/config type
11
11
  * re-exports at the bottom of the file.
12
12
  */
13
+ import { requireSubFlowLogger } from '../diagnostics/sub-flow.js';
14
+ import { nativeSubFlowAsync } from '../diagnostics/native-sub-flow.js';
13
15
  import { executionEvents } from '../execution/events.js';
14
16
  import { ModelCallBudget, parentModelBudget } from './call-budget.js';
15
17
  import { synchronous } from '../utils/synchronous.js';
@@ -317,6 +319,19 @@ export class ModelRunner {
317
319
  return snapshot;
318
320
  }
319
321
  async invoke(request) {
322
+ const context = typeof request === 'string' ? { logger: undefined, signal: undefined } : request;
323
+ requireSubFlowLogger(context.logger);
324
+ const callId = invocationId();
325
+ return nativeSubFlowAsync({
326
+ logger: context.logger,
327
+ signal: combineSignals(this.signal, context.signal),
328
+ invocationId: callId,
329
+ }, 'model', typeof request === 'string'
330
+ ? modelRunnerDefaults.stage
331
+ : (request.stageId ?? request.prepared?.stageId ?? modelRunnerDefaults.stage), () => this.invokeRequest(request, callId));
332
+ }
333
+ /** Validate and execute a single request; optional recording is owned by invoke. */
334
+ async invokeRequest(request, callId) {
320
335
  const call = typeof request === 'string' ? { input: request } : request;
321
336
  const state = call.prepared === undefined ? undefined : this.preparedCalls.get(call.prepared);
322
337
  if (call.prepared !== undefined && (!state || state.used))
@@ -380,7 +395,7 @@ export class ModelRunner {
380
395
  throw invalid('Prepared model request settings cannot be replaced');
381
396
  if (state)
382
397
  state.used = true;
383
- return this.invokePrepared(stage, input, tokens, transportId, call.diagnostics, prepared, call.signal);
398
+ return this.invokePrepared(stage, input, tokens, transportId, call.diagnostics, prepared, call.signal, callId);
384
399
  }
385
400
  /**
386
401
  * Acquire the per-runner call slot and execute one call.
@@ -399,7 +414,7 @@ export class ModelRunner {
399
414
  * @returns The parsed result with invocation metadata.
400
415
  * @throws Error When a call is already in flight or the request capacity is exhausted.
401
416
  */
402
- async invokePrepared(stage, input, tokens, transportId, diagnostics = this.ports.diagnostics, prepared, requestSignal) {
417
+ async invokePrepared(stage, input, tokens, transportId, diagnostics = this.ports.diagnostics, prepared, requestSignal, callId = invocationId()) {
403
418
  const configuredRun = this.ports.diagnostics?.identity?.runId, selectedRun = diagnostics?.identity?.runId;
404
419
  if (configuredRun && selectedRun && configuredRun !== selectedRun)
405
420
  throw recordErrorContext(new TypeError('Model observation parent belongs to a different run'), { stageId: stage, codes: ['MODEL_CONFIG'], isRetryable: false });
@@ -409,7 +424,7 @@ export class ModelRunner {
409
424
  this.requestGate.active++;
410
425
  let failure;
411
426
  try {
412
- return await this.execute(stage, input, tokens, transportId, combineSignals(this.signal, requestSignal), diagnostics, prepared);
427
+ return await this.execute(stage, input, tokens, transportId, combineSignals(this.signal, requestSignal), diagnostics, prepared, callId);
413
428
  }
414
429
  catch (error) {
415
430
  failure = recordErrorContext(error, { stageId: stage });
@@ -440,8 +455,8 @@ export class ModelRunner {
440
455
  * @returns The parsed result with invocation metadata.
441
456
  * @throws Error or ValidationError from any phase, decorated with stage context.
442
457
  */
443
- async execute(stage, input, tokens, transportId, signal, parentDiagnostics = this.ports.diagnostics, prepared) {
444
- const id = invocationId(), startedAtMs = Date.now();
458
+ async execute(stage, input, tokens, transportId, signal, parentDiagnostics = this.ports.diagnostics, prepared, callId = invocationId()) {
459
+ const id = callId, startedAtMs = Date.now();
445
460
  this.activeCall = undefined;
446
461
  const invocationDiagnostics = parentDiagnostics?.child(id, {
447
462
  invocationId: id,
@@ -49,6 +49,8 @@ export interface InvocationHandler<Input, Context> {
49
49
  }
50
50
  /** Optional capabilities the lifecycle resolves from the host context for one invocation. */
51
51
  export interface ToolInvocationPorts<Context> {
52
+ /** Explicit recorder selected for this invocation only. */
53
+ readonly logger?: (context: Context) => import('../debug.js').SubFlowLogger | undefined;
52
54
  /** Diagnostic scope; omitted disables recording for this tool. */
53
55
  readonly diagnostics?: (context: Context) => import('../diagnostics.js').DiagnosticContext | undefined;
54
56
  /** Extra cancellation; must not replace the tool execution signal. */
@@ -27,13 +27,15 @@ export interface ToolDefinition<Input = unknown, Output = unknown, App = unknown
27
27
  authorize(input: Input, context: OperationContext<App>): MaybePromise<void>;
28
28
  /** Executes the already-authorized input; must not repeat authorization decisions. */
29
29
  execute(input: Input, context: OperationContext<App> & import('../debug.js').NodeReportingContext): MaybePromise<Output>;
30
- /** Optional reporting hook that turns the result into self-reported sub-steps. */
31
- debugSteps?(result: Output, context: import('../debug.js').NodeReportingContext): import('../debug.js').SubFlowLogger;
30
+ /** Optional result hook; writes through context.createSubFlowLogger() are recorded immediately. */
31
+ debugSteps?(result: Output, context: import('../debug.js').NodeReportingContext): void;
32
32
  /** Validates untrusted output (for example a cached replay) before it is returned. */
33
33
  parseOutput(value: unknown): Output;
34
34
  }
35
35
  /** Execution facts handed to a tool for one invocation. */
36
36
  export interface ToolExecutionContext extends ExecutionContext, NodeReportingContext {
37
+ /** Delegate bound by Core to this actual invocation. */
38
+ readonly logger?: import('../debug.js').ScopedSubFlowLogger;
37
39
  /** Diagnostic scope of the invocation, when recording is enabled. */
38
40
  readonly diagnostics?: DiagnosticContext;
39
41
  /** Declared execution discipline; mirrors whether the registry accepted an async tool. */
@@ -49,7 +51,7 @@ export interface ExecutableTool {
49
51
  readonly kind: string;
50
52
  /** Optional here because the erased view does not require model-facing prose. */
51
53
  readonly description?: string;
52
- readonly debugSteps?: (result: unknown, context: NodeReportingContext) => import('../debug.js').SubFlowLogger;
54
+ readonly debugSteps?: (result: unknown, context: NodeReportingContext) => void;
53
55
  parseInput(value: unknown): unknown;
54
56
  parseOutput(value: unknown): unknown;
55
57
  execute(input: unknown, context: ToolExecutionContext): unknown;
@@ -79,6 +81,8 @@ export interface ToolRegistryPorts {
79
81
  }
80
82
  /** One invocation request; the registry supplies signal and diagnostics when omitted. */
81
83
  export interface ToolInvocationRequest<Id extends string = string> {
84
+ /** Explicit recorder for this call, also available to its handler for deliberate use. */
85
+ readonly logger?: import('../debug.js').SubFlowLogger;
82
86
  readonly toolId: Id;
83
87
  /** Raw model arguments; parsed by the tool's input schema before authorization. */
84
88
  readonly input: unknown;
@@ -93,6 +93,8 @@ export interface ExecutionOptions {
93
93
  }
94
94
  /** Required execution facts available inside a running call. */
95
95
  export interface ExecutionContext {
96
+ /** Explicit optional recorder for this operation; never inferred from diagnostics. */
97
+ readonly logger?: import('./debug.js').SubFlowLog;
96
98
  /** Non-optional cancellation signal; never `undefined` once execution has started. */
97
99
  readonly signal: AbortSignal;
98
100
  /** Diagnostic scope of the enclosing call, when recording is enabled. */
@@ -55,12 +55,18 @@ export declare const reportedStepStatuses: readonly (keyof typeof reportedStepSt
55
55
  export type ReportedStepStatus = keyof typeof reportedStepStatusCatalog;
56
56
  /** One step as reported by a node; fields beyond identity are optional detail. */
57
57
  export interface ReportedStep {
58
- /** Step identity, unique within one node visit. */
58
+ /** App-defined business log type; never a write/fix operation enum. */
59
+ readonly type?: string;
60
+ /** Logical subnode grouping within this node visit; independent of individual log ids. */
61
+ readonly subnodeId?: string;
62
+ /** Log record identity, unique within one node visit; not a subnode id. */
59
63
  readonly stepId: string;
60
64
  /** Reported parent step, forming a tree; absent for a top-level step. */
61
65
  readonly parentStepId?: string;
62
66
  /** Self-reported reference only; never a workflow dispatch identity. */
63
67
  readonly relatedDefinitionId?: string;
68
+ /** Real model/tool invocation identity, kept separate from the node-local subnode id. */
69
+ readonly relatedInvocationId?: string;
64
70
  /** Short reported label. */
65
71
  readonly name: string;
66
72
  /** Reported step kind (for example a tool or model call); free-form and node-owned. */
@@ -88,7 +94,11 @@ export type SubFlowValue = null | boolean | number | string | readonly SubFlowVa
88
94
  export interface RecordedSubStep extends ReportedStep {
89
95
  /** Historical marker: only self-reported steps are recorded. */
90
96
  readonly origin: 'reported';
91
- /** One-based write order within the node visit. */
97
+ /** Append-only logging operation, separate from the App-owned business type. */
98
+ readonly operation?: 'write' | 'fix';
99
+ /** Original write record revised by this fix; fixes have their own stepId. */
100
+ readonly targetStepId?: string;
101
+ /** One-based append order within the node visit, including every fix. */
92
102
  readonly sequence: number;
93
103
  /** Optional only for historical records produced before the logger contract. */
94
104
  readonly createdAtMs?: number;
@@ -98,10 +108,14 @@ export interface RecordedSubStep extends ReportedStep {
98
108
  readonly payload?: Readonly<Record<string, SubFlowValue>>;
99
109
  }
100
110
  /** Input accepted by `write`; the logger assigns the step id and sequence. */
101
- export interface SubFlowStepInput extends Omit<ReportedStep, 'stepId'> {
111
+ export interface SubFlowStepInput extends Omit<ReportedStep, 'stepId' | 'kind'> {
112
+ /** Optional family classification; defaults to the custom type when omitted. */
113
+ readonly kind?: string;
102
114
  readonly payload?: Readonly<Record<string, SubFlowValue>>;
103
115
  }
104
- /** Fields a later `fix` may change on an already written step. */
116
+ /** Scoped writes cannot supply or override the identity bound by their delegate. */
117
+ export type ScopedSubFlowStepInput = Omit<SubFlowStepInput, 'subnodeId'>;
118
+ /** Explicit revision whitelist: identity, grouping and business type are never patchable. */
105
119
  export interface SubFlowStepFix {
106
120
  readonly status?: ReportedStepStatus;
107
121
  readonly usedModel?: boolean;
@@ -112,31 +126,34 @@ export interface SubFlowStepFix {
112
126
  /** Fill missing result fields; existing facts cannot be replaced. */
113
127
  readonly payload?: Readonly<Record<string, SubFlowValue>>;
114
128
  }
115
- declare const subFlowLoggerBrand: unique symbol;
116
- /** Execution-owned instance. Obtain it from the active node/tool context. */
117
- export interface SubFlowLogger {
118
- /** Brand makes structural lookalikes incompatible with the recorder's own instance. */
119
- readonly [subFlowLoggerBrand]: true;
120
- /** Appends one step and returns its assigned step id. */
121
- write(input: SubFlowStepInput): string;
122
- /** Applies a partial update; illegal status transitions and replacements are rejected. */
123
- fix(stepId: string, patch: SubFlowStepFix): void;
124
- /** Snapshot of the steps recorded so far, in write order. */
129
+ /** Common read/write surface for both independently implemented logger classes. */
130
+ export interface SubFlowLog {
131
+ write(input: ScopedSubFlowStepInput): string | -1;
132
+ fix(stepId: string | -1, patch: SubFlowStepFix): boolean;
133
+ /** Both logger classes return the same complete immutable ledger. */
125
134
  readAll(): readonly Readonly<RecordedSubStep>[];
126
135
  }
136
+ /** Nominal classes: only the full logger is accepted by native invocation requests. */
137
+ export type { SubFlowLogger, ScopedSubFlowLogger } from '../diagnostics/sub-flow.js';
138
+ import type { SubFlowLogger } from '../diagnostics/sub-flow.js';
127
139
  /** Reporting surface handed to a node or tool for the duration of one visit. */
128
140
  export interface NodeReportingContext {
129
141
  /** Reports progress for the active node; Core supplies the execution identity. */
130
142
  report(update: import('./execution/workflow.js').WorkflowNodeReport): void;
131
- /** One logger per node visit; repeated calls return the same instance. */
143
+ /** Bound to this context and owned by runtime; repeated calls in one visit return the same instance. */
132
144
  createSubFlowLogger(): SubFlowLogger;
133
145
  }
134
146
  /** Compact progress notification emitted for each accepted sub-step write or fix. */
135
147
  export interface SubStepProgress {
148
+ readonly relatedInvocationId?: string;
136
149
  readonly operation: 'write' | 'fix';
137
- /** Step the operation applied to. */
150
+ readonly type?: string;
151
+ readonly subnodeId?: string;
152
+ /** Original write record when this operation is a fix. */
153
+ readonly targetStepId?: string;
154
+ /** Unique id of this append-only record. */
138
155
  readonly stepId: string;
139
- /** Write sequence of that step within the node visit. */
156
+ /** Append sequence of this record within the node visit, including every fix. */
140
157
  readonly sequence: number;
141
158
  /** Revision after the operation; `write` starts at 1. */
142
159
  readonly revision: number;
@@ -151,7 +168,6 @@ export interface SubStepProgress {
151
168
  export interface DebugStepsOutput {
152
169
  /** Model use reported by this handler; omitted when unknown. */
153
170
  readonly usedModel?: boolean;
154
- /** Accessor invoked at emit time so recording can be skipped when disabled. */
155
- readonly subSteps?: () => SubFlowLogger | undefined;
171
+ /** Runtime-owned immutable snapshot, committed when a logger writes or fixes a step. */
172
+ readonly recordedSubSteps?: readonly Readonly<RecordedSubStep>[];
156
173
  }
157
- export {};
@@ -0,0 +1,123 @@
1
+ /** Serializable topology and observed execution contracts; layout belongs to consumers. */
2
+ import type { CoreDiagnosticRecord } from '../diagnostics.js';
3
+ import type { RecordedSubStep, SubFlowValue } from '../debug.js';
4
+ import type { WorkflowEvent } from './workflow.js';
5
+ /** Pure registered topology; routes sharing a target share an edge within one kind. */
6
+ export interface WorkflowGraph {
7
+ readonly schemaVersion: 1;
8
+ readonly workflowId: string;
9
+ readonly revision?: string;
10
+ readonly entryNodeId: string;
11
+ readonly nodes: readonly WorkflowGraphNode[];
12
+ readonly edges: readonly WorkflowGraphEdge[];
13
+ }
14
+ /** Node metadata and derived incident edges, without executable callbacks. */
15
+ export interface WorkflowGraphNode {
16
+ readonly id: string;
17
+ readonly name: string;
18
+ readonly description: string;
19
+ readonly order: number;
20
+ readonly startsRound: boolean;
21
+ readonly requires: readonly string[];
22
+ readonly provides: readonly string[];
23
+ readonly routes: readonly WorkflowGraphRoute[];
24
+ readonly incomingEdgeIds: readonly string[];
25
+ readonly outgoingEdgeIds: readonly string[];
26
+ }
27
+ /** Declaration order is retained even when multiple routes share a topology edge. */
28
+ export interface WorkflowGraphRoute {
29
+ readonly order: number;
30
+ readonly routeId?: string;
31
+ readonly description?: string;
32
+ readonly conditional: boolean;
33
+ readonly conditionId?: string;
34
+ readonly edgeId: string;
35
+ }
36
+ /** Completion has no target node; recovery edges remain distinct from normal edges. */
37
+ export interface WorkflowGraphEdge {
38
+ readonly id: string;
39
+ readonly kind: 'transition' | 'error' | 'complete';
40
+ readonly sourceNodeId: string;
41
+ readonly targetNodeId?: string;
42
+ readonly routeOrders: readonly number[];
43
+ readonly errorRouteId?: string;
44
+ }
45
+ /** Capture completeness is independent of whether diagnostic content was included. */
46
+ export interface WorkflowChainCoverage {
47
+ readonly complete: boolean | 'unknown';
48
+ readonly content: 'included' | 'omitted' | 'mixed' | 'unknown';
49
+ readonly issues: readonly {
50
+ readonly code: string;
51
+ readonly spanId?: string;
52
+ readonly eventId?: string;
53
+ readonly count?: number;
54
+ }[];
55
+ }
56
+ /** App resolves authorization, retention, pagination and storage paths before returning. */
57
+ export type WorkflowChainSource = (runId: string) => Promise<{
58
+ readonly events: readonly CoreDiagnosticRecord[];
59
+ readonly coverage: WorkflowChainCoverage;
60
+ }>;
61
+ /** Observed visits, never merged by static node id or synthesized from adjacency. */
62
+ export interface WorkflowChain {
63
+ readonly schemaVersion: 1;
64
+ readonly workflowId: string;
65
+ readonly runId: string;
66
+ readonly nodes: readonly WorkflowChainNode[];
67
+ readonly transitions: readonly WorkflowChainTransition[];
68
+ readonly coverage: WorkflowChainCoverage;
69
+ }
70
+ /** Historical subflow snapshots may omit recorder fields or descriptive content. */
71
+ export type WorkflowSubflowRecord = Omit<RecordedSubStep, 'name' | 'kind'> & {
72
+ readonly name?: string;
73
+ readonly kind?: string;
74
+ readonly sourceEventId?: string;
75
+ readonly producerSequence?: number;
76
+ };
77
+ /** A single node visit, with flat ordered subflow records including every independent fix. */
78
+ export interface WorkflowChainNode {
79
+ readonly spanId: string;
80
+ readonly nodeId: string;
81
+ readonly workflowInvocationId?: string;
82
+ readonly definitionRevision?: string;
83
+ readonly name?: string;
84
+ readonly description?: string;
85
+ readonly kind?: string;
86
+ readonly roundIndex?: number;
87
+ readonly firstEventSequence: number;
88
+ readonly lastEventSequence: number;
89
+ readonly enteredAtMs?: number;
90
+ readonly endedAtMs?: number;
91
+ readonly durationMs?: number;
92
+ readonly status: 'running' | 'success' | 'error' | 'cancelled' | 'unknown';
93
+ readonly usedModel?: boolean;
94
+ readonly events: readonly WorkflowChainNodeEvent[];
95
+ readonly subflow: readonly WorkflowSubflowRecord[];
96
+ }
97
+ /** Node-local lifecycle facts; payload remains subject to the source's content policy. */
98
+ export interface WorkflowChainNodeEvent {
99
+ readonly eventId: string;
100
+ readonly producerSequence: number;
101
+ readonly occurredAtMs: number;
102
+ readonly type: WorkflowEvent['type'] | 'progress';
103
+ readonly phase?: WorkflowEvent['phase'];
104
+ readonly routeId?: string;
105
+ readonly conditionId?: string;
106
+ readonly errorCode?: string;
107
+ readonly errorEventId?: string;
108
+ readonly payload?: SubFlowValue;
109
+ }
110
+ /** Actual handoff evidence; a target span does not prove its enter event was captured. */
111
+ export interface WorkflowChainTransition {
112
+ readonly eventId: string;
113
+ readonly producerSequence: number;
114
+ readonly occurredAtMs: number;
115
+ readonly type: 'transition' | 'complete';
116
+ readonly sourceSpanId: string;
117
+ readonly targetSpanId?: string;
118
+ readonly targetNodeId?: string;
119
+ readonly routeOrder?: number;
120
+ readonly routeId?: string;
121
+ readonly conditionId?: string;
122
+ readonly errorEventId?: string;
123
+ }
@@ -0,0 +1 @@
1
+ export {};