omk-agent-core 0.90.7 → 0.90.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -1
- package/dist/agent-loop.d.ts +25 -2
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +524 -189
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +29 -7
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +81 -46
- package/dist/agent.js.map +1 -1
- package/dist/builtin-tool-resource-claims.d.ts +19 -0
- package/dist/builtin-tool-resource-claims.d.ts.map +1 -0
- package/dist/builtin-tool-resource-claims.js +200 -0
- package/dist/builtin-tool-resource-claims.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/node-resource-resolver.d.ts +42 -0
- package/dist/node-resource-resolver.d.ts.map +1 -0
- package/dist/node-resource-resolver.js +149 -0
- package/dist/node-resource-resolver.js.map +1 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +2 -0
- package/dist/node.js.map +1 -1
- package/dist/parallel-tool-batch.d.ts +12 -1
- package/dist/parallel-tool-batch.d.ts.map +1 -1
- package/dist/parallel-tool-batch.js +71 -49
- package/dist/parallel-tool-batch.js.map +1 -1
- package/dist/path-segments.d.ts +21 -1
- package/dist/path-segments.d.ts.map +1 -1
- package/dist/path-segments.js +91 -9
- package/dist/path-segments.js.map +1 -1
- package/dist/plain-data.d.ts +7 -0
- package/dist/plain-data.d.ts.map +1 -0
- package/dist/plain-data.js +70 -0
- package/dist/plain-data.js.map +1 -0
- package/dist/tool-dag-scheduler.d.ts +86 -0
- package/dist/tool-dag-scheduler.d.ts.map +1 -0
- package/dist/tool-dag-scheduler.js +171 -0
- package/dist/tool-dag-scheduler.js.map +1 -0
- package/dist/tool-execution-boundary.d.ts +52 -0
- package/dist/tool-execution-boundary.d.ts.map +1 -0
- package/dist/tool-execution-boundary.js +185 -0
- package/dist/tool-execution-boundary.js.map +1 -0
- package/dist/tool-resource-claims.d.ts +31 -0
- package/dist/tool-resource-claims.d.ts.map +1 -0
- package/dist/tool-resource-claims.js +128 -0
- package/dist/tool-resource-claims.js.map +1 -0
- package/dist/tool-timeout.d.ts +96 -0
- package/dist/tool-timeout.d.ts.map +1 -0
- package/dist/tool-timeout.js +173 -0
- package/dist/tool-timeout.js.map +1 -0
- package/dist/tool-transcript-integrity.d.ts +65 -0
- package/dist/tool-transcript-integrity.d.ts.map +1 -0
- package/dist/tool-transcript-integrity.js +223 -0
- package/dist/tool-transcript-integrity.js.map +1 -0
- package/dist/types.d.ts +219 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +50 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parallel-tool-batch.js","sourceRoot":"","sources":["../src/parallel-tool-batch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEjG,oFAAoF;AACpF,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAS,CAAC,SAAS,CAAC,CAAC,CAAC;AAEjE,qFAAqF;AACrF,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAS;IAClD,MAAM;IACN,MAAM;IACN,IAAI;IACJ,MAAM;IACN,cAAc;IACd,gBAAgB;IAChB,YAAY;IACZ,aAAa;IACb,gBAAgB;IAChB,aAAa;IACb,YAAY;CACZ,CAAC,CAAC;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAoB5E,MAAM,mBAAmB,GACxB,6GAA6G,CAAC;AAC/G,MAAM,kBAAkB,GAAG,kBAAkB,CAAC;AAE9C,MAAM,UAAU,wBAAwB,CAAC,OAAe,EAAW;IAClE,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,MAAM,UAAU,wBAAwB,CACvC,QAAgB,EAChB,YAAqC,EACrC,GAAG,GAAW,OAAO,CAAC,GAAG,EAAE,EACX;IAChB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC;IAClC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACpD,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,QAAQ,GAAG,oBAAoB,CACpC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CACjF,CAAC;IACF,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,QAAQ,CAAC;IACjB,CAAC;IACD,OAAO,gBAAgB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAAA,CACvC;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAa,EAAW;IAClE,OAAO,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAAA,CACxC;
|
|
1
|
+
{"version":3,"file":"parallel-tool-batch.js","sourceRoot":"","sources":["../src/parallel-tool-batch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEjG,oFAAoF;AACpF,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAS,CAAC,SAAS,CAAC,CAAC,CAAC;AAEjE,qFAAqF;AACrF,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAS;IAClD,MAAM;IACN,MAAM;IACN,IAAI;IACJ,MAAM;IACN,cAAc;IACd,gBAAgB;IAChB,YAAY;IACZ,aAAa;IACb,gBAAgB;IAChB,aAAa;IACb,YAAY;CACZ,CAAC,CAAC;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAoB5E,MAAM,mBAAmB,GACxB,6GAA6G,CAAC;AAC/G,MAAM,kBAAkB,GAAG,kBAAkB,CAAC;AAE9C,MAAM,UAAU,wBAAwB,CAAC,OAAe,EAAW;IAClE,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,MAAM,UAAU,wBAAwB,CACvC,QAAgB,EAChB,YAAqC,EACrC,GAAG,GAAW,OAAO,CAAC,GAAG,EAAE,EACX;IAChB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC;IAClC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACpD,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,QAAQ,GAAG,oBAAoB,CACpC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CACjF,CAAC;IACF,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,QAAQ,CAAC;IACjB,CAAC;IACD,OAAO,gBAAgB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAAA,CACvC;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAa,EAAW;IAClE,OAAO,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAAA,CACxC;AAID;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,QAAgC,EAAE,OAA0C,EAAgB;IACrH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3B,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,MAAM,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,YAAY,EAAE,CAAC;QACtD,OAAO,MAAM,CAAC;IACf,CAAC;IACD,MAAM,YAAY,GAAG,QAAQ,CAAC,SAAS,CAAC;IACxC,IAAI,CAAC,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACtF,OAAO,MAAM,CAAC;IACf,CAAC;IACD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC;IACf,CAAC;IACD,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC;IACf,CAAC;IACD,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,UAAU,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5F,OAAO,MAAM,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CACtC,SAAmC,EACnC,OAAO,GAAsC,EAAE,EAClC;IACb,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,IAAI,IAAI,GAAa,EAAE,CAAC;IACxB,IAAI,SAAS,GAAa,EAAE,CAAC;IAE7B,MAAM,SAAS,GAAG,GAAS,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,GAAG,EAAE,CAAC;YACV,SAAS,GAAG,EAAE,CAAC;QAChB,CAAC;IAAA,CACD,CAAC;IAEF,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACrB,SAAS,EAAE,CAAC;YACZ,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YACpB,SAAS;QACV,CAAC;QACD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACrB,MAAM,UAAU,GAAG,wBAAwB,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5F,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;gBACzB,SAAS,EAAE,CAAC;gBACZ,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;gBACpB,SAAS;YACV,CAAC;YACD,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;gBACtE,SAAS,EAAE,CAAC;YACb,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;IACD,SAAS,EAAE,CAAC;IACZ,OAAO,KAAK,CAAC;AAAA,CACb;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACzC,SAAmC,EACnC,OAAO,GAAsC,EAAE,EACrC;IACV,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,uBAAuB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAAA,CAChE","sourcesContent":["import { joinPathSegments, normalizePathSlashes, pathSegmentsOverlap } from \"./path-segments.ts\";\n\n/** Tool names that must never run concurrently with other tool calls in a batch. */\nexport const NEVER_PARALLEL_TOOLS = new Set<string>([\"clarify\"]);\n\n/** Read-only tools with no shared mutable session state (OMK built-in / harness). */\nexport const PARALLEL_SAFE_TOOLS = new Set<string>([\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"read\",\n\t\"search_files\",\n\t\"session_search\",\n\t\"skill_view\",\n\t\"skills_list\",\n\t\"vision_analyze\",\n\t\"web_extract\",\n\t\"web_search\",\n]);\n\n/** File tools that may run concurrently when they target independent paths. */\nexport const PATH_SCOPED_TOOLS = new Set<string>([\"read\", \"write\", \"edit\"]);\n\nexport type ParallelizableToolCall = {\n\tname: string;\n\targuments: Record<string, unknown>;\n};\n\nexport type ToolParallelPolicy = \"sequential\" | \"parallel\";\n\nexport type ShouldParallelizeToolBatchOptions = {\n\tcwd?: string;\n\t/** Per-tool executionMode from AgentTool definitions (extension/custom tools). */\n\ttoolPolicies?: ReadonlyMap<string, ToolParallelPolicy>;\n\t/**\n\t * When a tool name is not in built-in parallel/path sets and has no explicit policy,\n\t * return true to allow parallel batching (e.g. test harness tools with executionMode=parallel).\n\t */\n\tallowUnknownParallel?: (toolName: string) => boolean;\n};\n\nconst DESTRUCTIVE_COMMAND =\n\t/(?:^|\\s|&&|\\|\\||;|`)(?:rm\\s|rmdir\\s|mv\\s|sed\\s+-i|truncate\\s|dd\\s|shred\\s|git\\s+(?:reset|clean|checkout)\\s)/;\nconst REDIRECT_OVERWRITE = /[^>]>[^>]|^>[^>]/;\n\nexport function isDestructiveBashCommand(command: string): boolean {\n\tif (!command) {\n\t\treturn false;\n\t}\n\tif (DESTRUCTIVE_COMMAND.test(command)) {\n\t\treturn true;\n\t}\n\tif (REDIRECT_OVERWRITE.test(command)) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nexport function extractParallelScopePath(\n\ttoolName: string,\n\tfunctionArgs: Record<string, unknown>,\n\tcwd: string = process.cwd(),\n): string | null {\n\tif (!PATH_SCOPED_TOOLS.has(toolName)) {\n\t\treturn null;\n\t}\n\tconst rawPath = functionArgs.path;\n\tif (typeof rawPath !== \"string\" || !rawPath.trim()) {\n\t\treturn null;\n\t}\n\tconst expanded = normalizePathSlashes(\n\t\trawPath.startsWith(\"~\") ? rawPath.replace(/^~/, process.env.HOME ?? \"\") : rawPath,\n\t);\n\tif (expanded.startsWith(\"/\")) {\n\t\treturn expanded;\n\t}\n\treturn joinPathSegments(cwd, expanded);\n}\n\n/** True when two normalized paths may refer to the same file or subtree. */\nexport function pathsOverlap(left: string, right: string): boolean {\n\treturn pathSegmentsOverlap(left, right);\n}\n\ntype ToolCallKind = \"solo\" | \"safe\" | \"path\";\n\n/**\n * Classify one tool call for wave scheduling. \"solo\" calls run alone in their\n * own wave (fail closed): clarify-style tools, sequential-policy tools, bash\n * (never parallel-safe with any other call), invalid argument shapes, and\n * unknown tools without an explicit parallel grant.\n */\nfunction classifyToolCall(toolCall: ParallelizableToolCall, options: ShouldParallelizeToolBatchOptions): ToolCallKind {\n\tconst name = toolCall.name;\n\tif (NEVER_PARALLEL_TOOLS.has(name)) {\n\t\treturn \"solo\";\n\t}\n\tif (options.toolPolicies?.get(name) === \"sequential\") {\n\t\treturn \"solo\";\n\t}\n\tconst functionArgs = toolCall.arguments;\n\tif (!functionArgs || typeof functionArgs !== \"object\" || Array.isArray(functionArgs)) {\n\t\treturn \"solo\";\n\t}\n\tif (name === \"bash\") {\n\t\treturn \"solo\";\n\t}\n\tif (PATH_SCOPED_TOOLS.has(name)) {\n\t\treturn \"path\";\n\t}\n\tif (PARALLEL_SAFE_TOOLS.has(name)) {\n\t\treturn \"safe\";\n\t}\n\tif (options.toolPolicies?.get(name) === \"parallel\" || options.allowUnknownParallel?.(name)) {\n\t\treturn \"safe\";\n\t}\n\treturn \"solo\";\n}\n\n/**\n * Partition a tool-call batch into ordered, contiguous waves of original\n * indices. Calls inside one wave may run concurrently; waves run one after\n * another in source order, so cross-wave ordering always matches the model's\n * emission order. Solo-classified calls become single-call waves, and a\n * path-scoped call starts a new wave when its target overlaps a path already\n * reserved in the current wave.\n */\nexport function partitionToolBatchWaves(\n\ttoolCalls: ParallelizableToolCall[],\n\toptions: ShouldParallelizeToolBatchOptions = {},\n): number[][] {\n\tconst waves: number[][] = [];\n\tlet wave: number[] = [];\n\tlet wavePaths: string[] = [];\n\n\tconst closeWave = (): void => {\n\t\tif (wave.length > 0) {\n\t\t\twaves.push(wave);\n\t\t\twave = [];\n\t\t\twavePaths = [];\n\t\t}\n\t};\n\n\tfor (let index = 0; index < toolCalls.length; index++) {\n\t\tconst toolCall = toolCalls[index];\n\t\tconst kind = classifyToolCall(toolCall, options);\n\t\tif (kind === \"solo\") {\n\t\t\tcloseWave();\n\t\t\twaves.push([index]);\n\t\t\tcontinue;\n\t\t}\n\t\tif (kind === \"path\") {\n\t\t\tconst scopedPath = extractParallelScopePath(toolCall.name, toolCall.arguments, options.cwd);\n\t\t\tif (scopedPath === null) {\n\t\t\t\tcloseWave();\n\t\t\t\twaves.push([index]);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (wavePaths.some((existing) => pathsOverlap(scopedPath, existing))) {\n\t\t\t\tcloseWave();\n\t\t\t}\n\t\t\twavePaths.push(scopedPath);\n\t\t}\n\t\twave.push(index);\n\t}\n\tcloseWave();\n\treturn waves;\n}\n\n/**\n * Return true when an entire tool-call batch is safe to run concurrently as a\n * single wave (Hermes-style policy, OMK tool names). Wave-based execution in\n * the agent loop uses partitionToolBatchWaves directly for partial parallelism.\n */\nexport function shouldParallelizeToolBatch(\n\ttoolCalls: ParallelizableToolCall[],\n\toptions: ShouldParallelizeToolBatchOptions = {},\n): boolean {\n\tif (toolCalls.length <= 1) {\n\t\treturn false;\n\t}\n\treturn partitionToolBatchWaves(toolCalls, options).length === 1;\n}\n"]}
|
package/dist/path-segments.d.ts
CHANGED
|
@@ -5,6 +5,26 @@ export declare function normalizePathSlashes(raw: string): string;
|
|
|
5
5
|
export declare function pathSegments(normalized: string): string[];
|
|
6
6
|
/** Join cwd and relative path using forward slashes only. */
|
|
7
7
|
export declare function joinPathSegments(cwd: string, relative: string): string;
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Collapse "." and ".." segments without touching the filesystem. Returns null
|
|
10
|
+
* when a ".." escapes above the root, so callers can fail closed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function collapsePathSegments(segments: readonly string[]): string[] | null;
|
|
13
|
+
/**
|
|
14
|
+
* Canonicalize a path lexically while preserving a Windows drive root. Drive-
|
|
15
|
+
* relative forms such as `C:foo` and UNC forms are ambiguous without platform
|
|
16
|
+
* state and fail closed. Windows drive-root traversal clamps at the drive root,
|
|
17
|
+
* matching rooted path semantics; POSIX/rootless traversal keeps the existing
|
|
18
|
+
* fail-closed behavior.
|
|
19
|
+
*/
|
|
20
|
+
export declare function canonicalizeLexicalPath(raw: string): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* True when two normalized paths may refer to the same file or subtree.
|
|
23
|
+
* Fail-closed: "."/".." are collapsed first, root-escaping paths always count
|
|
24
|
+
* as overlapping, and segments compare case-insensitively so case-aliasing
|
|
25
|
+
* filesystems never split one file into two "independent" paths. Symlinks and
|
|
26
|
+
* hardlinks are not resolved here (no fs access); overlap only forces
|
|
27
|
+
* sequential execution, so false positives are safe.
|
|
28
|
+
*/
|
|
9
29
|
export declare function pathSegmentsOverlap(left: string, right: string): boolean;
|
|
10
30
|
//# sourceMappingURL=path-segments.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-segments.d.ts","sourceRoot":"","sources":["../src/path-segments.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzD;AAED,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUtE;AAED,
|
|
1
|
+
{"version":3,"file":"path-segments.d.ts","sourceRoot":"","sources":["../src/path-segments.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzD;AAED,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUtE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,IAAI,CAYjF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4BlE;AAoBD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAsBxE","sourcesContent":["/**\n * Path segment helpers without node:path — safe for browser bundles (browser-smoke).\n */\n\nexport function normalizePathSlashes(raw: string): string {\n\treturn raw.replace(/\\\\/g, \"/\");\n}\n\nexport function pathSegments(normalized: string): string[] {\n\treturn normalized.split(\"/\").filter((s) => s.length > 0);\n}\n\n/** Join cwd and relative path using forward slashes only. */\nexport function joinPathSegments(cwd: string, relative: string): string {\n\tconst base = normalizePathSlashes(cwd).replace(/\\/+$/, \"\");\n\tconst rel = normalizePathSlashes(relative);\n\tif (rel.length === 0) {\n\t\treturn base || \"/\";\n\t}\n\tif (rel.startsWith(\"/\") || /^[A-Za-z]:/.test(rel)) {\n\t\treturn rel;\n\t}\n\treturn `${base}/${rel}`;\n}\n\n/**\n * Collapse \".\" and \"..\" segments without touching the filesystem. Returns null\n * when a \"..\" escapes above the root, so callers can fail closed.\n */\nexport function collapsePathSegments(segments: readonly string[]): string[] | null {\n\tconst collapsed: string[] = [];\n\tfor (const segment of segments) {\n\t\tif (segment === \".\") continue;\n\t\tif (segment === \"..\") {\n\t\t\tif (collapsed.length === 0) return null;\n\t\t\tcollapsed.pop();\n\t\t\tcontinue;\n\t\t}\n\t\tcollapsed.push(segment);\n\t}\n\treturn collapsed;\n}\n\n/**\n * Canonicalize a path lexically while preserving a Windows drive root. Drive-\n * relative forms such as `C:foo` and UNC forms are ambiguous without platform\n * state and fail closed. Windows drive-root traversal clamps at the drive root,\n * matching rooted path semantics; POSIX/rootless traversal keeps the existing\n * fail-closed behavior.\n */\nexport function canonicalizeLexicalPath(raw: string): string | null {\n\tconst normalized = normalizePathSlashes(raw);\n\tif (normalized.startsWith(\"//\")) {\n\t\treturn null;\n\t}\n\tconst driveAbsolute = /^([A-Za-z]:)\\/+(.*)$/.exec(normalized);\n\tif (driveAbsolute) {\n\t\tconst collapsed: string[] = [];\n\t\tfor (const segment of pathSegments(driveAbsolute[2])) {\n\t\t\tif (segment === \".\") continue;\n\t\t\tif (segment === \"..\") {\n\t\t\t\tcollapsed.pop();\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcollapsed.push(segment);\n\t\t}\n\t\treturn `${driveAbsolute[1]}/${collapsed.join(\"/\")}`;\n\t}\n\tif (/^[A-Za-z]:/.test(normalized)) {\n\t\treturn null;\n\t}\n\n\tconst rooted = normalized.startsWith(\"/\");\n\tconst collapsed = collapsePathSegments(pathSegments(normalized));\n\tif (collapsed === null) {\n\t\treturn null;\n\t}\n\treturn rooted ? `/${collapsed.join(\"/\")}` : collapsed.join(\"/\");\n}\n\ninterface CanonicalPathParts {\n\tdrive: string | null;\n\trooted: boolean;\n\tsegments: string[];\n}\n\nfunction canonicalPathParts(raw: string): CanonicalPathParts | null {\n\tconst canonical = canonicalizeLexicalPath(raw);\n\tif (canonical === null) {\n\t\treturn null;\n\t}\n\tconst driveAbsolute = /^([A-Za-z]:)\\/(.*)$/.exec(canonical);\n\tif (driveAbsolute) {\n\t\treturn { drive: driveAbsolute[1].toLowerCase(), rooted: true, segments: pathSegments(driveAbsolute[2]) };\n\t}\n\treturn { drive: null, rooted: canonical.startsWith(\"/\"), segments: pathSegments(canonical) };\n}\n\n/**\n * True when two normalized paths may refer to the same file or subtree.\n * Fail-closed: \".\"/\"..\" are collapsed first, root-escaping paths always count\n * as overlapping, and segments compare case-insensitively so case-aliasing\n * filesystems never split one file into two \"independent\" paths. Symlinks and\n * hardlinks are not resolved here (no fs access); overlap only forces\n * sequential execution, so false positives are safe.\n */\nexport function pathSegmentsOverlap(left: string, right: string): boolean {\n\tconst leftPath = canonicalPathParts(left);\n\tconst rightPath = canonicalPathParts(right);\n\tif (leftPath === null || rightPath === null) {\n\t\treturn true;\n\t}\n\tif (leftPath.drive !== null && rightPath.drive !== null && leftPath.drive !== rightPath.drive) {\n\t\treturn false;\n\t}\n\tif ((leftPath.drive !== null && !rightPath.rooted) || (rightPath.drive !== null && !leftPath.rooted)) {\n\t\treturn true;\n\t}\n\tif (leftPath.segments.length === 0 || rightPath.segments.length === 0) {\n\t\treturn true;\n\t}\n\tconst commonLen = Math.min(leftPath.segments.length, rightPath.segments.length);\n\tfor (let i = 0; i < commonLen; i++) {\n\t\tif (leftPath.segments[i].toLowerCase() !== rightPath.segments[i].toLowerCase()) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n"]}
|
package/dist/path-segments.js
CHANGED
|
@@ -10,25 +10,107 @@ export function pathSegments(normalized) {
|
|
|
10
10
|
/** Join cwd and relative path using forward slashes only. */
|
|
11
11
|
export function joinPathSegments(cwd, relative) {
|
|
12
12
|
const base = normalizePathSlashes(cwd).replace(/\/+$/, "");
|
|
13
|
-
const rel = normalizePathSlashes(relative)
|
|
13
|
+
const rel = normalizePathSlashes(relative);
|
|
14
14
|
if (rel.length === 0) {
|
|
15
15
|
return base || "/";
|
|
16
16
|
}
|
|
17
|
-
if (rel.startsWith("/")) {
|
|
17
|
+
if (rel.startsWith("/") || /^[A-Za-z]:/.test(rel)) {
|
|
18
18
|
return rel;
|
|
19
19
|
}
|
|
20
20
|
return `${base}/${rel}`;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Collapse "." and ".." segments without touching the filesystem. Returns null
|
|
24
|
+
* when a ".." escapes above the root, so callers can fail closed.
|
|
25
|
+
*/
|
|
26
|
+
export function collapsePathSegments(segments) {
|
|
27
|
+
const collapsed = [];
|
|
28
|
+
for (const segment of segments) {
|
|
29
|
+
if (segment === ".")
|
|
30
|
+
continue;
|
|
31
|
+
if (segment === "..") {
|
|
32
|
+
if (collapsed.length === 0)
|
|
33
|
+
return null;
|
|
34
|
+
collapsed.pop();
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
collapsed.push(segment);
|
|
38
|
+
}
|
|
39
|
+
return collapsed;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Canonicalize a path lexically while preserving a Windows drive root. Drive-
|
|
43
|
+
* relative forms such as `C:foo` and UNC forms are ambiguous without platform
|
|
44
|
+
* state and fail closed. Windows drive-root traversal clamps at the drive root,
|
|
45
|
+
* matching rooted path semantics; POSIX/rootless traversal keeps the existing
|
|
46
|
+
* fail-closed behavior.
|
|
47
|
+
*/
|
|
48
|
+
export function canonicalizeLexicalPath(raw) {
|
|
49
|
+
const normalized = normalizePathSlashes(raw);
|
|
50
|
+
if (normalized.startsWith("//")) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const driveAbsolute = /^([A-Za-z]:)\/+(.*)$/.exec(normalized);
|
|
54
|
+
if (driveAbsolute) {
|
|
55
|
+
const collapsed = [];
|
|
56
|
+
for (const segment of pathSegments(driveAbsolute[2])) {
|
|
57
|
+
if (segment === ".")
|
|
58
|
+
continue;
|
|
59
|
+
if (segment === "..") {
|
|
60
|
+
collapsed.pop();
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
collapsed.push(segment);
|
|
64
|
+
}
|
|
65
|
+
return `${driveAbsolute[1]}/${collapsed.join("/")}`;
|
|
66
|
+
}
|
|
67
|
+
if (/^[A-Za-z]:/.test(normalized)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const rooted = normalized.startsWith("/");
|
|
71
|
+
const collapsed = collapsePathSegments(pathSegments(normalized));
|
|
72
|
+
if (collapsed === null) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return rooted ? `/${collapsed.join("/")}` : collapsed.join("/");
|
|
76
|
+
}
|
|
77
|
+
function canonicalPathParts(raw) {
|
|
78
|
+
const canonical = canonicalizeLexicalPath(raw);
|
|
79
|
+
if (canonical === null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const driveAbsolute = /^([A-Za-z]:)\/(.*)$/.exec(canonical);
|
|
83
|
+
if (driveAbsolute) {
|
|
84
|
+
return { drive: driveAbsolute[1].toLowerCase(), rooted: true, segments: pathSegments(driveAbsolute[2]) };
|
|
85
|
+
}
|
|
86
|
+
return { drive: null, rooted: canonical.startsWith("/"), segments: pathSegments(canonical) };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* True when two normalized paths may refer to the same file or subtree.
|
|
90
|
+
* Fail-closed: "."/".." are collapsed first, root-escaping paths always count
|
|
91
|
+
* as overlapping, and segments compare case-insensitively so case-aliasing
|
|
92
|
+
* filesystems never split one file into two "independent" paths. Symlinks and
|
|
93
|
+
* hardlinks are not resolved here (no fs access); overlap only forces
|
|
94
|
+
* sequential execution, so false positives are safe.
|
|
95
|
+
*/
|
|
23
96
|
export function pathSegmentsOverlap(left, right) {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
return
|
|
97
|
+
const leftPath = canonicalPathParts(left);
|
|
98
|
+
const rightPath = canonicalPathParts(right);
|
|
99
|
+
if (leftPath === null || rightPath === null) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (leftPath.drive !== null && rightPath.drive !== null && leftPath.drive !== rightPath.drive) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if ((leftPath.drive !== null && !rightPath.rooted) || (rightPath.drive !== null && !leftPath.rooted)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (leftPath.segments.length === 0 || rightPath.segments.length === 0) {
|
|
109
|
+
return true;
|
|
28
110
|
}
|
|
29
|
-
const commonLen = Math.min(
|
|
111
|
+
const commonLen = Math.min(leftPath.segments.length, rightPath.segments.length);
|
|
30
112
|
for (let i = 0; i < commonLen; i++) {
|
|
31
|
-
if (
|
|
113
|
+
if (leftPath.segments[i].toLowerCase() !== rightPath.segments[i].toLowerCase()) {
|
|
32
114
|
return false;
|
|
33
115
|
}
|
|
34
116
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-segments.js","sourceRoot":"","sources":["../src/path-segments.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAU;IACzD,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAAA,CAC/B;AAED,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAY;IAC1D,OAAO,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAAA,CACzD;AAED,6DAA6D;AAC7D,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,QAAgB,EAAU;IACvE,MAAM,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"path-segments.js","sourceRoot":"","sources":["../src/path-segments.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Path segment helpers without node:path — safe for browser bundles (browser-smoke).\n */\n\nexport function normalizePathSlashes(raw: string): string {\n\treturn raw.replace(/\\\\/g, \"/\");\n}\n\nexport function pathSegments(normalized: string): string[] {\n\treturn normalized.split(\"/\").filter((s) => s.length > 0);\n}\n\n/** Join cwd and relative path using forward slashes only. */\nexport function joinPathSegments(cwd: string, relative: string): string {\n\tconst base = normalizePathSlashes(cwd).replace(/\\/+$/, \"\");\n\tconst rel = normalizePathSlashes(relative);\n\tif (rel.length === 0) {\n\t\treturn base || \"/\";\n\t}\n\tif (rel.startsWith(\"/\") || /^[A-Za-z]:/.test(rel)) {\n\t\treturn rel;\n\t}\n\treturn `${base}/${rel}`;\n}\n\n/**\n * Collapse \".\" and \"..\" segments without touching the filesystem. Returns null\n * when a \"..\" escapes above the root, so callers can fail closed.\n */\nexport function collapsePathSegments(segments: readonly string[]): string[] | null {\n\tconst collapsed: string[] = [];\n\tfor (const segment of segments) {\n\t\tif (segment === \".\") continue;\n\t\tif (segment === \"..\") {\n\t\t\tif (collapsed.length === 0) return null;\n\t\t\tcollapsed.pop();\n\t\t\tcontinue;\n\t\t}\n\t\tcollapsed.push(segment);\n\t}\n\treturn collapsed;\n}\n\n/**\n * Canonicalize a path lexically while preserving a Windows drive root. Drive-\n * relative forms such as `C:foo` and UNC forms are ambiguous without platform\n * state and fail closed. Windows drive-root traversal clamps at the drive root,\n * matching rooted path semantics; POSIX/rootless traversal keeps the existing\n * fail-closed behavior.\n */\nexport function canonicalizeLexicalPath(raw: string): string | null {\n\tconst normalized = normalizePathSlashes(raw);\n\tif (normalized.startsWith(\"//\")) {\n\t\treturn null;\n\t}\n\tconst driveAbsolute = /^([A-Za-z]:)\\/+(.*)$/.exec(normalized);\n\tif (driveAbsolute) {\n\t\tconst collapsed: string[] = [];\n\t\tfor (const segment of pathSegments(driveAbsolute[2])) {\n\t\t\tif (segment === \".\") continue;\n\t\t\tif (segment === \"..\") {\n\t\t\t\tcollapsed.pop();\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcollapsed.push(segment);\n\t\t}\n\t\treturn `${driveAbsolute[1]}/${collapsed.join(\"/\")}`;\n\t}\n\tif (/^[A-Za-z]:/.test(normalized)) {\n\t\treturn null;\n\t}\n\n\tconst rooted = normalized.startsWith(\"/\");\n\tconst collapsed = collapsePathSegments(pathSegments(normalized));\n\tif (collapsed === null) {\n\t\treturn null;\n\t}\n\treturn rooted ? `/${collapsed.join(\"/\")}` : collapsed.join(\"/\");\n}\n\ninterface CanonicalPathParts {\n\tdrive: string | null;\n\trooted: boolean;\n\tsegments: string[];\n}\n\nfunction canonicalPathParts(raw: string): CanonicalPathParts | null {\n\tconst canonical = canonicalizeLexicalPath(raw);\n\tif (canonical === null) {\n\t\treturn null;\n\t}\n\tconst driveAbsolute = /^([A-Za-z]:)\\/(.*)$/.exec(canonical);\n\tif (driveAbsolute) {\n\t\treturn { drive: driveAbsolute[1].toLowerCase(), rooted: true, segments: pathSegments(driveAbsolute[2]) };\n\t}\n\treturn { drive: null, rooted: canonical.startsWith(\"/\"), segments: pathSegments(canonical) };\n}\n\n/**\n * True when two normalized paths may refer to the same file or subtree.\n * Fail-closed: \".\"/\"..\" are collapsed first, root-escaping paths always count\n * as overlapping, and segments compare case-insensitively so case-aliasing\n * filesystems never split one file into two \"independent\" paths. Symlinks and\n * hardlinks are not resolved here (no fs access); overlap only forces\n * sequential execution, so false positives are safe.\n */\nexport function pathSegmentsOverlap(left: string, right: string): boolean {\n\tconst leftPath = canonicalPathParts(left);\n\tconst rightPath = canonicalPathParts(right);\n\tif (leftPath === null || rightPath === null) {\n\t\treturn true;\n\t}\n\tif (leftPath.drive !== null && rightPath.drive !== null && leftPath.drive !== rightPath.drive) {\n\t\treturn false;\n\t}\n\tif ((leftPath.drive !== null && !rightPath.rooted) || (rightPath.drive !== null && !leftPath.rooted)) {\n\t\treturn true;\n\t}\n\tif (leftPath.segments.length === 0 || rightPath.segments.length === 0) {\n\t\treturn true;\n\t}\n\tconst commonLen = Math.min(leftPath.segments.length, rightPath.segments.length);\n\tfor (let i = 0; i < commonLen; i++) {\n\t\tif (leftPath.segments[i].toLowerCase() !== rightPath.segments[i].toLowerCase()) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Parse a detached mutable JSON-domain value, rejecting every unsupported shape. */
|
|
2
|
+
export declare function parseJsonValue<T>(value: T): T;
|
|
3
|
+
/** Clone a plain-data payload, then recursively freeze its executor-owned graph. */
|
|
4
|
+
export declare function createImmutableSnapshot<T>(value: T): T;
|
|
5
|
+
/** Parse and freeze a detached JSON-domain value. */
|
|
6
|
+
export declare function createImmutableJsonSnapshot<T>(value: T): T;
|
|
7
|
+
//# sourceMappingURL=plain-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plain-data.d.ts","sourceRoot":"","sources":["../src/plain-data.ts"],"names":[],"mappings":"AAwCA,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAM7C;AAED,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAMtD;AAED,qDAAqD;AACrD,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAI1D","sourcesContent":["function assertPlainData(value: unknown, jsonOnly: boolean, ancestors = new WeakSet<object>()): void {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return;\n\tif (typeof value === \"number\") {\n\t\tif (Number.isFinite(value)) return;\n\t\tthrow new TypeError(\"Non-finite numbers are not supported data values\");\n\t}\n\tif (value === undefined && !jsonOnly) return;\n\tif (typeof value !== \"object\") throw new TypeError(\"Unsupported non-data value\");\n\tif (ancestors.has(value)) throw new TypeError(\"Cyclic data values are not supported\");\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {\n\t\tthrow new TypeError(\"Unsupported data object prototype\");\n\t}\n\tancestors.add(value);\n\ttry {\n\t\tif (Array.isArray(value)) {\n\t\t\tif (prototype !== Array.prototype || Object.keys(value).length !== value.length) {\n\t\t\t\tthrow new TypeError(\"Sparse or extended arrays are not supported data values\");\n\t\t\t}\n\t\t\tfor (const item of value) assertPlainData(item, jsonOnly, ancestors);\n\t\t\treturn;\n\t\t}\n\t\tfor (const key of Reflect.ownKeys(value)) {\n\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, key);\n\t\t\tif (typeof key !== \"string\" || descriptor?.enumerable !== true || !(\"value\" in descriptor)) {\n\t\t\t\tthrow new TypeError(\"Symbol, hidden, and accessor properties are not supported data values\");\n\t\t\t}\n\t\t\tassertPlainData(descriptor.value, jsonOnly, ancestors);\n\t\t}\n\t} finally {\n\t\tancestors.delete(value);\n\t}\n}\n\nfunction freezePlainData(value: unknown): void {\n\tif (typeof value !== \"object\" || value === null || Object.isFrozen(value)) return;\n\tfor (const key of Reflect.ownKeys(value)) freezePlainData(Reflect.get(value, key));\n\tObject.freeze(value);\n}\n\n/** Parse a detached mutable JSON-domain value, rejecting every unsupported shape. */\nexport function parseJsonValue<T>(value: T): T {\n\tassertPlainData(value, true);\n\tconst snapshot = structuredClone(value);\n\t// Cloning restores masked intrinsic prototypes (Map, views, buffers, etc.).\n\tassertPlainData(snapshot, true);\n\treturn snapshot;\n}\n\n/** Clone a plain-data payload, then recursively freeze its executor-owned graph. */\nexport function createImmutableSnapshot<T>(value: T): T {\n\tassertPlainData(value, false);\n\tconst snapshot = structuredClone(value);\n\tassertPlainData(snapshot, false);\n\tfreezePlainData(snapshot);\n\treturn snapshot;\n}\n\n/** Parse and freeze a detached JSON-domain value. */\nexport function createImmutableJsonSnapshot<T>(value: T): T {\n\tconst snapshot = parseJsonValue(value);\n\tfreezePlainData(snapshot);\n\treturn snapshot;\n}\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
function assertPlainData(value, jsonOnly, ancestors = new WeakSet()) {
|
|
2
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
3
|
+
return;
|
|
4
|
+
if (typeof value === "number") {
|
|
5
|
+
if (Number.isFinite(value))
|
|
6
|
+
return;
|
|
7
|
+
throw new TypeError("Non-finite numbers are not supported data values");
|
|
8
|
+
}
|
|
9
|
+
if (value === undefined && !jsonOnly)
|
|
10
|
+
return;
|
|
11
|
+
if (typeof value !== "object")
|
|
12
|
+
throw new TypeError("Unsupported non-data value");
|
|
13
|
+
if (ancestors.has(value))
|
|
14
|
+
throw new TypeError("Cyclic data values are not supported");
|
|
15
|
+
const prototype = Object.getPrototypeOf(value);
|
|
16
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
|
|
17
|
+
throw new TypeError("Unsupported data object prototype");
|
|
18
|
+
}
|
|
19
|
+
ancestors.add(value);
|
|
20
|
+
try {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
if (prototype !== Array.prototype || Object.keys(value).length !== value.length) {
|
|
23
|
+
throw new TypeError("Sparse or extended arrays are not supported data values");
|
|
24
|
+
}
|
|
25
|
+
for (const item of value)
|
|
26
|
+
assertPlainData(item, jsonOnly, ancestors);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
30
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
31
|
+
if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
|
|
32
|
+
throw new TypeError("Symbol, hidden, and accessor properties are not supported data values");
|
|
33
|
+
}
|
|
34
|
+
assertPlainData(descriptor.value, jsonOnly, ancestors);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
ancestors.delete(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function freezePlainData(value) {
|
|
42
|
+
if (typeof value !== "object" || value === null || Object.isFrozen(value))
|
|
43
|
+
return;
|
|
44
|
+
for (const key of Reflect.ownKeys(value))
|
|
45
|
+
freezePlainData(Reflect.get(value, key));
|
|
46
|
+
Object.freeze(value);
|
|
47
|
+
}
|
|
48
|
+
/** Parse a detached mutable JSON-domain value, rejecting every unsupported shape. */
|
|
49
|
+
export function parseJsonValue(value) {
|
|
50
|
+
assertPlainData(value, true);
|
|
51
|
+
const snapshot = structuredClone(value);
|
|
52
|
+
// Cloning restores masked intrinsic prototypes (Map, views, buffers, etc.).
|
|
53
|
+
assertPlainData(snapshot, true);
|
|
54
|
+
return snapshot;
|
|
55
|
+
}
|
|
56
|
+
/** Clone a plain-data payload, then recursively freeze its executor-owned graph. */
|
|
57
|
+
export function createImmutableSnapshot(value) {
|
|
58
|
+
assertPlainData(value, false);
|
|
59
|
+
const snapshot = structuredClone(value);
|
|
60
|
+
assertPlainData(snapshot, false);
|
|
61
|
+
freezePlainData(snapshot);
|
|
62
|
+
return snapshot;
|
|
63
|
+
}
|
|
64
|
+
/** Parse and freeze a detached JSON-domain value. */
|
|
65
|
+
export function createImmutableJsonSnapshot(value) {
|
|
66
|
+
const snapshot = parseJsonValue(value);
|
|
67
|
+
freezePlainData(snapshot);
|
|
68
|
+
return snapshot;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=plain-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plain-data.js","sourceRoot":"","sources":["../src/plain-data.ts"],"names":[],"mappings":"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","sourcesContent":["function assertPlainData(value: unknown, jsonOnly: boolean, ancestors = new WeakSet<object>()): void {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return;\n\tif (typeof value === \"number\") {\n\t\tif (Number.isFinite(value)) return;\n\t\tthrow new TypeError(\"Non-finite numbers are not supported data values\");\n\t}\n\tif (value === undefined && !jsonOnly) return;\n\tif (typeof value !== \"object\") throw new TypeError(\"Unsupported non-data value\");\n\tif (ancestors.has(value)) throw new TypeError(\"Cyclic data values are not supported\");\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {\n\t\tthrow new TypeError(\"Unsupported data object prototype\");\n\t}\n\tancestors.add(value);\n\ttry {\n\t\tif (Array.isArray(value)) {\n\t\t\tif (prototype !== Array.prototype || Object.keys(value).length !== value.length) {\n\t\t\t\tthrow new TypeError(\"Sparse or extended arrays are not supported data values\");\n\t\t\t}\n\t\t\tfor (const item of value) assertPlainData(item, jsonOnly, ancestors);\n\t\t\treturn;\n\t\t}\n\t\tfor (const key of Reflect.ownKeys(value)) {\n\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, key);\n\t\t\tif (typeof key !== \"string\" || descriptor?.enumerable !== true || !(\"value\" in descriptor)) {\n\t\t\t\tthrow new TypeError(\"Symbol, hidden, and accessor properties are not supported data values\");\n\t\t\t}\n\t\t\tassertPlainData(descriptor.value, jsonOnly, ancestors);\n\t\t}\n\t} finally {\n\t\tancestors.delete(value);\n\t}\n}\n\nfunction freezePlainData(value: unknown): void {\n\tif (typeof value !== \"object\" || value === null || Object.isFrozen(value)) return;\n\tfor (const key of Reflect.ownKeys(value)) freezePlainData(Reflect.get(value, key));\n\tObject.freeze(value);\n}\n\n/** Parse a detached mutable JSON-domain value, rejecting every unsupported shape. */\nexport function parseJsonValue<T>(value: T): T {\n\tassertPlainData(value, true);\n\tconst snapshot = structuredClone(value);\n\t// Cloning restores masked intrinsic prototypes (Map, views, buffers, etc.).\n\tassertPlainData(snapshot, true);\n\treturn snapshot;\n}\n\n/** Clone a plain-data payload, then recursively freeze its executor-owned graph. */\nexport function createImmutableSnapshot<T>(value: T): T {\n\tassertPlainData(value, false);\n\tconst snapshot = structuredClone(value);\n\tassertPlainData(snapshot, false);\n\tfreezePlainData(snapshot);\n\treturn snapshot;\n}\n\n/** Parse and freeze a detached JSON-domain value. */\nexport function createImmutableJsonSnapshot<T>(value: T): T {\n\tconst snapshot = parseJsonValue(value);\n\tfreezePlainData(snapshot);\n\treturn snapshot;\n}\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, browser-safe deterministic resource-claim DAG scheduler.
|
|
3
|
+
*
|
|
4
|
+
* Given a source-ordered batch of tool calls and their resolved resource
|
|
5
|
+
* claims, this module assigns each call after every earlier call it conflicts
|
|
6
|
+
* with. Levels run in source order; calls inside one level are mutually
|
|
7
|
+
* conflict-free and may execute concurrently. A positive `maxConcurrency`
|
|
8
|
+
* width cap splits each level into deterministic contiguous source-ordered
|
|
9
|
+
* chunks.
|
|
10
|
+
*
|
|
11
|
+
* Determinism contract:
|
|
12
|
+
* - The level assignment is a pure function of the source-ordered input
|
|
13
|
+
* claims. The canonical example is the head-of-line example
|
|
14
|
+
* `write x, write x, write y`, which schedules as `[[0, 2], [1]]`: the second
|
|
15
|
+
* write to `x` is pushed to level 1, but the independent write to `y` is not
|
|
16
|
+
* blocked and joins level 0.
|
|
17
|
+
* - Reordering claims *within* a single call does not change the plan: claims
|
|
18
|
+
* are canonicalized (sorted, fixed property order) before comparison.
|
|
19
|
+
* - {@link DagSchedulePlan.planKey} is a canonical serialization of the
|
|
20
|
+
* resolved claim sequence (canonical claim data only — never execution
|
|
21
|
+
* timing or outcomes). It is collision-free for distinct canonical claim
|
|
22
|
+
* sequences.
|
|
23
|
+
*
|
|
24
|
+
* This module uses no platform APIs (no `process`, fs, `node:path`, or timers).
|
|
25
|
+
*/
|
|
26
|
+
import { type ClaimableToolCall, type ResolveToolClaimsOptions, type ToolClaimResolution, type ToolResourceClaim } from "./tool-resource-claims.ts";
|
|
27
|
+
/** Options for scheduling a batch into DAG levels. */
|
|
28
|
+
export interface ScheduleDagLevelsOptions extends ResolveToolClaimsOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Optional positive width cap. When set, each level is split into contiguous
|
|
31
|
+
* source-ordered chunks of at most this many calls so a wide conflict-free
|
|
32
|
+
* level does not fan out unbounded. Absent, non-finite, or non-positive
|
|
33
|
+
* values leave each level whole. Does not affect `planKey`.
|
|
34
|
+
*/
|
|
35
|
+
maxConcurrency?: number;
|
|
36
|
+
}
|
|
37
|
+
/** A scheduled plan: ordered levels of source indices plus a canonical key. */
|
|
38
|
+
export interface DagSchedulePlan {
|
|
39
|
+
/** Levels in execution order; each level holds source indices that may run concurrently. */
|
|
40
|
+
levels: number[][];
|
|
41
|
+
/**
|
|
42
|
+
* Canonical deterministic key over the resolved claim sequence only (never
|
|
43
|
+
* execution timing/outcomes). Stable under claim reordering within a call.
|
|
44
|
+
*/
|
|
45
|
+
planKey: string;
|
|
46
|
+
}
|
|
47
|
+
/** One tool call's resolved claim data, canonicalized for stable planning. */
|
|
48
|
+
export interface ResolvedClaimEntry {
|
|
49
|
+
sourceIndex: number;
|
|
50
|
+
resolution: ToolClaimResolution;
|
|
51
|
+
canonicalClaims: ToolResourceClaim[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve and canonicalize claims for a whole batch, preserving source order.
|
|
55
|
+
* Registered custom resolvers are awaited one call at a time in source order,
|
|
56
|
+
* and every resolution completes before a schedule is constructed.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveBatchClaims(toolCalls: readonly ClaimableToolCall[], options: ResolveToolClaimsOptions): Promise<ResolvedClaimEntry[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Assign each source-ordered claim entry one level after its latest earlier
|
|
61
|
+
* conflict. Deterministic and stable: equal inputs (including claim reordering
|
|
62
|
+
* within a call, which is canonicalized away) always produce equal levels, and
|
|
63
|
+
* every directed conflict edge advances at least one level.
|
|
64
|
+
*/
|
|
65
|
+
export declare function assignDagLevels(entries: readonly ResolvedClaimEntry[]): number[][];
|
|
66
|
+
/**
|
|
67
|
+
* Split each level into contiguous source-ordered chunks of at most `cap` calls.
|
|
68
|
+
* Absent/non-finite/non-positive `cap` returns the levels unchanged.
|
|
69
|
+
*/
|
|
70
|
+
export declare function applyConcurrencyCap(levels: readonly number[][], cap: number | undefined): number[][];
|
|
71
|
+
/**
|
|
72
|
+
* Canonical deterministic key over the resolved claim sequence. Uses
|
|
73
|
+
* `JSON.stringify` of each call's canonicalized claims (fixed property order,
|
|
74
|
+
* sorted) with an `"E"`/`"C"` discriminator, so it is collision-free for
|
|
75
|
+
* distinct canonical claim sequences and stable under claim reordering within a
|
|
76
|
+
* call. Contains no execution timing or outcomes.
|
|
77
|
+
*/
|
|
78
|
+
export declare function computePlanKey(entries: readonly ResolvedClaimEntry[]): string;
|
|
79
|
+
/**
|
|
80
|
+
* Schedule a source-ordered tool-call batch into deterministic DAG levels.
|
|
81
|
+
* Resolves default claims, computes source-directed dependency levels, applies
|
|
82
|
+
* the optional width cap, and computes the canonical plan key. Pure and
|
|
83
|
+
* deterministic.
|
|
84
|
+
*/
|
|
85
|
+
export declare function scheduleDagLevels(toolCalls: readonly ClaimableToolCall[], options: ScheduleDagLevelsOptions): Promise<DagSchedulePlan>;
|
|
86
|
+
//# sourceMappingURL=tool-dag-scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-dag-scheduler.d.ts","sourceRoot":"","sources":["../src/tool-dag-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EACN,KAAK,iBAAiB,EAGtB,KAAK,wBAAwB,EAE7B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,MAAM,2BAA2B,CAAC;AAEnC,sDAAsD;AACtD,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACzE;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC/B,4FAA4F;IAC5F,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,mBAAmB,CAAC;IAChC,eAAe,EAAE,iBAAiB,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACvC,SAAS,EAAE,SAAS,iBAAiB,EAAE,EACvC,OAAO,EAAE,wBAAwB,GAC/B,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAQ/B;AAqDD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,EAAE,EAAE,CA6BlF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,EAAE,CAgBpG;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,CAU7E;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACtC,SAAS,EAAE,SAAS,iBAAiB,EAAE,EACvC,OAAO,EAAE,wBAAwB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAK1B","sourcesContent":["/**\n * Pure, browser-safe deterministic resource-claim DAG scheduler.\n *\n * Given a source-ordered batch of tool calls and their resolved resource\n * claims, this module assigns each call after every earlier call it conflicts\n * with. Levels run in source order; calls inside one level are mutually\n * conflict-free and may execute concurrently. A positive `maxConcurrency`\n * width cap splits each level into deterministic contiguous source-ordered\n * chunks.\n *\n * Determinism contract:\n * - The level assignment is a pure function of the source-ordered input\n * claims. The canonical example is the head-of-line example\n * `write x, write x, write y`, which schedules as `[[0, 2], [1]]`: the second\n * write to `x` is pushed to level 1, but the independent write to `y` is not\n * blocked and joins level 0.\n * - Reordering claims *within* a single call does not change the plan: claims\n * are canonicalized (sorted, fixed property order) before comparison.\n * - {@link DagSchedulePlan.planKey} is a canonical serialization of the\n * resolved claim sequence (canonical claim data only — never execution\n * timing or outcomes). It is collision-free for distinct canonical claim\n * sequences.\n *\n * This module uses no platform APIs (no `process`, fs, `node:path`, or timers).\n */\n\nimport {\n\ttype ClaimableToolCall,\n\tcanonicalizeClaims,\n\tclaimsConflict,\n\ttype ResolveToolClaimsOptions,\n\tresolveToolClaimsForCall,\n\ttype ToolClaimResolution,\n\ttype ToolResourceClaim,\n} from \"./tool-resource-claims.ts\";\n\n/** Options for scheduling a batch into DAG levels. */\nexport interface ScheduleDagLevelsOptions extends ResolveToolClaimsOptions {\n\t/**\n\t * Optional positive width cap. When set, each level is split into contiguous\n\t * source-ordered chunks of at most this many calls so a wide conflict-free\n\t * level does not fan out unbounded. Absent, non-finite, or non-positive\n\t * values leave each level whole. Does not affect `planKey`.\n\t */\n\tmaxConcurrency?: number;\n}\n\n/** A scheduled plan: ordered levels of source indices plus a canonical key. */\nexport interface DagSchedulePlan {\n\t/** Levels in execution order; each level holds source indices that may run concurrently. */\n\tlevels: number[][];\n\t/**\n\t * Canonical deterministic key over the resolved claim sequence only (never\n\t * execution timing/outcomes). Stable under claim reordering within a call.\n\t */\n\tplanKey: string;\n}\n\n/** One tool call's resolved claim data, canonicalized for stable planning. */\nexport interface ResolvedClaimEntry {\n\tsourceIndex: number;\n\tresolution: ToolClaimResolution;\n\tcanonicalClaims: ToolResourceClaim[];\n}\n\n/**\n * Resolve and canonicalize claims for a whole batch, preserving source order.\n * Registered custom resolvers are awaited one call at a time in source order,\n * and every resolution completes before a schedule is constructed.\n */\nexport async function resolveBatchClaims(\n\ttoolCalls: readonly ClaimableToolCall[],\n\toptions: ResolveToolClaimsOptions,\n): Promise<ResolvedClaimEntry[]> {\n\tconst entries: ResolvedClaimEntry[] = [];\n\tfor (let index = 0; index < toolCalls.length; index++) {\n\t\tconst resolution = await resolveToolClaimsForCall(toolCalls[index], options);\n\t\tconst canonicalClaims = resolution.kind === \"claims\" ? canonicalizeClaims(resolution.claims) : [];\n\t\tentries.push({ sourceIndex: index, resolution, canonicalClaims });\n\t}\n\treturn entries;\n}\n\ninterface DagLevel {\n\tindices: number[];\n\t/** Write claims currently placed in this level (for fast read-vs-write checks). */\n\twriteClaims: ToolResourceClaim[];\n\t/** Read claims currently placed in this level (for fast write-vs-read checks). */\n\treadClaims: ToolResourceClaim[];\n\t/** True once an exclusive call is placed here; such a level accepts no more. */\n\thasExclusive: boolean;\n}\n\nfunction claimConflictsAny(claim: ToolResourceClaim, candidates: readonly ToolResourceClaim[]): boolean {\n\tfor (const candidate of candidates) {\n\t\tif (claimsConflict(claim, candidate)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * True when `resolution` cannot join `level`. Equivalent to the full pairwise\n * `resolutionsConflict` check, optimized so read/read never triggers a scan:\n * a read claim only needs to scan the level's writes, and a write claim scans\n * both writes and reads. An exclusive resolution, or a level that already holds\n * an exclusive call, conflicts unconditionally.\n */\nfunction resolutionConflictsLevel(resolution: ToolClaimResolution, level: DagLevel): boolean {\n\tif (level.hasExclusive) {\n\t\treturn true;\n\t}\n\tif (resolution.kind === \"exclusive\") {\n\t\treturn true;\n\t}\n\tfor (const claim of resolution.claims) {\n\t\tif (claim.access === \"exclusive\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (claim.access === \"write\") {\n\t\t\tif (claimConflictsAny(claim, level.writeClaims)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (claimConflictsAny(claim, level.readClaims)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (claimConflictsAny(claim, level.writeClaims)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Assign each source-ordered claim entry one level after its latest earlier\n * conflict. Deterministic and stable: equal inputs (including claim reordering\n * within a call, which is canonicalized away) always produce equal levels, and\n * every directed conflict edge advances at least one level.\n */\nexport function assignDagLevels(entries: readonly ResolvedClaimEntry[]): number[][] {\n\tconst levels: DagLevel[] = [];\n\tfor (const entry of entries) {\n\t\tconst resolution = entry.resolution;\n\t\tlet targetIndex = 0;\n\t\tfor (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {\n\t\t\tif (resolutionConflictsLevel(resolution, levels[levelIndex])) {\n\t\t\t\ttargetIndex = levelIndex + 1;\n\t\t\t}\n\t\t}\n\t\tlet target = levels[targetIndex];\n\t\tif (!target) {\n\t\t\ttarget = { indices: [], writeClaims: [], readClaims: [], hasExclusive: false };\n\t\t\tlevels.push(target);\n\t\t}\n\t\ttarget.indices.push(entry.sourceIndex);\n\t\tif (resolution.kind === \"exclusive\" || entry.canonicalClaims.some((claim) => claim.access === \"exclusive\")) {\n\t\t\ttarget.hasExclusive = true;\n\t\t} else {\n\t\t\tfor (const claim of entry.canonicalClaims) {\n\t\t\t\tif (claim.access === \"write\") {\n\t\t\t\t\ttarget.writeClaims.push(claim);\n\t\t\t\t} else {\n\t\t\t\t\ttarget.readClaims.push(claim);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn levels.map((level) => level.indices);\n}\n\n/**\n * Split each level into contiguous source-ordered chunks of at most `cap` calls.\n * Absent/non-finite/non-positive `cap` returns the levels unchanged.\n */\nexport function applyConcurrencyCap(levels: readonly number[][], cap: number | undefined): number[][] {\n\tif (typeof cap !== \"number\" || !Number.isFinite(cap) || cap <= 0) {\n\t\treturn levels.map((level) => level.slice());\n\t}\n\tconst integerCap = Math.max(1, Math.floor(cap));\n\tconst chunked: number[][] = [];\n\tfor (const level of levels) {\n\t\tif (level.length <= integerCap) {\n\t\t\tchunked.push(level.slice());\n\t\t\tcontinue;\n\t\t}\n\t\tfor (let start = 0; start < level.length; start += integerCap) {\n\t\t\tchunked.push(level.slice(start, start + integerCap));\n\t\t}\n\t}\n\treturn chunked;\n}\n\n/**\n * Canonical deterministic key over the resolved claim sequence. Uses\n * `JSON.stringify` of each call's canonicalized claims (fixed property order,\n * sorted) with an `\"E\"`/`\"C\"` discriminator, so it is collision-free for\n * distinct canonical claim sequences and stable under claim reordering within a\n * call. Contains no execution timing or outcomes.\n */\nexport function computePlanKey(entries: readonly ResolvedClaimEntry[]): string {\n\tlet key = \"\";\n\tfor (const entry of entries) {\n\t\tif (entry.resolution.kind === \"exclusive\") {\n\t\t\tkey += \"E,\";\n\t\t} else {\n\t\t\tkey += `C${JSON.stringify(entry.canonicalClaims)},`;\n\t\t}\n\t}\n\treturn key;\n}\n\n/**\n * Schedule a source-ordered tool-call batch into deterministic DAG levels.\n * Resolves default claims, computes source-directed dependency levels, applies\n * the optional width cap, and computes the canonical plan key. Pure and\n * deterministic.\n */\nexport async function scheduleDagLevels(\n\ttoolCalls: readonly ClaimableToolCall[],\n\toptions: ScheduleDagLevelsOptions,\n): Promise<DagSchedulePlan> {\n\tconst entries = await resolveBatchClaims(toolCalls, options);\n\tconst baseLevels = assignDagLevels(entries);\n\tconst levels = applyConcurrencyCap(baseLevels, options.maxConcurrency);\n\treturn { levels, planKey: computePlanKey(entries) };\n}\n"]}
|