@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.
- package/CHANGELOG.md +44 -0
- package/README.md +386 -50
- package/development/docs/api/README.en.md +4 -0
- package/development/docs/api/README.md +4 -0
- package/development/docs/api/decorators.en.md +83 -0
- package/development/docs/api/decorators.md +108 -0
- package/development/docs/api/observability.en.md +37 -11
- package/development/docs/api/observability.md +64 -11
- package/development/docs/api/reference.en.md +469 -348
- package/development/docs/api/reference.md +225 -137
- package/development/docs/api/types.en.md +1 -0
- package/development/docs/api/types.md +1 -0
- package/development/docs/api/workflow-views.en.md +64 -0
- package/development/docs/api/workflow-views.md +78 -0
- package/development/docs/api/workflow.en.md +6 -2
- package/development/docs/api/workflow.md +6 -2
- package/development/skills/agent-core-app/SKILL.md +16 -0
- package/dist/cjs/capabilities/invocation.js +21 -1
- package/dist/cjs/capabilities/tool-runtime.js +8 -1
- package/dist/cjs/capabilities/tools.js +12 -17
- package/dist/cjs/diagnostics/index.js +51 -3
- package/dist/cjs/diagnostics/native-sub-flow.js +106 -0
- package/dist/cjs/diagnostics/sub-flow.js +278 -32
- package/dist/cjs/execution/engine.js +3 -2
- package/dist/cjs/execution/events.js +5 -5
- package/dist/cjs/execution/reporting.js +18 -28
- package/dist/cjs/execution/workflow-chain.js +211 -0
- package/dist/cjs/execution/workflow-graph.js +72 -0
- package/dist/cjs/execution/workflow.js +34 -6
- package/dist/cjs/kits/aggregation.js +17 -14
- package/dist/cjs/kits/answering/checks.js +2 -1
- package/dist/cjs/kits/answering/flow.js +24 -21
- package/dist/cjs/kits/answering/tasks.js +25 -20
- package/dist/cjs/kits/delivery.js +36 -24
- package/dist/cjs/kits/knowledge/catalog.js +33 -30
- package/dist/cjs/kits/policies.js +3 -2
- package/dist/cjs/kits/presentation/outputs.js +2 -1
- package/dist/cjs/kits/retrieval.js +7 -1
- package/dist/cjs/kits/tools.js +1 -0
- package/dist/cjs/model/decision-retry.js +2 -1
- package/dist/cjs/model/runner.js +20 -5
- package/dist/cjs/structs/execution/workflow-views.js +2 -0
- package/dist/cjs/support/context/nodes.js +48 -45
- package/dist/cjs/support/context/processing.js +6 -3
- package/dist/cjs/support/decorators/definition.js +42 -0
- package/dist/cjs/support/decorators/index.js +63 -0
- package/dist/cjs/support/decorators/pool.js +87 -0
- package/dist/cjs/support/decorators/types.js +2 -0
- package/dist/cjs/utils/invocation.js +1 -0
- package/dist/esm/capabilities/invocation.js +21 -1
- package/dist/esm/capabilities/tool-runtime.js +8 -1
- package/dist/esm/capabilities/tools.js +12 -17
- package/dist/esm/diagnostics/index.d.ts +1 -0
- package/dist/esm/diagnostics/index.js +48 -2
- package/dist/esm/diagnostics/native-sub-flow.d.ts +94 -0
- package/dist/esm/diagnostics/native-sub-flow.js +101 -0
- package/dist/esm/diagnostics/sub-flow.d.ts +56 -4
- package/dist/esm/diagnostics/sub-flow.js +271 -32
- package/dist/esm/execution/engine.d.ts +1 -0
- package/dist/esm/execution/engine.js +3 -2
- package/dist/esm/execution/events.js +5 -5
- package/dist/esm/execution/reporting.d.ts +6 -12
- package/dist/esm/execution/reporting.js +19 -29
- package/dist/esm/execution/workflow-chain.d.ts +3 -0
- package/dist/esm/execution/workflow-chain.js +208 -0
- package/dist/esm/execution/workflow-graph.d.ts +7 -0
- package/dist/esm/execution/workflow-graph.js +68 -0
- package/dist/esm/execution/workflow.d.ts +6 -0
- package/dist/esm/execution/workflow.js +34 -6
- package/dist/esm/kits/aggregation.d.ts +1 -1
- package/dist/esm/kits/aggregation.js +17 -14
- package/dist/esm/kits/answering/checks.js +2 -1
- package/dist/esm/kits/answering/flow.js +24 -21
- package/dist/esm/kits/answering/tasks.js +25 -20
- package/dist/esm/kits/delivery.d.ts +0 -9
- package/dist/esm/kits/delivery.js +36 -24
- package/dist/esm/kits/knowledge/catalog.js +33 -30
- package/dist/esm/kits/policies.d.ts +1 -1
- package/dist/esm/kits/policies.js +3 -2
- package/dist/esm/kits/presentation/outputs.js +2 -1
- package/dist/esm/kits/retrieval.js +7 -1
- package/dist/esm/kits/tools.js +1 -0
- package/dist/esm/model/decision-retry.d.ts +2 -0
- package/dist/esm/model/decision-retry.js +2 -1
- package/dist/esm/model/runner.d.ts +2 -0
- package/dist/esm/model/runner.js +20 -5
- package/dist/esm/structs/capabilities/invocation.d.ts +2 -0
- package/dist/esm/structs/capabilities/tools.d.ts +7 -3
- package/dist/esm/structs/common.d.ts +2 -0
- package/dist/esm/structs/debug.d.ts +36 -20
- package/dist/esm/structs/execution/workflow-views.d.ts +123 -0
- package/dist/esm/structs/execution/workflow-views.js +1 -0
- package/dist/esm/structs/execution/workflow.d.ts +4 -3
- package/dist/esm/structs/index.d.ts +1 -0
- package/dist/esm/structs/model/runtime.d.ts +2 -0
- package/dist/esm/support/context/nodes.js +48 -45
- package/dist/esm/support/context/processing.js +6 -3
- package/dist/esm/support/decorators/definition.d.ts +19 -0
- package/dist/esm/support/decorators/definition.js +38 -0
- package/dist/esm/support/decorators/index.d.ts +39 -0
- package/dist/esm/support/decorators/index.js +48 -0
- package/dist/esm/support/decorators/pool.d.ts +97 -0
- package/dist/esm/support/decorators/pool.js +83 -0
- package/dist/esm/support/decorators/types.d.ts +22 -0
- package/dist/esm/support/decorators/types.js +1 -0
- package/dist/esm/utils/invocation.d.ts +1 -0
- package/dist/esm/utils/invocation.js +1 -0
- package/package.json +10 -2
- package/dist/cjs/utils/debug.js +0 -30
- package/dist/esm/utils/debug.d.ts +0 -27
- package/dist/esm/utils/debug.js +0 -27
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Workflow graphs and execution chains
|
|
2
|
+
|
|
3
|
+
`workflow.toGraph()` returns registered topology. `await workflow.toChain(runId)` returns observed visits of this Workflow within a run. Both return deeply readonly JSON data, without handlers, condition functions, layout or renderers.
|
|
4
|
+
|
|
5
|
+
These interfaces support developer reviews, debugging, observability clients and demonstrations. Export does not participate in Agent decisions, scheduling or task execution and adds no business capability. A chain is a readonly projection of existing records, not a replay or resume entry. Apps can authorize log-based reflection; graphs and chains do not automatically enter learning or rule modification.
|
|
6
|
+
|
|
7
|
+
## Reading a run
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const workflow = defineWorkflow({
|
|
11
|
+
id: 'assistant', nodes,
|
|
12
|
+
chainSource: async runId => {
|
|
13
|
+
const capture = await authorizedRunReader.readAll(runId);
|
|
14
|
+
return { events: capture.events, coverage: capture.coverage };
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
const graph = workflow.toGraph();
|
|
18
|
+
const chain = await workflow.toChain(runId);
|
|
19
|
+
const exported = await workflow.toChain(runId, authorizedSource);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`WorkflowChainSource` receives a runId and returns `Promise<{ events: readonly CoreDiagnosticRecord[]; coverage: WorkflowChainCoverage }>`. The App owns authorization, account isolation, storage paths, retention, pagination and read limits. Core installs no global run store and does not retain the latest run. A per-call source overrides the bound source without modifying the Workflow. `add()` carries the bound source into the derived Workflow.
|
|
23
|
+
|
|
24
|
+
An empty runId or absent source throws TypeError. Source errors propagate. No matching Workflow visits throws RangeError, rather than returning an apparently successful empty history. Readers must provide a consistent snapshot and state whether the capture is complete, live or uncertain.
|
|
25
|
+
|
|
26
|
+
## WorkflowGraph
|
|
27
|
+
|
|
28
|
+
The root contains `schemaVersion: 1`, `workflowId`, optional `revision`, `entryNodeId`, `nodes` and `edges`.
|
|
29
|
+
|
|
30
|
+
Each `WorkflowGraphNode` contains `id`, `name` (defaults to id), `description`, zero-based `order`, `startsRound`, `requires`, `provides`, `routes`, `incomingEdgeIds` and `outgoingEdgeIds`. Empty collections are arrays. The explicit entry cannot be inferred from incoming edges because loops may lead back to it.
|
|
31
|
+
|
|
32
|
+
Each `WorkflowGraphRoute` contains zero-based `order`, optional `routeId`, `description` and `conditionId`, a `conditional` boolean, and `edgeId`. Order is matching priority. Export never calls `when` or `complete`.
|
|
33
|
+
|
|
34
|
+
Each `WorkflowGraphEdge` contains `id`, `kind: transition | error | complete`, `sourceNodeId`, optional `targetNodeId`, `routeOrders` and optional `errorRouteId`. Routes with the same source, kind and target share one edge while retaining their individual orders. Recovery and normal edges remain separate. Completion has no target node. Recovery has an empty routeOrders array and takes errorRouteId from onErrorRouteId. IDs are deterministic JSON tuples `[sourceNodeId, kind, targetNodeId ?? null]`; consumers should treat them as opaque strings.
|
|
35
|
+
|
|
36
|
+
Registration reads a routes getter exactly once and seals a copied snapshot. The getter must be pure and depend only on immutable definitions. Runtime conditions belong in `when`. Execution and graph export use the same snapshot. Export does not inspect function bodies or infer internal tool calls and custom subnodes.
|
|
37
|
+
|
|
38
|
+
## WorkflowChain
|
|
39
|
+
|
|
40
|
+
The root contains `schemaVersion: 1`, `workflowId`, `runId`, `nodes`, `transitions` and `coverage`. Nodes are sorted by first observed producerSequence, transitions by their original producerSequence.
|
|
41
|
+
|
|
42
|
+
A `WorkflowChainNode` identifies one visit with `spanId` and `nodeId`, plus optional `workflowInvocationId`, `definitionRevision`, `name`, `description`, `kind` and `roundIndex`. Repeated visits and multiple Workflow invocations within one run stay separate. Historical nodes absent from the current graph remain in the chain. Descriptive fields come only from captured events, never from current definitions.
|
|
43
|
+
|
|
44
|
+
Visit fields also include `firstEventSequence`, `lastEventSequence`, optional `enteredAtMs`, `endedAtMs`, `durationMs`, `usedModel`, and mandatory `status`, `events`, `subflow`. Time is epoch milliseconds. Sequence values are original producer values. Status is `running | success | error | cancelled | unknown` for this visit alone; recovery does not overwrite an error with success. Missing exits in a complete capture yield unknown; an entered visit in an incomplete live capture can remain running. TIMEOUT and CANCELLED map to cancelled.
|
|
45
|
+
|
|
46
|
+
Each node event contains `eventId`, `producerSequence`, `occurredAtMs`, `type: enter | progress | transition | complete | error`, and optional `phase`, `routeId`, `conditionId`, `errorCode`, `errorEventId`, `payload`. Sub-step progress is represented by subflow rather than duplicated as ordinary progress.
|
|
47
|
+
|
|
48
|
+
Each `WorkflowChainTransition` contains `eventId`, `producerSequence`, `occurredAtMs`, `type: transition | complete`, `sourceSpanId`, and optional `targetSpanId`, `targetNodeId`, `routeOrder`, `routeId`, `conditionId`, `errorEventId`. Only explicit handoff facts create connections. An error event with a recovery target also creates a transition, with eventId and errorEventId referencing that original error event. Neither adjacency nor timestamps imply a handoff. A selected targetSpanId does not prove that its entry was captured; missing target visits are reported.
|
|
49
|
+
|
|
50
|
+
### Ordered subflow
|
|
51
|
+
|
|
52
|
+
`WorkflowSubflowRecord` retains RecordedSubStep fields: `origin`, `stepId`, `operation`, `targetStepId`, `sequence`, `createdAtMs`, `revision`, `subnodeId`, `type`, `kind`, `name`, `status`, `usedModel`, `startedAtMs`, `endedAtMs`, `relatedDefinitionId`, `relatedInvocationId`, `parentStepId`, `nextStepIds`, `caseId`, `summary`, `payload`.
|
|
53
|
+
|
|
54
|
+
Records stay in node-local sequence order. Every fix retains its own stepId and targetStepId; original writes are never overwritten. Repeated snapshots deduplicate by stepId within one span. Direct progress evidence adds `sourceEventId` and `producerSequence`. Records found only in later snapshots receive no fabricated emission sequence. Historical operation/createdAtMs/revision omissions remain absent and are reported. Metadata captures may omit name, kind, payload and other content.
|
|
55
|
+
|
|
56
|
+
subnodeId and type are caller-defined strings. Records without subnodeId belong to the main node's own process. Core creates no subnodes array, implicit group, merged view or dependency between subnodes. Consumer DataModels choose grouping, fix projection, filtering and expansion. A group need not be contiguous or sequential.
|
|
57
|
+
|
|
58
|
+
### Coverage
|
|
59
|
+
|
|
60
|
+
`complete: boolean | 'unknown'` states source event coverage; projection issues downgrade it to false. `content: included | omitted | mixed | unknown` independently describes content capture. Omitted content does not imply missing events. Each issue has a `code` and optional `spanId`, `eventId`, `count`.
|
|
61
|
+
|
|
62
|
+
Core issue codes are `conflicting-event`, `invalid-workflow-event`, `missing-node-id`, `conflicting-span`, `subflow-truncated`, `conflicting-subflow`, `invalid-subflow-progress`, `missing-enter`, `missing-exit`, `historical-subflow-fields`, `missing-target-enter`. Apps can supply additional read-side issue codes. Invalid v2 diagnostic envelopes throw through normalizeDiagnosticEvent rather than becoming apparently valid history.
|
|
63
|
+
|
|
64
|
+
Readonly types are exported from the root, `/engine`, `/types`, and `/structs`. See the [symbol reference](reference.en.md).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# 工作流图与执行链
|
|
2
|
+
|
|
3
|
+
`workflow.toGraph()` 返回注册定义的纯拓扑,`await workflow.toChain(runId)` 返回一次运行中该 Workflow 的实际节点执行。两者都是深度只读、可 JSON 序列化的数据,不包含 handler、条件函数、布局或渲染器。
|
|
4
|
+
|
|
5
|
+
这两个接口用于开发审查、调试、观测界面和演示。导出不参与 Agent 的决策、调度或任务执行,不增加业务能力;执行链是既有记录的只读投影,不是重放或恢复入口。日志可由 App 授权接入复盘,图与执行链没有自动接入学习或规则修改。
|
|
6
|
+
|
|
7
|
+
## 数据源与调用
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const workflow = defineWorkflow({
|
|
11
|
+
id: 'assistant',
|
|
12
|
+
nodes,
|
|
13
|
+
chainSource: async runId => {
|
|
14
|
+
const capture = await authorizedRunReader.readAll(runId);
|
|
15
|
+
return { events: capture.events, coverage: capture.coverage };
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const graph = workflow.toGraph();
|
|
19
|
+
const chain = await workflow.toChain(runId);
|
|
20
|
+
// 临时导出可传入本次专用数据源,不修改 Workflow。
|
|
21
|
+
const exported = await workflow.toChain(runId, authorizedSource);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`chainSource: WorkflowChainSource` 是 App 的只读接口,接收 `runId`,返回 `Promise<{ events: readonly CoreDiagnosticRecord[]; coverage: WorkflowChainCoverage }>`。App 负责授权、账户隔离、存储路径解析、保留期、分页和读取边界。Core 不保存全局运行日志,也不从最近一次运行取得数据。`add()` 返回的新 Workflow 沿用绑定的数据源。
|
|
25
|
+
|
|
26
|
+
空 runId 或未配置数据源抛出 `TypeError`;数据源读取错误原样传播;没有该 run/Workflow 的节点执行记录时抛出 `RangeError`。这与成功返回空图不同。读取期间 App 应返回一致快照,并对正在运行、缺失或无法证明完整的记录准确填写 coverage。
|
|
27
|
+
|
|
28
|
+
## WorkflowGraph
|
|
29
|
+
|
|
30
|
+
| 字段 | 类型与含义 |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| schemaVersion | `1` |
|
|
33
|
+
| workflowId / revision | 定义 ID/可选定义版本 |
|
|
34
|
+
| entryNodeId | 显式入口,不能用入边数推断 |
|
|
35
|
+
| nodes | 按注册顺序排列的 `WorkflowGraphNode[]` |
|
|
36
|
+
| edges | 按节点和路由声明顺序生成的 `WorkflowGraphEdge[]` |
|
|
37
|
+
|
|
38
|
+
节点字段:`id`、`name`(未声明时为 id)、`description`、`order`(从 0 开始)、`startsRound`、`requires`、`provides`、`routes`、`incomingEdgeIds`、`outgoingEdgeIds`。空合同及边集合返回空数组。
|
|
39
|
+
|
|
40
|
+
路由字段:`order`(节点内从 0 开始)、可选 `routeId`/`description`/`conditionId`、`conditional: boolean`、`edgeId`。路由顺序是匹配优先级;导图不执行 `when` 或 `complete`。
|
|
41
|
+
|
|
42
|
+
边字段:`id`、`kind: 'transition' | 'error' | 'complete'`、`sourceNodeId`、可选 `targetNodeId`、`routeOrders: number[]`、可选 `errorRouteId`。同一源节点、种类、目标合成一条边,多条路由通过 `routeOrders` 保留;普通跳转与错误恢复分别成边。完成边没有目标节点;错误边的 routeOrders 为空,errorRouteId 来自 onErrorRouteId。边 ID 是稳定的 JSON 元组 `[sourceNodeId, kind, targetNodeId ?? null]`,使用方将其视为不透明字符串。
|
|
43
|
+
|
|
44
|
+
注册时仅读取一次 `get routes()`,复制并封存路由。getter 必须是只依赖不可变定义的纯函数;运行条件应写在 `when` 中。执行与导图共享注册快照。导图不分析函数体,因此不包含节点内部工具调用或自定义子节点。
|
|
45
|
+
|
|
46
|
+
## WorkflowChain
|
|
47
|
+
|
|
48
|
+
| 字段 | 类型与含义 |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| schemaVersion | `1` |
|
|
51
|
+
| workflowId / runId | 查询的 Workflow 与运行 ID |
|
|
52
|
+
| nodes | 按首次观察的 producerSequence 排序的节点执行 |
|
|
53
|
+
| transitions | 按 producerSequence 排序的真实跳转/完成事件 |
|
|
54
|
+
| coverage | 完整性、内容策略与缺失诊断 |
|
|
55
|
+
|
|
56
|
+
`WorkflowChainNode` 的身份是 `spanId`,另含 `nodeId`、可选 `workflowInvocationId`/`definitionRevision`/`name`/`description`/`kind`/`roundIndex`。同一节点的多次进入和同一 run 中多次调用 Workflow 均保留独立执行;历史或图外 nodeId 不丢弃,名称等只取历史日志,不从当前定义补齐。
|
|
57
|
+
|
|
58
|
+
每次执行还包含 `firstEventSequence`、`lastEventSequence`、可选 `enteredAtMs`/`endedAtMs`/`durationMs`/`usedModel`、`status`、`events`、`subflow`。时间为 epoch 毫秒,序号保留原始 producerSequence,不重新编号。status 为 `running | success | error | cancelled | unknown`,只反映本节点,错误恢复跳转不把失败节点改成成功。完整记录缺少退出时返回 unknown;不完整快照中已经进入但尚未退出的节点为 running。TIMEOUT 与 CANCELLED 均为 cancelled。
|
|
59
|
+
|
|
60
|
+
`events` 保存节点生命周期和普通 progress:`eventId`、`producerSequence`、`occurredAtMs`、`type: enter | progress | transition | complete | error`,以及可选 `phase`、`routeId`、`conditionId`、`errorCode`、`errorEventId`、`payload`。专用于 subStep 的 progress 归入 subflow,不重复记为普通 progress。
|
|
61
|
+
|
|
62
|
+
`WorkflowChainTransition` 字段为 `eventId`、`producerSequence`、`occurredAtMs`、`type: transition | complete`、`sourceSpanId`,以及可选 `targetSpanId`、`targetNodeId`、`routeOrder`、`routeId`、`conditionId`、`errorEventId`。只有实际事件才生成连线,不按相邻顺序或时间猜测。错误事件声明恢复目标时也生成 transition,其 eventId 与 errorEventId 均关联该原始错误事件。targetSpanId 只是选择后继的证据,不代替后继 enter;缺失后继记录会进入 coverage。
|
|
63
|
+
|
|
64
|
+
### 有序 subflow
|
|
65
|
+
|
|
66
|
+
`subflow` 是按节点内 sequence 排列的原始日志。它保留 `RecordedSubStep` 字段:`origin`、`stepId`、`operation`、`targetStepId`、`sequence`、`createdAtMs`、`revision`、`subnodeId`、`type`、`kind`、`name`、`status`、`usedModel`、`startedAtMs`、`endedAtMs`、`relatedDefinitionId`、`relatedInvocationId`、`parentStepId`、`nextStepIds`、`caseId`、`summary`、`payload`。
|
|
67
|
+
|
|
68
|
+
每次 fix 保留独立 stepId 和 targetStepId,不合并或改写原记录。重复事件快照按本次 spanId 内的 stepId 去重。直接捕获写入/修正 progress 时补充 `sourceEventId` 和 `producerSequence`;仅在后续快照找到的记录不伪造原始事件序号。旧日志缺失 operation/createdAtMs/revision 时保留缺失并报告 historical-subflow-fields;元数据日志允许 name/kind/payload 等内容字段缺失。
|
|
69
|
+
|
|
70
|
+
subnodeId 和 type 由调用方定义;没有 subnodeId 的记录属于主节点自己的过程。Core 不生成 subnodes、默认分组、合并视图或子节点之间的连线。展示端可依自己的 DataModel 分组、投影多次 fix、过滤或展开;同组不保证连续,不推导串行或依赖。
|
|
71
|
+
|
|
72
|
+
### Coverage
|
|
73
|
+
|
|
74
|
+
`complete: boolean | 'unknown'` 表示数据源声明的事件覆盖;投影发现问题时降为 false。`content: included | omitted | mixed | unknown` 独立说明内容采集策略,未采集正文不等于事件丢失。`issues` 每项含 `code` 和可选 `spanId`/`eventId`/`count`。
|
|
75
|
+
|
|
76
|
+
Core 会追加 `conflicting-event`、`invalid-workflow-event`、`missing-node-id`、`conflicting-span`、`subflow-truncated`、`conflicting-subflow`、`invalid-subflow-progress`、`missing-enter`、`missing-exit`、`historical-subflow-fields`、`missing-target-enter`。App 可附加自己的读取诊断码。当前 v2 诊断封装不合法时由 normalizeDiagnosticEvent 抛错;Core 不把不可解析的数据伪装成成功历史。
|
|
77
|
+
|
|
78
|
+
精确的 readonly 类型通过根入口、`/engine`、`/types` 和 `/structs` 导出,见[公开符号参考](reference.md)。
|
|
@@ -176,7 +176,7 @@ Workflow uniformly handles identifier collisions and placeholders in existing co
|
|
|
176
176
|
## Node reports
|
|
177
177
|
|
|
178
178
|
The Loop automatically records node entry, jumps, completion, and exceptions. When a node needs to add business details, it can provide
|
|
179
|
-
`report(update, context)` in its definition, returning `{ usedModel, payload
|
|
179
|
+
`report(update, context)` in its definition, returning `{ usedModel, payload }`, where every field is optional. Sub-logs use the execution-bound `context.createSubFlowLogger()`; write/fix commit immediately without returning or submitting the instance. This completion callback is called only when diagnostics allow content, and it reads a copy of the state update; a `usedModel` that must be retained in metadata mode should be submitted during the run through `context.report`.
|
|
180
180
|
The Loop attaches this supplementary information to the node's completion or jump record. If reporting fails, the original node event is still recorded.
|
|
181
181
|
|
|
182
182
|
|
|
@@ -185,7 +185,7 @@ infers the output type from the object contract, and the same field also accepts
|
|
|
185
185
|
|
|
186
186
|
### Reports during node execution
|
|
187
187
|
|
|
188
|
-
`run(state, context)` can call `context.report({ usedModel, payload
|
|
188
|
+
`run(state, context)` can call `context.report({ usedModel, payload })` to report progress. usedModel is optional boolean metadata about this node itself and remains unknown when not reported; payload is read only when content is allowed. `context.createSubFlowLogger()` returns the unique logger for this execution; write/fix commit automatically and readAll is a read-only query for any holder. write records the intent first and returns an ID, fix adds later results, and readAll returns an immutable snapshot in a fixed format; writes and completions reuse the existing node.progress, and a short circuit or throw still preserves the start record already sent. Reports do not submit business state and do not determine routing; the Loop adds node identity, time, and event ordinal. After cancellation or node completion, the detached logger returns write=-1 and fix=false without throwing; readAll remains readable. The node's `report(update, context)` at completion continues to serve supplementary reporting when content is allowed. For the contract, see [Child log instances](observability.md#sub-log-instances-and-record-first-then-complete).
|
|
189
189
|
|
|
190
190
|
## Node caches
|
|
191
191
|
|
|
@@ -196,3 +196,7 @@ context.cache belongs to the current node. Only get(key, { nodeId }) may read an
|
|
|
196
196
|
Optional Engine option agentCache accepts an AgentCachePort. context.agentCache exposes asynchronous get/has/set/delete/clear, or is absent when no port is supplied. The App receives immutable addresses with nodeId, key, persistence and signal; clear receives an address without key. Port callbacks retain their receiver. Cancellation is checked before dispatch and after completion; it does not roll back an App write already started.
|
|
197
197
|
|
|
198
198
|
Persistence defaults to false. Passing true requires supportsPersistence: true; otherwise the call rejects. Volatile and persistent spaces are independent, with no fallback or mirroring. The App owns capacity, TTL, serialization, user isolation and storage. Cache partitions are not authorization boundaries. Fork resume may explicitly return agentCache to restore a borrowed port; it is never inherited implicitly. Cache data is not checkpoint state or durable business evidence, and captured views must not be used after cancellation.
|
|
199
|
+
|
|
200
|
+
## Developer aids: workflow graphs and execution chains
|
|
201
|
+
|
|
202
|
+
These readonly exports do not participate in Agent scheduling or business execution. `toGraph()` exports registered topology; `toChain(runId)` aggregates observed main-node visits and retains ordered subflow records. See [Workflow graphs and execution chains](workflow-views.en.md) for fields, sources, coverage and consumer responsibilities.
|
|
@@ -177,7 +177,7 @@ Workflow 统一处理已有配置中的标识冲突和占位符,缺失的标
|
|
|
177
177
|
## 节点报告
|
|
178
178
|
|
|
179
179
|
Loop 自动记录节点进入、跳转、完成和异常。节点需要补充业务细节时,可在定义中提供
|
|
180
|
-
`report(update, context)`,返回 `{ usedModel, payload
|
|
180
|
+
`report(update, context)`,返回 `{ usedModel, payload }`,各字段均可省略。子日志通过 `context.createSubFlowLogger()` 绑定当前执行,write/fix 自动实时入账,无需返回或提交实例。这个完成回调只在诊断允许内容时调用,读取的是状态更新的副本;需要在元数据模式下保留的 `usedModel` 应在运行中通过 `context.report` 提交。
|
|
181
181
|
Loop 将这些补充信息附在节点的完成或跳转记录上。报告失败时,原节点事件仍然记录。
|
|
182
182
|
|
|
183
183
|
|
|
@@ -186,7 +186,7 @@ Loop 将这些补充信息附在节点的完成或跳转记录上。报告失败
|
|
|
186
186
|
|
|
187
187
|
### 节点运行中的报告
|
|
188
188
|
|
|
189
|
-
`run(state, context)` 可调用 `context.report({ usedModel, payload
|
|
189
|
+
`run(state, context)` 可调用 `context.report({ usedModel, payload })` 报告进度。usedModel 是本次节点自身的可选布尔元数据,未上报保持未知;payload 仅在内容获准时读取。`context.createSubFlowLogger()` 创建本次执行唯一实例,write/fix 自动入账,无需在报告中提交;readAll 向任何实例持有者提供只读查询。write 先记意图并返回 ID,fix 补后续结果,readAll 返回固定格式不可变快照;写入和补全复用现有 node.progress,短路或抛错也保留已送出的起始记录。报告不提交业务状态,也不决定路由;Loop 补齐节点身份、时间及事件序号。取消或节点结束后 logger 解绑,write 返回 -1、fix 返回 false 且不报错,readAll 持续可读。结束时的节点 `report(update, context)` 继续用于内容获准时的补充报告。合同见[子日志实例](observability.md#子日志实例与先记后补)。
|
|
190
190
|
|
|
191
191
|
## 节点缓存
|
|
192
192
|
|
|
@@ -209,3 +209,7 @@ if (context.agentCache) {
|
|
|
209
209
|
agentCache 每个操作默认 persistence: false;true 必须由 supportsPersistence: true 的 App 端口明确支持,否则拒绝。两种空间不回退、不镜像,delete/clear 仅影响指定空间及当前分区。App 负责容量、TTL、序列化、用户/租户隔离和持久化。节点分区不是授权边界,不复用权限结论。
|
|
210
210
|
|
|
211
211
|
缓存不进入 checkpoint/状态/Durable 恢复。fork 的 resume 可显式返回 agentCache 重新绑定 App 端口;默认不继承。需要重放或持久业务事实的数据仍使用原 state 补丁或 App 存储。取消后不可继续使用捕获的节点缓存视图。
|
|
212
|
+
|
|
213
|
+
## 开发辅助:工作流图与执行链
|
|
214
|
+
|
|
215
|
+
这两个只读导出接口不参与 Agent 的实际调度或业务执行。`toGraph()` 导出注册拓扑,`toChain(runId)` 读取并聚合真实主节点执行,节点内部保留有序 subflow。字段、数据源、覆盖与展示边界见[工作流图与执行链](workflow-views.md)。
|
|
@@ -74,3 +74,19 @@ A custom response collector is synchronous and request-local. Its outputChars mu
|
|
|
74
74
|
## Node cache ownership
|
|
75
75
|
|
|
76
76
|
Use context.cache for bounded Engine memory; its default capacity is 1024, partitions are node-local and only get may name another node. The low-level step partition is `$step`. An App may explicitly inject an asynchronous AgentCachePort for context.agentCache. Persistence must be supported explicitly, is separate from volatile storage and never falls back. The App owns user isolation, serialization, capacity and expiry; do not cache authorization decisions. Forks start with empty Engine memory and only regain an App port when resume explicitly supplies agentCache. Keep durable facts in state/App storage, not caches.
|
|
77
|
+
|
|
78
|
+
## Optional definition decorators
|
|
79
|
+
|
|
80
|
+
Use the standard-only /support/decorators entry for class-based definitions or existing host methods. Classes supply original options except id; the decorator constructs once and supplies id. Host methods bind and register automatically during standard instance initialization; read and execute only after successful construction. Constructor failures do not roll back initializer registration. Use long-lived service hosts, never request-scoped ones. Keep helper methods/private fields in their real class, never a duplicate options metadata block or empty carrier. Existing Nest legacy compilation keeps object definitions. No public metadata-plus-function adapters, Engine factories, automatic DI or request lifecycle wrappers are provided. Use nodePool/toolPool/fragmentPool/workflowPool/agentPool/modelPool, without creating pools or manually registering. Decorator ns is a string or string array, default 'default'; same-kind ids are unique within a namespace. Read get(id, ns?), getAll(ns?) or pick(ids, ns?); get may return undefined. Multi-namespace registration shares one definition and preflights all conflicts. ESM/CJS copies share readers within one realm. Read api/decorators.md for registration, receivers and type limits.
|
|
81
|
+
|
|
82
|
+
## Optional native sub-flow logging
|
|
83
|
+
|
|
84
|
+
Use the active context's createSubFlowLogger() when the App chooses to record a node's internal execution. Pass logger explicitly to documented native model/tool requests or Kit execution contexts for fixed lifecycle records; omission adds no native sub-log and does not enable implicit downstream propagation. Keep business steps App-owned. Every successful fix appends its own operation=fix record with a new stepId, append sequence and timestamp, referencing the original write via targetStepId. readAll returns the complete immutable ledger, not a merged view. Detached write returns -1, failed/detached fix returns false, and retained instances remain readable. Follow the shipped API observability chapter for producer-specific call signatures and content policy.
|
|
85
|
+
|
|
86
|
+
Use subnodeId for grouping and type for an arbitrary business log type; operation alone is write/fix. A node may write subnodeId directly or fork a ScopedSubFlowLogger for helper functions. The scoped class delegates to the original and owns no storage; both readAll methods read the full ledger. Scoped writes cannot override the group, scoped fixes cannot revise other groups, and the scoped class cannot fork or be passed to native invoke. Pass the original full logger to actual invocations so Core binds the real invocation identity. Fix has an explicit patch whitelist; forbidden fields and ownership failures return false with safe deduplicated developer warnings.
|
|
87
|
+
|
|
88
|
+
Registered subnode IDs use kind:URL-encoded-definition-id:ordinal within one node visit. Ordinals are allocated at call start per kind/definition and are not reused after failure/cancellation; a new node visit resets them. relatedInvocationId separately links the actual invocation. Do not require callers to parse parent span or invocation UUIDs from subnodeId.
|
|
89
|
+
|
|
90
|
+
### Workflow graph and chain exports
|
|
91
|
+
|
|
92
|
+
Use `workflow.toGraph()` for registered topology and `await workflow.toChain(runId)` with an App-authorized `chainSource` (or per-call source) for observed visits. Keep routes getters pure. Do not infer tool/subnode topology from handler bodies, merge visits by nodeId, infer handoffs from adjacency, or fold fix records in Core. Keep ordered flat subflow; consumers own grouping and rendering. See `../../docs/api/workflow-views.md`.
|
|
@@ -11,6 +11,7 @@ exports.executeInvocation = executeInvocation;
|
|
|
11
11
|
*
|
|
12
12
|
* Exports: `executeInvocation` and the invocation contracts re-exported at the bottom.
|
|
13
13
|
*/
|
|
14
|
+
const native_sub_flow_js_1 = require("../diagnostics/native-sub-flow.js");
|
|
14
15
|
const invocation_js_1 = require("../utils/invocation.js");
|
|
15
16
|
const synchronous_js_1 = require("../utils/synchronous.js");
|
|
16
17
|
const index_js_1 = require("../diagnostics/index.js");
|
|
@@ -31,12 +32,30 @@ const reporting_js_1 = require("../execution/reporting.js");
|
|
|
31
32
|
* signal checks.
|
|
32
33
|
*/
|
|
33
34
|
function executeInvocation(invocation, input, context, ports = {}) {
|
|
35
|
+
let logger;
|
|
36
|
+
try {
|
|
37
|
+
logger = ports.logger?.(context);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* Optional recording cannot block execution. */
|
|
41
|
+
}
|
|
42
|
+
const callId = (0, invocation_js_1.invocationId)();
|
|
43
|
+
return (0, native_sub_flow_js_1.nativeSubFlowSync)({
|
|
44
|
+
logger,
|
|
45
|
+
invocationId: callId,
|
|
46
|
+
get signal() {
|
|
47
|
+
return ports.signal?.(context);
|
|
48
|
+
},
|
|
49
|
+
}, 'invocation', invocation.id, (scopedLogger) => runInvocation(invocation, input, context, ports, scopedLogger, callId));
|
|
50
|
+
}
|
|
51
|
+
/** Execute the existing synchronous lifecycle without changing journal or error precedence. */
|
|
52
|
+
function runInvocation(invocation, input, context, ports, logger, executionId = (0, invocation_js_1.invocationId)()) {
|
|
34
53
|
const toolId = invocation.id;
|
|
35
54
|
// Identify the call before touching the ports: journal and diagnostics records cannot be
|
|
36
55
|
// reconciled with an anonymous handler.
|
|
37
56
|
if (!toolId?.trim())
|
|
38
57
|
throw new TypeError('Invalid invocation handler identity');
|
|
39
|
-
const journal = ports.journal?.(context), id =
|
|
58
|
+
const journal = ports.journal?.(context), id = executionId, startedAtMs = Date.now();
|
|
40
59
|
const diagnostics = ports.diagnostics?.(context)?.child(id, {
|
|
41
60
|
invocationId: id,
|
|
42
61
|
nodeRole: 'invocation',
|
|
@@ -67,6 +86,7 @@ function executeInvocation(invocation, input, context, ports = {}) {
|
|
|
67
86
|
// The helper rejects a promise and drains its rejection: a handler that turns async must
|
|
68
87
|
// fail here instead of escaping as an unhandled rejection.
|
|
69
88
|
(0, synchronous_js_1.synchronous)(invocation.call(input, context, {
|
|
89
|
+
logger,
|
|
70
90
|
mode: 'sync',
|
|
71
91
|
invocationId: id,
|
|
72
92
|
signal: signal ?? new AbortController().signal,
|
|
@@ -30,7 +30,14 @@ function execute(definition, input, context, execution) {
|
|
|
30
30
|
signal.throwIfAborted();
|
|
31
31
|
return value;
|
|
32
32
|
};
|
|
33
|
-
const operation = {
|
|
33
|
+
const operation = {
|
|
34
|
+
app: context,
|
|
35
|
+
signal,
|
|
36
|
+
diagnostics,
|
|
37
|
+
logger: execution.logger,
|
|
38
|
+
report,
|
|
39
|
+
createSubFlowLogger,
|
|
40
|
+
};
|
|
34
41
|
const authorized = (result) => {
|
|
35
42
|
(0, synchronous_js_1.assertion)(result, 'Tool authorization must return void');
|
|
36
43
|
signal.throwIfAborted();
|
|
@@ -14,6 +14,7 @@ exports.executeInvocation = exports.ToolRegistry = void 0;
|
|
|
14
14
|
*
|
|
15
15
|
* Exports: `ToolRegistry`, `executeInvocation`, and the tool/invocation contracts.
|
|
16
16
|
*/
|
|
17
|
+
const native_sub_flow_js_1 = require("../diagnostics/native-sub-flow.js");
|
|
17
18
|
const events_js_1 = require("../execution/events.js");
|
|
18
19
|
const invocation_js_1 = require("../utils/invocation.js");
|
|
19
20
|
const synchronous_js_1 = require("../utils/synchronous.js");
|
|
@@ -119,7 +120,8 @@ class ToolRegistry {
|
|
|
119
120
|
*/
|
|
120
121
|
invoke(request) {
|
|
121
122
|
const call = (0, invocation_js_1.toolInvocation)(request, arguments.length);
|
|
122
|
-
|
|
123
|
+
const callId = (0, invocation_js_1.invocationId)();
|
|
124
|
+
return (0, native_sub_flow_js_1.nativeSubFlowSync)({ ...call, signal: (0, cancellation_js_1.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));
|
|
123
125
|
}
|
|
124
126
|
/**
|
|
125
127
|
* Invoke a registered tool and await its result.
|
|
@@ -131,7 +133,8 @@ class ToolRegistry {
|
|
|
131
133
|
*/
|
|
132
134
|
async invokeAsync(request) {
|
|
133
135
|
const call = (0, invocation_js_1.toolInvocation)(request, arguments.length);
|
|
134
|
-
|
|
136
|
+
const callId = (0, invocation_js_1.invocationId)();
|
|
137
|
+
return (0, native_sub_flow_js_1.nativeSubFlowAsync)({ ...call, signal: (0, cancellation_js_1.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));
|
|
135
138
|
}
|
|
136
139
|
/**
|
|
137
140
|
* Run one call: validate, parse input, execute, parse output, then record the outcome.
|
|
@@ -143,7 +146,7 @@ class ToolRegistry {
|
|
|
143
146
|
* @param requestSignal Per-call signal, combined with the request-lifetime signal.
|
|
144
147
|
* @returns The frozen result on success; every failure path throws.
|
|
145
148
|
*/
|
|
146
|
-
execute(name, input, asynchronous, parentDiagnostics = this.diagnostics, requestSignal) {
|
|
149
|
+
execute(name, input, asynchronous, parentDiagnostics = this.diagnostics, requestSignal, logger, callId = (0, invocation_js_1.invocationId)()) {
|
|
147
150
|
// A tool call stays inside the run that invoked it: mixing two diagnostic trees would
|
|
148
151
|
// produce records with two parents and break run-scoped correlation.
|
|
149
152
|
if (this.diagnostics?.identity?.runId &&
|
|
@@ -155,7 +158,7 @@ class ToolRegistry {
|
|
|
155
158
|
throw this.ports.unavailable?.(name) ?? new Error(`Tool is not registered: ${name}`);
|
|
156
159
|
// Either signal may cancel the call; combining them keeps both lifetimes effective.
|
|
157
160
|
const signal = (0, cancellation_js_1.combineSignals)(this.ports.signal, requestSignal);
|
|
158
|
-
const invocationId =
|
|
161
|
+
const invocationId = callId;
|
|
159
162
|
const diagnostics = parentDiagnostics?.child(invocationId, {
|
|
160
163
|
invocationId,
|
|
161
164
|
nodeRole: 'invocation',
|
|
@@ -193,24 +196,15 @@ class ToolRegistry {
|
|
|
193
196
|
};
|
|
194
197
|
const succeed = (result) => {
|
|
195
198
|
signal.throwIfAborted();
|
|
196
|
-
let debug = reports.debug();
|
|
197
199
|
if (tool.debugSteps && diagnostics?.enabled('tool.result', 'content')) {
|
|
198
200
|
try {
|
|
199
|
-
debug
|
|
200
|
-
subs: (0, synchronous_js_1.synchronous)(tool.debugSteps(result, reporting), 'Tool debug reports must be synchronous'),
|
|
201
|
-
});
|
|
201
|
+
(0, synchronous_js_1.synchronous)(tool.debugSteps(result, reporting), 'Tool debug reports must be synchronous');
|
|
202
202
|
}
|
|
203
|
-
catch
|
|
204
|
-
|
|
205
|
-
// a failure. Defer the error until a consumer actually asks for the sub-steps.
|
|
206
|
-
debug = {
|
|
207
|
-
usedModel: reports.debug().usedModel,
|
|
208
|
-
subSteps: () => {
|
|
209
|
-
throw error;
|
|
210
|
-
},
|
|
211
|
-
};
|
|
203
|
+
catch {
|
|
204
|
+
diagnostics?.emit('node.progress', () => ({ reportError: 'INVALID_NODE_REPORT' }));
|
|
212
205
|
}
|
|
213
206
|
}
|
|
207
|
+
const debug = reports.debug();
|
|
214
208
|
signal.throwIfAborted();
|
|
215
209
|
reports.close();
|
|
216
210
|
diagnostics?.emit('tool.result', () => ({
|
|
@@ -280,6 +274,7 @@ class ToolRegistry {
|
|
|
280
274
|
signal.throwIfAborted();
|
|
281
275
|
phase = 'execute';
|
|
282
276
|
const value = tool.execute(parsed, {
|
|
277
|
+
logger,
|
|
283
278
|
mode: asynchronous ? 'async' : 'sync',
|
|
284
279
|
invocationId,
|
|
285
280
|
signal,
|
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.reportedStepStatuses = exports.reportedStepStatusCatalog = void 0;
|
|
17
|
+
exports.reportedStepStatuses = exports.reportedStepStatusCatalog = exports.ScopedSubFlowLogger = exports.SubFlowLogger = void 0;
|
|
18
18
|
exports.createDiagnostics = createDiagnostics;
|
|
19
19
|
exports.diagnosticError = diagnosticError;
|
|
20
20
|
exports.normalizeDiagnosticEvent = normalizeDiagnosticEvent;
|
|
@@ -37,6 +37,9 @@ const sub_flow_js_1 = require("./sub-flow.js");
|
|
|
37
37
|
const rules_js_1 = require("../utils/rules.js");
|
|
38
38
|
__exportStar(require("../structs/diagnostics.js"), exports);
|
|
39
39
|
__exportStar(require("./catalog.js"), exports);
|
|
40
|
+
var sub_flow_js_2 = require("./sub-flow.js");
|
|
41
|
+
Object.defineProperty(exports, "SubFlowLogger", { enumerable: true, get: function () { return sub_flow_js_2.SubFlowLogger; } });
|
|
42
|
+
Object.defineProperty(exports, "ScopedSubFlowLogger", { enumerable: true, get: function () { return sub_flow_js_2.ScopedSubFlowLogger; } });
|
|
40
43
|
var debug_js_2 = require("../structs/debug.js");
|
|
41
44
|
Object.defineProperty(exports, "reportedStepStatusCatalog", { enumerable: true, get: function () { return debug_js_2.reportedStepStatusCatalog; } });
|
|
42
45
|
Object.defineProperty(exports, "reportedStepStatuses", { enumerable: true, get: function () { return debug_js_2.reportedStepStatuses; } });
|
|
@@ -129,7 +132,7 @@ function normalizeDiagnosticEvent(input) {
|
|
|
129
132
|
// The count and size bounds match what the sub-flow logger enforces, so a record produced by
|
|
130
133
|
// Core can never be rejected here; only hand-edited or foreign records can.
|
|
131
134
|
if (!Array.isArray(steps) ||
|
|
132
|
-
steps.length >
|
|
135
|
+
steps.length > 1024 ||
|
|
133
136
|
new Set(steps.map((step) => step?.stepId)).size !== steps.length ||
|
|
134
137
|
steps.some((step) => !step ||
|
|
135
138
|
step.origin !== 'reported' ||
|
|
@@ -140,7 +143,15 @@ function normalizeDiagnosticEvent(input) {
|
|
|
140
143
|
!Number.isSafeInteger(step.sequence) ||
|
|
141
144
|
step.sequence < 1 ||
|
|
142
145
|
(step.status !== undefined && !debug_js_1.reportedStepStatuses.includes(step.status)) ||
|
|
143
|
-
[
|
|
146
|
+
[
|
|
147
|
+
'type',
|
|
148
|
+
'subnodeId',
|
|
149
|
+
'parentStepId',
|
|
150
|
+
'relatedDefinitionId',
|
|
151
|
+
'relatedInvocationId',
|
|
152
|
+
'caseId',
|
|
153
|
+
'summary',
|
|
154
|
+
].some((key) => step[key] !== undefined && typeof step[key] !== 'string') ||
|
|
144
155
|
(step.usedModel !== undefined && typeof step.usedModel !== 'boolean') ||
|
|
145
156
|
(step.revision !== undefined &&
|
|
146
157
|
(!Number.isSafeInteger(step.revision) ||
|
|
@@ -155,6 +166,37 @@ function normalizeDiagnosticEvent(input) {
|
|
|
155
166
|
(!Number.isSafeInteger(raw.subStepsDropped) || Number(raw.subStepsDropped) < 0)) ||
|
|
156
167
|
(raw.subStepsTruncated !== undefined && typeof raw.subStepsTruncated !== 'boolean'))
|
|
157
168
|
throw new TypeError('Invalid reported diagnostic steps');
|
|
169
|
+
const originals = new Map();
|
|
170
|
+
let lastSequence = 0;
|
|
171
|
+
for (const step of steps) {
|
|
172
|
+
if (step.operation === undefined)
|
|
173
|
+
continue; // Historical projected snapshots remain readable.
|
|
174
|
+
(0, rules_js_1.breakWhile)(!['write', 'fix'].includes(step.operation) || step.sequence <= lastSequence, () => new TypeError('Invalid sub-flow record order'));
|
|
175
|
+
lastSequence = step.sequence;
|
|
176
|
+
if (step.operation === 'write') {
|
|
177
|
+
(0, rules_js_1.breakWhile)(step.targetStepId !== undefined || step.revision !== 1, () => new TypeError('Invalid sub-flow write record'));
|
|
178
|
+
originals.set(step.stepId, {
|
|
179
|
+
revision: 1,
|
|
180
|
+
subnodeId: step.subnodeId,
|
|
181
|
+
type: step.type,
|
|
182
|
+
relatedInvocationId: step.relatedInvocationId,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const previous = originals.get(step.targetStepId);
|
|
187
|
+
(0, rules_js_1.breakWhile)(previous === undefined ||
|
|
188
|
+
step.revision !== previous.revision + 1 ||
|
|
189
|
+
step.subnodeId !== previous.subnodeId ||
|
|
190
|
+
step.type !== previous.type ||
|
|
191
|
+
step.relatedInvocationId !== previous.relatedInvocationId, () => new TypeError('Invalid sub-flow fix reference'));
|
|
192
|
+
originals.set(step.targetStepId, {
|
|
193
|
+
revision: step.revision,
|
|
194
|
+
subnodeId: step.subnodeId,
|
|
195
|
+
type: step.type,
|
|
196
|
+
relatedInvocationId: step.relatedInvocationId,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
158
200
|
// Structural check only: the returned snapshot is discarded because the clone below already
|
|
159
201
|
// produces the detached copy kept by the normalized record.
|
|
160
202
|
for (const step of steps)
|
|
@@ -167,8 +209,14 @@ function normalizeDiagnosticEvent(input) {
|
|
|
167
209
|
(0, rules_js_1.breakWhile)(progress !== undefined &&
|
|
168
210
|
(!progress ||
|
|
169
211
|
!['write', 'fix'].includes(progress.operation) ||
|
|
212
|
+
['type', 'subnodeId', 'relatedInvocationId'].some((key) => progress[key] !== undefined &&
|
|
213
|
+
typeof progress[key] !== 'string') ||
|
|
170
214
|
typeof progress.stepId !== 'string' ||
|
|
171
215
|
!progress.stepId ||
|
|
216
|
+
(progress.targetStepId !== undefined &&
|
|
217
|
+
(progress.operation !== 'fix' ||
|
|
218
|
+
typeof progress.targetStepId !== 'string' ||
|
|
219
|
+
!progress.targetStepId)) ||
|
|
172
220
|
!Number.isSafeInteger(progress.sequence) ||
|
|
173
221
|
progress.sequence < 1 ||
|
|
174
222
|
!Number.isSafeInteger(progress.revision) ||
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nativeSubFlowCatalog = void 0;
|
|
4
|
+
exports.nativeSubFlowSync = nativeSubFlowSync;
|
|
5
|
+
exports.nativeSubFlowAsync = nativeSubFlowAsync;
|
|
6
|
+
const sub_flow_js_1 = require("./sub-flow.js");
|
|
7
|
+
/** Fixed metadata shared by native producers; pure getters and formatters are not producers. */
|
|
8
|
+
exports.nativeSubFlowCatalog = {
|
|
9
|
+
model: { name: 'Model call', kind: 'model', type: 'model.call' },
|
|
10
|
+
tool: { name: 'Tool call', kind: 'tool', type: 'tool.call' },
|
|
11
|
+
invocation: { name: 'Invocation', kind: 'invocation', type: 'handler.call' },
|
|
12
|
+
retrieval: { name: 'Retrieval', kind: 'retrieval', type: 'retrieval.run' },
|
|
13
|
+
task: { name: 'Read answer evidence', kind: 'retrieval', type: 'evidence.read' },
|
|
14
|
+
check: { name: 'Check answer', kind: 'validation', type: 'answer.check' },
|
|
15
|
+
output: { name: 'Compose output', kind: 'presentation', type: 'output.compose' },
|
|
16
|
+
answer: { name: 'Answer flow', kind: 'answering', type: 'answer.run' },
|
|
17
|
+
retry: { name: 'Retry model proposal', kind: 'recovery', type: 'model.retry' },
|
|
18
|
+
context: { name: 'Process context', kind: 'context', type: 'context.process' },
|
|
19
|
+
recovery: { name: 'Recovery attempt', kind: 'recovery', type: 'recovery.try' },
|
|
20
|
+
delivery: { name: 'Deliver result', kind: 'delivery', type: 'delivery.run' },
|
|
21
|
+
aggregation: { name: 'Aggregate queries', kind: 'retrieval', type: 'queries.collect' },
|
|
22
|
+
knowledge: { name: 'Read knowledge', kind: 'retrieval', type: 'knowledge.read' },
|
|
23
|
+
};
|
|
24
|
+
/** Start one fixed record; its revision is best-effort and never controls the operation. */
|
|
25
|
+
function begin(context, producer, id) {
|
|
26
|
+
let logger;
|
|
27
|
+
try {
|
|
28
|
+
logger =
|
|
29
|
+
context.logger instanceof sub_flow_js_1.SubFlowLogger
|
|
30
|
+
? (0, sub_flow_js_1.bindNativeSubFlowLogger)(context.logger, producer, id)
|
|
31
|
+
: context.logger;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* A recording limit must not turn a valid operation into a failure. */
|
|
35
|
+
}
|
|
36
|
+
let step = -1;
|
|
37
|
+
try {
|
|
38
|
+
if (logger)
|
|
39
|
+
step = logger.write({
|
|
40
|
+
...exports.nativeSubFlowCatalog[producer],
|
|
41
|
+
relatedDefinitionId: id,
|
|
42
|
+
...(context.invocationId === undefined
|
|
43
|
+
? {}
|
|
44
|
+
: { relatedInvocationId: context.invocationId }),
|
|
45
|
+
status: 'running',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* Optional recording must not change execution. */
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
logger,
|
|
53
|
+
finish: (status) => {
|
|
54
|
+
try {
|
|
55
|
+
if (logger && step !== -1)
|
|
56
|
+
logger.fix(step, { status });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* A broken or detached logger cannot replace the operation result. */
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Signal access is diagnostic metadata and cannot replace an operation failure. */
|
|
65
|
+
function failureStatus(context) {
|
|
66
|
+
try {
|
|
67
|
+
return context.signal?.aborted ? 'cancelled' : 'error';
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return 'error';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Preserve a synchronous boundary while pairing an optional write with one result revision. */
|
|
74
|
+
function nativeSubFlowSync(context, producer, id, invoke) {
|
|
75
|
+
if (producer === 'model' || producer === 'tool' || producer === 'invocation')
|
|
76
|
+
(0, sub_flow_js_1.requireSubFlowLogger)(context.logger);
|
|
77
|
+
if (!context.logger)
|
|
78
|
+
return invoke();
|
|
79
|
+
const { logger, finish } = begin(context, producer, id);
|
|
80
|
+
try {
|
|
81
|
+
const value = invoke(logger);
|
|
82
|
+
finish('success');
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
finish(failureStatus(context));
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Await the original operation without introducing another timeout or cancellation race. */
|
|
91
|
+
async function nativeSubFlowAsync(context, producer, id, invoke) {
|
|
92
|
+
if (producer === 'model' || producer === 'tool' || producer === 'invocation')
|
|
93
|
+
(0, sub_flow_js_1.requireSubFlowLogger)(context.logger);
|
|
94
|
+
if (!context.logger)
|
|
95
|
+
return invoke();
|
|
96
|
+
const { logger, finish } = begin(context, producer, id);
|
|
97
|
+
try {
|
|
98
|
+
const value = await invoke(logger);
|
|
99
|
+
finish('success');
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
finish(failureStatus(context));
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|