omk-agent-core 0.90.8 → 0.91.0

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 (58) hide show
  1. package/README.md +97 -1
  2. package/dist/agent-loop.d.ts +25 -2
  3. package/dist/agent-loop.d.ts.map +1 -1
  4. package/dist/agent-loop.js +492 -187
  5. package/dist/agent-loop.js.map +1 -1
  6. package/dist/agent.d.ts +29 -7
  7. package/dist/agent.d.ts.map +1 -1
  8. package/dist/agent.js +81 -46
  9. package/dist/agent.js.map +1 -1
  10. package/dist/builtin-tool-resource-claims.d.ts +19 -0
  11. package/dist/builtin-tool-resource-claims.d.ts.map +1 -0
  12. package/dist/builtin-tool-resource-claims.js +200 -0
  13. package/dist/builtin-tool-resource-claims.js.map +1 -0
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/node-resource-resolver.d.ts +42 -0
  19. package/dist/node-resource-resolver.d.ts.map +1 -0
  20. package/dist/node-resource-resolver.js +149 -0
  21. package/dist/node-resource-resolver.js.map +1 -0
  22. package/dist/node.d.ts +1 -0
  23. package/dist/node.d.ts.map +1 -1
  24. package/dist/node.js +2 -0
  25. package/dist/node.js.map +1 -1
  26. package/dist/path-segments.d.ts +8 -0
  27. package/dist/path-segments.d.ts.map +1 -1
  28. package/dist/path-segments.js +62 -9
  29. package/dist/path-segments.js.map +1 -1
  30. package/dist/plain-data.d.ts +7 -0
  31. package/dist/plain-data.d.ts.map +1 -0
  32. package/dist/plain-data.js +70 -0
  33. package/dist/plain-data.js.map +1 -0
  34. package/dist/tool-dag-scheduler.d.ts +86 -0
  35. package/dist/tool-dag-scheduler.d.ts.map +1 -0
  36. package/dist/tool-dag-scheduler.js +171 -0
  37. package/dist/tool-dag-scheduler.js.map +1 -0
  38. package/dist/tool-execution-boundary.d.ts +52 -0
  39. package/dist/tool-execution-boundary.d.ts.map +1 -0
  40. package/dist/tool-execution-boundary.js +185 -0
  41. package/dist/tool-execution-boundary.js.map +1 -0
  42. package/dist/tool-resource-claims.d.ts +31 -0
  43. package/dist/tool-resource-claims.d.ts.map +1 -0
  44. package/dist/tool-resource-claims.js +128 -0
  45. package/dist/tool-resource-claims.js.map +1 -0
  46. package/dist/tool-timeout.d.ts +96 -0
  47. package/dist/tool-timeout.d.ts.map +1 -0
  48. package/dist/tool-timeout.js +173 -0
  49. package/dist/tool-timeout.js.map +1 -0
  50. package/dist/tool-transcript-integrity.d.ts +65 -0
  51. package/dist/tool-transcript-integrity.d.ts.map +1 -0
  52. package/dist/tool-transcript-integrity.js +223 -0
  53. package/dist/tool-transcript-integrity.js.map +1 -0
  54. package/dist/types.d.ts +219 -10
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/types.js +50 -1
  57. package/dist/types.js.map +1 -1
  58. package/package.json +2 -2
