omk-agent-core 1.2.4 → 1.3.1

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 (51) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +35 -47
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/agent.d.ts +6 -1
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +9 -5
  8. package/dist/agent.js.map +1 -1
  9. package/dist/pending-message-queue.d.ts +1 -0
  10. package/dist/pending-message-queue.d.ts.map +1 -1
  11. package/dist/pending-message-queue.js +16 -5
  12. package/dist/pending-message-queue.js.map +1 -1
  13. package/dist/tool-dag-ecraf.d.ts.map +1 -1
  14. package/dist/tool-dag-ecraf.js +15 -8
  15. package/dist/tool-dag-ecraf.js.map +1 -1
  16. package/dist/tool-dag-index.d.ts +19 -0
  17. package/dist/tool-dag-index.d.ts.map +1 -0
  18. package/dist/tool-dag-index.js +175 -0
  19. package/dist/tool-dag-index.js.map +1 -0
  20. package/dist/tool-dag-memo.d.ts +13 -3
  21. package/dist/tool-dag-memo.d.ts.map +1 -1
  22. package/dist/tool-dag-memo.js +64 -25
  23. package/dist/tool-dag-memo.js.map +1 -1
  24. package/dist/tool-dag-own-number.d.ts +3 -0
  25. package/dist/tool-dag-own-number.d.ts.map +1 -0
  26. package/dist/tool-dag-own-number.js +5 -0
  27. package/dist/tool-dag-own-number.js.map +1 -0
  28. package/dist/tool-dag-owned-task.d.ts +15 -0
  29. package/dist/tool-dag-owned-task.d.ts.map +1 -0
  30. package/dist/tool-dag-owned-task.js +33 -0
  31. package/dist/tool-dag-owned-task.js.map +1 -0
  32. package/dist/tool-dag-plan-copy.d.ts +5 -0
  33. package/dist/tool-dag-plan-copy.d.ts.map +1 -0
  34. package/dist/tool-dag-plan-copy.js +18 -0
  35. package/dist/tool-dag-plan-copy.js.map +1 -0
  36. package/dist/tool-dag-read-aware.d.ts +11 -0
  37. package/dist/tool-dag-read-aware.d.ts.map +1 -0
  38. package/dist/tool-dag-read-aware.js +34 -0
  39. package/dist/tool-dag-read-aware.js.map +1 -0
  40. package/dist/tool-dag-reduce.d.ts +29 -0
  41. package/dist/tool-dag-reduce.d.ts.map +1 -0
  42. package/dist/tool-dag-reduce.js +146 -0
  43. package/dist/tool-dag-reduce.js.map +1 -0
  44. package/dist/tool-dag-scheduler.d.ts.map +1 -1
  45. package/dist/tool-dag-scheduler.js +3 -14
  46. package/dist/tool-dag-scheduler.js.map +1 -1
  47. package/dist/tool-terminal-index.d.ts +4 -0
  48. package/dist/tool-terminal-index.d.ts.map +1 -0
  49. package/dist/tool-terminal-index.js +12 -0
  50. package/dist/tool-terminal-index.js.map +1 -0
  51. package/package.json +2 -2
