sortie-dogs 0.5.2 → 0.5.4
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 +1 -1
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/worktree-commit-artifact.d.ts +1 -0
- package/dist/core/worktree-commit-artifact.js +89 -23
- package/dist/core/worktree-lifecycle.js +3 -3
- package/dist/plugin/continuation.js +1 -1
- package/dist/plugin/fast-lane.d.ts +1 -0
- package/dist/plugin/fast-lane.js +13 -3
- package/dist/plugin/index.js +152 -17
- package/dist/runtime-assets.d.ts +7 -7
- package/dist/runtime-assets.js +21 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Requirements: Node.js 22.6 or newer, npm, and OpenCode.
|
|
|
20
20
|
|
|
21
21
|
Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
|
|
22
22
|
|
|
23
|
-
Release: [v0.5.
|
|
23
|
+
Release: [v0.5.4](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.5.4)
|
|
24
24
|
|
|
25
25
|
## Quick start
|
|
26
26
|
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Version of the installable runtime assets. Kept in its own module so the plugin can compare an
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
|
-
export declare const RUNTIME_ASSET_VERSION = "0.3.
|
|
5
|
+
export declare const RUNTIME_ASSET_VERSION = "0.3.34-parallel-host-contract-v1";
|
|
6
6
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Version of the installable runtime assets. Kept in its own module so the plugin can compare an
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
|
-
export const RUNTIME_ASSET_VERSION = "0.3.
|
|
5
|
+
export const RUNTIME_ASSET_VERSION = "0.3.34-parallel-host-contract-v1";
|
|
@@ -4,6 +4,7 @@ export declare class WorktreeCommitArtifactError extends Error {
|
|
|
4
4
|
readonly code: WorktreeCommitArtifactErrorCode;
|
|
5
5
|
constructor(code: WorktreeCommitArtifactErrorCode, message: string);
|
|
6
6
|
}
|
|
7
|
+
export declare function resolveValidationExecutable(executable: string): Promise<string | undefined>;
|
|
7
8
|
/** Runs one bounded command without exposing output or performing Git mutations. */
|
|
8
9
|
export declare function runContainedValidation(request: ContainedValidationRequest): Promise<ContainedValidationResult>;
|
|
9
10
|
export declare function produceWorktreeCommitArtifact(request: WorktreeCommitProduceRequest): Promise<WorktreeCommitArtifact>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { lstat, realpath } from "node:fs/promises";
|
|
4
|
-
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
4
|
+
import { delimiter, extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
5
5
|
import { normalizeWorktreeScopePath } from "./worktree-scope.js";
|
|
6
6
|
const SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u;
|
|
7
7
|
const HASH = /^[0-9a-f]{64}$/u;
|
|
@@ -368,6 +368,26 @@ function exactKeys(value, keys) {
|
|
|
368
368
|
function validText(value, max = 256) {
|
|
369
369
|
return typeof value === "string" && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
370
370
|
}
|
|
371
|
+
export async function resolveValidationExecutable(executable) {
|
|
372
|
+
if (isAbsolute(executable))
|
|
373
|
+
return realpath(executable).catch(() => undefined);
|
|
374
|
+
if (!validText(executable, MAX_COMMAND_TEXT) || executable.startsWith("-") || /[\\/]/u.test(executable))
|
|
375
|
+
return undefined;
|
|
376
|
+
const path = process.env.PATH ?? process.env.Path;
|
|
377
|
+
if (path === undefined)
|
|
378
|
+
return undefined;
|
|
379
|
+
const extensions = process.platform === "win32" && extname(executable).length === 0
|
|
380
|
+
? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter((value) => value.length > 0)
|
|
381
|
+
: [""];
|
|
382
|
+
for (const directory of path.split(delimiter)) {
|
|
383
|
+
for (const extension of extensions) {
|
|
384
|
+
const candidate = await realpath(resolve(directory || ".", `${executable}${extension}`)).catch(() => undefined);
|
|
385
|
+
if (candidate !== undefined && isAbsolute(candidate))
|
|
386
|
+
return candidate;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
371
391
|
function pathIdentity(value) {
|
|
372
392
|
const normalized = resolve(value).split(sep).join("/");
|
|
373
393
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
@@ -550,6 +570,10 @@ async function runBounded(executable, args, cwd, timeout, kind) {
|
|
|
550
570
|
clearTimeout(timer);
|
|
551
571
|
}
|
|
552
572
|
}
|
|
573
|
+
function boundedCommandKind(executable, args) {
|
|
574
|
+
return /(?:^|[\\/])git(?:\.exe)?$/iu.test(executable) && args.length === 2 &&
|
|
575
|
+
args[0] === "diff" && args[1] === "--check" ? "git" : "validation";
|
|
576
|
+
}
|
|
553
577
|
/** Runs one bounded command without exposing output or performing Git mutations. */
|
|
554
578
|
export async function runContainedValidation(request) {
|
|
555
579
|
const suppliedArgs = isRecord(request) && Array.isArray(request.args) ? request.args : [];
|
|
@@ -560,21 +584,23 @@ export async function runContainedValidation(request) {
|
|
|
560
584
|
const failed = (command, exitCode, error) => Object.freeze({ ok: false, command, exit_code: exitCode,
|
|
561
585
|
fingerprint: createHash("sha256").update(JSON.stringify([command, exitCode, error])).digest("hex"), error });
|
|
562
586
|
if (!isRecord(request) || !exactKeys(request, ["cwd", "executable", "timeout_ms", ...(request.args === undefined ? [] : ["args"])]) ||
|
|
563
|
-
!validText(request.executable, MAX_COMMAND_TEXT) ||
|
|
587
|
+
!validText(request.executable, MAX_COMMAND_TEXT) ||
|
|
588
|
+
(!isAbsolute(request.executable) && (request.executable.startsWith("-") || /[\\/]/u.test(request.executable))) ||
|
|
564
589
|
!validText(request.cwd, MAX_PATH) || !isAbsolute(request.cwd) ||
|
|
565
590
|
!Number.isInteger(request.timeout_ms) || request.timeout_ms < 1 || request.timeout_ms > MAX_TIMEOUT ||
|
|
566
591
|
!Array.isArray(suppliedArgs) || suppliedArgs.length > MAX_ARGUMENTS ||
|
|
567
592
|
!suppliedArgs.every((arg) => validText(arg, MAX_COMMAND_TEXT)))
|
|
568
593
|
return failed(fallbackCommand, null, "invalid-request");
|
|
569
594
|
const [executable, cwd] = await Promise.all([
|
|
570
|
-
|
|
595
|
+
resolveValidationExecutable(request.executable),
|
|
596
|
+
realpath(request.cwd).catch(() => undefined),
|
|
571
597
|
]);
|
|
572
|
-
if (executable === undefined || cwd === undefined || !isAbsolute(
|
|
598
|
+
if (executable === undefined || cwd === undefined || !isAbsolute(cwd)) {
|
|
573
599
|
return failed(fallbackCommand, null, "invalid-request");
|
|
574
600
|
}
|
|
575
601
|
const command = Object.freeze([executable, ...suppliedArgs]);
|
|
576
602
|
try {
|
|
577
|
-
const result = await runBounded(executable, suppliedArgs, cwd, request.timeout_ms,
|
|
603
|
+
const result = await runBounded(executable, suppliedArgs, cwd, request.timeout_ms, boundedCommandKind(executable, suppliedArgs));
|
|
578
604
|
const fingerprint = createHash("sha256").update(JSON.stringify([command, result.code])).digest("hex");
|
|
579
605
|
return result.code === 0
|
|
580
606
|
? Object.freeze({ ok: true, command, exit_code: 0, fingerprint, error: null })
|
|
@@ -679,12 +705,15 @@ function parseStatus(source, staged) {
|
|
|
679
705
|
throw new WorktreeCommitArtifactError("invalid-state", "Git status is ambiguous.");
|
|
680
706
|
const x = field[0];
|
|
681
707
|
const y = field[1];
|
|
682
|
-
const untracked =
|
|
708
|
+
const untracked = x === "?" && y === "?";
|
|
683
709
|
if (!untracked && (x === "?" || y === "?" || x === "!" || y === "!" || "RCUT".includes(x) || "RCUT".includes(y))) {
|
|
684
710
|
throw new WorktreeCommitArtifactError("invalid-state", "Untracked, renamed, copied, or unsupported changes are forbidden.");
|
|
685
711
|
}
|
|
686
|
-
|
|
687
|
-
|
|
712
|
+
const unstagedEdit = x === " " && (y === "M" || y === "D");
|
|
713
|
+
const stagedEdit = (x === "A" || x === "M" || x === "D") && y === " ";
|
|
714
|
+
if ((!untracked && staged === "forbid" && !unstagedEdit) ||
|
|
715
|
+
(!untracked && staged === "require" && !stagedEdit) ||
|
|
716
|
+
(!untracked && staged === "either" && !unstagedEdit && !stagedEdit)) {
|
|
688
717
|
throw new WorktreeCommitArtifactError("invalid-state", "Index or worktree status is not an accepted implementation edit.");
|
|
689
718
|
}
|
|
690
719
|
const path = field.slice(3);
|
|
@@ -695,7 +724,7 @@ function parseStatus(source, staged) {
|
|
|
695
724
|
catch {
|
|
696
725
|
throw new WorktreeCommitArtifactError("invalid-state", "Changed path is not canonical.");
|
|
697
726
|
}
|
|
698
|
-
entries.push({ code: untracked ? "A" :
|
|
727
|
+
entries.push({ code: untracked ? "A" : stagedEdit ? x : y, path });
|
|
699
728
|
if (folded.length === 0)
|
|
700
729
|
throw new WorktreeCommitArtifactError("invalid-state", "Changed path is invalid.");
|
|
701
730
|
}
|
|
@@ -714,9 +743,34 @@ function assertScope(entries, scope) {
|
|
|
714
743
|
}
|
|
715
744
|
}
|
|
716
745
|
async function status(context, staged) {
|
|
717
|
-
|
|
746
|
+
const entries = parseStatus(await context.git([
|
|
718
747
|
"status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignore-submodules=none",
|
|
719
748
|
]), staged);
|
|
749
|
+
const controls = new Set([
|
|
750
|
+
`handoff.${context.descriptor.task_id}.json`,
|
|
751
|
+
`${context.descriptor.task_id}.operation-manifest.json`,
|
|
752
|
+
]);
|
|
753
|
+
return entries.filter(({ code, path }) => code !== "A" || !controls.has(path));
|
|
754
|
+
}
|
|
755
|
+
async function stagedPaths(context) {
|
|
756
|
+
if ((await context.git(["ls-files", "-u", "-z"])).length !== 0) {
|
|
757
|
+
throw new WorktreeCommitArtifactError("invalid-state", "An unmerged index is forbidden.");
|
|
758
|
+
}
|
|
759
|
+
const fields = decodeGitOutput(await context.git([
|
|
760
|
+
"diff", "--cached", "--name-only", "--no-renames", "-z", "--",
|
|
761
|
+
]), "Staged path encoding is invalid.").split("\0");
|
|
762
|
+
if (fields.at(-1) !== "")
|
|
763
|
+
throw new WorktreeCommitArtifactError("invalid-state", "Staged paths are ambiguous.");
|
|
764
|
+
fields.pop();
|
|
765
|
+
for (const path of fields) {
|
|
766
|
+
if (normalizeWorktreeScopePath(path) !== path) {
|
|
767
|
+
throw new WorktreeCommitArtifactError("invalid-state", "A staged path is not canonical.");
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (new Set(fields.map((path) => path.toLowerCase())).size !== fields.length) {
|
|
771
|
+
throw new WorktreeCommitArtifactError("invalid-state", "Staged path identities are ambiguous.");
|
|
772
|
+
}
|
|
773
|
+
return new Set(fields);
|
|
720
774
|
}
|
|
721
775
|
async function assertNoSubmodules(context, entries) {
|
|
722
776
|
for (const entry of entries) {
|
|
@@ -884,9 +938,7 @@ async function committedEntries(context, base, commit) {
|
|
|
884
938
|
}
|
|
885
939
|
async function validateCommit(context, artifact) {
|
|
886
940
|
await assertBaseState(context, artifact.commit_sha);
|
|
887
|
-
if ((await context.
|
|
888
|
-
"status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignore-submodules=none",
|
|
889
|
-
])).length !== 0) {
|
|
941
|
+
if ((await status(context, "forbid")).length !== 0) {
|
|
890
942
|
throw new WorktreeCommitArtifactError("verification-failed", "Committed checkout is not clean.");
|
|
891
943
|
}
|
|
892
944
|
const parentLine = (await context.git(["rev-list", "--parents", "-n", "1", artifact.commit_sha])).toString("utf8").trim().split(" ");
|
|
@@ -934,7 +986,9 @@ export async function produceWorktreeCommitArtifact(request) {
|
|
|
934
986
|
if (!isRecord(request) || !exactKeys(request, ["descriptor", "managed_path", "validation", ...(request.git_path === undefined ? [] : ["git_path"])]) ||
|
|
935
987
|
!isRecord(request.validation) || !exactKeys(request.validation, ["executable", ...(request.validation.args === undefined ? [] : ["args"]),
|
|
936
988
|
...(request.validation.timeout_ms === undefined ? [] : ["timeout_ms"])]) ||
|
|
937
|
-
!validText(request.validation.executable, MAX_COMMAND_TEXT) ||
|
|
989
|
+
!validText(request.validation.executable, MAX_COMMAND_TEXT) ||
|
|
990
|
+
(!isAbsolute(request.validation.executable) &&
|
|
991
|
+
(request.validation.executable.startsWith("-") || /[\\/]/u.test(request.validation.executable))) ||
|
|
938
992
|
(request.validation.args !== undefined && (!Array.isArray(request.validation.args) || request.validation.args.length > MAX_ARGUMENTS ||
|
|
939
993
|
!request.validation.args.every((arg) => validText(arg, MAX_COMMAND_TEXT)))) ||
|
|
940
994
|
(request.validation.timeout_ms !== undefined && (!Number.isInteger(request.validation.timeout_ms) ||
|
|
@@ -942,26 +996,38 @@ export async function produceWorktreeCommitArtifact(request) {
|
|
|
942
996
|
throw new WorktreeCommitArtifactError("invalid-request", "Commit producer request is invalid.");
|
|
943
997
|
}
|
|
944
998
|
const context = await makeContext(request);
|
|
945
|
-
const executable = await
|
|
999
|
+
const executable = await resolveValidationExecutable(request.validation.executable);
|
|
946
1000
|
if (executable === undefined || !isAbsolute(executable)) {
|
|
947
1001
|
throw new WorktreeCommitArtifactError("invalid-request", "Validation executable does not exist.");
|
|
948
1002
|
}
|
|
949
1003
|
const command = Object.freeze([executable, ...(request.validation.args ?? [])]);
|
|
950
1004
|
await assertBaseState(context, context.descriptor.base_sha);
|
|
951
|
-
const before = await status(context, "
|
|
1005
|
+
const before = await status(context, "either");
|
|
952
1006
|
if (before.length === 0)
|
|
953
1007
|
throw new WorktreeCommitArtifactError("invalid-state", "No implementation changes exist.");
|
|
954
1008
|
assertScope(before, context.descriptor.scope_write);
|
|
955
1009
|
await assertNoSubmodules(context, before);
|
|
1010
|
+
const staged = await stagedPaths(context);
|
|
1011
|
+
const hostStaged = staged.size > 0;
|
|
1012
|
+
if (hostStaged && (boundedCommandKind(executable, request.validation.args ?? []) !== "git" ||
|
|
1013
|
+
staged.size !== before.length || !before.every(({ path }) => staged.has(path)))) {
|
|
1014
|
+
throw new WorktreeCommitArtifactError("invalid-state", "Staged changes are not an exact host snapshot of the implementation.");
|
|
1015
|
+
}
|
|
956
1016
|
const beforeFingerprint = await changeFingerprint(context, before, "worktree");
|
|
957
|
-
const validation = await runBounded(executable, request.validation.args ?? [], context.managedPath, request.validation.timeout_ms ?? GIT_TIMEOUT,
|
|
1017
|
+
const validation = await runBounded(executable, request.validation.args ?? [], context.managedPath, request.validation.timeout_ms ?? GIT_TIMEOUT, boundedCommandKind(executable, request.validation.args ?? []));
|
|
958
1018
|
if (validation.code !== 0)
|
|
959
1019
|
throw new WorktreeCommitArtifactError("validation-failed", validation.code === 240 ? "Validation containment setup failed."
|
|
960
1020
|
: validation.code === 241 ? "Validation left a descendant process."
|
|
961
1021
|
: validation.code === 238 ? "Validation exceeded its resource bound."
|
|
962
1022
|
: "Validation exited unsuccessfully.");
|
|
1023
|
+
if (hostStaged) {
|
|
1024
|
+
const stagedValidation = await runBounded(executable, ["diff", "--cached", "--check"], context.managedPath, request.validation.timeout_ms ?? GIT_TIMEOUT, "git");
|
|
1025
|
+
if (stagedValidation.code !== 0) {
|
|
1026
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Staged implementation validation exited unsuccessfully.");
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
963
1029
|
await assertBaseState(context, context.descriptor.base_sha);
|
|
964
|
-
const after = await status(context, "
|
|
1030
|
+
const after = await status(context, "either");
|
|
965
1031
|
assertScope(after, context.descriptor.scope_write);
|
|
966
1032
|
await assertNoSubmodules(context, after);
|
|
967
1033
|
if (JSON.stringify(after) !== JSON.stringify(before) || await changeFingerprint(context, after, "worktree") !== beforeFingerprint) {
|
|
@@ -1000,7 +1066,9 @@ export async function recoverWorktreeCommitArtifact(request) {
|
|
|
1000
1066
|
if (!isRecord(request) || !exactKeys(request, ["descriptor", "managed_path", "validation", ...(request.git_path === undefined ? [] : ["git_path"])]) ||
|
|
1001
1067
|
!isRecord(request.validation) || !exactKeys(request.validation, ["executable", ...(request.validation.args === undefined ? [] : ["args"]),
|
|
1002
1068
|
...(request.validation.timeout_ms === undefined ? [] : ["timeout_ms"])]) ||
|
|
1003
|
-
!validText(request.validation.executable, MAX_COMMAND_TEXT) ||
|
|
1069
|
+
!validText(request.validation.executable, MAX_COMMAND_TEXT) ||
|
|
1070
|
+
(!isAbsolute(request.validation.executable) &&
|
|
1071
|
+
(request.validation.executable.startsWith("-") || /[\\/]/u.test(request.validation.executable))) ||
|
|
1004
1072
|
(request.validation.args !== undefined && (!Array.isArray(request.validation.args) || request.validation.args.length > MAX_ARGUMENTS ||
|
|
1005
1073
|
!request.validation.args.every((arg) => validText(arg, MAX_COMMAND_TEXT)))) ||
|
|
1006
1074
|
(request.validation.timeout_ms !== undefined && (!Number.isInteger(request.validation.timeout_ms) ||
|
|
@@ -1008,7 +1076,7 @@ export async function recoverWorktreeCommitArtifact(request) {
|
|
|
1008
1076
|
throw new WorktreeCommitArtifactError("invalid-request", "Commit recovery request is invalid.");
|
|
1009
1077
|
}
|
|
1010
1078
|
const context = await makeContext(request);
|
|
1011
|
-
const executable = await
|
|
1079
|
+
const executable = await resolveValidationExecutable(request.validation.executable);
|
|
1012
1080
|
if (executable === undefined || !isAbsolute(executable)) {
|
|
1013
1081
|
throw new WorktreeCommitArtifactError("invalid-request", "Validation executable does not exist.");
|
|
1014
1082
|
}
|
|
@@ -1019,9 +1087,7 @@ export async function recoverWorktreeCommitArtifact(request) {
|
|
|
1019
1087
|
}
|
|
1020
1088
|
if (state.head === context.descriptor.base_sha)
|
|
1021
1089
|
return undefined;
|
|
1022
|
-
if ((await context.
|
|
1023
|
-
"status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignore-submodules=none",
|
|
1024
|
-
])).length !== 0) {
|
|
1090
|
+
if ((await status(context, "forbid")).length !== 0) {
|
|
1025
1091
|
throw new WorktreeCommitArtifactError("invalid-state", "Managed checkout is not clean.");
|
|
1026
1092
|
}
|
|
1027
1093
|
const parentLine = (await context.git(["rev-list", "--parents", "-n", "1", state.head])).toString("utf8").trim().split(" ");
|
|
@@ -20,7 +20,7 @@ const SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u;
|
|
|
20
20
|
const HASH = /^[0-9a-f]{64}$/u;
|
|
21
21
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
22
22
|
const DEVICE_ID = /^[1-9][0-9]*$/u;
|
|
23
|
-
const
|
|
23
|
+
const MAX_FILE_ID = (1n << 64n) - 1n;
|
|
24
24
|
const ACTIVE_PATH = /^wt-([0-9a-f]{16})-([0-9a-f]{32})$/u;
|
|
25
25
|
const QUARANTINE_PATH = /^rm-([0-9a-f]{16})-([0-9a-f]{32})$/u;
|
|
26
26
|
const PHASES = new Set(["creating", "setting-up", "ready", "removing", "orphaned"]);
|
|
@@ -53,10 +53,10 @@ function pathWithin(path, parent) {
|
|
|
53
53
|
return childID === parentID || childID.startsWith(`${parentID}/`);
|
|
54
54
|
}
|
|
55
55
|
function safeStoredFileID(value) {
|
|
56
|
-
return typeof value === "string" && DEVICE_ID.test(value) && BigInt(value) <=
|
|
56
|
+
return typeof value === "string" && DEVICE_ID.test(value) && BigInt(value) <= MAX_FILE_ID;
|
|
57
57
|
}
|
|
58
58
|
function fileID(path, value, kind) {
|
|
59
|
-
const native = value > 0n && value <=
|
|
59
|
+
const native = value > 0n && value <= MAX_FILE_ID ? value.toString() : undefined;
|
|
60
60
|
if (native !== undefined)
|
|
61
61
|
return native;
|
|
62
62
|
if (process.platform !== "win32" || (kind === "inode" && value === 0n))
|
|
@@ -885,7 +885,7 @@ export function createContinuationHooks(client, directory, policySource, timings
|
|
|
885
885
|
identity.agent !== policy().agent)
|
|
886
886
|
return;
|
|
887
887
|
}
|
|
888
|
-
if (pending || state?.ownsHostContinuation === true)
|
|
888
|
+
if (pending || (state?.ownsHostContinuation === true && input.overflow !== true))
|
|
889
889
|
output.enabled = false;
|
|
890
890
|
},
|
|
891
891
|
observeModel(sessionID, model, synthetic = false) {
|
|
@@ -6,6 +6,7 @@ export declare class FastLaneDeniedError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
export interface FastLaneToolOptions {
|
|
8
8
|
readonly consultationFallbackAuthorized?: boolean;
|
|
9
|
+
readonly parallelWorkerAlreadyBound?: boolean;
|
|
9
10
|
readonly parallelWorkerAuthorized?: boolean;
|
|
10
11
|
}
|
|
11
12
|
export declare class FastLaneController {
|
package/dist/plugin/fast-lane.js
CHANGED
|
@@ -165,7 +165,7 @@ export class FastLaneController {
|
|
|
165
165
|
state.parallelMode = true;
|
|
166
166
|
state.workerLimit = totalTasks;
|
|
167
167
|
state.workerDispatches = running;
|
|
168
|
-
state.totalWorkerDispatches =
|
|
168
|
+
state.totalWorkerDispatches = dispatched;
|
|
169
169
|
}
|
|
170
170
|
continuationQueued(sessionID) {
|
|
171
171
|
const state = this.sessions.get(sessionID);
|
|
@@ -224,9 +224,19 @@ export class FastLaneController {
|
|
|
224
224
|
throw new FastLaneDeniedError("WORKER_RESUME_INVALID");
|
|
225
225
|
}
|
|
226
226
|
if (state.parallelMode) {
|
|
227
|
-
if (options.parallelWorkerAuthorized !== true
|
|
228
|
-
state.totalWorkerDispatches >= state.workerLimit)
|
|
227
|
+
if (options.parallelWorkerAuthorized !== true)
|
|
229
228
|
throw new FastLaneDeniedError("WORKER_LIMIT");
|
|
229
|
+
if (options.parallelWorkerAlreadyBound === true) {
|
|
230
|
+
if (state.workerDispatches < 1 || state.workerDispatches > state.parallelWorkerLimit ||
|
|
231
|
+
state.totalWorkerDispatches < 1 || state.totalWorkerDispatches > state.workerLimit) {
|
|
232
|
+
throw new FastLaneDeniedError("WORKER_LIMIT");
|
|
233
|
+
}
|
|
234
|
+
state.workerInFlight = true;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (state.workerDispatches >= state.parallelWorkerLimit || state.totalWorkerDispatches >= state.workerLimit) {
|
|
238
|
+
throw new FastLaneDeniedError("WORKER_LIMIT");
|
|
239
|
+
}
|
|
230
240
|
}
|
|
231
241
|
else if (state.backlogDrain &&
|
|
232
242
|
(state.workerDispatches >= 1 || state.totalWorkerDispatches >= state.workerLimit)) {
|