@@ -0,0 +1,70 @@
1
+ function assertPlainData(value, jsonOnly, ancestors = new WeakSet()) {
2
+ if (value === null || typeof value === "string" || typeof value === "boolean")
3
+ return;
4
+ if (typeof value === "number") {
5
+ if (Number.isFinite(value))
6
+ return;
7
+ throw new TypeError("Non-finite numbers are not supported data values");
8
+ }
9
+ if (value === undefined && !jsonOnly)
10
+ return;
11
+ if (typeof value !== "object")
12
+ throw new TypeError("Unsupported non-data value");
13
+ if (ancestors.has(value))
14
+ throw new TypeError("Cyclic data values are not supported");
15
+ const prototype = Object.getPrototypeOf(value);
16
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
17
+ throw new TypeError("Unsupported data object prototype");
18
+ }
19
+ ancestors.add(value);
20
+ try {
21
+ if (Array.isArray(value)) {
22
+ if (prototype !== Array.prototype || Object.keys(value).length !== value.length) {
23
+ throw new TypeError("Sparse or extended arrays are not supported data values");
24
+ }
25
+ for (const item of value)
26
+ assertPlainData(item, jsonOnly, ancestors);
27
+ return;
28
+ }
29
+ for (const key of Reflect.ownKeys(value)) {
30
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
31
+ if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
32
+ throw new TypeError("Symbol, hidden, and accessor properties are not supported data values");
33
+ }
34
+ assertPlainData(descriptor.value, jsonOnly, ancestors);
35
+ }
36
+ }
37
+ finally {
38
+ ancestors.delete(value);
39
+ }
40
+ }
41
+ function freezePlainData(value) {
42
+ if (typeof value !== "object" || value === null || Object.isFrozen(value))
43
+ return;
44
+ for (const key of Reflect.ownKeys(value))
45
+ freezePlainData(Reflect.get(value, key));
46
+ Object.freeze(value);
47
+ }
48
+ /** Parse a detached mutable JSON-domain value, rejecting every unsupported shape. */
49
+ export function parseJsonValue(value) {
50
+ assertPlainData(value, true);
51
+ const snapshot = structuredClone(value);
52
+ // Cloning restores masked intrinsic prototypes (Map, views, buffers, etc.).
53
+ assertPlainData(snapshot, true);
54
+ return snapshot;
55
+ }
56
+ /** Clone a plain-data payload, then recursively freeze its executor-owned graph. */
57
+ export function createImmutableSnapshot(value) {
58
+ assertPlainData(value, false);
59
+ const snapshot = structuredClone(value);
60
+ assertPlainData(snapshot, false);
61
+ freezePlainData(snapshot);
62
+ return snapshot;
63
+ }
64
+ /** Parse and freeze a detached JSON-domain value. */
65
+ export function createImmutableJsonSnapshot(value) {
66
+ const snapshot = parseJsonValue(value);
67
+ freezePlainData(snapshot);
68
+ return snapshot;
69
+ }
70
+ //# sourceMappingURL=plain-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain-data.js","sourceRoot":"","sources":["../src/plain-data.ts"],"names":[],"mappings":"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","sourcesContent":["function assertPlainData(value: unknown, jsonOnly: boolean, ancestors = new WeakSet<object>()): void {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return;\n\tif (typeof value === \"number\") {\n\t\tif (Number.isFinite(value)) return;\n\t\tthrow new TypeError(\"Non-finite numbers are not supported data values\");\n\t}\n\tif (value === undefined && !jsonOnly) return;\n\tif (typeof value !== \"object\") throw new TypeError(\"Unsupported non-data value\");\n\tif (ancestors.has(value)) throw new TypeError(\"Cyclic data values are not supported\");\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {\n\t\tthrow new TypeError(\"Unsupported data object prototype\");\n\t}\n\tancestors.add(value);\n\ttry {\n\t\tif (Array.isArray(value)) {\n\t\t\tif (prototype !== Array.prototype || Object.keys(value).length !== value.length) {\n\t\t\t\tthrow new TypeError(\"Sparse or extended arrays are not supported data values\");\n\t\t\t}\n\t\t\tfor (const item of value) assertPlainData(item, jsonOnly, ancestors);\n\t\t\treturn;\n\t\t}\n\t\tfor (const key of Reflect.ownKeys(value)) {\n\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, key);\n\t\t\tif (typeof key !== \"string\" || descriptor?.enumerable !== true || !(\"value\" in descriptor)) {\n\t\t\t\tthrow new TypeError(\"Symbol, hidden, and accessor properties are not supported data values\");\n\t\t\t}\n\t\t\tassertPlainData(descriptor.value, jsonOnly, ancestors);\n\t\t}\n\t} finally {\n\t\tancestors.delete(value);\n\t}\n}\n\nfunction freezePlainData(value: unknown): void {\n\tif (typeof value !== \"object\" || value === null || Object.isFrozen(value)) return;\n\tfor (const key of Reflect.ownKeys(value)) freezePlainData(Reflect.get(value, key));\n\tObject.freeze(value);\n}\n\n/** Parse a detached mutable JSON-domain value, rejecting every unsupported shape. */\nexport function parseJsonValue<T>(value: T): T {\n\tassertPlainData(value, true);\n\tconst snapshot = structuredClone(value);\n\t// Cloning restores masked intrinsic prototypes (Map, views, buffers, etc.).\n\tassertPlainData(snapshot, true);\n\treturn snapshot;\n}\n\n/** Clone a plain-data payload, then recursively freeze its executor-owned graph. */\nexport function createImmutableSnapshot<T>(value: T): T {\n\tassertPlainData(value, false);\n\tconst snapshot = structuredClone(value);\n\tassertPlainData(snapshot, false);\n\tfreezePlainData(snapshot);\n\treturn snapshot;\n}\n\n/** Parse and freeze a detached JSON-domain value. */\nexport function createImmutableJsonSnapshot<T>(value: T): T {\n\tconst snapshot = parseJsonValue(value);\n\tfreezePlainData(snapshot);\n\treturn snapshot;\n}\n"]}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Pure, browser-safe deterministic resource-claim DAG scheduler.
3
+ *
4
+ * Given a source-ordered batch of tool calls and their resolved resource
5
+ * claims, this module assigns each call after every earlier call it conflicts
6
+ * with. Levels run in source order; calls inside one level are mutually
7
+ * conflict-free and may execute concurrently. A positive `maxConcurrency`
8
+ * width cap splits each level into deterministic contiguous source-ordered
9
+ * chunks.
10
+ *
11
+ * Determinism contract:
12
+ * - The level assignment is a pure function of the source-ordered input
13
+ * claims. The canonical example is the head-of-line example
14
+ * `write x, write x, write y`, which schedules as `[[0, 2], [1]]`: the second
15
+ * write to `x` is pushed to level 1, but the independent write to `y` is not
16
+ * blocked and joins level 0.
17
+ * - Reordering claims *within* a single call does not change the plan: claims
18
+ * are canonicalized (sorted, fixed property order) before comparison.
19
+ * - {@link DagSchedulePlan.planKey} is a canonical serialization of the
20
+ * resolved claim sequence (canonical claim data only — never execution
21
+ * timing or outcomes). It is collision-free for distinct canonical claim
22
+ * sequences.
23
+ *
24
+ * This module uses no platform APIs (no `process`, fs, `node:path`, or timers).
25
+ */
26
+ import { type ClaimableToolCall, type ResolveToolClaimsOptions, type ToolClaimResolution, type ToolResourceClaim } from "./tool-resource-claims.ts";
27
+ /** Options for scheduling a batch into DAG levels. */
28
+ export interface ScheduleDagLevelsOptions extends ResolveToolClaimsOptions {
29
+ /**
30
+ * Optional positive width cap. When set, each level is split into contiguous
31
+ * source-ordered chunks of at most this many calls so a wide conflict-free
32
+ * level does not fan out unbounded. Absent, non-finite, or non-positive
33
+ * values leave each level whole. Does not affect `planKey`.
34
+ */
35
+ maxConcurrency?: number;
36
+ }
37
+ /** A scheduled plan: ordered levels of source indices plus a canonical key. */
38
+ export interface DagSchedulePlan {
39
+ /** Levels in execution order; each level holds source indices that may run concurrently. */
40
+ levels: number[][];
41
+ /**
42
+ * Canonical deterministic key over the resolved claim sequence only (never
43
+ * execution timing/outcomes). Stable under claim reordering within a call.
44
+ */
45
+ planKey: string;
46
+ }
47
+ /** One tool call's resolved claim data, canonicalized for stable planning. */
48
+ export interface ResolvedClaimEntry {
49
+ sourceIndex: number;
50
+ resolution: ToolClaimResolution;
51
+ canonicalClaims: ToolResourceClaim[];
52
+ }
53
+ /**
54
+ * Resolve and canonicalize claims for a whole batch, preserving source order.
55
+ * Registered custom resolvers are awaited one call at a time in source order,
56
+ * and every resolution completes before a schedule is constructed.
57
+ */
58
+ export declare function resolveBatchClaims(toolCalls: readonly ClaimableToolCall[], options: ResolveToolClaimsOptions): Promise<ResolvedClaimEntry[]>;
59
+ /**
60
+ * Assign each source-ordered claim entry one level after its latest earlier
61
+ * conflict. Deterministic and stable: equal inputs (including claim reordering
62
+ * within a call, which is canonicalized away) always produce equal levels, and
63
+ * every directed conflict edge advances at least one level.
64
+ */
65
+ export declare function assignDagLevels(entries: readonly ResolvedClaimEntry[]): number[][];
66
+ /**
67
+ * Split each level into contiguous source-ordered chunks of at most `cap` calls.
68
+ * Absent/non-finite/non-positive `cap` returns the levels unchanged.
69
+ */
70
+ export declare function applyConcurrencyCap(levels: readonly number[][], cap: number | undefined): number[][];
71
+ /**
72
+ * Canonical deterministic key over the resolved claim sequence. Uses
73
+ * `JSON.stringify` of each call's canonicalized claims (fixed property order,
74
+ * sorted) with an `"E"`/`"C"` discriminator, so it is collision-free for
75
+ * distinct canonical claim sequences and stable under claim reordering within a
76
+ * call. Contains no execution timing or outcomes.
77
+ */
78
+ export declare function computePlanKey(entries: readonly ResolvedClaimEntry[]): string;
79
+ /**
80
+ * Schedule a source-ordered tool-call batch into deterministic DAG levels.
81
+ * Resolves default claims, computes source-directed dependency levels, applies
82
+ * the optional width cap, and computes the canonical plan key. Pure and
83
+ * deterministic.
84
+ */
85
+ export declare function scheduleDagLevels(toolCalls: readonly ClaimableToolCall[], options: ScheduleDagLevelsOptions): Promise<DagSchedulePlan>;
86
+ //# sourceMappingURL=tool-dag-scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-scheduler.d.ts","sourceRoot":"","sources":["../src/tool-dag-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EACN,KAAK,iBAAiB,EAGtB,KAAK,wBAAwB,EAE7B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,MAAM,2BAA2B,CAAC;AAEnC,sDAAsD;AACtD,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACzE;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC/B,4FAA4F;IAC5F,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,mBAAmB,CAAC;IAChC,eAAe,EAAE,iBAAiB,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACvC,SAAS,EAAE,SAAS,iBAAiB,EAAE,EACvC,OAAO,EAAE,wBAAwB,GAC/B,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAQ/B;AAqDD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,EAAE,EAAE,CA6BlF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,EAAE,CAgBpG;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,CAU7E;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACtC,SAAS,EAAE,SAAS,iBAAiB,EAAE,EACvC,OAAO,EAAE,wBAAwB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAK1B","sourcesContent":["/**\n * Pure, browser-safe deterministic resource-claim DAG scheduler.\n *\n * Given a source-ordered batch of tool calls and their resolved resource\n * claims, this module assigns each call after every earlier call it conflicts\n * with. Levels run in source order; calls inside one level are mutually\n * conflict-free and may execute concurrently. A positive `maxConcurrency`\n * width cap splits each level into deterministic contiguous source-ordered\n * chunks.\n *\n * Determinism contract:\n * - The level assignment is a pure function of the source-ordered input\n * claims. The canonical example is the head-of-line example\n * `write x, write x, write y`, which schedules as `[[0, 2], [1]]`: the second\n * write to `x` is pushed to level 1, but the independent write to `y` is not\n * blocked and joins level 0.\n * - Reordering claims *within* a single call does not change the plan: claims\n * are canonicalized (sorted, fixed property order) before comparison.\n * - {@link DagSchedulePlan.planKey} is a canonical serialization of the\n * resolved claim sequence (canonical claim data only — never execution\n * timing or outcomes). It is collision-free for distinct canonical claim\n * sequences.\n *\n * This module uses no platform APIs (no `process`, fs, `node:path`, or timers).\n */\n\nimport {\n\ttype ClaimableToolCall,\n\tcanonicalizeClaims,\n\tclaimsConflict,\n\ttype ResolveToolClaimsOptions,\n\tresolveToolClaimsForCall,\n\ttype ToolClaimResolution,\n\ttype ToolResourceClaim,\n} from \"./tool-resource-claims.ts\";\n\n/** Options for scheduling a batch into DAG levels. */\nexport interface ScheduleDagLevelsOptions extends ResolveToolClaimsOptions {\n\t/**\n\t * Optional positive width cap. When set, each level is split into contiguous\n\t * source-ordered chunks of at most this many calls so a wide conflict-free\n\t * level does not fan out unbounded. Absent, non-finite, or non-positive\n\t * values leave each level whole. Does not affect `planKey`.\n\t */\n\tmaxConcurrency?: number;\n}\n\n/** A scheduled plan: ordered levels of source indices plus a canonical key. */\nexport interface DagSchedulePlan {\n\t/** Levels in execution order; each level holds source indices that may run concurrently. */\n\tlevels: number[][];\n\t/**\n\t * Canonical deterministic key over the resolved claim sequence only (never\n\t * execution timing/outcomes). Stable under claim reordering within a call.\n\t */\n\tplanKey: string;\n}\n\n/** One tool call's resolved claim data, canonicalized for stable planning. */\nexport interface ResolvedClaimEntry {\n\tsourceIndex: number;\n\tresolution: ToolClaimResolution;\n\tcanonicalClaims: ToolResourceClaim[];\n}\n\n/**\n * Resolve and canonicalize claims for a whole batch, preserving source order.\n * Registered custom resolvers are awaited one call at a time in source order,\n * and every resolution completes before a schedule is constructed.\n */\nexport async function resolveBatchClaims(\n\ttoolCalls: readonly ClaimableToolCall[],\n\toptions: ResolveToolClaimsOptions,\n): Promise<ResolvedClaimEntry[]> {\n\tconst entries: ResolvedClaimEntry[] = [];\n\tfor (let index = 0; index < toolCalls.length; index++) {\n\t\tconst resolution = await resolveToolClaimsForCall(toolCalls[index], options);\n\t\tconst canonicalClaims = resolution.kind === \"claims\" ? canonicalizeClaims(resolution.claims) : [];\n\t\tentries.push({ sourceIndex: index, resolution, canonicalClaims });\n\t}\n\treturn entries;\n}\n\ninterface DagLevel {\n\tindices: number[];\n\t/** Write claims currently placed in this level (for fast read-vs-write checks). */\n\twriteClaims: ToolResourceClaim[];\n\t/** Read claims currently placed in this level (for fast write-vs-read checks). */\n\treadClaims: ToolResourceClaim[];\n\t/** True once an exclusive call is placed here; such a level accepts no more. */\n\thasExclusive: boolean;\n}\n\nfunction claimConflictsAny(claim: ToolResourceClaim, candidates: readonly ToolResourceClaim[]): boolean {\n\tfor (const candidate of candidates) {\n\t\tif (claimsConflict(claim, candidate)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * True when `resolution` cannot join `level`. Equivalent to the full pairwise\n * `resolutionsConflict` check, optimized so read/read never triggers a scan:\n * a read claim only needs to scan the level's writes, and a write claim scans\n * both writes and reads. An exclusive resolution, or a level that already holds\n * an exclusive call, conflicts unconditionally.\n */\nfunction resolutionConflictsLevel(resolution: ToolClaimResolution, level: DagLevel): boolean {\n\tif (level.hasExclusive) {\n\t\treturn true;\n\t}\n\tif (resolution.kind === \"exclusive\") {\n\t\treturn true;\n\t}\n\tfor (const claim of resolution.claims) {\n\t\tif (claim.access === \"exclusive\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (claim.access === \"write\") {\n\t\t\tif (claimConflictsAny(claim, level.writeClaims)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (claimConflictsAny(claim, level.readClaims)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (claimConflictsAny(claim, level.writeClaims)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Assign each source-ordered claim entry one level after its latest earlier\n * conflict. Deterministic and stable: equal inputs (including claim reordering\n * within a call, which is canonicalized away) always produce equal levels, and\n * every directed conflict edge advances at least one level.\n */\nexport function assignDagLevels(entries: readonly ResolvedClaimEntry[]): number[][] {\n\tconst levels: DagLevel[] = [];\n\tfor (const entry of entries) {\n\t\tconst resolution = entry.resolution;\n\t\tlet targetIndex = 0;\n\t\tfor (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {\n\t\t\tif (resolutionConflictsLevel(resolution, levels[levelIndex])) {\n\t\t\t\ttargetIndex = levelIndex + 1;\n\t\t\t}\n\t\t}\n\t\tlet target = levels[targetIndex];\n\t\tif (!target) {\n\t\t\ttarget = { indices: [], writeClaims: [], readClaims: [], hasExclusive: false };\n\t\t\tlevels.push(target);\n\t\t}\n\t\ttarget.indices.push(entry.sourceIndex);\n\t\tif (resolution.kind === \"exclusive\" || entry.canonicalClaims.some((claim) => claim.access === \"exclusive\")) {\n\t\t\ttarget.hasExclusive = true;\n\t\t} else {\n\t\t\tfor (const claim of entry.canonicalClaims) {\n\t\t\t\tif (claim.access === \"write\") {\n\t\t\t\t\ttarget.writeClaims.push(claim);\n\t\t\t\t} else {\n\t\t\t\t\ttarget.readClaims.push(claim);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn levels.map((level) => level.indices);\n}\n\n/**\n * Split each level into contiguous source-ordered chunks of at most `cap` calls.\n * Absent/non-finite/non-positive `cap` returns the levels unchanged.\n */\nexport function applyConcurrencyCap(levels: readonly number[][], cap: number | undefined): number[][] {\n\tif (typeof cap !== \"number\" || !Number.isFinite(cap) || cap <= 0) {\n\t\treturn levels.map((level) => level.slice());\n\t}\n\tconst integerCap = Math.max(1, Math.floor(cap));\n\tconst chunked: number[][] = [];\n\tfor (const level of levels) {\n\t\tif (level.length <= integerCap) {\n\t\t\tchunked.push(level.slice());\n\t\t\tcontinue;\n\t\t}\n\t\tfor (let start = 0; start < level.length; start += integerCap) {\n\t\t\tchunked.push(level.slice(start, start + integerCap));\n\t\t}\n\t}\n\treturn chunked;\n}\n\n/**\n * Canonical deterministic key over the resolved claim sequence. Uses\n * `JSON.stringify` of each call's canonicalized claims (fixed property order,\n * sorted) with an `\"E\"`/`\"C\"` discriminator, so it is collision-free for\n * distinct canonical claim sequences and stable under claim reordering within a\n * call. Contains no execution timing or outcomes.\n */\nexport function computePlanKey(entries: readonly ResolvedClaimEntry[]): string {\n\tlet key = \"\";\n\tfor (const entry of entries) {\n\t\tif (entry.resolution.kind === \"exclusive\") {\n\t\t\tkey += \"E,\";\n\t\t} else {\n\t\t\tkey += `C${JSON.stringify(entry.canonicalClaims)},`;\n\t\t}\n\t}\n\treturn key;\n}\n\n/**\n * Schedule a source-ordered tool-call batch into deterministic DAG levels.\n * Resolves default claims, computes source-directed dependency levels, applies\n * the optional width cap, and computes the canonical plan key. Pure and\n * deterministic.\n */\nexport async function scheduleDagLevels(\n\ttoolCalls: readonly ClaimableToolCall[],\n\toptions: ScheduleDagLevelsOptions,\n): Promise<DagSchedulePlan> {\n\tconst entries = await resolveBatchClaims(toolCalls, options);\n\tconst baseLevels = assignDagLevels(entries);\n\tconst levels = applyConcurrencyCap(baseLevels, options.maxConcurrency);\n\treturn { levels, planKey: computePlanKey(entries) };\n}\n"]}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Pure, browser-safe deterministic resource-claim DAG scheduler.
3
+ *
4
+ * Given a source-ordered batch of tool calls and their resolved resource
5
+ * claims, this module assigns each call after every earlier call it conflicts
6
+ * with. Levels run in source order; calls inside one level are mutually
7
+ * conflict-free and may execute concurrently. A positive `maxConcurrency`
8
+ * width cap splits each level into deterministic contiguous source-ordered
9
+ * chunks.
10
+ *
11
+ * Determinism contract:
12
+ * - The level assignment is a pure function of the source-ordered input
13
+ * claims. The canonical example is the head-of-line example
14
+ * `write x, write x, write y`, which schedules as `[[0, 2], [1]]`: the second
15
+ * write to `x` is pushed to level 1, but the independent write to `y` is not
16
+ * blocked and joins level 0.
17
+ * - Reordering claims *within* a single call does not change the plan: claims
18
+ * are canonicalized (sorted, fixed property order) before comparison.
19
+ * - {@link DagSchedulePlan.planKey} is a canonical serialization of the
20
+ * resolved claim sequence (canonical claim data only — never execution
21
+ * timing or outcomes). It is collision-free for distinct canonical claim
22
+ * sequences.
23
+ *
24
+ * This module uses no platform APIs (no `process`, fs, `node:path`, or timers).
25
+ */
26
+ import { canonicalizeClaims, claimsConflict, resolveToolClaimsForCall, } from "./tool-resource-claims.js";
27
+ /**
28
+ * Resolve and canonicalize claims for a whole batch, preserving source order.
29
+ * Registered custom resolvers are awaited one call at a time in source order,
30
+ * and every resolution completes before a schedule is constructed.
31
+ */
32
+ export async function resolveBatchClaims(toolCalls, options) {
33
+ const entries = [];
34
+ for (let index = 0; index < toolCalls.length; index++) {
35
+ const resolution = await resolveToolClaimsForCall(toolCalls[index], options);
36
+ const canonicalClaims = resolution.kind === "claims" ? canonicalizeClaims(resolution.claims) : [];
37
+ entries.push({ sourceIndex: index, resolution, canonicalClaims });
38
+ }
39
+ return entries;
40
+ }
41
+ function claimConflictsAny(claim, candidates) {
42
+ for (const candidate of candidates) {
43
+ if (claimsConflict(claim, candidate)) {
44
+ return true;
45
+ }
46
+ }
47
+ return false;
48
+ }
49
+ /**
50
+ * True when `resolution` cannot join `level`. Equivalent to the full pairwise
51
+ * `resolutionsConflict` check, optimized so read/read never triggers a scan:
52
+ * a read claim only needs to scan the level's writes, and a write claim scans
53
+ * both writes and reads. An exclusive resolution, or a level that already holds
54
+ * an exclusive call, conflicts unconditionally.
55
+ */
56
+ function resolutionConflictsLevel(resolution, level) {
57
+ if (level.hasExclusive) {
58
+ return true;
59
+ }
60
+ if (resolution.kind === "exclusive") {
61
+ return true;
62
+ }
63
+ for (const claim of resolution.claims) {
64
+ if (claim.access === "exclusive") {
65
+ return true;
66
+ }
67
+ if (claim.access === "write") {
68
+ if (claimConflictsAny(claim, level.writeClaims)) {
69
+ return true;
70
+ }
71
+ if (claimConflictsAny(claim, level.readClaims)) {
72
+ return true;
73
+ }
74
+ }
75
+ else if (claimConflictsAny(claim, level.writeClaims)) {
76
+ return true;
77
+ }
78
+ }
79
+ return false;
80
+ }
81
+ /**
82
+ * Assign each source-ordered claim entry one level after its latest earlier
83
+ * conflict. Deterministic and stable: equal inputs (including claim reordering
84
+ * within a call, which is canonicalized away) always produce equal levels, and
85
+ * every directed conflict edge advances at least one level.
86
+ */
87
+ export function assignDagLevels(entries) {
88
+ const levels = [];
89
+ for (const entry of entries) {
90
+ const resolution = entry.resolution;
91
+ let targetIndex = 0;
92
+ for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {
93
+ if (resolutionConflictsLevel(resolution, levels[levelIndex])) {
94
+ targetIndex = levelIndex + 1;
95
+ }
96
+ }
97
+ let target = levels[targetIndex];
98
+ if (!target) {
99
+ target = { indices: [], writeClaims: [], readClaims: [], hasExclusive: false };
100
+ levels.push(target);
101
+ }
102
+ target.indices.push(entry.sourceIndex);
103
+ if (resolution.kind === "exclusive" || entry.canonicalClaims.some((claim) => claim.access === "exclusive")) {
104
+ target.hasExclusive = true;
105
+ }
106
+ else {
107
+ for (const claim of entry.canonicalClaims) {
108
+ if (claim.access === "write") {
109
+ target.writeClaims.push(claim);
110
+ }
111
+ else {
112
+ target.readClaims.push(claim);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ return levels.map((level) => level.indices);
118
+ }
119
+ /**
120
+ * Split each level into contiguous source-ordered chunks of at most `cap` calls.
121
+ * Absent/non-finite/non-positive `cap` returns the levels unchanged.
122
+ */
123
+ export function applyConcurrencyCap(levels, cap) {
124
+ if (typeof cap !== "number" || !Number.isFinite(cap) || cap <= 0) {
125
+ return levels.map((level) => level.slice());
126
+ }
127
+ const integerCap = Math.max(1, Math.floor(cap));
128
+ const chunked = [];
129
+ for (const level of levels) {
130
+ if (level.length <= integerCap) {
131
+ chunked.push(level.slice());
132
+ continue;
133
+ }
134
+ for (let start = 0; start < level.length; start += integerCap) {
135
+ chunked.push(level.slice(start, start + integerCap));
136
+ }
137
+ }
138
+ return chunked;
139
+ }
140
+ /**
141
+ * Canonical deterministic key over the resolved claim sequence. Uses
142
+ * `JSON.stringify` of each call's canonicalized claims (fixed property order,
143
+ * sorted) with an `"E"`/`"C"` discriminator, so it is collision-free for
144
+ * distinct canonical claim sequences and stable under claim reordering within a
145
+ * call. Contains no execution timing or outcomes.
146
+ */
147
+ export function computePlanKey(entries) {
148
+ let key = "";
149
+ for (const entry of entries) {
150
+ if (entry.resolution.kind === "exclusive") {
151
+ key += "E,";
152
+ }
153
+ else {
154
+ key += `C${JSON.stringify(entry.canonicalClaims)},`;
155
+ }
156
+ }
157
+ return key;
158
+ }
159
+ /**
160
+ * Schedule a source-ordered tool-call batch into deterministic DAG levels.
161
+ * Resolves default claims, computes source-directed dependency levels, applies
162
+ * the optional width cap, and computes the canonical plan key. Pure and
163
+ * deterministic.
164
+ */
165
+ export async function scheduleDagLevels(toolCalls, options) {
166
+ const entries = await resolveBatchClaims(toolCalls, options);
167
+ const baseLevels = assignDagLevels(entries);
168
+ const levels = applyConcurrencyCap(baseLevels, options.maxConcurrency);
169
+ return { levels, planKey: computePlanKey(entries) };
170
+ }
171
+ //# sourceMappingURL=tool-dag-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-scheduler.js","sourceRoot":"","sources":["../src/tool-dag-scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure, browser-safe deterministic resource-claim DAG scheduler.\n *\n * Given a source-ordered batch of tool calls and their resolved resource\n * claims, this module assigns each call after every earlier call it conflicts\n * with. Levels run in source order; calls inside one level are mutually\n * conflict-free and may execute concurrently. A positive `maxConcurrency`\n * width cap splits each level into deterministic contiguous source-ordered\n * chunks.\n *\n * Determinism contract:\n * - The level assignment is a pure function of the source-ordered input\n * claims. The canonical example is the head-of-line example\n * `write x, write x, write y`, which schedules as `[[0, 2], [1]]`: the second\n * write to `x` is pushed to level 1, but the independent write to `y` is not\n * blocked and joins level 0.\n * - Reordering claims *within* a single call does not change the plan: claims\n * are canonicalized (sorted, fixed property order) before comparison.\n * - {@link DagSchedulePlan.planKey} is a canonical serialization of the\n * resolved claim sequence (canonical claim data only — never execution\n * timing or outcomes). It is collision-free for distinct canonical claim\n * sequences.\n *\n * This module uses no platform APIs (no `process`, fs, `node:path`, or timers).\n */\n\nimport {\n\ttype ClaimableToolCall,\n\tcanonicalizeClaims,\n\tclaimsConflict,\n\ttype ResolveToolClaimsOptions,\n\tresolveToolClaimsForCall,\n\ttype ToolClaimResolution,\n\ttype ToolResourceClaim,\n} from \"./tool-resource-claims.ts\";\n\n/** Options for scheduling a batch into DAG levels. */\nexport interface ScheduleDagLevelsOptions extends ResolveToolClaimsOptions {\n\t/**\n\t * Optional positive width cap. When set, each level is split into contiguous\n\t * source-ordered chunks of at most this many calls so a wide conflict-free\n\t * level does not fan out unbounded. Absent, non-finite, or non-positive\n\t * values leave each level whole. Does not affect `planKey`.\n\t */\n\tmaxConcurrency?: number;\n}\n\n/** A scheduled plan: ordered levels of source indices plus a canonical key. */\nexport interface DagSchedulePlan {\n\t/** Levels in execution order; each level holds source indices that may run concurrently. */\n\tlevels: number[][];\n\t/**\n\t * Canonical deterministic key over the resolved claim sequence only (never\n\t * execution timing/outcomes). Stable under claim reordering within a call.\n\t */\n\tplanKey: string;\n}\n\n/** One tool call's resolved claim data, canonicalized for stable planning. */\nexport interface ResolvedClaimEntry {\n\tsourceIndex: number;\n\tresolution: ToolClaimResolution;\n\tcanonicalClaims: ToolResourceClaim[];\n}\n\n/**\n * Resolve and canonicalize claims for a whole batch, preserving source order.\n * Registered custom resolvers are awaited one call at a time in source order,\n * and every resolution completes before a schedule is constructed.\n */\nexport async function resolveBatchClaims(\n\ttoolCalls: readonly ClaimableToolCall[],\n\toptions: ResolveToolClaimsOptions,\n): Promise<ResolvedClaimEntry[]> {\n\tconst entries: ResolvedClaimEntry[] = [];\n\tfor (let index = 0; index < toolCalls.length; index++) {\n\t\tconst resolution = await resolveToolClaimsForCall(toolCalls[index], options);\n\t\tconst canonicalClaims = resolution.kind === \"claims\" ? canonicalizeClaims(resolution.claims) : [];\n\t\tentries.push({ sourceIndex: index, resolution, canonicalClaims });\n\t}\n\treturn entries;\n}\n\ninterface DagLevel {\n\tindices: number[];\n\t/** Write claims currently placed in this level (for fast read-vs-write checks). */\n\twriteClaims: ToolResourceClaim[];\n\t/** Read claims currently placed in this level (for fast write-vs-read checks). */\n\treadClaims: ToolResourceClaim[];\n\t/** True once an exclusive call is placed here; such a level accepts no more. */\n\thasExclusive: boolean;\n}\n\nfunction claimConflictsAny(claim: ToolResourceClaim, candidates: readonly ToolResourceClaim[]): boolean {\n\tfor (const candidate of candidates) {\n\t\tif (claimsConflict(claim, candidate)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * True when `resolution` cannot join `level`. Equivalent to the full pairwise\n * `resolutionsConflict` check, optimized so read/read never triggers a scan:\n * a read claim only needs to scan the level's writes, and a write claim scans\n * both writes and reads. An exclusive resolution, or a level that already holds\n * an exclusive call, conflicts unconditionally.\n */\nfunction resolutionConflictsLevel(resolution: ToolClaimResolution, level: DagLevel): boolean {\n\tif (level.hasExclusive) {\n\t\treturn true;\n\t}\n\tif (resolution.kind === \"exclusive\") {\n\t\treturn true;\n\t}\n\tfor (const claim of resolution.claims) {\n\t\tif (claim.access === \"exclusive\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (claim.access === \"write\") {\n\t\t\tif (claimConflictsAny(claim, level.writeClaims)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (claimConflictsAny(claim, level.readClaims)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (claimConflictsAny(claim, level.writeClaims)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Assign each source-ordered claim entry one level after its latest earlier\n * conflict. Deterministic and stable: equal inputs (including claim reordering\n * within a call, which is canonicalized away) always produce equal levels, and\n * every directed conflict edge advances at least one level.\n */\nexport function assignDagLevels(entries: readonly ResolvedClaimEntry[]): number[][] {\n\tconst levels: DagLevel[] = [];\n\tfor (const entry of entries) {\n\t\tconst resolution = entry.resolution;\n\t\tlet targetIndex = 0;\n\t\tfor (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {\n\t\t\tif (resolutionConflictsLevel(resolution, levels[levelIndex])) {\n\t\t\t\ttargetIndex = levelIndex + 1;\n\t\t\t}\n\t\t}\n\t\tlet target = levels[targetIndex];\n\t\tif (!target) {\n\t\t\ttarget = { indices: [], writeClaims: [], readClaims: [], hasExclusive: false };\n\t\t\tlevels.push(target);\n\t\t}\n\t\ttarget.indices.push(entry.sourceIndex);\n\t\tif (resolution.kind === \"exclusive\" || entry.canonicalClaims.some((claim) => claim.access === \"exclusive\")) {\n\t\t\ttarget.hasExclusive = true;\n\t\t} else {\n\t\t\tfor (const claim of entry.canonicalClaims) {\n\t\t\t\tif (claim.access === \"write\") {\n\t\t\t\t\ttarget.writeClaims.push(claim);\n\t\t\t\t} else {\n\t\t\t\t\ttarget.readClaims.push(claim);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn levels.map((level) => level.indices);\n}\n\n/**\n * Split each level into contiguous source-ordered chunks of at most `cap` calls.\n * Absent/non-finite/non-positive `cap` returns the levels unchanged.\n */\nexport function applyConcurrencyCap(levels: readonly number[][], cap: number | undefined): number[][] {\n\tif (typeof cap !== \"number\" || !Number.isFinite(cap) || cap <= 0) {\n\t\treturn levels.map((level) => level.slice());\n\t}\n\tconst integerCap = Math.max(1, Math.floor(cap));\n\tconst chunked: number[][] = [];\n\tfor (const level of levels) {\n\t\tif (level.length <= integerCap) {\n\t\t\tchunked.push(level.slice());\n\t\t\tcontinue;\n\t\t}\n\t\tfor (let start = 0; start < level.length; start += integerCap) {\n\t\t\tchunked.push(level.slice(start, start + integerCap));\n\t\t}\n\t}\n\treturn chunked;\n}\n\n/**\n * Canonical deterministic key over the resolved claim sequence. Uses\n * `JSON.stringify` of each call's canonicalized claims (fixed property order,\n * sorted) with an `\"E\"`/`\"C\"` discriminator, so it is collision-free for\n * distinct canonical claim sequences and stable under claim reordering within a\n * call. Contains no execution timing or outcomes.\n */\nexport function computePlanKey(entries: readonly ResolvedClaimEntry[]): string {\n\tlet key = \"\";\n\tfor (const entry of entries) {\n\t\tif (entry.resolution.kind === \"exclusive\") {\n\t\t\tkey += \"E,\";\n\t\t} else {\n\t\t\tkey += `C${JSON.stringify(entry.canonicalClaims)},`;\n\t\t}\n\t}\n\treturn key;\n}\n\n/**\n * Schedule a source-ordered tool-call batch into deterministic DAG levels.\n * Resolves default claims, computes source-directed dependency levels, applies\n * the optional width cap, and computes the canonical plan key. Pure and\n * deterministic.\n */\nexport async function scheduleDagLevels(\n\ttoolCalls: readonly ClaimableToolCall[],\n\toptions: ScheduleDagLevelsOptions,\n): Promise<DagSchedulePlan> {\n\tconst entries = await resolveBatchClaims(toolCalls, options);\n\tconst baseLevels = assignDagLevels(entries);\n\tconst levels = applyConcurrencyCap(baseLevels, options.maxConcurrency);\n\treturn { levels, planKey: computePlanKey(entries) };\n}\n"]}
@@ -0,0 +1,52 @@
1
+ import type { AssistantMessage } from "omk-ai";
2
+ import type { AgentContext, AgentLoopConfig, AgentToolCall, AgentToolResult, ToolResultEnvelope, ToolTimeoutDisposition } from "./types.ts";
3
+ export { createImmutableJsonSnapshot, createImmutableSnapshot, parseJsonValue } from "./plain-data.ts";
4
+ export type AbortBoundResult<T> = {
5
+ kind: "completed";
6
+ value: T;
7
+ } | {
8
+ kind: "aborted";
9
+ };
10
+ /** Race one async extension boundary against the parent run's abort signal. */
11
+ export declare function awaitWithAbort<T>(start: () => Promise<T> | T, signal: AbortSignal | undefined): Promise<AbortBoundResult<T>>;
12
+ export interface ToolDispositionEnvelope {
13
+ omk: ToolResultEnvelope;
14
+ }
15
+ /** Build the immutable terminal committed when a tool timeout wins. */
16
+ export declare function createTimeoutToolResult(toolName: string, timeoutMs: number): AgentToolResult<ToolDispositionEnvelope>;
17
+ /** Build the immutable terminal committed when parent abort wins. */
18
+ export declare function createAbortedToolResult(executionStarted: boolean): AgentToolResult<ToolDispositionEnvelope>;
19
+ export interface ExecutedToolCallOutcome {
20
+ result: AgentToolResult<unknown>;
21
+ isError: boolean;
22
+ executionStarted: boolean;
23
+ terminalDisposition?: ToolTimeoutDisposition;
24
+ isRealPromiseSettled: () => boolean;
25
+ commitTerminal: () => void;
26
+ }
27
+ export interface FinalizedToolCallOutcome {
28
+ toolCall: AgentToolCall;
29
+ result: AgentToolResult<unknown>;
30
+ isError: boolean;
31
+ envelope: ToolResultEnvelope;
32
+ isRealPromiseSettled?: () => boolean;
33
+ commitTerminal?: () => void;
34
+ }
35
+ interface FinalizeToolCallOptions {
36
+ currentContext: AgentContext;
37
+ assistantMessage: AssistantMessage;
38
+ prepared: {
39
+ toolCall: AgentToolCall;
40
+ args: unknown;
41
+ timeoutMs: number;
42
+ };
43
+ executed: ExecutedToolCallOutcome;
44
+ afterToolCall: AgentLoopConfig["afterToolCall"];
45
+ signal: AbortSignal | undefined;
46
+ }
47
+ export declare function createErrorToolResult(message: string): AgentToolResult<unknown>;
48
+ /** Commit a real result or immutable timeout/abort result across the after hook boundary. */
49
+ export declare function finalizeExecutedToolCall(options: FinalizeToolCallOptions): Promise<FinalizedToolCallOutcome>;
50
+ /** Preserve compatibility details while replacing any untrusted `omk` field. */
51
+ export declare function stampToolResultEnvelope(details: unknown, envelope: ToolResultEnvelope): unknown;
52
+ //# sourceMappingURL=tool-execution-boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-execution-boundary.d.ts","sourceRoot":"","sources":["../src/tool-execution-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAE/C,OAAO,KAAK,EACX,YAAY,EACZ,eAAe,EACf,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEvG,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExF,+EAA+E;AAC/E,wBAAsB,cAAc,CAAC,CAAC,EACrC,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAC3B,MAAM,EAAE,WAAW,GAAG,SAAS,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAiB9B;AAED,MAAM,WAAW,uBAAuB;IACvC,GAAG,EAAE,kBAAkB,CAAC;CACxB;AAwBD,uEAAuE;AACvE,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC,uBAAuB,CAAC,CAarH;AAED,qEAAqE;AACrE,wBAAgB,uBAAuB,CAAC,gBAAgB,EAAE,OAAO,GAAG,eAAe,CAAC,uBAAuB,CAAC,CAY3G;AAED,MAAM,WAAW,uBAAuB;IACvC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,sBAAsB,CAAC;IAC7C,oBAAoB,EAAE,MAAM,OAAO,CAAC;IACpC,cAAc,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC;IACrC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,UAAU,uBAAuB;IAChC,cAAc,EAAE,YAAY,CAAC;IAC7B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,QAAQ,EAAE;QAAE,QAAQ,EAAE,aAAa,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,QAAQ,EAAE,uBAAuB,CAAC;IAClC,aAAa,EAAE,eAAe,CAAC,eAAe,CAAC,CAAC;IAChD,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;CAChC;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAE/E;AAED,6FAA6F;AAC7F,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CA8FlH;AAQD,gFAAgF;AAChF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAO/F","sourcesContent":["import type { AssistantMessage } from \"omk-ai\";\nimport { createImmutableSnapshot } from \"./plain-data.ts\";\nimport type {\n\tAgentContext,\n\tAgentLoopConfig,\n\tAgentToolCall,\n\tAgentToolResult,\n\tToolResultEnvelope,\n\tToolTimeoutDisposition,\n} from \"./types.ts\";\nimport { createToolResultEnvelope, isToolResultEnvelope } from \"./types.ts\";\n\nexport { createImmutableJsonSnapshot, createImmutableSnapshot, parseJsonValue } from \"./plain-data.ts\";\n\nexport type AbortBoundResult<T> = { kind: \"completed\"; value: T } | { kind: \"aborted\" };\n\n/** Race one async extension boundary against the parent run's abort signal. */\nexport async function awaitWithAbort<T>(\n\tstart: () => Promise<T> | T,\n\tsignal: AbortSignal | undefined,\n): Promise<AbortBoundResult<T>> {\n\tif (signal?.aborted) return { kind: \"aborted\" };\n\tif (signal === undefined) return { kind: \"completed\", value: await start() };\n\n\tlet notifyAbort: (() => void) | undefined;\n\tconst aborted = new Promise<AbortBoundResult<T>>((resolve) => {\n\t\tnotifyAbort = () => resolve({ kind: \"aborted\" });\n\t\tsignal.addEventListener(\"abort\", notifyAbort, { once: true });\n\t});\n\tif (signal.aborted) notifyAbort?.();\n\n\ttry {\n\t\tconst operation = Promise.resolve(start()).then((value): AbortBoundResult<T> => ({ kind: \"completed\", value }));\n\t\treturn await Promise.race([operation, aborted]);\n\t} finally {\n\t\tif (notifyAbort !== undefined) signal.removeEventListener(\"abort\", notifyAbort);\n\t}\n}\n\nexport interface ToolDispositionEnvelope {\n\tomk: ToolResultEnvelope;\n}\n\nfunction createValidatedToolResultSnapshot(result: AgentToolResult<unknown>): AgentToolResult<unknown> {\n\tconst snapshot = createImmutableSnapshot(result);\n\tif (!Array.isArray(snapshot.content)) throw new TypeError(\"Tool result content must be an array\");\n\tfor (const block of snapshot.content) {\n\t\tif (typeof block !== \"object\" || block === null) throw new TypeError(\"Invalid tool result content block\");\n\t\tconst type = Reflect.get(block, \"type\");\n\t\tif (type === \"text\" && typeof Reflect.get(block, \"text\") === \"string\") continue;\n\t\tif (\n\t\t\ttype === \"image\" &&\n\t\t\ttypeof Reflect.get(block, \"data\") === \"string\" &&\n\t\t\ttypeof Reflect.get(block, \"mimeType\") === \"string\"\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tthrow new TypeError(\"Invalid tool result content block\");\n\t}\n\tif (snapshot.terminate !== undefined && typeof snapshot.terminate !== \"boolean\") {\n\t\tthrow new TypeError(\"Tool result terminate must be a boolean\");\n\t}\n\treturn snapshot;\n}\n\n/** Build the immutable terminal committed when a tool timeout wins. */\nexport function createTimeoutToolResult(toolName: string, timeoutMs: number): AgentToolResult<ToolDispositionEnvelope> {\n\treturn createImmutableSnapshot({\n\t\tcontent: [{ type: \"text\", text: `Tool \"${toolName}\" timed out after ${timeoutMs}ms and was terminated.` }],\n\t\tdetails: {\n\t\t\tomk: createToolResultEnvelope({\n\t\t\t\tsynthetic: true,\n\t\t\t\tdisposition: \"timeout\",\n\t\t\t\treason: `Tool \"${toolName}\" timed out after ${timeoutMs}ms`,\n\t\t\t\ttimeoutMs,\n\t\t\t\texecutionStarted: true,\n\t\t\t}),\n\t\t},\n\t});\n}\n\n/** Build the immutable terminal committed when parent abort wins. */\nexport function createAbortedToolResult(executionStarted: boolean): AgentToolResult<ToolDispositionEnvelope> {\n\treturn createImmutableSnapshot({\n\t\tcontent: [{ type: \"text\", text: \"Operation aborted\" }],\n\t\tdetails: {\n\t\t\tomk: createToolResultEnvelope({\n\t\t\t\tsynthetic: true,\n\t\t\t\tdisposition: \"aborted\",\n\t\t\t\treason: \"Operation aborted\",\n\t\t\t\texecutionStarted,\n\t\t\t}),\n\t\t},\n\t});\n}\n\nexport interface ExecutedToolCallOutcome {\n\tresult: AgentToolResult<unknown>;\n\tisError: boolean;\n\texecutionStarted: boolean;\n\tterminalDisposition?: ToolTimeoutDisposition;\n\tisRealPromiseSettled: () => boolean;\n\tcommitTerminal: () => void;\n}\n\nexport interface FinalizedToolCallOutcome {\n\ttoolCall: AgentToolCall;\n\tresult: AgentToolResult<unknown>;\n\tisError: boolean;\n\tenvelope: ToolResultEnvelope;\n\tisRealPromiseSettled?: () => boolean;\n\tcommitTerminal?: () => void;\n}\n\ninterface FinalizeToolCallOptions {\n\tcurrentContext: AgentContext;\n\tassistantMessage: AssistantMessage;\n\tprepared: { toolCall: AgentToolCall; args: unknown; timeoutMs: number };\n\texecuted: ExecutedToolCallOutcome;\n\tafterToolCall: AgentLoopConfig[\"afterToolCall\"];\n\tsignal: AbortSignal | undefined;\n}\n\nexport function createErrorToolResult(message: string): AgentToolResult<unknown> {\n\treturn createImmutableSnapshot({ content: [{ type: \"text\", text: message }], details: {} });\n}\n\n/** Commit a real result or immutable timeout/abort result across the after hook boundary. */\nexport async function finalizeExecutedToolCall(options: FinalizeToolCallOptions): Promise<FinalizedToolCallOutcome> {\n\tconst { currentContext, assistantMessage, prepared, executed, afterToolCall, signal } = options;\n\tlet result = executed.result;\n\tlet isError = executed.isError;\n\tlet syntheticFailure = false;\n\tlet terminalDisposition = executed.terminalDisposition;\n\n\ttry {\n\t\tresult = createValidatedToolResultSnapshot(result);\n\t} catch (error) {\n\t\tresult = createErrorToolResult(`Invalid tool result: ${error instanceof Error ? error.message : String(error)}`);\n\t\tisError = true;\n\t\tsyntheticFailure = true;\n\t}\n\n\tif (terminalDisposition === undefined && signal?.aborted) {\n\t\tterminalDisposition = \"aborted\";\n\t\tresult = createAbortedToolResult(executed.executionStarted);\n\t\tisError = true;\n\t}\n\tif (terminalDisposition === undefined && afterToolCall && !syntheticFailure) {\n\t\ttry {\n\t\t\tconst bounded = await awaitWithAbort(\n\t\t\t\t() =>\n\t\t\t\t\tafterToolCall(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tassistantMessage,\n\t\t\t\t\t\t\ttoolCall: prepared.toolCall,\n\t\t\t\t\t\t\targs: prepared.args,\n\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t\tisError,\n\t\t\t\t\t\t\tcontext: currentContext,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t),\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tif (bounded.kind === \"aborted\" || signal?.aborted) {\n\t\t\t\tterminalDisposition = \"aborted\";\n\t\t\t\tresult = createAbortedToolResult(executed.executionStarted);\n\t\t\t\tisError = true;\n\t\t\t} else if (bounded.value) {\n\t\t\t\tresult = {\n\t\t\t\t\tcontent: bounded.value.content ?? result.content,\n\t\t\t\t\tdetails: Object.hasOwn(bounded.value, \"details\") ? bounded.value.details : result.details,\n\t\t\t\t\tterminate: bounded.value.terminate ?? result.terminate,\n\t\t\t\t};\n\t\t\t\tisError = bounded.value.isError ?? isError;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tresult = createErrorToolResult(error instanceof Error ? error.message : String(error));\n\t\t\tisError = true;\n\t\t\tsyntheticFailure = true;\n\t\t}\n\t}\n\n\tif (terminalDisposition === undefined) {\n\t\ttry {\n\t\t\tresult = createValidatedToolResultSnapshot(result);\n\t\t} catch (error) {\n\t\t\tresult = createErrorToolResult(\n\t\t\t\t`Invalid tool result: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tisError = true;\n\t\t\tsyntheticFailure = true;\n\t\t}\n\t} else {\n\t\tisError = true;\n\t}\n\tconst envelope =\n\t\tterminalDisposition !== undefined\n\t\t\t? createToolResultEnvelope({\n\t\t\t\t\tdisposition: terminalDisposition,\n\t\t\t\t\tsynthetic: true,\n\t\t\t\t\texecutionStarted: executed.executionStarted,\n\t\t\t\t\treason:\n\t\t\t\t\t\tterminalDisposition === \"timeout\"\n\t\t\t\t\t\t\t? `Tool \"${prepared.toolCall.name}\" timed out after ${prepared.timeoutMs}ms`\n\t\t\t\t\t\t\t: \"Operation aborted\",\n\t\t\t\t\t...(terminalDisposition === \"timeout\" ? { timeoutMs: prepared.timeoutMs } : {}),\n\t\t\t\t})\n\t\t\t: createToolResultEnvelope({\n\t\t\t\t\tdisposition: isError ? \"failed\" : \"completed\",\n\t\t\t\t\tsynthetic: syntheticFailure,\n\t\t\t\t\texecutionStarted: executed.executionStarted,\n\t\t\t\t});\n\treturn {\n\t\ttoolCall: prepared.toolCall,\n\t\tresult,\n\t\tisError,\n\t\tenvelope,\n\t\tisRealPromiseSettled: executed.isRealPromiseSettled,\n\t\tcommitTerminal: executed.commitTerminal,\n\t};\n}\n\nfunction isPlainDetails(details: unknown): details is Record<string, unknown> {\n\tif (typeof details !== \"object\" || details === null || Array.isArray(details)) return false;\n\tconst prototype = Object.getPrototypeOf(details);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\n/** Preserve compatibility details while replacing any untrusted `omk` field. */\nexport function stampToolResultEnvelope(details: unknown, envelope: ToolResultEnvelope): unknown {\n\tif (!isToolResultEnvelope(envelope)) throw new TypeError(\"Refusing to persist an invalid tool-result/v2 envelope\");\n\tif (details === undefined) return { omk: envelope };\n\tif (!isPlainDetails(details)) return { originalDetails: details, omk: envelope };\n\tconst preserved = { ...details };\n\tdelete preserved.omk;\n\treturn { ...preserved, omk: envelope };\n}\n"]}