@@ -0,0 +1,15 @@
1
+ /** Attach rejection ownership in the same synchronous turn as task creation. */
2
+ export type OwnedTaskResult<T> = {
3
+ readonly sourceIndex: number;
4
+ readonly status: "fulfilled";
5
+ readonly value: T;
6
+ } | {
7
+ readonly sourceIndex: number;
8
+ readonly status: "rejected";
9
+ readonly reason: unknown;
10
+ };
11
+ export declare function ownDagTask<T>(sourceIndex: number, task: Promise<T>): Promise<OwnedTaskResult<T>>;
12
+ export declare function startDagTask<T>(sourceIndex: number, execute: () => Promise<T>): Promise<OwnedTaskResult<T>>;
13
+ /** Join all started work before reporting failures, including failures during drain. */
14
+ export declare function finishDagTasks<T>(running: Map<number, Promise<OwnedTaskResult<T>>>, settle: (value: T) => Promise<void>, errors: unknown[]): Promise<void>;
15
+ //# sourceMappingURL=tool-dag-owned-task.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-owned-task.d.ts","sourceRoot":"","sources":["../src/tool-dag-owned-task.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,MAAM,eAAe,CAAC,CAAC,IAC1B;IAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACjF;IAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3F,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAKhG;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAM3G;AAED,wFAAwF;AACxF,wBAAsB,cAAc,CAAC,CAAC,EACrC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,EACjD,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,EACnC,MAAM,EAAE,OAAO,EAAE,GACf,OAAO,CAAC,IAAI,CAAC,CAiBf","sourcesContent":["/** Attach rejection ownership in the same synchronous turn as task creation. */\nexport type OwnedTaskResult<T> =\n\t| { readonly sourceIndex: number; readonly status: \"fulfilled\"; readonly value: T }\n\t| { readonly sourceIndex: number; readonly status: \"rejected\"; readonly reason: unknown };\n\nexport function ownDagTask<T>(sourceIndex: number, task: Promise<T>): Promise<OwnedTaskResult<T>> {\n\treturn task.then(\n\t\t(value) => ({ sourceIndex, status: \"fulfilled\" as const, value }),\n\t\t(reason: unknown) => ({ sourceIndex, status: \"rejected\" as const, reason }),\n\t);\n}\n\nexport function startDagTask<T>(sourceIndex: number, execute: () => Promise<T>): Promise<OwnedTaskResult<T>> {\n\ttry {\n\t\treturn ownDagTask(sourceIndex, execute());\n\t} catch (reason) {\n\t\treturn Promise.resolve({ sourceIndex, status: \"rejected\", reason });\n\t}\n}\n\n/** Join all started work before reporting failures, including failures during drain. */\nexport async function finishDagTasks<T>(\n\trunning: Map<number, Promise<OwnedTaskResult<T>>>,\n\tsettle: (value: T) => Promise<void>,\n\terrors: unknown[],\n): Promise<void> {\n\tconst drained = await Promise.all([...running.values()]);\n\trunning.clear();\n\tfor (const result of drained) {\n\t\tif (result.status === \"rejected\") {\n\t\t\terrors.push(result.reason);\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tawait settle(result.value);\n\t\t} catch (error) {\n\t\t\terrors.push(error);\n\t\t}\n\t}\n\tif (errors.length === 1) throw errors[0];\n\tif (errors.length > 1)\n\t\tthrow new AggregateError(errors, \"DAG batch failed in multiple execution or settlement paths\");\n}\n"]}
@@ -0,0 +1,33 @@
1
+ export function ownDagTask(sourceIndex, task) {
2
+ return task.then((value) => ({ sourceIndex, status: "fulfilled", value }), (reason) => ({ sourceIndex, status: "rejected", reason }));
3
+ }
4
+ export function startDagTask(sourceIndex, execute) {
5
+ try {
6
+ return ownDagTask(sourceIndex, execute());
7
+ }
8
+ catch (reason) {
9
+ return Promise.resolve({ sourceIndex, status: "rejected", reason });
10
+ }
11
+ }
12
+ /** Join all started work before reporting failures, including failures during drain. */
13
+ export async function finishDagTasks(running, settle, errors) {
14
+ const drained = await Promise.all([...running.values()]);
15
+ running.clear();
16
+ for (const result of drained) {
17
+ if (result.status === "rejected") {
18
+ errors.push(result.reason);
19
+ continue;
20
+ }
21
+ try {
22
+ await settle(result.value);
23
+ }
24
+ catch (error) {
25
+ errors.push(error);
26
+ }
27
+ }
28
+ if (errors.length === 1)
29
+ throw errors[0];
30
+ if (errors.length > 1)
31
+ throw new AggregateError(errors, "DAG batch failed in multiple execution or settlement paths");
32
+ }
33
+ //# sourceMappingURL=tool-dag-owned-task.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-owned-task.js","sourceRoot":"","sources":["../src/tool-dag-owned-task.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,UAAU,CAAI,WAAmB,EAAE,IAAgB,EAA+B;IACjG,OAAO,IAAI,CAAC,IAAI,CACf,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,WAAoB,EAAE,KAAK,EAAE,CAAC,EACjE,CAAC,MAAe,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,UAAmB,EAAE,MAAM,EAAE,CAAC,CAC3E,CAAC;AAAA,CACF;AAED,MAAM,UAAU,YAAY,CAAI,WAAmB,EAAE,OAAyB,EAA+B;IAC5G,IAAI,CAAC;QACJ,OAAO,UAAU,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QACjB,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IACrE,CAAC;AAAA,CACD;AAED,wFAAwF;AACxF,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,OAAiD,EACjD,MAAmC,EACnC,MAAiB,EACD;IAChB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACzD,OAAO,CAAC,KAAK,EAAE,CAAC;IAChB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3B,SAAS;QACV,CAAC;QACD,IAAI,CAAC;YACJ,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;IACF,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;IACzC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QACpB,MAAM,IAAI,cAAc,CAAC,MAAM,EAAE,4DAA4D,CAAC,CAAC;AAAA,CAChG","sourcesContent":["/** Attach rejection ownership in the same synchronous turn as task creation. */\nexport type OwnedTaskResult<T> =\n\t| { readonly sourceIndex: number; readonly status: \"fulfilled\"; readonly value: T }\n\t| { readonly sourceIndex: number; readonly status: \"rejected\"; readonly reason: unknown };\n\nexport function ownDagTask<T>(sourceIndex: number, task: Promise<T>): Promise<OwnedTaskResult<T>> {\n\treturn task.then(\n\t\t(value) => ({ sourceIndex, status: \"fulfilled\" as const, value }),\n\t\t(reason: unknown) => ({ sourceIndex, status: \"rejected\" as const, reason }),\n\t);\n}\n\nexport function startDagTask<T>(sourceIndex: number, execute: () => Promise<T>): Promise<OwnedTaskResult<T>> {\n\ttry {\n\t\treturn ownDagTask(sourceIndex, execute());\n\t} catch (reason) {\n\t\treturn Promise.resolve({ sourceIndex, status: \"rejected\", reason });\n\t}\n}\n\n/** Join all started work before reporting failures, including failures during drain. */\nexport async function finishDagTasks<T>(\n\trunning: Map<number, Promise<OwnedTaskResult<T>>>,\n\tsettle: (value: T) => Promise<void>,\n\terrors: unknown[],\n): Promise<void> {\n\tconst drained = await Promise.all([...running.values()]);\n\trunning.clear();\n\tfor (const result of drained) {\n\t\tif (result.status === \"rejected\") {\n\t\t\terrors.push(result.reason);\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tawait settle(result.value);\n\t\t} catch (error) {\n\t\t\terrors.push(error);\n\t\t}\n\t}\n\tif (errors.length === 1) throw errors[0];\n\tif (errors.length > 1)\n\t\tthrow new AggregateError(errors, \"DAG batch failed in multiple execution or settlement paths\");\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import type { DagSchedulePlan, ResolvedClaimEntry } from "./tool-dag-scheduler.ts";
2
+ /** Detach every mutable claim array/object at both boundaries of the LRU cache. */
3
+ export declare function copyDagClaimEntries(entries: readonly ResolvedClaimEntry[]): ResolvedClaimEntry[];
4
+ export declare function copyDagSchedulePlan(plan: DagSchedulePlan): DagSchedulePlan;
5
+ //# sourceMappingURL=tool-dag-plan-copy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-plan-copy.d.ts","sourceRoot":"","sources":["../src/tool-dag-plan-copy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEnF,mFAAmF;AACnF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,kBAAkB,EAAE,CAShG;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe,CAM1E","sourcesContent":["import type { DagSchedulePlan, ResolvedClaimEntry } from \"./tool-dag-scheduler.ts\";\n\n/** Detach every mutable claim array/object at both boundaries of the LRU cache. */\nexport function copyDagClaimEntries(entries: readonly ResolvedClaimEntry[]): ResolvedClaimEntry[] {\n\treturn entries.map((entry) => ({\n\t\tsourceIndex: entry.sourceIndex,\n\t\tresolution:\n\t\t\tentry.resolution.kind === \"exclusive\"\n\t\t\t\t? { kind: \"exclusive\" as const }\n\t\t\t\t: { kind: \"claims\" as const, claims: entry.resolution.claims.map((claim) => ({ ...claim })) },\n\t\tcanonicalClaims: entry.canonicalClaims.map((claim) => ({ ...claim })),\n\t}));\n}\n\nexport function copyDagSchedulePlan(plan: DagSchedulePlan): DagSchedulePlan {\n\treturn {\n\t\tlevels: plan.levels.map((level) => level.slice()),\n\t\tplanKey: plan.planKey,\n\t\tentries: copyDagClaimEntries(plan.entries),\n\t};\n}\n"]}
@@ -0,0 +1,18 @@
1
+ /** Detach every mutable claim array/object at both boundaries of the LRU cache. */
2
+ export function copyDagClaimEntries(entries) {
3
+ return entries.map((entry) => ({
4
+ sourceIndex: entry.sourceIndex,
5
+ resolution: entry.resolution.kind === "exclusive"
6
+ ? { kind: "exclusive" }
7
+ : { kind: "claims", claims: entry.resolution.claims.map((claim) => ({ ...claim })) },
8
+ canonicalClaims: entry.canonicalClaims.map((claim) => ({ ...claim })),
9
+ }));
10
+ }
11
+ export function copyDagSchedulePlan(plan) {
12
+ return {
13
+ levels: plan.levels.map((level) => level.slice()),
14
+ planKey: plan.planKey,
15
+ entries: copyDagClaimEntries(plan.entries),
16
+ };
17
+ }
18
+ //# sourceMappingURL=tool-dag-plan-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-plan-copy.js","sourceRoot":"","sources":["../src/tool-dag-plan-copy.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,MAAM,UAAU,mBAAmB,CAAC,OAAsC,EAAwB;IACjG,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,UAAU,EACT,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,WAAW;YACpC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAoB,EAAE;YAChC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAiB,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,EAAE;QAC/F,eAAe,EAAE,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;KACrE,CAAC,CAAC,CAAC;AAAA,CACJ;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAqB,EAAmB;IAC3E,OAAO;QACN,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACjD,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,OAAO,EAAE,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC;KAC1C,CAAC;AAAA,CACF","sourcesContent":["import type { DagSchedulePlan, ResolvedClaimEntry } from \"./tool-dag-scheduler.ts\";\n\n/** Detach every mutable claim array/object at both boundaries of the LRU cache. */\nexport function copyDagClaimEntries(entries: readonly ResolvedClaimEntry[]): ResolvedClaimEntry[] {\n\treturn entries.map((entry) => ({\n\t\tsourceIndex: entry.sourceIndex,\n\t\tresolution:\n\t\t\tentry.resolution.kind === \"exclusive\"\n\t\t\t\t? { kind: \"exclusive\" as const }\n\t\t\t\t: { kind: \"claims\" as const, claims: entry.resolution.claims.map((claim) => ({ ...claim })) },\n\t\tcanonicalClaims: entry.canonicalClaims.map((claim) => ({ ...claim })),\n\t}));\n}\n\nexport function copyDagSchedulePlan(plan: DagSchedulePlan): DagSchedulePlan {\n\treturn {\n\t\tlevels: plan.levels.map((level) => level.slice()),\n\t\tplanKey: plan.planKey,\n\t\tentries: copyDagClaimEntries(plan.entries),\n\t};\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Source-directed dependency scan with the read/read no-conflict invariant.
3
+ * The existing conflict predicate stays authoritative for paths, aliases,
4
+ * exclusive barriers, and custom resource kinds.
5
+ *
6
+ * O(n + n*w + E) time, O(n + E) memory, where w is the number of entries
7
+ * not proved read-only and E is the output edge count. Worst case stays O(n²).
8
+ * `isReadOnly` must mean that any two entries it accepts cannot conflict.
9
+ */
10
+ export declare function buildReadAwareDependencies<T>(entries: readonly T[], isReadOnly: (entry: T) => boolean, conflicts: (earlier: T, current: T) => boolean): number[][];
11
+ //# sourceMappingURL=tool-dag-read-aware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-read-aware.d.ts","sourceRoot":"","sources":["../src/tool-dag-read-aware.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAC3C,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,EACjC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,OAAO,GAC5C,MAAM,EAAE,EAAE,CAoBZ","sourcesContent":["/**\n * Source-directed dependency scan with the read/read no-conflict invariant.\n * The existing conflict predicate stays authoritative for paths, aliases,\n * exclusive barriers, and custom resource kinds.\n *\n * O(n + n*w + E) time, O(n + E) memory, where w is the number of entries\n * not proved read-only and E is the output edge count. Worst case stays O(n²).\n * `isReadOnly` must mean that any two entries it accepts cannot conflict.\n */\nexport function buildReadAwareDependencies<T>(\n\tentries: readonly T[],\n\tisReadOnly: (entry: T) => boolean,\n\tconflicts: (earlier: T, current: T) => boolean,\n): number[][] {\n\tconst priorWriters: number[] = [];\n\tconst dependencies: number[][] = [];\n\tfor (let index = 0; index < entries.length; index++) {\n\t\tconst current = entries[index];\n\t\tconst readOnly = isReadOnly(current);\n\t\tconst blockers: number[] = [];\n\t\tif (readOnly) {\n\t\t\tfor (const earlier of priorWriters) {\n\t\t\t\tif (conflicts(entries[earlier], current)) blockers.push(earlier);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (let earlier = 0; earlier < index; earlier++) {\n\t\t\t\tif (conflicts(entries[earlier], current)) blockers.push(earlier);\n\t\t\t}\n\t\t\tpriorWriters.push(index);\n\t\t}\n\t\tdependencies.push(blockers);\n\t}\n\treturn dependencies;\n}\n"]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Source-directed dependency scan with the read/read no-conflict invariant.
3
+ * The existing conflict predicate stays authoritative for paths, aliases,
4
+ * exclusive barriers, and custom resource kinds.
5
+ *
6
+ * O(n + n*w + E) time, O(n + E) memory, where w is the number of entries
7
+ * not proved read-only and E is the output edge count. Worst case stays O(n²).
8
+ * `isReadOnly` must mean that any two entries it accepts cannot conflict.
9
+ */
10
+ export function buildReadAwareDependencies(entries, isReadOnly, conflicts) {
11
+ const priorWriters = [];
12
+ const dependencies = [];
13
+ for (let index = 0; index < entries.length; index++) {
14
+ const current = entries[index];
15
+ const readOnly = isReadOnly(current);
16
+ const blockers = [];
17
+ if (readOnly) {
18
+ for (const earlier of priorWriters) {
19
+ if (conflicts(entries[earlier], current))
20
+ blockers.push(earlier);
21
+ }
22
+ }
23
+ else {
24
+ for (let earlier = 0; earlier < index; earlier++) {
25
+ if (conflicts(entries[earlier], current))
26
+ blockers.push(earlier);
27
+ }
28
+ priorWriters.push(index);
29
+ }
30
+ dependencies.push(blockers);
31
+ }
32
+ return dependencies;
33
+ }
34
+ //# sourceMappingURL=tool-dag-read-aware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-read-aware.js","sourceRoot":"","sources":["../src/tool-dag-read-aware.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CACzC,OAAqB,EACrB,UAAiC,EACjC,SAA8C,EACjC;IACb,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,YAAY,GAAe,EAAE,CAAC;IACpC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,QAAQ,EAAE,CAAC;YACd,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;gBACpC,IAAI,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;oBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClE,CAAC;QACF,CAAC;aAAM,CAAC;YACP,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;gBAClD,IAAI,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;oBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClE,CAAC;YACD,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,YAAY,CAAC;AAAA,CACpB","sourcesContent":["/**\n * Source-directed dependency scan with the read/read no-conflict invariant.\n * The existing conflict predicate stays authoritative for paths, aliases,\n * exclusive barriers, and custom resource kinds.\n *\n * O(n + n*w + E) time, O(n + E) memory, where w is the number of entries\n * not proved read-only and E is the output edge count. Worst case stays O(n²).\n * `isReadOnly` must mean that any two entries it accepts cannot conflict.\n */\nexport function buildReadAwareDependencies<T>(\n\tentries: readonly T[],\n\tisReadOnly: (entry: T) => boolean,\n\tconflicts: (earlier: T, current: T) => boolean,\n): number[][] {\n\tconst priorWriters: number[] = [];\n\tconst dependencies: number[][] = [];\n\tfor (let index = 0; index < entries.length; index++) {\n\t\tconst current = entries[index];\n\t\tconst readOnly = isReadOnly(current);\n\t\tconst blockers: number[] = [];\n\t\tif (readOnly) {\n\t\t\tfor (const earlier of priorWriters) {\n\t\t\t\tif (conflicts(entries[earlier], current)) blockers.push(earlier);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (let earlier = 0; earlier < index; earlier++) {\n\t\t\t\tif (conflicts(entries[earlier], current)) blockers.push(earlier);\n\t\t\t}\n\t\t\tpriorWriters.push(index);\n\t\t}\n\t\tdependencies.push(blockers);\n\t}\n\treturn dependencies;\n}\n"]}
@@ -0,0 +1,29 @@
1
+ /** Reachability-preserving reduction; never changes the conflict predicate. */
2
+ export declare const DEFAULT_DAG_CLOSURE_BYTES: number;
3
+ export interface DagReductionOptions {
4
+ /** Budget for the closure bit matrix only, at most 16 MiB; zero selects exact traversal. */
5
+ readonly maxClosureBytes?: number;
6
+ }
7
+ export interface DagReductionDiagnostics {
8
+ readonly strategy: "already-reduced" | "bitset" | "traversal";
9
+ readonly inputEdges: number;
10
+ readonly outputEdges: number;
11
+ readonly closureBytes: number;
12
+ readonly wordUnions: number;
13
+ readonly traversalVisits: number;
14
+ }
15
+ export interface DagReductionResult {
16
+ readonly dependencies: number[][];
17
+ readonly diagnostics: DagReductionDiagnostics;
18
+ }
19
+ /** Same signature and exact transitive-reduction contract as the pinned implementation. */
20
+ export declare function reduceDagDependencies(dependencies: readonly (readonly number[])[]): number[][];
21
+ /**
22
+ * Process incoming edges nearest predecessor first. A predecessor already in
23
+ * the ancestor closure is redundant. Only retained edges require a closure
24
+ * union. The triangular Uint32 matrix stores ancestors below each node, not
25
+ * globally shifted BigInts. Large graphs use an exact iterative traversal;
26
+ * memory pressure never returns a partial or merely approximate reduction.
27
+ */
28
+ export declare function reduceDagDependenciesWithDiagnostics(dependencies: readonly (readonly number[])[], options?: DagReductionOptions): DagReductionResult;
29
+ //# sourceMappingURL=tool-dag-reduce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-reduce.d.ts","sourceRoot":"","sources":["../src/tool-dag-reduce.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,QAAmB,CAAC;AAE1D,MAAM,WAAW,mBAAmB;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,uBAAuB,CAAC;CAC9C;AAED,2FAA2F;AAC3F,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,EAAE,CAE9F;AAED;;;;;;GAMG;AACH,wBAAgB,oCAAoC,CACnD,YAAY,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EAC5C,OAAO,GAAE,mBAAwB,GAC/B,kBAAkB,CA0HpB","sourcesContent":["/** Reachability-preserving reduction; never changes the conflict predicate. */\nexport const DEFAULT_DAG_CLOSURE_BYTES = 16 * 1024 * 1024;\n\nexport interface DagReductionOptions {\n\t/** Budget for the closure bit matrix only, at most 16 MiB; zero selects exact traversal. */\n\treadonly maxClosureBytes?: number;\n}\n\nexport interface DagReductionDiagnostics {\n\treadonly strategy: \"already-reduced\" | \"bitset\" | \"traversal\";\n\treadonly inputEdges: number;\n\treadonly outputEdges: number;\n\treadonly closureBytes: number;\n\treadonly wordUnions: number;\n\treadonly traversalVisits: number;\n}\n\nexport interface DagReductionResult {\n\treadonly dependencies: number[][];\n\treadonly diagnostics: DagReductionDiagnostics;\n}\n\n/** Same signature and exact transitive-reduction contract as the pinned implementation. */\nexport function reduceDagDependencies(dependencies: readonly (readonly number[])[]): number[][] {\n\treturn reduceDagDependenciesWithDiagnostics(dependencies).dependencies;\n}\n\n/**\n * Process incoming edges nearest predecessor first. A predecessor already in\n * the ancestor closure is redundant. Only retained edges require a closure\n * union. The triangular Uint32 matrix stores ancestors below each node, not\n * globally shifted BigInts. Large graphs use an exact iterative traversal;\n * memory pressure never returns a partial or merely approximate reduction.\n */\nexport function reduceDagDependenciesWithDiagnostics(\n\tdependencies: readonly (readonly number[])[],\n\toptions: DagReductionOptions = {},\n): DagReductionResult {\n\tconst budget = options.maxClosureBytes ?? DEFAULT_DAG_CLOSURE_BYTES;\n\tif (!Number.isSafeInteger(budget) || budget < 0 || budget > DEFAULT_DAG_CLOSURE_BYTES) {\n\t\tthrow new RangeError(\"DAG closure budget must be between zero and 16 MiB\");\n\t}\n\tif (!Array.isArray(dependencies)) throw new RangeError(\"Invalid DAG predecessor graph\");\n\tconst n = dependencies.length;\n\tconst outdegrees = new Uint32Array(n);\n\tlet inputEdges = 0;\n\tlet multiParent = false;\n\tlet multiChild = false;\n\tfor (let target = 0; target < n; target++) {\n\t\tconst row = dependencies[target];\n\t\tif (!Array.isArray(row)) throw new RangeError(\"Invalid DAG predecessor row\");\n\t\tlet previous = -1;\n\t\tfor (const predecessor of row) {\n\t\t\tif (\n\t\t\t\t!Number.isSafeInteger(predecessor) ||\n\t\t\t\tpredecessor < 0 ||\n\t\t\t\tpredecessor >= target ||\n\t\t\t\tpredecessor <= previous\n\t\t\t) {\n\t\t\t\tthrow new RangeError(\"DAG predecessors must be unique, ascending, and earlier than their target\");\n\t\t\t}\n\t\t\tprevious = predecessor;\n\t\t\toutdegrees[predecessor]++;\n\t\t\tmultiChild ||= outdegrees[predecessor] > 1;\n\t\t}\n\t\tinputEdges += row.length;\n\t\tmultiParent ||= row.length > 1;\n\t}\n\tconst reduced: number[][] = Array.from({ length: n }, () => []);\n\tlet outputEdges = 0;\n\tlet closureBytes = 0;\n\tlet wordUnions = 0;\n\tlet traversalVisits = 0;\n\tlet strategy: DagReductionDiagnostics[\"strategy\"] = \"already-reduced\";\n\tif (!multiParent || !multiChild) {\n\t\tfor (let i = 0; i < n; i++) reduced[i] = dependencies[i].slice();\n\t\toutputEdges = inputEdges;\n\t} else {\n\t\tconst q = Math.floor((n - 1) / 32);\n\t\tconst r = (n - 1) % 32;\n\t\tconst words = 16 * q * (q + 1) + (q + 1) * r;\n\t\tlet matrix: Uint32Array | undefined;\n\t\tif (Number.isSafeInteger(words) && words * 4 <= budget) {\n\t\t\ttry {\n\t\t\t\tmatrix = new Uint32Array(words);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RangeError)) throw error;\n\t\t\t}\n\t\t}\n\t\tif (matrix !== undefined) {\n\t\t\tstrategy = \"bitset\";\n\t\t\tclosureBytes = matrix.byteLength;\n\t\t\tconst offsets = new Uint32Array(n + 1);\n\t\t\tfor (let i = 0; i < n; i++) offsets[i + 1] = offsets[i] + Math.ceil(i / 32);\n\t\t\tfor (let target = 0; target < n; target++) {\n\t\t\t\tconst offset = offsets[target];\n\t\t\t\tconst row = dependencies[target];\n\t\t\t\tfor (let j = row.length - 1; j >= 0; j--) {\n\t\t\t\t\tconst predecessor = row[j];\n\t\t\t\t\tconst word = Math.floor(predecessor / 32);\n\t\t\t\t\tconst bit = 1 << (predecessor % 32);\n\t\t\t\t\tif ((matrix[offset + word] & bit) !== 0) continue;\n\t\t\t\t\treduced[target].push(predecessor);\n\t\t\t\t\toutputEdges++;\n\t\t\t\t\tmatrix[offset + word] |= bit;\n\t\t\t\t\t// An empty ancestor row needs no union at all.\n\t\t\t\t\tconst ancestors = dependencies[predecessor];\n\t\t\t\t\tconst length = ancestors.length === 0 ? 0 : Math.floor(ancestors[ancestors.length - 1] / 32) + 1;\n\t\t\t\t\tfor (let k = 0; k < length; k++) {\n\t\t\t\t\t\tmatrix[offset + k] |= matrix[offsets[predecessor] + k];\n\t\t\t\t\t\twordUnions++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treduced[target].reverse();\n\t\t\t}\n\t\t} else {\n\t\t\tstrategy = \"traversal\";\n\t\t\tconst marks = new Uint32Array(n);\n\t\t\tconst stack = new Uint32Array(n);\n\t\t\tlet epoch = 0;\n\t\t\tfor (let target = 0; target < n; target++) {\n\t\t\t\tconst row: readonly number[] = dependencies[target];\n\t\t\t\tif (row.length <= 1) {\n\t\t\t\t\treduced[target] = row.slice();\n\t\t\t\t\toutputEdges += row.length;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tepoch = (epoch + 1) >>> 0;\n\t\t\t\tif (epoch === 0) {\n\t\t\t\t\tmarks.fill(0);\n\t\t\t\t\tepoch = 1;\n\t\t\t\t}\n\t\t\t\tfor (let j = row.length - 1; j >= 0; j--) {\n\t\t\t\t\tconst predecessor = row[j];\n\t\t\t\t\tif (marks[predecessor] === epoch) continue;\n\t\t\t\t\treduced[target].push(predecessor);\n\t\t\t\t\toutputEdges++;\n\t\t\t\t\tlet top = 0;\n\t\t\t\t\tmarks[predecessor] = epoch;\n\t\t\t\t\tstack[top++] = predecessor;\n\t\t\t\t\twhile (top > 0) {\n\t\t\t\t\t\tconst node = stack[--top];\n\t\t\t\t\t\ttraversalVisits++;\n\t\t\t\t\t\t// Earlier rows have already been exactly reduced.\n\t\t\t\t\t\tfor (const ancestor of reduced[node]) {\n\t\t\t\t\t\t\tif (marks[ancestor] === epoch) continue;\n\t\t\t\t\t\t\tmarks[ancestor] = epoch;\n\t\t\t\t\t\t\tstack[top++] = ancestor;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treduced[target].reverse();\n\t\t\t}\n\t\t}\n\t}\n\treturn {\n\t\tdependencies: reduced,\n\t\tdiagnostics: Object.freeze({ strategy, inputEdges, outputEdges, closureBytes, wordUnions, traversalVisits }),\n\t};\n}\n"]}
@@ -0,0 +1,146 @@
1
+ /** Reachability-preserving reduction; never changes the conflict predicate. */
2
+ export const DEFAULT_DAG_CLOSURE_BYTES = 16 * 1024 * 1024;
3
+ /** Same signature and exact transitive-reduction contract as the pinned implementation. */
4
+ export function reduceDagDependencies(dependencies) {
5
+ return reduceDagDependenciesWithDiagnostics(dependencies).dependencies;
6
+ }
7
+ /**
8
+ * Process incoming edges nearest predecessor first. A predecessor already in
9
+ * the ancestor closure is redundant. Only retained edges require a closure
10
+ * union. The triangular Uint32 matrix stores ancestors below each node, not
11
+ * globally shifted BigInts. Large graphs use an exact iterative traversal;
12
+ * memory pressure never returns a partial or merely approximate reduction.
13
+ */
14
+ export function reduceDagDependenciesWithDiagnostics(dependencies, options = {}) {
15
+ const budget = options.maxClosureBytes ?? DEFAULT_DAG_CLOSURE_BYTES;
16
+ if (!Number.isSafeInteger(budget) || budget < 0 || budget > DEFAULT_DAG_CLOSURE_BYTES) {
17
+ throw new RangeError("DAG closure budget must be between zero and 16 MiB");
18
+ }
19
+ if (!Array.isArray(dependencies))
20
+ throw new RangeError("Invalid DAG predecessor graph");
21
+ const n = dependencies.length;
22
+ const outdegrees = new Uint32Array(n);
23
+ let inputEdges = 0;
24
+ let multiParent = false;
25
+ let multiChild = false;
26
+ for (let target = 0; target < n; target++) {
27
+ const row = dependencies[target];
28
+ if (!Array.isArray(row))
29
+ throw new RangeError("Invalid DAG predecessor row");
30
+ let previous = -1;
31
+ for (const predecessor of row) {
32
+ if (!Number.isSafeInteger(predecessor) ||
33
+ predecessor < 0 ||
34
+ predecessor >= target ||
35
+ predecessor <= previous) {
36
+ throw new RangeError("DAG predecessors must be unique, ascending, and earlier than their target");
37
+ }
38
+ previous = predecessor;
39
+ outdegrees[predecessor]++;
40
+ multiChild ||= outdegrees[predecessor] > 1;
41
+ }
42
+ inputEdges += row.length;
43
+ multiParent ||= row.length > 1;
44
+ }
45
+ const reduced = Array.from({ length: n }, () => []);
46
+ let outputEdges = 0;
47
+ let closureBytes = 0;
48
+ let wordUnions = 0;
49
+ let traversalVisits = 0;
50
+ let strategy = "already-reduced";
51
+ if (!multiParent || !multiChild) {
52
+ for (let i = 0; i < n; i++)
53
+ reduced[i] = dependencies[i].slice();
54
+ outputEdges = inputEdges;
55
+ }
56
+ else {
57
+ const q = Math.floor((n - 1) / 32);
58
+ const r = (n - 1) % 32;
59
+ const words = 16 * q * (q + 1) + (q + 1) * r;
60
+ let matrix;
61
+ if (Number.isSafeInteger(words) && words * 4 <= budget) {
62
+ try {
63
+ matrix = new Uint32Array(words);
64
+ }
65
+ catch (error) {
66
+ if (!(error instanceof RangeError))
67
+ throw error;
68
+ }
69
+ }
70
+ if (matrix !== undefined) {
71
+ strategy = "bitset";
72
+ closureBytes = matrix.byteLength;
73
+ const offsets = new Uint32Array(n + 1);
74
+ for (let i = 0; i < n; i++)
75
+ offsets[i + 1] = offsets[i] + Math.ceil(i / 32);
76
+ for (let target = 0; target < n; target++) {
77
+ const offset = offsets[target];
78
+ const row = dependencies[target];
79
+ for (let j = row.length - 1; j >= 0; j--) {
80
+ const predecessor = row[j];
81
+ const word = Math.floor(predecessor / 32);
82
+ const bit = 1 << (predecessor % 32);
83
+ if ((matrix[offset + word] & bit) !== 0)
84
+ continue;
85
+ reduced[target].push(predecessor);
86
+ outputEdges++;
87
+ matrix[offset + word] |= bit;
88
+ // An empty ancestor row needs no union at all.
89
+ const ancestors = dependencies[predecessor];
90
+ const length = ancestors.length === 0 ? 0 : Math.floor(ancestors[ancestors.length - 1] / 32) + 1;
91
+ for (let k = 0; k < length; k++) {
92
+ matrix[offset + k] |= matrix[offsets[predecessor] + k];
93
+ wordUnions++;
94
+ }
95
+ }
96
+ reduced[target].reverse();
97
+ }
98
+ }
99
+ else {
100
+ strategy = "traversal";
101
+ const marks = new Uint32Array(n);
102
+ const stack = new Uint32Array(n);
103
+ let epoch = 0;
104
+ for (let target = 0; target < n; target++) {
105
+ const row = dependencies[target];
106
+ if (row.length <= 1) {
107
+ reduced[target] = row.slice();
108
+ outputEdges += row.length;
109
+ continue;
110
+ }
111
+ epoch = (epoch + 1) >>> 0;
112
+ if (epoch === 0) {
113
+ marks.fill(0);
114
+ epoch = 1;
115
+ }
116
+ for (let j = row.length - 1; j >= 0; j--) {
117
+ const predecessor = row[j];
118
+ if (marks[predecessor] === epoch)
119
+ continue;
120
+ reduced[target].push(predecessor);
121
+ outputEdges++;
122
+ let top = 0;
123
+ marks[predecessor] = epoch;
124
+ stack[top++] = predecessor;
125
+ while (top > 0) {
126
+ const node = stack[--top];
127
+ traversalVisits++;
128
+ // Earlier rows have already been exactly reduced.
129
+ for (const ancestor of reduced[node]) {
130
+ if (marks[ancestor] === epoch)
131
+ continue;
132
+ marks[ancestor] = epoch;
133
+ stack[top++] = ancestor;
134
+ }
135
+ }
136
+ }
137
+ reduced[target].reverse();
138
+ }
139
+ }
140
+ }
141
+ return {
142
+ dependencies: reduced,
143
+ diagnostics: Object.freeze({ strategy, inputEdges, outputEdges, closureBytes, wordUnions, traversalVisits }),
144
+ };
145
+ }
146
+ //# sourceMappingURL=tool-dag-reduce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-dag-reduce.js","sourceRoot":"","sources":["../src/tool-dag-reduce.ts"],"names":[],"mappings":"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","sourcesContent":["/** Reachability-preserving reduction; never changes the conflict predicate. */\nexport const DEFAULT_DAG_CLOSURE_BYTES = 16 * 1024 * 1024;\n\nexport interface DagReductionOptions {\n\t/** Budget for the closure bit matrix only, at most 16 MiB; zero selects exact traversal. */\n\treadonly maxClosureBytes?: number;\n}\n\nexport interface DagReductionDiagnostics {\n\treadonly strategy: \"already-reduced\" | \"bitset\" | \"traversal\";\n\treadonly inputEdges: number;\n\treadonly outputEdges: number;\n\treadonly closureBytes: number;\n\treadonly wordUnions: number;\n\treadonly traversalVisits: number;\n}\n\nexport interface DagReductionResult {\n\treadonly dependencies: number[][];\n\treadonly diagnostics: DagReductionDiagnostics;\n}\n\n/** Same signature and exact transitive-reduction contract as the pinned implementation. */\nexport function reduceDagDependencies(dependencies: readonly (readonly number[])[]): number[][] {\n\treturn reduceDagDependenciesWithDiagnostics(dependencies).dependencies;\n}\n\n/**\n * Process incoming edges nearest predecessor first. A predecessor already in\n * the ancestor closure is redundant. Only retained edges require a closure\n * union. The triangular Uint32 matrix stores ancestors below each node, not\n * globally shifted BigInts. Large graphs use an exact iterative traversal;\n * memory pressure never returns a partial or merely approximate reduction.\n */\nexport function reduceDagDependenciesWithDiagnostics(\n\tdependencies: readonly (readonly number[])[],\n\toptions: DagReductionOptions = {},\n): DagReductionResult {\n\tconst budget = options.maxClosureBytes ?? DEFAULT_DAG_CLOSURE_BYTES;\n\tif (!Number.isSafeInteger(budget) || budget < 0 || budget > DEFAULT_DAG_CLOSURE_BYTES) {\n\t\tthrow new RangeError(\"DAG closure budget must be between zero and 16 MiB\");\n\t}\n\tif (!Array.isArray(dependencies)) throw new RangeError(\"Invalid DAG predecessor graph\");\n\tconst n = dependencies.length;\n\tconst outdegrees = new Uint32Array(n);\n\tlet inputEdges = 0;\n\tlet multiParent = false;\n\tlet multiChild = false;\n\tfor (let target = 0; target < n; target++) {\n\t\tconst row = dependencies[target];\n\t\tif (!Array.isArray(row)) throw new RangeError(\"Invalid DAG predecessor row\");\n\t\tlet previous = -1;\n\t\tfor (const predecessor of row) {\n\t\t\tif (\n\t\t\t\t!Number.isSafeInteger(predecessor) ||\n\t\t\t\tpredecessor < 0 ||\n\t\t\t\tpredecessor >= target ||\n\t\t\t\tpredecessor <= previous\n\t\t\t) {\n\t\t\t\tthrow new RangeError(\"DAG predecessors must be unique, ascending, and earlier than their target\");\n\t\t\t}\n\t\t\tprevious = predecessor;\n\t\t\toutdegrees[predecessor]++;\n\t\t\tmultiChild ||= outdegrees[predecessor] > 1;\n\t\t}\n\t\tinputEdges += row.length;\n\t\tmultiParent ||= row.length > 1;\n\t}\n\tconst reduced: number[][] = Array.from({ length: n }, () => []);\n\tlet outputEdges = 0;\n\tlet closureBytes = 0;\n\tlet wordUnions = 0;\n\tlet traversalVisits = 0;\n\tlet strategy: DagReductionDiagnostics[\"strategy\"] = \"already-reduced\";\n\tif (!multiParent || !multiChild) {\n\t\tfor (let i = 0; i < n; i++) reduced[i] = dependencies[i].slice();\n\t\toutputEdges = inputEdges;\n\t} else {\n\t\tconst q = Math.floor((n - 1) / 32);\n\t\tconst r = (n - 1) % 32;\n\t\tconst words = 16 * q * (q + 1) + (q + 1) * r;\n\t\tlet matrix: Uint32Array | undefined;\n\t\tif (Number.isSafeInteger(words) && words * 4 <= budget) {\n\t\t\ttry {\n\t\t\t\tmatrix = new Uint32Array(words);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RangeError)) throw error;\n\t\t\t}\n\t\t}\n\t\tif (matrix !== undefined) {\n\t\t\tstrategy = \"bitset\";\n\t\t\tclosureBytes = matrix.byteLength;\n\t\t\tconst offsets = new Uint32Array(n + 1);\n\t\t\tfor (let i = 0; i < n; i++) offsets[i + 1] = offsets[i] + Math.ceil(i / 32);\n\t\t\tfor (let target = 0; target < n; target++) {\n\t\t\t\tconst offset = offsets[target];\n\t\t\t\tconst row = dependencies[target];\n\t\t\t\tfor (let j = row.length - 1; j >= 0; j--) {\n\t\t\t\t\tconst predecessor = row[j];\n\t\t\t\t\tconst word = Math.floor(predecessor / 32);\n\t\t\t\t\tconst bit = 1 << (predecessor % 32);\n\t\t\t\t\tif ((matrix[offset + word] & bit) !== 0) continue;\n\t\t\t\t\treduced[target].push(predecessor);\n\t\t\t\t\toutputEdges++;\n\t\t\t\t\tmatrix[offset + word] |= bit;\n\t\t\t\t\t// An empty ancestor row needs no union at all.\n\t\t\t\t\tconst ancestors = dependencies[predecessor];\n\t\t\t\t\tconst length = ancestors.length === 0 ? 0 : Math.floor(ancestors[ancestors.length - 1] / 32) + 1;\n\t\t\t\t\tfor (let k = 0; k < length; k++) {\n\t\t\t\t\t\tmatrix[offset + k] |= matrix[offsets[predecessor] + k];\n\t\t\t\t\t\twordUnions++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treduced[target].reverse();\n\t\t\t}\n\t\t} else {\n\t\t\tstrategy = \"traversal\";\n\t\t\tconst marks = new Uint32Array(n);\n\t\t\tconst stack = new Uint32Array(n);\n\t\t\tlet epoch = 0;\n\t\t\tfor (let target = 0; target < n; target++) {\n\t\t\t\tconst row: readonly number[] = dependencies[target];\n\t\t\t\tif (row.length <= 1) {\n\t\t\t\t\treduced[target] = row.slice();\n\t\t\t\t\toutputEdges += row.length;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tepoch = (epoch + 1) >>> 0;\n\t\t\t\tif (epoch === 0) {\n\t\t\t\t\tmarks.fill(0);\n\t\t\t\t\tepoch = 1;\n\t\t\t\t}\n\t\t\t\tfor (let j = row.length - 1; j >= 0; j--) {\n\t\t\t\t\tconst predecessor = row[j];\n\t\t\t\t\tif (marks[predecessor] === epoch) continue;\n\t\t\t\t\treduced[target].push(predecessor);\n\t\t\t\t\toutputEdges++;\n\t\t\t\t\tlet top = 0;\n\t\t\t\t\tmarks[predecessor] = epoch;\n\t\t\t\t\tstack[top++] = predecessor;\n\t\t\t\t\twhile (top > 0) {\n\t\t\t\t\t\tconst node = stack[--top];\n\t\t\t\t\t\ttraversalVisits++;\n\t\t\t\t\t\t// Earlier rows have already been exactly reduced.\n\t\t\t\t\t\tfor (const ancestor of reduced[node]) {\n\t\t\t\t\t\t\tif (marks[ancestor] === epoch) continue;\n\t\t\t\t\t\t\tmarks[ancestor] = epoch;\n\t\t\t\t\t\t\tstack[top++] = ancestor;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treduced[target].reverse();\n\t\t\t}\n\t\t}\n\t}\n\treturn {\n\t\tdependencies: reduced,\n\t\tdiagnostics: Object.freeze({ strategy, inputEdges, outputEdges, closureBytes, wordUnions, traversalVisits }),\n\t};\n}\n"]}
@@ -1 +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,EAG7B,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;IAChB;;;;OAIG;IACH,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACvC;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;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,EAAE,EAAE,CAoBxF;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;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAC1C,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,mBAAmB,EACpC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,EAC1B,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5B,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5B,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,GACnD,OAAO,CAYT","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\tresolutionsConflict,\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\t/**\n\t * The resolved, canonicalized claim entries the plan was built from. Callers\n\t * that re-resolve claims after argument mutation (e.g. authorization hooks)\n\t * compare against these to detect scope drift. Read-only for consumers.\n\t */\n\tentries: readonly ResolvedClaimEntry[];\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 * Direct conflicting predecessors for each source-ordered entry: `result[i]`\n * lists every earlier position `i` must wait for.\n *\n * This is the precedence graph that {@link assignDagLevels} approximates with a\n * barrier schedule. Levels force every call in level N+1 to wait for ALL of\n * level N, so one slow unrelated call in a level delays the whole next level.\n * Dependency edges only connect calls that actually conflict, so a consumer can\n * start each call as soon as its own predecessors settle.\n *\n * Deterministic and stable: the result is a pure function of the source-ordered\n * canonicalized claims, and every list is ascending and strictly earlier-only.\n * Transitively-implied edges are kept rather than reduced — waiting on an\n * already-finished ancestor costs nothing and keeps this a simple pure scan.\n *\n * The graph is never deeper than the barrier schedule: for any `j` in\n * `result[i]`, `i` conflicts with an entry in level `j`, so\n * `level(i) >= level(j) + 1`, and depth follows by induction.\n */\nexport function assignDagDependencies(entries: readonly ResolvedClaimEntry[]): number[][] {\n\t// No edge can exist when every claim is read-only; avoid scanning every pair.\n\tif (\n\t\tentries.every(\n\t\t\t({ resolution }) =>\n\t\t\t\tresolution.kind === \"claims\" && resolution.claims.every((claim) => claim.access === \"read\"),\n\t\t)\n\t)\n\t\treturn entries.map(() => []);\n\tconst dependencies: number[][] = [];\n\tfor (let index = 0; index < entries.length; index++) {\n\t\tconst blockers: number[] = [];\n\t\tfor (let earlier = 0; earlier < index; earlier++) {\n\t\t\tif (resolutionsConflict(entries[earlier].resolution, entries[index].resolution)) {\n\t\t\t\tblockers.push(earlier);\n\t\t\t}\n\t\t}\n\t\tdependencies.push(blockers);\n\t}\n\treturn dependencies;\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), entries };\n}\n\n/**\n * True when executing `sourceIndex` now would overlap an unsettled call whose\n * known claims conflict with `finalResolution`.\n *\n * Two distinct violations are covered:\n * - Earlier-source unsettled calls: the initial plan already ordered that\n * pair, so only post-hook scope drift can create the situation; the caller\n * defers the call instead of silently reversing the conflict pair.\n * - Later-source calls that are already running (present in `running`): a\n * hook that expands this call's scope onto their claims must defer — the\n * running call cannot be un-started. Later-source calls that are merely\n * pending keep source order: they may not overlap this call once it runs.\n *\n * Earlier-source calls inside `ready` (this same re-plan) are excluded — the\n * sub-level packing orders them.\n */\nexport function conflictsWithUnsettledClaim(\n\tsourceIndex: number,\n\tfinalResolution: ToolClaimResolution,\n\tready: ReadonlySet<number>,\n\tsettled: ReadonlySet<number>,\n\trunning: ReadonlySet<number>,\n\tresolutions: ReadonlyMap<number, ToolClaimResolution>,\n): boolean {\n\tfor (const [otherIndex, resolution] of resolutions) {\n\t\tif (otherIndex === sourceIndex || settled.has(otherIndex)) continue;\n\t\tif (otherIndex < sourceIndex) {\n\t\t\tif (ready.has(otherIndex)) continue;\n\t\t} else if (!running.has(otherIndex)) {\n\t\t\t// Later-source and not running: source order wins, no constraint.\n\t\t\tcontinue;\n\t\t}\n\t\tif (resolutionsConflict(resolution, finalResolution)) return true;\n\t}\n\treturn false;\n}\n"]}
1
+ {"version":3,"file":"tool-dag-scheduler.d.ts","sourceRoot":"","sources":["../src/tool-dag-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,EACN,KAAK,iBAAiB,EAGtB,KAAK,wBAAwB,EAG7B,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;IAChB;;;;OAIG;IACH,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACvC;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;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,EAAE,EAAE,CAGxF;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;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAC1C,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,mBAAmB,EACpC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,EAC1B,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5B,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5B,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,GACnD,OAAO,CAYT","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 { buildIndexedDagDependencies } from \"./tool-dag-index.ts\";\n\nimport {\n\ttype ClaimableToolCall,\n\tcanonicalizeClaims,\n\tclaimsConflict,\n\ttype ResolveToolClaimsOptions,\n\tresolutionsConflict,\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\t/**\n\t * The resolved, canonicalized claim entries the plan was built from. Callers\n\t * that re-resolve claims after argument mutation (e.g. authorization hooks)\n\t * compare against these to detect scope drift. Read-only for consumers.\n\t */\n\tentries: readonly ResolvedClaimEntry[];\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 * Direct conflicting predecessors for each source-ordered entry: `result[i]`\n * lists every earlier position `i` must wait for.\n *\n * This is the precedence graph that {@link assignDagLevels} approximates with a\n * barrier schedule. Levels force every call in level N+1 to wait for ALL of\n * level N, so one slow unrelated call in a level delays the whole next level.\n * Dependency edges only connect calls that actually conflict, so a consumer can\n * start each call as soon as its own predecessors settle.\n *\n * Deterministic and stable: the result is a pure function of the source-ordered\n * canonicalized claims, and every list is ascending and strictly earlier-only.\n * Transitively-implied edges are kept rather than reduced — waiting on an\n * already-finished ancestor costs nothing and keeps this a simple pure scan.\n *\n * The graph is never deeper than the barrier schedule: for any `j` in\n * `result[i]`, `i` conflicts with an entry in level `j`, so\n * `level(i) >= level(j) + 1`, and depth follows by induction.\n */\nexport function assignDagDependencies(entries: readonly ResolvedClaimEntry[]): number[][] {\n\treturn buildIndexedDagDependencies(entries, (left, right) => resolutionsConflict(left.resolution, right.resolution))\n\t\t.dependencies;\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), entries };\n}\n\n/**\n * True when executing `sourceIndex` now would overlap an unsettled call whose\n * known claims conflict with `finalResolution`.\n *\n * Two distinct violations are covered:\n * - Earlier-source unsettled calls: the initial plan already ordered that\n * pair, so only post-hook scope drift can create the situation; the caller\n * defers the call instead of silently reversing the conflict pair.\n * - Later-source calls that are already running (present in `running`): a\n * hook that expands this call's scope onto their claims must defer — the\n * running call cannot be un-started. Later-source calls that are merely\n * pending keep source order: they may not overlap this call once it runs.\n *\n * Earlier-source calls inside `ready` (this same re-plan) are excluded — the\n * sub-level packing orders them.\n */\nexport function conflictsWithUnsettledClaim(\n\tsourceIndex: number,\n\tfinalResolution: ToolClaimResolution,\n\tready: ReadonlySet<number>,\n\tsettled: ReadonlySet<number>,\n\trunning: ReadonlySet<number>,\n\tresolutions: ReadonlyMap<number, ToolClaimResolution>,\n): boolean {\n\tfor (const [otherIndex, resolution] of resolutions) {\n\t\tif (otherIndex === sourceIndex || settled.has(otherIndex)) continue;\n\t\tif (otherIndex < sourceIndex) {\n\t\t\tif (ready.has(otherIndex)) continue;\n\t\t} else if (!running.has(otherIndex)) {\n\t\t\t// Later-source and not running: source order wins, no constraint.\n\t\t\tcontinue;\n\t\t}\n\t\tif (resolutionsConflict(resolution, finalResolution)) return true;\n\t}\n\treturn false;\n}\n"]}
@@ -23,6 +23,7 @@
23
23
  *
24
24
  * This module uses no platform APIs (no `process`, fs, `node:path`, or timers).
25
25
  */
26
+ import { buildIndexedDagDependencies } from "./tool-dag-index.js";
26
27
  import { canonicalizeClaims, claimsConflict, resolutionsConflict, resolveToolClaimsForCall, } from "./tool-resource-claims.js";
27
28
  /**
28
29
  * Resolve and canonicalize claims for a whole batch, preserving source order.
@@ -136,20 +137,8 @@ export function assignDagLevels(entries) {
136
137
  * `level(i) >= level(j) + 1`, and depth follows by induction.
137
138
  */
138
139
  export function assignDagDependencies(entries) {
139
- // No edge can exist when every claim is read-only; avoid scanning every pair.
140
- if (entries.every(({ resolution }) => resolution.kind === "claims" && resolution.claims.every((claim) => claim.access === "read")))
141
- return entries.map(() => []);
142
- const dependencies = [];
143
- for (let index = 0; index < entries.length; index++) {
144
- const blockers = [];
145
- for (let earlier = 0; earlier < index; earlier++) {
146
- if (resolutionsConflict(entries[earlier].resolution, entries[index].resolution)) {
147
- blockers.push(earlier);
148
- }
149
- }
150
- dependencies.push(blockers);
151
- }
152
- return dependencies;
140
+ return buildIndexedDagDependencies(entries, (left, right) => resolutionsConflict(left.resolution, right.resolution))
141
+ .dependencies;
153
142
  }
154
143
  /**
155
144
  * Split each level into contiguous source-ordered chunks of at most `cap` calls.
@@ -1 +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\tresolutionsConflict,\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\t/**\n\t * The resolved, canonicalized claim entries the plan was built from. Callers\n\t * that re-resolve claims after argument mutation (e.g. authorization hooks)\n\t * compare against these to detect scope drift. Read-only for consumers.\n\t */\n\tentries: readonly ResolvedClaimEntry[];\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 * Direct conflicting predecessors for each source-ordered entry: `result[i]`\n * lists every earlier position `i` must wait for.\n *\n * This is the precedence graph that {@link assignDagLevels} approximates with a\n * barrier schedule. Levels force every call in level N+1 to wait for ALL of\n * level N, so one slow unrelated call in a level delays the whole next level.\n * Dependency edges only connect calls that actually conflict, so a consumer can\n * start each call as soon as its own predecessors settle.\n *\n * Deterministic and stable: the result is a pure function of the source-ordered\n * canonicalized claims, and every list is ascending and strictly earlier-only.\n * Transitively-implied edges are kept rather than reduced — waiting on an\n * already-finished ancestor costs nothing and keeps this a simple pure scan.\n *\n * The graph is never deeper than the barrier schedule: for any `j` in\n * `result[i]`, `i` conflicts with an entry in level `j`, so\n * `level(i) >= level(j) + 1`, and depth follows by induction.\n */\nexport function assignDagDependencies(entries: readonly ResolvedClaimEntry[]): number[][] {\n\t// No edge can exist when every claim is read-only; avoid scanning every pair.\n\tif (\n\t\tentries.every(\n\t\t\t({ resolution }) =>\n\t\t\t\tresolution.kind === \"claims\" && resolution.claims.every((claim) => claim.access === \"read\"),\n\t\t)\n\t)\n\t\treturn entries.map(() => []);\n\tconst dependencies: number[][] = [];\n\tfor (let index = 0; index < entries.length; index++) {\n\t\tconst blockers: number[] = [];\n\t\tfor (let earlier = 0; earlier < index; earlier++) {\n\t\t\tif (resolutionsConflict(entries[earlier].resolution, entries[index].resolution)) {\n\t\t\t\tblockers.push(earlier);\n\t\t\t}\n\t\t}\n\t\tdependencies.push(blockers);\n\t}\n\treturn dependencies;\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), entries };\n}\n\n/**\n * True when executing `sourceIndex` now would overlap an unsettled call whose\n * known claims conflict with `finalResolution`.\n *\n * Two distinct violations are covered:\n * - Earlier-source unsettled calls: the initial plan already ordered that\n * pair, so only post-hook scope drift can create the situation; the caller\n * defers the call instead of silently reversing the conflict pair.\n * - Later-source calls that are already running (present in `running`): a\n * hook that expands this call's scope onto their claims must defer — the\n * running call cannot be un-started. Later-source calls that are merely\n * pending keep source order: they may not overlap this call once it runs.\n *\n * Earlier-source calls inside `ready` (this same re-plan) are excluded — the\n * sub-level packing orders them.\n */\nexport function conflictsWithUnsettledClaim(\n\tsourceIndex: number,\n\tfinalResolution: ToolClaimResolution,\n\tready: ReadonlySet<number>,\n\tsettled: ReadonlySet<number>,\n\trunning: ReadonlySet<number>,\n\tresolutions: ReadonlyMap<number, ToolClaimResolution>,\n): boolean {\n\tfor (const [otherIndex, resolution] of resolutions) {\n\t\tif (otherIndex === sourceIndex || settled.has(otherIndex)) continue;\n\t\tif (otherIndex < sourceIndex) {\n\t\t\tif (ready.has(otherIndex)) continue;\n\t\t} else if (!running.has(otherIndex)) {\n\t\t\t// Later-source and not running: source order wins, no constraint.\n\t\t\tcontinue;\n\t\t}\n\t\tif (resolutionsConflict(resolution, finalResolution)) return true;\n\t}\n\treturn false;\n}\n"]}
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 { buildIndexedDagDependencies } from \"./tool-dag-index.ts\";\n\nimport {\n\ttype ClaimableToolCall,\n\tcanonicalizeClaims,\n\tclaimsConflict,\n\ttype ResolveToolClaimsOptions,\n\tresolutionsConflict,\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\t/**\n\t * The resolved, canonicalized claim entries the plan was built from. Callers\n\t * that re-resolve claims after argument mutation (e.g. authorization hooks)\n\t * compare against these to detect scope drift. Read-only for consumers.\n\t */\n\tentries: readonly ResolvedClaimEntry[];\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 * Direct conflicting predecessors for each source-ordered entry: `result[i]`\n * lists every earlier position `i` must wait for.\n *\n * This is the precedence graph that {@link assignDagLevels} approximates with a\n * barrier schedule. Levels force every call in level N+1 to wait for ALL of\n * level N, so one slow unrelated call in a level delays the whole next level.\n * Dependency edges only connect calls that actually conflict, so a consumer can\n * start each call as soon as its own predecessors settle.\n *\n * Deterministic and stable: the result is a pure function of the source-ordered\n * canonicalized claims, and every list is ascending and strictly earlier-only.\n * Transitively-implied edges are kept rather than reduced — waiting on an\n * already-finished ancestor costs nothing and keeps this a simple pure scan.\n *\n * The graph is never deeper than the barrier schedule: for any `j` in\n * `result[i]`, `i` conflicts with an entry in level `j`, so\n * `level(i) >= level(j) + 1`, and depth follows by induction.\n */\nexport function assignDagDependencies(entries: readonly ResolvedClaimEntry[]): number[][] {\n\treturn buildIndexedDagDependencies(entries, (left, right) => resolutionsConflict(left.resolution, right.resolution))\n\t\t.dependencies;\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), entries };\n}\n\n/**\n * True when executing `sourceIndex` now would overlap an unsettled call whose\n * known claims conflict with `finalResolution`.\n *\n * Two distinct violations are covered:\n * - Earlier-source unsettled calls: the initial plan already ordered that\n * pair, so only post-hook scope drift can create the situation; the caller\n * defers the call instead of silently reversing the conflict pair.\n * - Later-source calls that are already running (present in `running`): a\n * hook that expands this call's scope onto their claims must defer — the\n * running call cannot be un-started. Later-source calls that are merely\n * pending keep source order: they may not overlap this call once it runs.\n *\n * Earlier-source calls inside `ready` (this same re-plan) are excluded — the\n * sub-level packing orders them.\n */\nexport function conflictsWithUnsettledClaim(\n\tsourceIndex: number,\n\tfinalResolution: ToolClaimResolution,\n\tready: ReadonlySet<number>,\n\tsettled: ReadonlySet<number>,\n\trunning: ReadonlySet<number>,\n\tresolutions: ReadonlyMap<number, ToolClaimResolution>,\n): boolean {\n\tfor (const [otherIndex, resolution] of resolutions) {\n\t\tif (otherIndex === sourceIndex || settled.has(otherIndex)) continue;\n\t\tif (otherIndex < sourceIndex) {\n\t\t\tif (ready.has(otherIndex)) continue;\n\t\t} else if (!running.has(otherIndex)) {\n\t\t\t// Later-source and not running: source order wins, no constraint.\n\t\t\tcontinue;\n\t\t}\n\t\tif (resolutionsConflict(resolution, finalResolution)) return true;\n\t}\n\treturn false;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { FinalizedToolCallOutcome } from "./tool-execution-boundary.ts";
2
+ /** Bind terminal callbacks to the transcript gate's unique tool-call IDs. */
3
+ export declare function indexFinalizedToolCalls(calls: readonly FinalizedToolCallOutcome[]): ReadonlyMap<string, FinalizedToolCallOutcome>;
4
+ //# sourceMappingURL=tool-terminal-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-terminal-index.d.ts","sourceRoot":"","sources":["../src/tool-terminal-index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAE7E,6EAA6E;AAC7E,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,SAAS,wBAAwB,EAAE,GACxC,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAQ/C","sourcesContent":["import type { FinalizedToolCallOutcome } from \"./tool-execution-boundary.ts\";\n\n/** Bind terminal callbacks to the transcript gate's unique tool-call IDs. */\nexport function indexFinalizedToolCalls(\n\tcalls: readonly FinalizedToolCallOutcome[],\n): ReadonlyMap<string, FinalizedToolCallOutcome> {\n\tconst index = new Map<string, FinalizedToolCallOutcome>();\n\tfor (const call of calls) {\n\t\tconst id = call.toolCall.id;\n\t\tif (index.has(id)) throw new Error(`duplicate finalized tool call id: ${id}`);\n\t\tindex.set(id, call);\n\t}\n\treturn index;\n}\n"]}
@@ -0,0 +1,12 @@
1
+ /** Bind terminal callbacks to the transcript gate's unique tool-call IDs. */
2
+ export function indexFinalizedToolCalls(calls) {
3
+ const index = new Map();
4
+ for (const call of calls) {
5
+ const id = call.toolCall.id;
6
+ if (index.has(id))
7
+ throw new Error(`duplicate finalized tool call id: ${id}`);
8
+ index.set(id, call);
9
+ }
10
+ return index;
11
+ }
12
+ //# sourceMappingURL=tool-terminal-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-terminal-index.js","sourceRoot":"","sources":["../src/tool-terminal-index.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,MAAM,UAAU,uBAAuB,CACtC,KAA0C,EACM;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoC,CAAC;IAC1D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,EAAE,EAAE,CAAC,CAAC;QAC9E,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb","sourcesContent":["import type { FinalizedToolCallOutcome } from \"./tool-execution-boundary.ts\";\n\n/** Bind terminal callbacks to the transcript gate's unique tool-call IDs. */\nexport function indexFinalizedToolCalls(\n\tcalls: readonly FinalizedToolCallOutcome[],\n): ReadonlyMap<string, FinalizedToolCallOutcome> {\n\tconst index = new Map<string, FinalizedToolCallOutcome>();\n\tfor (const call of calls) {\n\t\tconst id = call.toolCall.id;\n\t\tif (index.has(id)) throw new Error(`duplicate finalized tool call id: ${id}`);\n\t\tindex.set(id, call);\n\t}\n\treturn index;\n}\n"]}