sortie-dogs 0.5.16 → 0.5.17
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/core/worktree-commit-artifact.js +158 -1
- package/dist/plugin/index.js +49 -44
- 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.17](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.5.17)
|
|
24
24
|
|
|
25
25
|
## Quick start
|
|
26
26
|
|
|
@@ -13,6 +13,7 @@ const MAX_OUTPUT = 1024 * 1024;
|
|
|
13
13
|
const MAX_TIMEOUT = 10 * 60_000;
|
|
14
14
|
const GIT_TIMEOUT = 30_000;
|
|
15
15
|
const EXIT_GRACE = 500;
|
|
16
|
+
let systemdUnitSequence = 0;
|
|
16
17
|
const KILL_WAIT = 2_000;
|
|
17
18
|
const WINDOWS_WRAPPER_GRACE = 15_000;
|
|
18
19
|
const LINUX_WRAPPER_GRACE = 10_000;
|
|
@@ -483,9 +484,163 @@ async function terminateTree(child, closed) {
|
|
|
483
484
|
return false;
|
|
484
485
|
};
|
|
485
486
|
if (!(await groupGone())) {
|
|
486
|
-
|
|
487
|
+
try {
|
|
488
|
+
process.kill(-child.pid, "SIGKILL");
|
|
489
|
+
}
|
|
490
|
+
catch { /* Already closed. */ }
|
|
491
|
+
if (!(await groupGone())) {
|
|
492
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Process-tree termination could not be confirmed.");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async function linuxSystemdRun() {
|
|
498
|
+
for (const candidate of ["/usr/bin/systemd-run", "/bin/systemd-run"]) {
|
|
499
|
+
const canonical = await realpath(candidate).catch(() => undefined);
|
|
500
|
+
if (canonical === undefined || !canonical.startsWith("/usr/bin/") && !canonical.startsWith("/bin/"))
|
|
501
|
+
continue;
|
|
502
|
+
const info = await lstat(canonical).catch(() => undefined);
|
|
503
|
+
if (info !== undefined && info.isFile() && !info.isSymbolicLink() && info.uid === 0 && (info.mode & 0o111) !== 0)
|
|
504
|
+
return canonical;
|
|
505
|
+
}
|
|
506
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation containment setup failed.");
|
|
507
|
+
}
|
|
508
|
+
async function linuxSystemctl() {
|
|
509
|
+
for (const candidate of ["/usr/bin/systemctl", "/bin/systemctl"]) {
|
|
510
|
+
const canonical = await realpath(candidate).catch(() => undefined);
|
|
511
|
+
if (canonical === undefined || !canonical.startsWith("/usr/bin/") && !canonical.startsWith("/bin/"))
|
|
512
|
+
continue;
|
|
513
|
+
const info = await lstat(canonical).catch(() => undefined);
|
|
514
|
+
if (info !== undefined && info.isFile() && !info.isSymbolicLink() && info.uid === 0 && (info.mode & 0o111) !== 0)
|
|
515
|
+
return canonical;
|
|
516
|
+
}
|
|
517
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation containment setup failed.");
|
|
518
|
+
}
|
|
519
|
+
async function linuxEnvironmentExecutable() {
|
|
520
|
+
for (const candidate of ["/usr/bin/env", "/bin/env"]) {
|
|
521
|
+
const canonical = await realpath(candidate).catch(() => undefined);
|
|
522
|
+
if (canonical === undefined || !canonical.startsWith("/usr/bin/") && !canonical.startsWith("/bin/"))
|
|
523
|
+
continue;
|
|
524
|
+
const info = await lstat(canonical).catch(() => undefined);
|
|
525
|
+
if (info !== undefined && info.isFile() && !info.isSymbolicLink() && info.uid === 0 && (info.mode & 0o111) !== 0)
|
|
526
|
+
return canonical;
|
|
527
|
+
}
|
|
528
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation containment setup failed.");
|
|
529
|
+
}
|
|
530
|
+
async function linuxShell() {
|
|
531
|
+
for (const candidate of ["/bin/sh", "/usr/bin/sh"]) {
|
|
532
|
+
const canonical = await realpath(candidate).catch(() => undefined);
|
|
533
|
+
if (canonical === undefined || !canonical.startsWith("/usr/bin/") && !canonical.startsWith("/bin/"))
|
|
534
|
+
continue;
|
|
535
|
+
const info = await lstat(canonical).catch(() => undefined);
|
|
536
|
+
if (info !== undefined && info.isFile() && !info.isSymbolicLink() && info.uid === 0 && (info.mode & 0o111) !== 0)
|
|
537
|
+
return canonical;
|
|
538
|
+
}
|
|
539
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation containment setup failed.");
|
|
540
|
+
}
|
|
541
|
+
async function stopSystemdUnit(unit, environment) {
|
|
542
|
+
const systemctl = await linuxSystemctl();
|
|
543
|
+
const invoke = async (args) => {
|
|
544
|
+
const child = spawn(systemctl, args, { env: environment, shell: false, windowsHide: true, stdio: "ignore" });
|
|
545
|
+
const closed = new Promise((done, reject) => {
|
|
546
|
+
child.once("error", reject);
|
|
547
|
+
child.once("close", (code) => done({ code }));
|
|
548
|
+
});
|
|
549
|
+
if (!(await waitForClose(closed, KILL_WAIT + 1_500))) {
|
|
550
|
+
child.kill("SIGKILL");
|
|
551
|
+
if (!(await waitForClose(closed, KILL_WAIT))) {
|
|
552
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation containment shutdown failed.");
|
|
553
|
+
}
|
|
487
554
|
}
|
|
555
|
+
return (await closed).code;
|
|
556
|
+
};
|
|
557
|
+
await invoke(["--user", "stop", unit]).catch(() => undefined);
|
|
558
|
+
const activeState = async () => {
|
|
559
|
+
const code = await invoke(["--user", "is-active", "--quiet", unit]);
|
|
560
|
+
if (code === 0)
|
|
561
|
+
return true;
|
|
562
|
+
if (code === 3 || code === 4)
|
|
563
|
+
return false;
|
|
564
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation containment shutdown failed.");
|
|
565
|
+
};
|
|
566
|
+
const deadline = Date.now() + KILL_WAIT;
|
|
567
|
+
while (Date.now() < deadline) {
|
|
568
|
+
if (await activeState()) {
|
|
569
|
+
await invoke(["--user", "stop", unit]).catch(() => undefined);
|
|
570
|
+
}
|
|
571
|
+
await new Promise((done) => setTimeout(done, 25));
|
|
572
|
+
}
|
|
573
|
+
if (await activeState()) {
|
|
574
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation containment shutdown failed.");
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function runLinuxSystemd(executable, args, cwd, timeout) {
|
|
578
|
+
const systemdRun = await linuxSystemdRun();
|
|
579
|
+
const [environmentExecutable, shell] = await Promise.all([linuxEnvironmentExecutable(), linuxShell()]);
|
|
580
|
+
const uid = process.getuid?.();
|
|
581
|
+
if (uid === undefined)
|
|
582
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation containment setup failed.");
|
|
583
|
+
const runtimeDirectory = `/run/user/${uid}`;
|
|
584
|
+
const environment = {
|
|
585
|
+
...cleanEnvironment(),
|
|
586
|
+
XDG_RUNTIME_DIR: runtimeDirectory,
|
|
587
|
+
DBUS_SESSION_BUS_ADDRESS: `unix:path=${runtimeDirectory}/bus`,
|
|
588
|
+
};
|
|
589
|
+
systemdUnitSequence = (systemdUnitSequence + 1) % Number.MAX_SAFE_INTEGER;
|
|
590
|
+
const unit = `sortie-dogs-${process.pid}-${Date.now()}-${systemdUnitSequence}`;
|
|
591
|
+
const validationEnvironment = Object.entries(cleanEnvironment()).map(([key, value]) => `${key}=${value}`);
|
|
592
|
+
const child = spawn(systemdRun, [
|
|
593
|
+
"--user", "--wait", "--collect", "--quiet", `--unit=${unit}`,
|
|
594
|
+
"--property=KillMode=control-group", `--property=RuntimeMaxSec=${timeout}ms`,
|
|
595
|
+
"--property=TimeoutStopSec=1s", "--working-directory", cwd, "--",
|
|
596
|
+
environmentExecutable, "-i", ...validationEnvironment, shell, "-c",
|
|
597
|
+
'"$@"; code=$?; if [ "$code" -eq 0 ]; then exit 0; else exit 239; fi',
|
|
598
|
+
"sortie-validation", executable, ...args,
|
|
599
|
+
], {
|
|
600
|
+
cwd, env: environment, shell: false, windowsHide: true, detached: true, stdio: ["ignore", "pipe", "pipe"],
|
|
601
|
+
});
|
|
602
|
+
const chunks = [];
|
|
603
|
+
let outputBytes = 0;
|
|
604
|
+
let overflow = false;
|
|
605
|
+
const collect = (chunk) => {
|
|
606
|
+
outputBytes += chunk.byteLength;
|
|
607
|
+
if (outputBytes <= MAX_OUTPUT)
|
|
608
|
+
chunks.push(chunk);
|
|
609
|
+
else
|
|
610
|
+
overflow = true;
|
|
611
|
+
};
|
|
612
|
+
child.stdout.on("data", collect);
|
|
613
|
+
child.stderr.on("data", (chunk) => {
|
|
614
|
+
outputBytes += chunk.byteLength;
|
|
615
|
+
if (outputBytes > MAX_OUTPUT)
|
|
616
|
+
overflow = true;
|
|
617
|
+
});
|
|
618
|
+
const closed = new Promise((done, reject) => {
|
|
619
|
+
child.once("error", reject);
|
|
620
|
+
child.once("close", (code) => done({ code }));
|
|
621
|
+
});
|
|
622
|
+
let timer;
|
|
623
|
+
const bounded = await Promise.race([
|
|
624
|
+
closed.then((result) => ({ kind: "closed", result })),
|
|
625
|
+
new Promise((done) => {
|
|
626
|
+
timer = setTimeout(() => done({ kind: "timeout" }), timeout + LINUX_WRAPPER_GRACE);
|
|
627
|
+
}),
|
|
628
|
+
]).catch(async () => {
|
|
629
|
+
await terminateTree(child, closed).catch(() => undefined);
|
|
630
|
+
await stopSystemdUnit(unit, environment);
|
|
631
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation executable failed.");
|
|
632
|
+
});
|
|
633
|
+
if (timer !== undefined)
|
|
634
|
+
clearTimeout(timer);
|
|
635
|
+
if (bounded.kind === "timeout" || overflow) {
|
|
636
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
637
|
+
await terminateTree(child, closed);
|
|
638
|
+
await stopSystemdUnit(unit, environment);
|
|
639
|
+
throw new WorktreeCommitArtifactError("validation-failed", "Validation exceeded its resource bound.");
|
|
488
640
|
}
|
|
641
|
+
await stopSystemdUnit(unit, environment);
|
|
642
|
+
const code = bounded.result.code === 0 ? 0 : bounded.result.code === 1 ? 238 : 239;
|
|
643
|
+
return { code, stdout: Buffer.concat(chunks) };
|
|
489
644
|
}
|
|
490
645
|
async function runBounded(executable, args, cwd, timeout, kind) {
|
|
491
646
|
const windowsWrapper = kind === "validation" && process.platform === "win32";
|
|
@@ -557,6 +712,8 @@ async function runBounded(executable, args, cwd, timeout, kind) {
|
|
|
557
712
|
}
|
|
558
713
|
const code = linuxWrapper && result.code !== 0 && ![238, 239, 240, 241].includes(result.code ?? -1)
|
|
559
714
|
? 240 : result.code ?? -1;
|
|
715
|
+
if (linuxWrapper && code === 240)
|
|
716
|
+
return await runLinuxSystemd(executable, args, cwd, timeout);
|
|
560
717
|
return { code, stdout: Buffer.concat(chunks) };
|
|
561
718
|
}
|
|
562
719
|
catch (error) {
|
package/dist/plugin/index.js
CHANGED
|
@@ -919,6 +919,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
919
919
|
return loaded?.gate === undefined && manifestAbsent;
|
|
920
920
|
}
|
|
921
921
|
const inspected = new Map();
|
|
922
|
+
const inspectionOperations = new Map();
|
|
922
923
|
const sessionAuthorizations = new Map();
|
|
923
924
|
const bindingPins = new Map();
|
|
924
925
|
const bindingOperations = new Set();
|
|
@@ -1383,8 +1384,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1383
1384
|
if (JSON.stringify(running.artifact.validation.command) !== JSON.stringify(requestedCommand)) {
|
|
1384
1385
|
return deny("artifact-replay");
|
|
1385
1386
|
}
|
|
1386
|
-
await (await getParallelCoordinator()).acceptArtifact(binding.ownerRoot, binding.completionCallID, sessionID, binding.descriptor, running.artifact);
|
|
1387
1387
|
await removeParallelControlFiles(binding.descriptor);
|
|
1388
|
+
await (await getParallelCoordinator()).acceptArtifact(binding.ownerRoot, binding.completionCallID, sessionID, binding.descriptor, running.artifact);
|
|
1388
1389
|
parallelArtifacts.set(sessionID, { requestFingerprint: request.fingerprint, artifact: running.artifact });
|
|
1389
1390
|
pruneParallelChildMap(parallelArtifacts);
|
|
1390
1391
|
return JSON.stringify({ status: "created", replay: true, artifact: boundedParallelArtifact(running.artifact) });
|
|
@@ -1397,8 +1398,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1397
1398
|
};
|
|
1398
1399
|
const recovered = await recoverWorktreeCommitArtifact(produceRequest);
|
|
1399
1400
|
const artifact = recovered ?? await produceWorktreeCommitArtifact(produceRequest);
|
|
1400
|
-
await (await getParallelCoordinator()).acceptArtifact(binding.ownerRoot, binding.completionCallID, sessionID, binding.descriptor, artifact);
|
|
1401
1401
|
await removeParallelControlFiles(binding.descriptor);
|
|
1402
|
+
await (await getParallelCoordinator()).acceptArtifact(binding.ownerRoot, binding.completionCallID, sessionID, binding.descriptor, artifact);
|
|
1402
1403
|
parallelArtifacts.set(sessionID, { requestFingerprint: request.fingerprint, artifact });
|
|
1403
1404
|
pruneParallelChildMap(parallelArtifacts);
|
|
1404
1405
|
return JSON.stringify({ status: "created", ...(recovered === undefined ? {} : { replay: true }),
|
|
@@ -1953,6 +1954,11 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1953
1954
|
return deny("binding-replay");
|
|
1954
1955
|
}
|
|
1955
1956
|
const existingAuthorization = sessionAuthorizations.get(sessionID);
|
|
1957
|
+
const pendingInspections = [...inspectionOperations.entries()]
|
|
1958
|
+
.filter(([key]) => key.startsWith(`${sessionID}\u0000`))
|
|
1959
|
+
.map(([, operation]) => operation);
|
|
1960
|
+
if (pendingInspections.length > 0)
|
|
1961
|
+
await Promise.allSettled(pendingInspections);
|
|
1956
1962
|
pruneInspections(now);
|
|
1957
1963
|
const inspectedEntry = [...inspected.entries()].find(([key, entry]) => key.startsWith(`${sessionID}\u0000`) &&
|
|
1958
1964
|
entry.ownerSessionID === sessionID &&
|
|
@@ -2401,7 +2407,17 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2401
2407
|
const path = input.args.filePath;
|
|
2402
2408
|
if (typeof path !== "string" || path.length === 0)
|
|
2403
2409
|
return;
|
|
2404
|
-
|
|
2410
|
+
const absolutePath = resolve(path);
|
|
2411
|
+
const key = `${input.sessionID}\u0000${absolutePath}`;
|
|
2412
|
+
const operation = inspect(path, input.sessionID).then(() => undefined);
|
|
2413
|
+
inspectionOperations.set(key, operation);
|
|
2414
|
+
try {
|
|
2415
|
+
await operation;
|
|
2416
|
+
}
|
|
2417
|
+
finally {
|
|
2418
|
+
if (inspectionOperations.get(key) === operation)
|
|
2419
|
+
inspectionOperations.delete(key);
|
|
2420
|
+
}
|
|
2405
2421
|
}
|
|
2406
2422
|
async function invalidateEditedHandoff(path) {
|
|
2407
2423
|
await ensureLoaded();
|
|
@@ -2482,7 +2498,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2482
2498
|
return undefined;
|
|
2483
2499
|
}
|
|
2484
2500
|
}
|
|
2485
|
-
async function
|
|
2501
|
+
async function hostSessionRecoveryHistory(sessionID) {
|
|
2486
2502
|
const messages = input.client?.session?.messages;
|
|
2487
2503
|
if (messages === undefined)
|
|
2488
2504
|
return undefined;
|
|
@@ -2494,55 +2510,40 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2494
2510
|
const payload = isRecord(response) && "data" in response ? response.data : response;
|
|
2495
2511
|
if (!Array.isArray(payload))
|
|
2496
2512
|
return undefined;
|
|
2513
|
+
let persistedTurn;
|
|
2514
|
+
let hasForeignUserTurn = false;
|
|
2497
2515
|
for (let index = payload.length - 1; index >= 0; index -= 1) {
|
|
2498
2516
|
const message = payload[index];
|
|
2499
|
-
if ((message
|
|
2500
|
-
continue;
|
|
2501
|
-
const agent = message.info?.agent ?? message.agent;
|
|
2502
|
-
if (typeof agent !== "string")
|
|
2517
|
+
if (!isRecord(message))
|
|
2503
2518
|
return undefined;
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
catch {
|
|
2512
|
-
return undefined;
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
async function hostSessionForeignUserAgent(sessionID) {
|
|
2516
|
-
const messages = input.client?.session?.messages;
|
|
2517
|
-
if (messages === undefined)
|
|
2518
|
-
return undefined;
|
|
2519
|
-
try {
|
|
2520
|
-
const response = await messages.call(input.client.session, {
|
|
2521
|
-
path: { id: sessionID },
|
|
2522
|
-
query: { directory: input.directory },
|
|
2523
|
-
});
|
|
2524
|
-
const payload = isRecord(response) && "data" in response ? response.data : response;
|
|
2525
|
-
if (!Array.isArray(payload))
|
|
2526
|
-
return undefined;
|
|
2527
|
-
for (const message of payload) {
|
|
2528
|
-
if ((message.info?.role ?? message.role) !== "user")
|
|
2519
|
+
if (message.info !== undefined && !isRecord(message.info))
|
|
2520
|
+
return undefined;
|
|
2521
|
+
const info = isRecord(message.info) ? message.info : undefined;
|
|
2522
|
+
const role = info?.role ?? message.role;
|
|
2523
|
+
if (role !== "user" && role !== "assistant")
|
|
2524
|
+
return undefined;
|
|
2525
|
+
if (role !== "user")
|
|
2529
2526
|
continue;
|
|
2530
|
-
const agent =
|
|
2527
|
+
const agent = info?.agent ?? message.agent;
|
|
2531
2528
|
if (typeof agent !== "string")
|
|
2532
2529
|
return undefined;
|
|
2533
2530
|
if (agent !== COORDINATOR_AGENT)
|
|
2534
|
-
|
|
2531
|
+
hasForeignUserTurn = true;
|
|
2532
|
+
if (persistedTurn === undefined) {
|
|
2533
|
+
if (message.parts !== undefined && !Array.isArray(message.parts))
|
|
2534
|
+
return undefined;
|
|
2535
|
+
persistedTurn = {
|
|
2536
|
+
agent,
|
|
2537
|
+
synthetic: Array.isArray(message.parts) && message.parts.some((part) => isRecord(part) && part.synthetic === true),
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2535
2540
|
}
|
|
2536
|
-
return
|
|
2541
|
+
return { hasForeignUserTurn, persistedTurn };
|
|
2537
2542
|
}
|
|
2538
2543
|
catch {
|
|
2539
2544
|
return undefined;
|
|
2540
2545
|
}
|
|
2541
2546
|
}
|
|
2542
|
-
async function hostSessionHasForeignUserTurn(sessionID) {
|
|
2543
|
-
const agent = await hostSessionForeignUserAgent(sessionID);
|
|
2544
|
-
return typeof agent === "string" ? true : agent;
|
|
2545
|
-
}
|
|
2546
2547
|
async function assistantMessageText(sessionID, messageID, expectedAgent, partID) {
|
|
2547
2548
|
const messages = input.client?.session?.messages;
|
|
2548
2549
|
if (messages === undefined)
|
|
@@ -2600,10 +2601,13 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2600
2601
|
return false;
|
|
2601
2602
|
if (identity.agent !== undefined && identity.agent !== COORDINATOR_AGENT)
|
|
2602
2603
|
return false;
|
|
2603
|
-
|
|
2604
|
+
const history = await hostSessionRecoveryHistory(sessionID);
|
|
2605
|
+
if (history === undefined || history.hasForeignUserTurn)
|
|
2606
|
+
return false;
|
|
2607
|
+
const persistedTurn = history.persistedTurn;
|
|
2608
|
+
if (persistedTurn === undefined && identity.agent !== COORDINATOR_AGENT)
|
|
2604
2609
|
return false;
|
|
2605
|
-
|
|
2606
|
-
if (persistedTurn?.agent !== COORDINATOR_AGENT)
|
|
2610
|
+
if (persistedTurn !== undefined && persistedTurn.agent !== COORDINATOR_AGENT)
|
|
2607
2611
|
return false;
|
|
2608
2612
|
await rememberCoordinatorRoot(sessionID);
|
|
2609
2613
|
releaseSessionEnforcement(sessionID);
|
|
@@ -3102,6 +3106,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
3102
3106
|
*/
|
|
3103
3107
|
"tool.execute.after": async (toolInput, output) => {
|
|
3104
3108
|
const completedChildSessionID = toolInput.tool === "task" ? taskChildSessionID(output) : undefined;
|
|
3109
|
+
const handoffInspection = inspectSuccessfulRead(toolInput);
|
|
3105
3110
|
try {
|
|
3106
3111
|
if (bootstrapRequired && toolInput.tool === "sortie_check_contract" && toolInput.sessionID !== undefined &&
|
|
3107
3112
|
isCoordinatorSession(toolInput.sessionID) && successfulBootstrapContractCheck(output)) {
|
|
@@ -3121,7 +3126,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
3121
3126
|
}
|
|
3122
3127
|
}
|
|
3123
3128
|
}
|
|
3124
|
-
await
|
|
3129
|
+
await handoffInspection;
|
|
3125
3130
|
let parallel = parallelCalls.get(toolInput.callID ?? "");
|
|
3126
3131
|
if (parallel === undefined && toolInput.tool === "task" && toolInput.sessionID !== undefined &&
|
|
3127
3132
|
toolInput.callID !== undefined &&
|