@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
@@ -1,8 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScopedSubFlowLogger = exports.SubFlowLogger = void 0;
4
+ exports.captureSubFlowSnapshot = captureSubFlowSnapshot;
3
5
  exports.payloadSnapshot = payloadSnapshot;
6
+ exports.requireSubFlowLogger = requireSubFlowLogger;
7
+ exports.bindNativeSubFlowLogger = bindNativeSubFlowLogger;
4
8
  exports.createSubFlowLogger = createSubFlowLogger;
5
9
  exports.readSubFlowLogger = readSubFlowLogger;
10
+ exports.detachSubFlowLogger = detachSubFlowLogger;
6
11
  /**
7
12
  * Bounded, self-reported sub-flow logging.
8
13
  *
@@ -13,7 +18,7 @@ exports.readSubFlowLogger = readSubFlowLogger;
13
18
  * Issued loggers are tracked in a module-private registry, so only the execution that created
14
19
  * one can read it. A lookalike object with a `readAll` method is never accepted.
15
20
  *
16
- * Exports: `payloadSnapshot`, `createSubFlowLogger`, `readSubFlowLogger`.
21
+ * Exports: `payloadSnapshot`, `createSubFlowLogger`, `readSubFlowLogger`, `captureSubFlowSnapshot`.
17
22
  */
18
23
  const rules_js_1 = require("../utils/rules.js");
19
24
  const invocation_js_1 = require("../utils/invocation.js");
@@ -23,12 +28,29 @@ const debug_js_1 = require("../structs/debug.js");
23
28
  * private so a structurally identical object cannot impersonate a real logger.
24
29
  */
25
30
  const instances = new WeakMap();
31
+ /** Only recorder-issued snapshots may enter a diagnostic fact, within their original span. */
32
+ const snapshots = new WeakMap();
33
+ /** Validate a committed snapshot without consulting its logger or invoking App code. */
34
+ function captureSubFlowSnapshot(steps, identity) {
35
+ const empty = { subSteps: Object.freeze([]), subStepsDropped: 0, subStepsTruncated: false };
36
+ if (steps === undefined)
37
+ return empty;
38
+ const scope = snapshots.get(steps);
39
+ if (!scope ||
40
+ (identity &&
41
+ (scope.identity?.runId !== identity.runId || scope.identity?.spanId !== identity.spanId)))
42
+ return { ...empty, subStepsDropped: 1 };
43
+ return { ...empty, subSteps: steps };
44
+ }
26
45
  /** Fields accepted by `write`; anything else is rejected so a declaration cannot smuggle state. */
27
46
  const writeFields = [
47
+ 'type',
48
+ 'subnodeId',
28
49
  'name',
29
50
  'kind',
30
51
  'parentStepId',
31
52
  'relatedDefinitionId',
53
+ 'relatedInvocationId',
32
54
  'status',
33
55
  'usedModel',
34
56
  'startedAtMs',
@@ -50,10 +72,13 @@ const fixFields = [
50
72
  ];
51
73
  /** Per-field character caps, bounding what one reported step can add to a persisted record. */
52
74
  const textLimits = {
75
+ type: 256,
76
+ subnodeId: 1024,
53
77
  name: 256,
54
78
  kind: 128,
55
79
  parentStepId: 256,
56
80
  relatedDefinitionId: 256,
81
+ relatedInvocationId: 256,
57
82
  caseId: 256,
58
83
  summary: 1024,
59
84
  };
@@ -175,6 +200,126 @@ function fill(previous, patch) {
175
200
  }
176
201
  return Object.freeze(result);
177
202
  }
