@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,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.modelPool = exports.agentPool = exports.workflowPool = exports.fragmentPool = exports.toolPool = exports.nodePool = exports.definitionKinds = void 0;
|
|
4
|
+
exports.registerDefinition = registerDefinition;
|
|
5
|
+
/**
|
|
6
|
+
* One process-realm definition registry per kind. The shared symbol makes ESM/CJS copies
|
|
7
|
+
* consume the same stores; consumers receive frozen read-only views, never pool factories.
|
|
8
|
+
*/
|
|
9
|
+
const protocol_registry_js_1 = require("../../utils/protocol-registry.js");
|
|
10
|
+
/** Enumerable categories drive allocation and lookup; no additional runtime dispatch layer. */
|
|
11
|
+
exports.definitionKinds = ['node', 'tool', 'fragment', 'workflow', 'agent', 'model'];
|
|
12
|
+
const sharedKey = Symbol.for('@jangts/agent-core/decorator-roots/v2');
|
|
13
|
+
const realm = globalThis;
|
|
14
|
+
/** Allocate once per realm, including when another module format initialized the roots first. */
|
|
15
|
+
function sharedPools() {
|
|
16
|
+
const existing = realm[sharedKey];
|
|
17
|
+
if (existing) {
|
|
18
|
+
if (existing.version !== 2 || exports.definitionKinds.some((kind) => !existing.stores[kind]))
|
|
19
|
+
throw new TypeError('Incompatible decorator root registry');
|
|
20
|
+
return existing;
|
|
21
|
+
}
|
|
22
|
+
const stores = Object.fromEntries(exports.definitionKinds.map((kind) => [kind, new Map()]));
|
|
23
|
+
const pools = Object.freeze({ version: 2, stores: Object.freeze(stores) });
|
|
24
|
+
Object.defineProperty(globalThis, sharedKey, { value: pools });
|
|
25
|
+
return pools;
|
|
26
|
+
}
|
|
27
|
+
/** Namespace is an exact, nonblank key; no trimming, fallback or hierarchy is applied. */
|
|
28
|
+
function namespace(value) {
|
|
29
|
+
if (typeof value !== 'string' || !value.trim())
|
|
30
|
+
throw new TypeError('Decorator namespace must be a nonempty string');
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
/** Preflight every destination so a conflict never leaves partial multi-namespace registration. */
|
|
34
|
+
function registerDefinition(kind, definition, ns = 'default') {
|
|
35
|
+
if (typeof definition.id !== 'string' || !definition.id.trim())
|
|
36
|
+
throw new TypeError('Invalid decorator definition id');
|
|
37
|
+
const requested = typeof ns === 'string' ? [ns] : ns;
|
|
38
|
+
if (!Array.isArray(requested) || requested.length === 0)
|
|
39
|
+
throw new TypeError('Decorator ns must be a namespace or a nonempty namespace array');
|
|
40
|
+
const names = [...new Set(requested.map(namespace))];
|
|
41
|
+
const store = sharedPools().stores[kind];
|
|
42
|
+
for (const name of names) {
|
|
43
|
+
if (store.get(name)?.has(definition.id))
|
|
44
|
+
throw new Error(`Duplicate ${kind} definition: ${definition.id} (ns: ${name})`);
|
|
45
|
+
}
|
|
46
|
+
for (const name of names) {
|
|
47
|
+
let entries = store.get(name);
|
|
48
|
+
if (!entries)
|
|
49
|
+
store.set(name, (entries = new protocol_registry_js_1.ProtocolRegistry()));
|
|
50
|
+
entries.set(definition.id, definition);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Root reads never instantiate or execute definitions; array results are immutable snapshots. */
|
|
54
|
+
function reader(kind) {
|
|
55
|
+
const store = sharedPools().stores[kind];
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
get(id, ns = 'default') {
|
|
58
|
+
return store.get(namespace(ns))?.get(id);
|
|
59
|
+
},
|
|
60
|
+
getAll(ns = 'default') {
|
|
61
|
+
return Object.freeze([...(store.get(namespace(ns))?.values() ?? [])]);
|
|
62
|
+
},
|
|
63
|
+
pick(ids, ns = 'default') {
|
|
64
|
+
const entries = store.get(namespace(ns));
|
|
65
|
+
const selected = new protocol_registry_js_1.ProtocolRegistry();
|
|
66
|
+
for (const id of ids) {
|
|
67
|
+
const definition = entries?.get(id);
|
|
68
|
+
if (!definition)
|
|
69
|
+
throw new TypeError(`Unknown ${kind} definition: ${id} (ns: ${ns})`);
|
|
70
|
+
selected.set(id, definition);
|
|
71
|
+
}
|
|
72
|
+
return Object.freeze([...selected.values()]);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Decorated nodes; ids are unique within each namespace. */
|
|
77
|
+
exports.nodePool = reader('node');
|
|
78
|
+
/** Decorated tools; namespaces isolate otherwise identical ids. */
|
|
79
|
+
exports.toolPool = reader('tool');
|
|
80
|
+
/** Decorated fragment configurations; use the original builder to assemble them. */
|
|
81
|
+
exports.fragmentPool = reader('fragment');
|
|
82
|
+
/** Decorated workflow definitions; execution still belongs to the original Engine. */
|
|
83
|
+
exports.workflowPool = reader('workflow');
|
|
84
|
+
/** Decorated Engine options; request-specific App contracts are supplied on typed reads. */
|
|
85
|
+
exports.agentPool = reader('agent');
|
|
86
|
+
/** Decorated Runner options; reading does not create a Runner or spend model calls. */
|
|
87
|
+
exports.modelPool = reader('model');
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Exports: `executeInvocation` and the invocation contracts re-exported at the bottom.
|
|
10
10
|
*/
|
|
11
|
+
import { nativeSubFlowSync } from '../diagnostics/native-sub-flow.js';
|
|
11
12
|
import { invocationId } from '../utils/invocation.js';
|
|
12
13
|
import { synchronous } from '../utils/synchronous.js';
|
|
13
14
|
import { diagnosticError } from '../diagnostics/index.js';
|
|
@@ -28,12 +29,30 @@ import { NodeReports } from '../execution/reporting.js';
|
|
|
28
29
|
* signal checks.
|
|
29
30
|
*/
|
|
30
31
|
export function executeInvocation(invocation, input, context, ports = {}) {
|
|
32
|
+
let logger;
|
|
33
|
+
try {
|
|
34
|
+
logger = ports.logger?.(context);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* Optional recording cannot block execution. */
|
|
38
|
+
}
|
|
39
|
+
const callId = invocationId();
|
|
40
|
+
return nativeSubFlowSync({
|
|
41
|
+
logger,
|
|
42
|
+
invocationId: callId,
|
|
43
|
+
get signal() {
|
|
44
|
+
return ports.signal?.(context);
|
|
45
|
+
},
|
|
46
|
+
}, 'invocation', invocation.id, (scopedLogger) => runInvocation(invocation, input, context, ports, scopedLogger, callId));
|
|
47
|
+
}
|
|
48
|
+
/** Execute the existing synchronous lifecycle without changing journal or error precedence. */
|
|
49
|
+
function runInvocation(invocation, input, context, ports, logger, executionId = invocationId()) {
|
|
31
50
|
const toolId = invocation.id;
|
|
32
51
|
// Identify the call before touching the ports: journal and diagnostics records cannot be
|
|
33
52
|
// reconciled with an anonymous handler.
|
|
34
53
|
if (!toolId?.trim())
|
|
35
54
|
throw new TypeError('Invalid invocation handler identity');
|
|
36
|
-
const journal = ports.journal?.(context), id =
|
|
55
|
+
const journal = ports.journal?.(context), id = executionId, startedAtMs = Date.now();
|
|
37
56
|
const diagnostics = ports.diagnostics?.(context)?.child(id, {
|
|
38
57
|
invocationId: id,
|
|
39
58
|
nodeRole: 'invocation',
|
|
@@ -64,6 +83,7 @@ export function executeInvocation(invocation, input, context, ports = {}) {
|
|
|
64
83
|
// The helper rejects a promise and drains its rejection: a handler that turns async must
|
|
65
84
|
// fail here instead of escaping as an unhandled rejection.
|
|
66
85
|
synchronous(invocation.call(input, context, {
|
|
86
|
+
logger,
|
|
67
87
|
mode: 'sync',
|
|
68
88
|
invocationId: id,
|
|
69
89
|
signal: signal ?? new AbortController().signal,
|
|
@@ -27,7 +27,14 @@ function execute(definition, input, context, execution) {
|
|
|
27
27
|
signal.throwIfAborted();
|
|
28
28
|
return value;
|
|
29
29
|
};
|
|
30
|
-
const operation = {
|
|
30
|
+
const operation = {
|
|
31
|
+
app: context,
|
|
32
|
+
signal,
|
|
33
|
+
diagnostics,
|
|
34
|
+
logger: execution.logger,
|
|
35
|
+
report,
|
|
36
|
+
createSubFlowLogger,
|
|
37
|
+
};
|
|
31
38
|
const authorized = (result) => {
|
|
32
39
|
assertion(result, 'Tool authorization must return void');
|
|
33
40
|
signal.throwIfAborted();
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Exports: `ToolRegistry`, `executeInvocation`, and the tool/invocation contracts.
|
|
13
13
|
*/
|
|
14
|
+
import { nativeSubFlowSync, nativeSubFlowAsync } from '../diagnostics/native-sub-flow.js';
|
|
14
15
|
import { executionEvents } from '../execution/events.js';
|
|
15
16
|
import { invocationId as nextInvocationId, invocationResult, toolInvocation, } from '../utils/invocation.js';
|
|
16
17
|
import { synchronous } from '../utils/synchronous.js';
|
|
@@ -116,7 +117,8 @@ export class ToolRegistry {
|
|
|
116
117
|
*/
|
|
117
118
|
invoke(request) {
|
|
118
119
|
const call = toolInvocation(request, arguments.length);
|
|
119
|
-
|
|
120
|
+
const callId = nextInvocationId();
|
|
121
|
+
return nativeSubFlowSync({ ...call, signal: combineSignals(this.ports.signal, call.signal), invocationId: callId }, 'tool', call.toolId, (scopedLogger) => this.execute(call.toolId, call.input, false, call.diagnostics, call.signal, scopedLogger, callId));
|
|
120
122
|
}
|
|
121
123
|
/**
|
|
122
124
|
* Invoke a registered tool and await its result.
|
|
@@ -128,7 +130,8 @@ export class ToolRegistry {
|
|
|
128
130
|
*/
|
|
129
131
|
async invokeAsync(request) {
|
|
130
132
|
const call = toolInvocation(request, arguments.length);
|
|
131
|
-
|
|
133
|
+
const callId = nextInvocationId();
|
|
134
|
+
return nativeSubFlowAsync({ ...call, signal: combineSignals(this.ports.signal, call.signal), invocationId: callId }, 'tool', call.toolId, (scopedLogger) => this.execute(call.toolId, call.input, true, call.diagnostics, call.signal, scopedLogger, callId));
|
|
132
135
|
}
|
|
133
136
|
/**
|
|
134
137
|
* Run one call: validate, parse input, execute, parse output, then record the outcome.
|
|
@@ -140,7 +143,7 @@ export class ToolRegistry {
|
|
|
140
143
|
* @param requestSignal Per-call signal, combined with the request-lifetime signal.
|
|
141
144
|
* @returns The frozen result on success; every failure path throws.
|
|
142
145
|
*/
|
|
143
|
-
execute(name, input, asynchronous, parentDiagnostics = this.diagnostics, requestSignal) {
|
|
146
|
+
execute(name, input, asynchronous, parentDiagnostics = this.diagnostics, requestSignal, logger, callId = nextInvocationId()) {
|
|
144
147
|
// A tool call stays inside the run that invoked it: mixing two diagnostic trees would
|
|
145
148
|
// produce records with two parents and break run-scoped correlation.
|
|
146
149
|
if (this.diagnostics?.identity?.runId &&
|
|
@@ -152,7 +155,7 @@ export class ToolRegistry {
|
|
|
152
155
|
throw this.ports.unavailable?.(name) ?? new Error(`Tool is not registered: ${name}`);
|
|
153
156
|
// Either signal may cancel the call; combining them keeps both lifetimes effective.
|
|
154
157
|
const signal = combineSignals(this.ports.signal, requestSignal);
|
|
155
|
-
const invocationId =
|
|
158
|
+
const invocationId = callId;
|
|
156
159
|
const diagnostics = parentDiagnostics?.child(invocationId, {
|
|
157
160
|
invocationId,
|
|
158
161
|
nodeRole: 'invocation',
|
|
@@ -190,24 +193,15 @@ export class ToolRegistry {
|
|
|
190
193
|
};
|
|
191
194
|
const succeed = (result) => {
|
|
192
195
|
signal.throwIfAborted();
|
|
193
|
-
let debug = reports.debug();
|
|
194
196
|
if (tool.debugSteps && diagnostics?.enabled('tool.result', 'content')) {
|
|
195
197
|
try {
|
|
196
|
-
debug
|
|
197
|
-
subs: synchronous(tool.debugSteps(result, reporting), 'Tool debug reports must be synchronous'),
|
|
198
|
-
});
|
|
198
|
+
synchronous(tool.debugSteps(result, reporting), 'Tool debug reports must be synchronous');
|
|
199
199
|
}
|
|
200
|
-
catch
|
|
201
|
-
|
|
202
|
-
// a failure. Defer the error until a consumer actually asks for the sub-steps.
|
|
203
|
-
debug = {
|
|
204
|
-
usedModel: reports.debug().usedModel,
|
|
205
|
-
subSteps: () => {
|
|
206
|
-
throw error;
|
|
207
|
-
},
|
|
208
|
-
};
|
|
200
|
+
catch {
|
|
201
|
+
diagnostics?.emit('node.progress', () => ({ reportError: 'INVALID_NODE_REPORT' }));
|
|
209
202
|
}
|
|
210
203
|
}
|
|
204
|
+
const debug = reports.debug();
|
|
211
205
|
signal.throwIfAborted();
|
|
212
206
|
reports.close();
|
|
213
207
|
diagnostics?.emit('tool.result', () => ({
|
|
@@ -277,6 +271,7 @@ export class ToolRegistry {
|
|
|
277
271
|
signal.throwIfAborted();
|
|
278
272
|
phase = 'execute';
|
|
279
273
|
const value = tool.execute(parsed, {
|
|
274
|
+
logger,
|
|
280
275
|
mode: asynchronous ? 'async' : 'sync',
|
|
281
276
|
invocationId,
|
|
282
277
|
signal,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DiagnosticContext, CreateDiagnosticsOptions } from '../structs/diagnostics.js';
|
|
2
2
|
export * from '../structs/diagnostics.js';
|
|
3
3
|
export * from './catalog.js';
|
|
4
|
+
export { SubFlowLogger, ScopedSubFlowLogger } from './sub-flow.js';
|
|
4
5
|
export type * from '../structs/debug.js';
|
|
5
6
|
export { reportedStepStatusCatalog, reportedStepStatuses } from '../structs/debug.js';
|
|
6
7
|
/**
|
|
@@ -17,6 +17,7 @@ import { payloadSnapshot } from './sub-flow.js';
|
|
|
17
17
|
import { breakWhile } from '../utils/rules.js';
|
|
18
18
|
export * from '../structs/diagnostics.js';
|
|
19
19
|
export * from './catalog.js';
|
|
20
|
+
export { SubFlowLogger, ScopedSubFlowLogger } from './sub-flow.js';
|
|
20
21
|
export { reportedStepStatusCatalog, reportedStepStatuses } from '../structs/debug.js';
|
|
21
22
|
/**
|
|
22
23
|
* Configure the optional log consumer of the shared execution fact source.
|
|
@@ -107,7 +108,7 @@ export function normalizeDiagnosticEvent(input) {
|
|
|
107
108
|
// The count and size bounds match what the sub-flow logger enforces, so a record produced by
|
|
108
109
|
// Core can never be rejected here; only hand-edited or foreign records can.
|
|
109
110
|
if (!Array.isArray(steps) ||
|
|
110
|
-
steps.length >
|
|
111
|
+
steps.length > 1024 ||
|
|
111
112
|
new Set(steps.map((step) => step?.stepId)).size !== steps.length ||
|
|
112
113
|
steps.some((step) => !step ||
|
|
113
114
|
step.origin !== 'reported' ||
|
|
@@ -118,7 +119,15 @@ export function normalizeDiagnosticEvent(input) {
|
|
|
118
119
|
!Number.isSafeInteger(step.sequence) ||
|
|
119
120
|
step.sequence < 1 ||
|
|
120
121
|
(step.status !== undefined && !reportedStepStatuses.includes(step.status)) ||
|
|
121
|
-
[
|
|
122
|
+
[
|
|
123
|
+
'type',
|
|
124
|
+
'subnodeId',
|
|
125
|
+
'parentStepId',
|
|
126
|
+
'relatedDefinitionId',
|
|
127
|
+
'relatedInvocationId',
|
|
128
|
+
'caseId',
|
|
129
|
+
'summary',
|
|
130
|
+
].some((key) => step[key] !== undefined && typeof step[key] !== 'string') ||
|
|
122
131
|
(step.usedModel !== undefined && typeof step.usedModel !== 'boolean') ||
|
|
123
132
|
(step.revision !== undefined &&
|
|
124
133
|
(!Number.isSafeInteger(step.revision) ||
|
|
@@ -133,6 +142,37 @@ export function normalizeDiagnosticEvent(input) {
|
|
|
133
142
|
(!Number.isSafeInteger(raw.subStepsDropped) || Number(raw.subStepsDropped) < 0)) ||
|
|
134
143
|
(raw.subStepsTruncated !== undefined && typeof raw.subStepsTruncated !== 'boolean'))
|
|
135
144
|
throw new TypeError('Invalid reported diagnostic steps');
|
|
145
|
+
const originals = new Map();
|
|
146
|
+
let lastSequence = 0;
|
|
147
|
+
for (const step of steps) {
|
|
148
|
+
if (step.operation === undefined)
|
|
149
|
+
continue; // Historical projected snapshots remain readable.
|
|
150
|
+
breakWhile(!['write', 'fix'].includes(step.operation) || step.sequence <= lastSequence, () => new TypeError('Invalid sub-flow record order'));
|
|
151
|
+
lastSequence = step.sequence;
|
|
152
|
+
if (step.operation === 'write') {
|
|
153
|
+
breakWhile(step.targetStepId !== undefined || step.revision !== 1, () => new TypeError('Invalid sub-flow write record'));
|
|
154
|
+
originals.set(step.stepId, {
|
|
155
|
+
revision: 1,
|
|
156
|
+
subnodeId: step.subnodeId,
|
|
157
|
+
type: step.type,
|
|
158
|
+
relatedInvocationId: step.relatedInvocationId,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const previous = originals.get(step.targetStepId);
|
|
163
|
+
breakWhile(previous === undefined ||
|
|
164
|
+
step.revision !== previous.revision + 1 ||
|
|
165
|
+
step.subnodeId !== previous.subnodeId ||
|
|
166
|
+
step.type !== previous.type ||
|
|
167
|
+
step.relatedInvocationId !== previous.relatedInvocationId, () => new TypeError('Invalid sub-flow fix reference'));
|
|
168
|
+
originals.set(step.targetStepId, {
|
|
169
|
+
revision: step.revision,
|
|
170
|
+
subnodeId: step.subnodeId,
|
|
171
|
+
type: step.type,
|
|
172
|
+
relatedInvocationId: step.relatedInvocationId,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
136
176
|
// Structural check only: the returned snapshot is discarded because the clone below already
|
|
137
177
|
// produces the detached copy kept by the normalized record.
|
|
138
178
|
for (const step of steps)
|
|
@@ -145,8 +185,14 @@ export function normalizeDiagnosticEvent(input) {
|
|
|
145
185
|
breakWhile(progress !== undefined &&
|
|
146
186
|
(!progress ||
|
|
147
187
|
!['write', 'fix'].includes(progress.operation) ||
|
|
188
|
+
['type', 'subnodeId', 'relatedInvocationId'].some((key) => progress[key] !== undefined &&
|
|
189
|
+
typeof progress[key] !== 'string') ||
|
|
148
190
|
typeof progress.stepId !== 'string' ||
|
|
149
191
|
!progress.stepId ||
|
|
192
|
+
(progress.targetStepId !== undefined &&
|
|
193
|
+
(progress.operation !== 'fix' ||
|
|
194
|
+
typeof progress.targetStepId !== 'string' ||
|
|
195
|
+
!progress.targetStepId)) ||
|
|
150
196
|
!Number.isSafeInteger(progress.sequence) ||
|
|
151
197
|
progress.sequence < 1 ||
|
|
152
198
|
!Number.isSafeInteger(progress.revision) ||
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional fixed sub-flow records for native execution boundaries.
|
|
3
|
+
*
|
|
4
|
+
* Callers choose the logger explicitly. A full logger issues a scoped delegate for this call; already-scoped Kit contexts reuse
|
|
5
|
+
* their delegate. Neither path creates storage or changes execution and cancellation.
|
|
6
|
+
* Logging failures are isolated from the wrapped operation.
|
|
7
|
+
*
|
|
8
|
+
* Exports: nativeSubFlowCatalog, nativeSubFlowSync, nativeSubFlowAsync.
|
|
9
|
+
*/
|
|
10
|
+
import type { SubFlowLog, ScopedSubFlowLogger } from '../structs/debug.js';
|
|
11
|
+
/** Fixed metadata shared by native producers; pure getters and formatters are not producers. */
|
|
12
|
+
export declare const nativeSubFlowCatalog: {
|
|
13
|
+
readonly model: {
|
|
14
|
+
readonly name: "Model call";
|
|
15
|
+
readonly kind: "model";
|
|
16
|
+
readonly type: "model.call";
|
|
17
|
+
};
|
|
18
|
+
readonly tool: {
|
|
19
|
+
readonly name: "Tool call";
|
|
20
|
+
readonly kind: "tool";
|
|
21
|
+
readonly type: "tool.call";
|
|
22
|
+
};
|
|
23
|
+
readonly invocation: {
|
|
24
|
+
readonly name: "Invocation";
|
|
25
|
+
readonly kind: "invocation";
|
|
26
|
+
readonly type: "handler.call";
|
|
27
|
+
};
|
|
28
|
+
readonly retrieval: {
|
|
29
|
+
readonly name: "Retrieval";
|
|
30
|
+
readonly kind: "retrieval";
|
|
31
|
+
readonly type: "retrieval.run";
|
|
32
|
+
};
|
|
33
|
+
readonly task: {
|
|
34
|
+
readonly name: "Read answer evidence";
|
|
35
|
+
readonly kind: "retrieval";
|
|
36
|
+
readonly type: "evidence.read";
|
|
37
|
+
};
|
|
38
|
+
readonly check: {
|
|
39
|
+
readonly name: "Check answer";
|
|
40
|
+
readonly kind: "validation";
|
|
41
|
+
readonly type: "answer.check";
|
|
42
|
+
};
|
|
43
|
+
readonly output: {
|
|
44
|
+
readonly name: "Compose output";
|
|
45
|
+
readonly kind: "presentation";
|
|
46
|
+
readonly type: "output.compose";
|
|
47
|
+
};
|
|
48
|
+
readonly answer: {
|
|
49
|
+
readonly name: "Answer flow";
|
|
50
|
+
readonly kind: "answering";
|
|
51
|
+
readonly type: "answer.run";
|
|
52
|
+
};
|
|
53
|
+
readonly retry: {
|
|
54
|
+
readonly name: "Retry model proposal";
|
|
55
|
+
readonly kind: "recovery";
|
|
56
|
+
readonly type: "model.retry";
|
|
57
|
+
};
|
|
58
|
+
readonly context: {
|
|
59
|
+
readonly name: "Process context";
|
|
60
|
+
readonly kind: "context";
|
|
61
|
+
readonly type: "context.process";
|
|
62
|
+
};
|
|
63
|
+
readonly recovery: {
|
|
64
|
+
readonly name: "Recovery attempt";
|
|
65
|
+
readonly kind: "recovery";
|
|
66
|
+
readonly type: "recovery.try";
|
|
67
|
+
};
|
|
68
|
+
readonly delivery: {
|
|
69
|
+
readonly name: "Deliver result";
|
|
70
|
+
readonly kind: "delivery";
|
|
71
|
+
readonly type: "delivery.run";
|
|
72
|
+
};
|
|
73
|
+
readonly aggregation: {
|
|
74
|
+
readonly name: "Aggregate queries";
|
|
75
|
+
readonly kind: "retrieval";
|
|
76
|
+
readonly type: "queries.collect";
|
|
77
|
+
};
|
|
78
|
+
readonly knowledge: {
|
|
79
|
+
readonly name: "Read knowledge";
|
|
80
|
+
readonly kind: "retrieval";
|
|
81
|
+
readonly type: "knowledge.read";
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
type Producer = keyof typeof nativeSubFlowCatalog;
|
|
85
|
+
type Context = {
|
|
86
|
+
readonly logger?: SubFlowLog;
|
|
87
|
+
readonly signal?: AbortSignal;
|
|
88
|
+
readonly invocationId?: string;
|
|
89
|
+
};
|
|
90
|
+
/** Preserve a synchronous boundary while pairing an optional write with one result revision. */
|
|
91
|
+
export declare function nativeSubFlowSync<Value>(context: Context, producer: Producer, id: string, invoke: (logger?: ScopedSubFlowLogger) => Value): Value;
|
|
92
|
+
/** Await the original operation without introducing another timeout or cancellation race. */
|
|
93
|
+
export declare function nativeSubFlowAsync<Value>(context: Context, producer: Producer, id: string, invoke: (logger?: ScopedSubFlowLogger) => Value | PromiseLike<Value>): Promise<Value>;
|
|
94
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { SubFlowLogger, requireSubFlowLogger, bindNativeSubFlowLogger } from './sub-flow.js';
|
|
2
|
+
/** Fixed metadata shared by native producers; pure getters and formatters are not producers. */
|
|
3
|
+
export const nativeSubFlowCatalog = {
|
|
4
|
+
model: { name: 'Model call', kind: 'model', type: 'model.call' },
|
|
5
|
+
tool: { name: 'Tool call', kind: 'tool', type: 'tool.call' },
|
|
6
|
+
invocation: { name: 'Invocation', kind: 'invocation', type: 'handler.call' },
|
|
7
|
+
retrieval: { name: 'Retrieval', kind: 'retrieval', type: 'retrieval.run' },
|
|
8
|
+
task: { name: 'Read answer evidence', kind: 'retrieval', type: 'evidence.read' },
|
|
9
|
+
check: { name: 'Check answer', kind: 'validation', type: 'answer.check' },
|
|
10
|
+
output: { name: 'Compose output', kind: 'presentation', type: 'output.compose' },
|
|
11
|
+
answer: { name: 'Answer flow', kind: 'answering', type: 'answer.run' },
|
|
12
|
+
retry: { name: 'Retry model proposal', kind: 'recovery', type: 'model.retry' },
|
|
13
|
+
context: { name: 'Process context', kind: 'context', type: 'context.process' },
|
|
14
|
+
recovery: { name: 'Recovery attempt', kind: 'recovery', type: 'recovery.try' },
|
|
15
|
+
delivery: { name: 'Deliver result', kind: 'delivery', type: 'delivery.run' },
|
|
16
|
+
aggregation: { name: 'Aggregate queries', kind: 'retrieval', type: 'queries.collect' },
|
|
17
|
+
knowledge: { name: 'Read knowledge', kind: 'retrieval', type: 'knowledge.read' },
|
|
18
|
+
};
|
|
19
|
+
/** Start one fixed record; its revision is best-effort and never controls the operation. */
|
|
20
|
+
function begin(context, producer, id) {
|
|
21
|
+
let logger;
|
|
22
|
+
try {
|
|
23
|
+
logger =
|
|
24
|
+
context.logger instanceof SubFlowLogger
|
|
25
|
+
? bindNativeSubFlowLogger(context.logger, producer, id)
|
|
26
|
+
: context.logger;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* A recording limit must not turn a valid operation into a failure. */
|
|
30
|
+
}
|
|
31
|
+
let step = -1;
|
|
32
|
+
try {
|
|
33
|
+
if (logger)
|
|
34
|
+
step = logger.write({
|
|
35
|
+
...nativeSubFlowCatalog[producer],
|
|
36
|
+
relatedDefinitionId: id,
|
|
37
|
+
...(context.invocationId === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { relatedInvocationId: context.invocationId }),
|
|
40
|
+
status: 'running',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
/* Optional recording must not change execution. */
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
logger,
|
|
48
|
+
finish: (status) => {
|
|
49
|
+
try {
|
|
50
|
+
if (logger && step !== -1)
|
|
51
|
+
logger.fix(step, { status });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* A broken or detached logger cannot replace the operation result. */
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Signal access is diagnostic metadata and cannot replace an operation failure. */
|
|
60
|
+
function failureStatus(context) {
|
|
61
|
+
try {
|
|
62
|
+
return context.signal?.aborted ? 'cancelled' : 'error';
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return 'error';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Preserve a synchronous boundary while pairing an optional write with one result revision. */
|
|
69
|
+
export function nativeSubFlowSync(context, producer, id, invoke) {
|
|
70
|
+
if (producer === 'model' || producer === 'tool' || producer === 'invocation')
|
|
71
|
+
requireSubFlowLogger(context.logger);
|
|
72
|
+
if (!context.logger)
|
|
73
|
+
return invoke();
|
|
74
|
+
const { logger, finish } = begin(context, producer, id);
|
|
75
|
+
try {
|
|
76
|
+
const value = invoke(logger);
|
|
77
|
+
finish('success');
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
finish(failureStatus(context));
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Await the original operation without introducing another timeout or cancellation race. */
|
|
86
|
+
export async function nativeSubFlowAsync(context, producer, id, invoke) {
|
|
87
|
+
if (producer === 'model' || producer === 'tool' || producer === 'invocation')
|
|
88
|
+
requireSubFlowLogger(context.logger);
|
|
89
|
+
if (!context.logger)
|
|
90
|
+
return invoke();
|
|
91
|
+
const { logger, finish } = begin(context, producer, id);
|
|
92
|
+
try {
|
|
93
|
+
const value = await invoke(logger);
|
|
94
|
+
finish('success');
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
finish(failureStatus(context));
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import type { RecordedSubStep,
|
|
1
|
+
import type { RecordedSubStep, SubFlowLog, SubFlowStepInput, SubFlowStepFix, SubStepProgress, SubFlowValue } from '../structs/debug.js';
|
|
2
2
|
import type { DiagnosticIdentity } from '../structs/diagnostics.js';
|
|
3
|
+
type Step = RecordedSubStep & Required<Pick<RecordedSubStep, 'createdAtMs' | 'revision' | 'status'>>;
|
|
4
|
+
/** Validate a committed snapshot without consulting its logger or invoking App code. */
|
|
5
|
+
export declare function captureSubFlowSnapshot(steps: readonly Readonly<RecordedSubStep>[] | undefined, identity?: Readonly<DiagnosticIdentity>): {
|
|
6
|
+
subSteps: readonly Readonly<RecordedSubStep>[];
|
|
7
|
+
subStepsDropped: number;
|
|
8
|
+
subStepsTruncated: boolean;
|
|
9
|
+
};
|
|
3
10
|
/**
|
|
4
11
|
* Validate and deep-copy a reported payload.
|
|
5
12
|
*
|
|
@@ -14,6 +21,48 @@ import type { DiagnosticIdentity } from '../structs/diagnostics.js';
|
|
|
14
21
|
* limits below.
|
|
15
22
|
*/
|
|
16
23
|
export declare function payloadSnapshot(input: unknown): Readonly<Record<string, SubFlowValue>>;
|
|
24
|
+
/** Stable diagnostic reasons; untrusted error text and caller payloads never reach the console. */
|
|
25
|
+
declare const rejectionReasons: {
|
|
26
|
+
readonly inactive: "logger is detached or execution is cancelled";
|
|
27
|
+
readonly scope: "target does not belong to this subnode";
|
|
28
|
+
readonly target: "original write record was not found";
|
|
29
|
+
readonly fields: "input contains a forbidden field";
|
|
30
|
+
readonly transition: "status transition is not allowed";
|
|
31
|
+
readonly sealed: "an established fact cannot be overwritten";
|
|
32
|
+
readonly limit: "recording limit exceeded";
|
|
33
|
+
readonly input: "invalid input or result patch";
|
|
34
|
+
};
|
|
35
|
+
type RejectionReason = keyof typeof rejectionReasons;
|
|
36
|
+
/** Unexported issuance token prevents constructing counterfeit full or scoped loggers. */
|
|
37
|
+
declare const loggerToken: unique symbol;
|
|
38
|
+
type Store = SubFlowLog & {
|
|
39
|
+
owns(stepId: string | -1, subnodeId: string): boolean;
|
|
40
|
+
isActive(): boolean;
|
|
41
|
+
nextNativeSubnodeId(kind: string, definitionId: string): string;
|
|
42
|
+
warn(operation: 'write' | 'fix', reason: RejectionReason): void;
|
|
43
|
+
};
|
|
44
|
+
/** Full execution-owned recorder. Owns its store and may issue restricted delegates. */
|
|
45
|
+
export declare class SubFlowLogger implements SubFlowLog {
|
|
46
|
+
#private;
|
|
47
|
+
constructor(token: typeof loggerToken, store: Store);
|
|
48
|
+
write(input: SubFlowStepInput): string | -1;
|
|
49
|
+
fix(stepId: string | -1, patch: SubFlowStepFix): boolean;
|
|
50
|
+
readAll(): readonly Readonly<RecordedSubStep>[];
|
|
51
|
+
/** Issue a delegate for an App-owned logical group; no execution or scheduling is implied. */
|
|
52
|
+
fork(subnodeId: string): ScopedSubFlowLogger;
|
|
53
|
+
}
|
|
54
|
+
/** Non-inheriting delegate. Stores only the original logger and its bound subnode identity. */
|
|
55
|
+
export declare class ScopedSubFlowLogger implements SubFlowLog {
|
|
56
|
+
#private;
|
|
57
|
+
constructor(token: typeof loggerToken, logger: SubFlowLogger, subnodeId: string);
|
|
58
|
+
write(input: Omit<SubFlowStepInput, 'subnodeId'>): string | -1;
|
|
59
|
+
fix(stepId: string | -1, patch: SubFlowStepFix): boolean;
|
|
60
|
+
readAll(): readonly Readonly<RecordedSubStep>[];
|
|
61
|
+
}
|
|
62
|
+
/** Runtime check complements nominal invocation types; scoped or forged inputs cannot invoke. */
|
|
63
|
+
export declare function requireSubFlowLogger(logger: unknown): asserts logger is SubFlowLogger | undefined;
|
|
64
|
+
/** Allocate a short node-local group; the owning node visit already supplies the namespace. */
|
|
65
|
+
export declare function bindNativeSubFlowLogger(logger: SubFlowLogger, kind: string, definitionId: string): ScopedSubFlowLogger;
|
|
17
66
|
/**
|
|
18
67
|
* Create a logger bound to one active execution visit.
|
|
19
68
|
*
|
|
@@ -22,14 +71,14 @@ export declare function payloadSnapshot(input: unknown): Readonly<Record<string,
|
|
|
22
71
|
*
|
|
23
72
|
* @param owner Object that owns the visit. `readSubFlowLogger` compares this identity before
|
|
24
73
|
* returning any step.
|
|
25
|
-
* @param active Reports whether the visit is still open; a
|
|
74
|
+
* @param active Reports whether the visit is still open; a detached logger returns write=-1 and fix=false.
|
|
26
75
|
* @param content Reports whether content-level detail is enabled. When false, summaries and
|
|
27
76
|
* payloads are dropped before anything is stored.
|
|
28
|
-
* @param changed
|
|
77
|
+
* @param changed Runtime receiver called after each accepted write/fix with its delta and immutable snapshot.
|
|
29
78
|
* @param identity Diagnostic run and span the logger belongs to; used to reject cross-scope reads.
|
|
30
79
|
* @returns A frozen logger instance.
|
|
31
80
|
*/
|
|
32
|
-
export declare function createSubFlowLogger(owner: object, active: () => boolean, content: () => boolean, changed: (logger: SubFlowLogger, progress: SubStepProgress) => void, identity?: Readonly<DiagnosticIdentity>): SubFlowLogger;
|
|
81
|
+
export declare function createSubFlowLogger(owner: object, active: () => boolean, content: () => boolean, changed: (logger: SubFlowLogger, progress: SubStepProgress, steps: readonly Step[]) => void, identity?: Readonly<DiagnosticIdentity>): SubFlowLogger;
|
|
33
82
|
/**
|
|
34
83
|
* Read the steps recorded by a logger issued by this module.
|
|
35
84
|
*
|
|
@@ -44,3 +93,6 @@ export declare function createSubFlowLogger(owner: object, active: () => boolean
|
|
|
44
93
|
* belongs to another diagnostic scope.
|
|
45
94
|
*/
|
|
46
95
|
export declare function readSubFlowLogger(input: unknown, owner?: object, identity?: Readonly<DiagnosticIdentity>): readonly Readonly<RecordedSubStep>[];
|
|
96
|
+
/** Release execution callbacks while preserving the instance's immutable query history. */
|
|
97
|
+
export declare function detachSubFlowLogger(logger: SubFlowLogger): void;
|
|
98
|
+
export {};
|