203
+ /** Stable diagnostic reasons; untrusted error text and caller payloads never reach the console. */
204
+ const rejectionReasons = {
205
+ inactive: 'logger is detached or execution is cancelled',
206
+ scope: 'target does not belong to this subnode',
207
+ target: 'original write record was not found',
208
+ fields: 'input contains a forbidden field',
209
+ transition: 'status transition is not allowed',
210
+ sealed: 'an established fact cannot be overwritten',
211
+ limit: 'recording limit exceeded',
212
+ input: 'invalid input or result patch',
213
+ };
214
+ const rejectionMessages = {
215
+ 'Unknown sub-flow field': 'fields',
216
+ 'Unknown sub-flow step': 'target',
217
+ 'Invalid sub-flow status transition': 'transition',
218
+ 'Terminal sub-flow status is sealed': 'transition',
219
+ 'Terminal sub-flow model use is sealed': 'sealed',
220
+ 'Cannot undo reported model use': 'sealed',
221
+ 'Cannot overwrite a sub-flow time': 'sealed',
222
+ 'Cannot overwrite sub-flow successors': 'sealed',
223
+ 'Cannot overwrite a sub-flow summary': 'sealed',
224
+ 'Sub-flow log limit exceeded': 'limit',
225
+ };
226
+ /** Map only known own data messages; arbitrary thrown values are described generically. */
227
+ function rejectionReason(error) {
228
+ try {
229
+ const message = error instanceof Error ? Object.getOwnPropertyDescriptor(error, 'message')?.value : undefined;
230
+ return typeof message === 'string' && Object.hasOwn(rejectionMessages, message)
231
+ ? rejectionMessages[message]
232
+ : 'input';
233
+ }
234
+ catch {
235
+ return 'input';
236
+ }
237
+ }
238
+ /** Unexported issuance token prevents constructing counterfeit full or scoped loggers. */
239
+ const loggerToken = Symbol('sub-flow-logger');
240
+ const stores = new WeakMap();
241
+ /** Full execution-owned recorder. Owns its store and may issue restricted delegates. */
242
+ class SubFlowLogger {
243
+ #store;
244
+ constructor(token, store) {
245
+ if (token !== loggerToken)
246
+ throw new TypeError('Use context.createSubFlowLogger()');
247
+ this.#store = store;
248
+ stores.set(this, store);
249
+ Object.freeze(this);
250
+ }
251
+ write(input) {
252
+ return this.#store.write(input);
253
+ }
254
+ fix(stepId, patch) {
255
+ return this.#store.fix(stepId, patch);
256
+ }
257
+ readAll() {
258
+ return this.#store.readAll();
259
+ }
260
+ /** Issue a delegate for an App-owned logical group; no execution or scheduling is implied. */
261
+ fork(subnodeId) {
262
+ if (typeof subnodeId !== 'string' || !subnodeId.trim() || subnodeId.length > 1024)
263
+ throw new TypeError('Invalid subnode id');
264
+ return new ScopedSubFlowLogger(loggerToken, this, subnodeId);
265
+ }
266
+ }
267
+ exports.SubFlowLogger = SubFlowLogger;
268
+ /** Non-inheriting delegate. Stores only the original logger and its bound subnode identity. */
269
+ class ScopedSubFlowLogger {
270
+ #logger;
271
+ #subnodeId;
272
+ constructor(token, logger, subnodeId) {
273
+ if (token !== loggerToken)
274
+ throw new TypeError('Use logger.fork(subnodeId)');
275
+ this.#logger = logger;
276
+ this.#subnodeId = subnodeId;
277
+ Object.freeze(this);
278
+ }
279
+ write(input) {
280
+ if (!stores.get(this.#logger).isActive()) {
281
+ stores.get(this.#logger).warn('write', 'inactive');
282
+ return -1;
283
+ }
284
+ try {
285
+ fields(input, writeFields.filter((field) => field !== 'subnodeId'));
286
+ // Copy descriptors so private content getters remain subject to the original policy.
287
+ const bound = Object.defineProperties({}, Object.getOwnPropertyDescriptors(input));
288
+ Object.defineProperty(bound, 'subnodeId', { value: this.#subnodeId, enumerable: true });
289
+ return this.#logger.write(bound);
290
+ }
291
+ catch (error) {
292
+ stores.get(this.#logger).warn('write', rejectionReason(error));
293
+ return -1;
294
+ }
295
+ }
296
+ fix(stepId, patch) {
297
+ const store = stores.get(this.#logger);
298
+ if (!store.isActive()) {
299
+ store.warn('fix', 'inactive');
300
+ return false;
301
+ }
302
+ if (!store.owns(stepId, this.#subnodeId)) {
303
+ store.warn('fix', 'scope');
304
+ return false;
305
+ }
306
+ return this.#logger.fix(stepId, patch);
307
+ }
308
+ readAll() {
309
+ return this.#logger.readAll();
310
+ }
311
+ }
312
+ exports.ScopedSubFlowLogger = ScopedSubFlowLogger;
313
+ /** Runtime check complements nominal invocation types; scoped or forged inputs cannot invoke. */
314
+ function requireSubFlowLogger(logger) {
315
+ if (logger !== undefined && (!(logger instanceof SubFlowLogger) || !instances.has(logger)))
316
+ throw new TypeError('Native invocation requires a full SubFlowLogger');
317
+ }
318
+ /** Allocate a short node-local group; the owning node visit already supplies the namespace. */
319
+ function bindNativeSubFlowLogger(logger, kind, definitionId) {
320
+ requireSubFlowLogger(logger);
321
+ return logger.fork(stores.get(logger).nextNativeSubnodeId(kind, definitionId));
322
+ }
178
323
  /**
179
324
  * Create a logger bound to one active execution visit.
180
325
  *
@@ -183,51 +328,95 @@ function fill(previous, patch) {
183
328
  *
184
329
  * @param owner Object that owns the visit. `readSubFlowLogger` compares this identity before
185
330
  * returning any step.
186
- * @param active Reports whether the visit is still open; a closed logger rejects writes.
331
+ * @param active Reports whether the visit is still open; a detached logger returns write=-1 and fix=false.
187
332
  * @param content Reports whether content-level detail is enabled. When false, summaries and
188
333
  * payloads are dropped before anything is stored.
189
- * @param changed Called after each accepted write or fix with the progress delta to publish.
334
+ * @param changed Runtime receiver called after each accepted write/fix with its delta and immutable snapshot.
190
335
  * @param identity Diagnostic run and span the logger belongs to; used to reject cross-scope reads.
191
336
  * @returns A frozen logger instance.
192
337
  */
193
338
  function createSubFlowLogger(owner, active, content, changed, identity) {
339
+ let root;
194
340
  class Logger {
195
341
  #steps = Object.freeze([]);
196
- #changes = 0;
342
+ #records = Object.freeze([]);
343
+ #warnings = new Set();
344
+ #nativeCalls = new Map();
197
345
  constructor() {
198
- instances.set(this, { owner, identity, read: () => this.#steps });
346
+ instances.set(this, {
347
+ owner,
348
+ identity,
349
+ read: () => this.#records,
350
+ detach: () => {
351
+ // Retained read-only instances must not keep the runtime or App consumers alive.
352
+ owner = {};
353
+ instances.get(this).owner = owner;
354
+ active = () => false;
355
+ content = () => false;
356
+ changed = () => { };
357
+ },
358
+ });
359
+ snapshots.set(this.#records, { identity });
199
360
  Object.freeze(this);
200
361
  }
201
- #assertActive() {
202
- // A logger that outlived its execution would append evidence to a finished request.
203
- (0, rules_js_1.breakWhile)(!active(), () => new TypeError('Sub-flow logger belongs to a closed execution'));
204
- }
205
362
  #save(step, operation) {
206
- this.#assertActive();
207
- // A fix replaces the step with the same id in place; a write always appends.
363
+ if (!active())
364
+ return false;
365
+ // Logical heads validate revisions; only the immutable ledger is published or queried.
208
366
  const next = operation === 'write'
209
367
  ? [...this.#steps, step]
210
368
  : this.#steps.map((item) => (item.stepId === step.stepId ? step : item));
211
369
  // Successors must resolve inside this logger, otherwise the recorded graph would have
212
370
  // dangling edges.
213
371
  (0, rules_js_1.breakWhile)(step.nextStepIds?.some((id) => !next.some((item) => item.stepId === id)), () => new TypeError('Sub-flow successors must belong to this logger'));
214
- // The step and size caps equal the persisted-record limits; the change cap bounds
215
- // write/fix churn even when the final step count stays small.
216
- (0, rules_js_1.breakWhile)(next.length > 256 || this.#changes >= 1024 || JSON.stringify(next).length > 65536, () => new RangeError('Sub-flow log limit exceeded'));
217
- this.#steps = Object.freeze(next);
218
- this.#changes++;
219
- changed(this, {
372
+ const record = Object.freeze({
373
+ ...step,
220
374
  operation,
221
- stepId: step.stepId,
222
- sequence: step.sequence,
223
- revision: step.revision,
224
- createdAtMs: step.createdAtMs,
225
- status: step.status,
226
- ...(step.usedModel === undefined ? {} : { usedModel: step.usedModel }),
375
+ stepId: operation === 'write' ? step.stepId : (0, invocation_js_1.invocationId)(),
376
+ ...(operation === 'fix' ? { targetStepId: step.stepId } : {}),
377
+ sequence: this.#records.length + 1,
378
+ createdAtMs: Date.now(),
227
379
  });
380
+ const records = Object.freeze([...this.#records, record]);
381
+ (0, rules_js_1.breakWhile)(next.length > 256 || records.length > 1024 || JSON.stringify(records).length > 65536, () => new RangeError('Sub-flow log limit exceeded'));
382
+ if (!active())
383
+ return false;
384
+ this.#steps = Object.freeze(next);
385
+ this.#records = records;
386
+ snapshots.set(records, { identity });
387
+ changed(root, {
388
+ operation,
389
+ type: record.type,
390
+ subnodeId: record.subnodeId,
391
+ relatedInvocationId: record.relatedInvocationId,
392
+ stepId: record.stepId,
393
+ ...(record.targetStepId ? { targetStepId: record.targetStepId } : {}),
394
+ sequence: record.sequence,
395
+ revision: record.revision,
396
+ createdAtMs: record.createdAtMs,
397
+ status: record.status,
398
+ ...(record.usedModel === undefined ? {} : { usedModel: record.usedModel }),
399
+ }, records);
400
+ return true;
228
401
  }
229
402
  write(input) {
230
- this.#assertActive();
403
+ if (!active()) {
404
+ this.warn('write', 'inactive');
405
+ return -1;
406
+ }
407
+ try {
408
+ const id = this.#append(input);
409
+ if (id === -1)
410
+ this.warn('write', 'inactive');
411
+ return id;
412
+ }
413
+ catch (error) {
414
+ this.warn('write', rejectionReason(error));
415
+ return -1;
416
+ }
417
+ }
418
+ /** Validate a new record before committing it; public write returns -1 on failure. */
419
+ #append(input) {
231
420
  fields(input, writeFields);
232
421
  (0, rules_js_1.breakWhile)(input.nextStepIds !== undefined && !Array.isArray(input.nextStepIds), () => new TypeError('Invalid sub-flow successors'));
233
422
  const now = Date.now(), status = input.status === undefined ? 'planned' : input.status;
@@ -236,16 +425,21 @@ function createSubFlowLogger(owner, active, content, changed, identity) {
236
425
  const step = {
237
426
  origin: 'reported',
238
427
  stepId: (0, invocation_js_1.invocationId)(),
239
- sequence: this.#steps.length + 1,
428
+ sequence: this.#records.length + 1,
240
429
  revision: 1,
241
430
  createdAtMs: now,
431
+ type: input.type ?? input.kind,
432
+ ...(input.subnodeId === undefined ? {} : { subnodeId: input.subnodeId }),
242
433
  name: input.name,
243
- kind: input.kind,
434
+ kind: input.kind ?? input.type,
244
435
  status,
245
436
  ...(input.parentStepId === undefined ? {} : { parentStepId: input.parentStepId }),
246
437
  ...(input.relatedDefinitionId === undefined
247
438
  ? {}
248
439
  : { relatedDefinitionId: input.relatedDefinitionId }),
440
+ ...(input.relatedInvocationId === undefined
441
+ ? {}
442
+ : { relatedInvocationId: input.relatedInvocationId }),
249
443
  ...(input.caseId === undefined ? {} : { caseId: input.caseId }),
250
444
  ...(input.usedModel === undefined ? {} : { usedModel: input.usedModel }),
251
445
  ...(input.startedAtMs === undefined && status !== 'running'
@@ -268,11 +462,30 @@ function createSubFlowLogger(owner, active, content, changed, identity) {
268
462
  }
269
463
  validate(step);
270
464
  (0, rules_js_1.breakWhile)(step.parentStepId && !this.#steps.some((item) => item.stepId === step.parentStepId), () => new TypeError('Sub-flow parent must belong to this logger'));
271
- this.#save(Object.freeze(step), 'write');
272
- return step.stepId;
465
+ return this.#save(Object.freeze(step), 'write') ? step.stepId : -1;
273
466
  }
274
467
  fix(stepId, patch) {
275
- this.#assertActive();
468
+ if (!active()) {
469
+ this.warn('fix', 'inactive');
470
+ return false;
471
+ }
472
+ if (stepId === -1) {
473
+ this.warn('fix', 'target');
474
+ return false;
475
+ }
476
+ try {
477
+ const fixed = this.#applyFix(stepId, patch);
478
+ if (!fixed)
479
+ this.warn('fix', 'inactive');
480
+ return fixed;
481
+ }
482
+ catch (error) {
483
+ this.warn('fix', rejectionReason(error));
484
+ return false;
485
+ }
486
+ }
487
+ /** Validate a revision completely before committing it; public fix reports failure as false. */
488
+ #applyFix(stepId, patch) {
276
489
  fields(patch, fixFields);
277
490
  const previous = this.#steps.find((step) => step.stepId === stepId);
278
491
  // The assertion above only satisfies the type checker; this check rejects an unknown id.
@@ -328,13 +541,42 @@ function createSubFlowLogger(owner, active, content, changed, identity) {
328
541
  });
329
542
  }
330
543
  validate(step);
331
- this.#save(Object.freeze(step), 'fix');
544
+ return this.#save(Object.freeze(step), 'fix');
545
+ }
546
+ owns(stepId, subnodeId) {
547
+ return this.#steps.some((step) => step.stepId === stepId && step.subnodeId === subnodeId);
548
+ }
549
+ isActive() {
550
+ return active();
551
+ }
552
+ /** Count starts, including failed/cancelled calls; each node visit owns a fresh store. */
553
+ nextNativeSubnodeId(kind, definitionId) {
554
+ const key = JSON.stringify([kind, definitionId]);
555
+ const next = (this.#nativeCalls.get(key) ?? 0) + 1;
556
+ this.#nativeCalls.set(key, next);
557
+ return `${kind}:${encodeURIComponent(definitionId)}:${next}`;
558
+ }
559
+ /** Deduplicate safe developer warnings in the sole owning store, including delegated calls. */
560
+ warn(operation, reason) {
561
+ const key = `${operation}:${reason}`;
562
+ if (this.#warnings.has(key))
563
+ return;
564
+ this.#warnings.add(key);
565
+ try {
566
+ console.warn(`[agent-core:subflow] ${operation} rejected: ${rejectionReasons[reason]}`);
567
+ }
568
+ catch {
569
+ /* A console extension cannot change logging return values. */
570
+ }
332
571
  }
333
572
  readAll() {
334
- return this.#steps;
573
+ return this.#records;
335
574
  }
336
575
  }
337
- return new Logger();
576
+ const store = new Logger();
577
+ root = new SubFlowLogger(loggerToken, store);
578
+ instances.set(root, instances.get(store));
579
+ return root;
338
580
  }
339
581
  /**
340
582
  * Read the steps recorded by a logger issued by this module.
@@ -357,3 +599,7 @@ function readSubFlowLogger(input, owner, identity) {
357
599
  instance.identity?.spanId !== identity.spanId), () => new TypeError('Sub-flow logger belongs to another diagnostic scope'));
358
600
  return instance.read();
359
601
  }
602
+ /** Release execution callbacks while preserving the instance's immutable query history. */
603
+ function detachSubFlowLogger(logger) {
604
+ instances.get(logger)?.detach();
605
+ }
@@ -658,6 +658,7 @@ class AgentCoreEngine {
658
658
  throw new errors_js_2.WorkflowContractError('Prepared model call belongs to another execution or model');
659
659
  const runner = owner?.runner ?? runnerFor(request.modelId);
660
660
  return runner.invoke({
661
+ logger: request.logger,
661
662
  stageId: request.stageId,
662
663
  maxOutputTokens: request.maxOutputTokens,
663
664
  transport: request.transport,
@@ -897,14 +898,14 @@ class AgentCoreEngine {
897
898
  try {
898
899
  const result = (0, synchronous_js_1.synchronous)(report(), 'Workflow reports must be synchronous');
899
900
  reports?.receive(result);
900
- detail = { payload: structuredClone(result.payload), subs: result.subs };
901
+ detail = { payload: structuredClone(result.payload) };
901
902
  }
902
903
  catch {
903
904
  /* A report cannot suppress the workflow event. */
904
905
  }
905
906
  }
906
907
  reports?.close();
907
- diagnostics?.emit('workflow.event', () => ({ event, ...(detail?.payload === undefined ? {} : { payload: detail.payload }) }), reports?.debug(detail));
908
+ diagnostics?.emit('workflow.event', () => ({ event, ...(detail?.payload === undefined ? {} : { payload: detail.payload }) }), reports?.debug());
908
909
  return event;
909
910
  }
910
911
  /**
@@ -12,7 +12,7 @@ exports.executionEvents = executionEvents;
12
12
  *
13
13
  * Exports: `createEventScope` and `executionEvents`.
14
14
  */
15
- const debug_js_1 = require("../utils/debug.js");
15
+ const sub_flow_js_1 = require("../diagnostics/sub-flow.js");
16
16
  const invocation_js_1 = require("../utils/invocation.js");
17
17
  const errors_js_1 = require("../errors.js");
18
18
  const catalog_js_1 = require("../diagnostics/catalog.js");
@@ -66,9 +66,7 @@ function createEventScope(options = {}) {
66
66
  };
67
67
  const fact = (event, payload, debug) => {
68
68
  const value = payload();
69
- const reported = scope.enabled(event, 'content')
70
- ? (0, debug_js_1.captureSubSteps)(debug?.subSteps, identity)
71
- : (0, debug_js_1.captureSubSteps)(undefined);
69
+ const reported = (0, sub_flow_js_1.captureSubFlowSnapshot)(scope.enabled(event, 'content') ? debug?.recordedSubSteps : undefined, identity);
72
70
  return {
73
71
  ...identity,
74
72
  ...reported,
@@ -219,7 +217,9 @@ function metadataPayload(type, payload) {
219
217
  function eventSnapshot(record, content = true) {
220
218
  const metadata = () => ({
221
219
  ...record,
222
- ...(0, debug_js_1.captureSubSteps)(undefined),
220
+ subSteps: [],
221
+ subStepsDropped: 0,
222
+ subStepsTruncated: false,
223
223
  payload: metadataPayload(record.type, record.payload),
224
224
  });
225
225
  try {
@@ -26,6 +26,7 @@ class NodeReports {
26
26
  active = true;
27
27
  logger;
28
28
  usedModel;
29
+ steps = Object.freeze([]);
29
30
  /**
30
31
  * @param diagnostics - Diagnostic scope of the visit; omitted when observation is off.
31
32
  * @param signal - Execution signal; an aborted signal suppresses all further reports.
@@ -39,6 +40,9 @@ class NodeReports {
39
40
  /** End the visit; later reports and logger creation are ignored or rejected. */
40
41
  close() {
41
42
  this.active = false;
43
+ if (this.logger)
44
+ (0, sub_flow_js_1.detachSubFlowLogger)(this.logger);
45
+ this.logger = undefined;
42
46
  }
43
47
  /**
44
48
  * Validate a report and retain its model-use flag.
@@ -51,34 +55,18 @@ class NodeReports {
51
55
  */
52
56
  receive(update) {
53
57
  (0, rules_js_1.breakWhile)(!update || typeof update !== 'object', () => new TypeError('Invalid node report'));
58
+ (0, rules_js_1.breakWhile)(Object.hasOwn(update, 'subs'), () => new TypeError('Sub-flow logs are recorded automatically'));
54
59
  const usedModel = update.usedModel;
55
60
  (0, rules_js_1.breakWhile)(usedModel !== undefined &&
56
61
  (typeof usedModel !== 'boolean' || (this.usedModel === true && !usedModel)), () => new TypeError('Invalid reported model use'));
57
62
  if (usedModel !== undefined)
58
63
  this.usedModel = usedModel;
59
64
  }
60
- /**
61
- * Build the debug view of this visit.
62
- *
63
- * Getters keep the values live while the fact is being built; `subSteps` prefers the
64
- * logger supplied in the report and falls back to the visit's own logger.
65
- *
66
- * @param update - Optional report whose logger replaces the visit logger.
67
- * @returns A debug output object consumed by the diagnostic pipeline.
68
- */
69
- debug(update) {
70
- const self = this;
65
+ /** Return the runtime-owned snapshot already received from write/fix. */
66
+ debug() {
71
67
  return {
72
- get usedModel() {
73
- return self.usedModel;
74
- },
75
- subSteps: () => {
76
- const supplied = update?.subs;
77
- const logger = supplied === undefined ? this.logger : supplied;
78
- if (logger !== undefined)
79
- (0, sub_flow_js_1.readSubFlowLogger)(logger, this);
80
- return logger;
81
- },
68
+ usedModel: this.usedModel,
69
+ recordedSubSteps: this.steps.length ? this.steps : undefined,
82
70
  };
83
71
  }
84
72
  /**
@@ -95,7 +83,7 @@ class NodeReports {
95
83
  return;
96
84
  try {
97
85
  this.receive(update);
98
- this.diagnostics?.emit('node.progress', () => this.diagnostics?.enabled('node.progress', 'content') ? { payload: update.payload } : {}, this.debug(update));
86
+ this.diagnostics?.emit('node.progress', () => this.diagnostics?.enabled('node.progress', 'content') ? { payload: update.payload } : {}, this.debug());
99
87
  }
100
88
  catch {
101
89
  this.diagnostics?.emit('node.progress', () => ({ reportError: 'INVALID_NODE_REPORT' }));
@@ -104,8 +92,9 @@ class NodeReports {
104
92
  /**
105
93
  * Create (once) the logger that records sub-steps for this visit.
106
94
  *
107
- * Sub-steps are only collected when progress or the terminal event carries content, so
108
- * a metadata-only observer pays no logging cost.
95
+ * Status metadata is retained for the active visit. Summary and payload capture require
96
+ * content permission from progress or the terminal event. The bound runtime receives
97
+ * every accepted revision before notifying consumers.
109
98
  *
110
99
  * @returns The visit's logger, identical on repeated calls.
111
100
  * @throws TypeError When the visit is already closed or the execution was aborted.
@@ -113,10 +102,11 @@ class NodeReports {
113
102
  createSubFlowLogger() {
114
103
  (0, rules_js_1.breakWhile)(!this.active || this.signal?.aborted, () => new TypeError('Cannot create a logger for a closed execution'));
115
104
  return (this.logger ??= (0, sub_flow_js_1.createSubFlowLogger)(this, () => this.active && !this.signal?.aborted, () => this.diagnostics?.enabled('node.progress', 'content') === true ||
116
- this.diagnostics?.enabled(this.terminalEvent, 'content') === true, (logger, progress) => this.diagnostics?.emit('node.progress', () => ({ subStep: progress }), {
117
- usedModel: this.usedModel,
118
- subSteps: () => logger,
119
- }), this.diagnostics?.identity));
105
+ this.diagnostics?.enabled(this.terminalEvent, 'content') === true, (_logger, progress, steps) => {
106
+ // Commit before notifying consumers; failed listeners cannot lose the accepted revision.
107
+ this.steps = steps;
108
+ this.diagnostics?.emit('node.progress', () => ({ subStep: progress }), this.debug());
109
+ }, this.diagnostics?.identity));
120
110
  }
121
111
  }
122
112
  exports.NodeReports = NodeReports;