pi-better-harness 0.12.0 → 0.12.2
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/node_modules/pi-better-background-tasks/package.json +1 -1
- package/node_modules/pi-better-background-tasks/src/shared-sandbox-core.ts +14 -47
- package/node_modules/pi-better-sandbox/README.md +2 -2
- package/node_modules/pi-better-sandbox/package.json +1 -1
- package/node_modules/pi-better-sandbox/shared-sandbox-core.ts +14 -47
- package/node_modules/pi-better-subagents/package.json +1 -1
- package/node_modules/pi-better-subagents/shared-sandbox-core.ts +14 -47
- package/node_modules/pi-better-subagents/tools.ts +2 -2
- package/package.json +4 -4
|
@@ -552,50 +552,18 @@ function contains(root: string, target: string): boolean {
|
|
|
552
552
|
return target.startsWith(root.endsWith(sep) ? root : `${root}${sep}`);
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
-
/** Per-device answer to "does this volume fold case?" (APFS/HFS+ default). */
|
|
556
|
-
const caseFoldingByDevice = new Map<number, boolean>();
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Whether the volume holding `path` (its nearest existing ancestor) ignores
|
|
560
|
-
* case: an existing component whose case-swapped spelling names the same
|
|
561
|
-
* inode proves it. macOS only; false whenever it cannot be shown.
|
|
562
|
-
*/
|
|
563
|
-
function caseInsensitiveVolume(path: string): boolean {
|
|
564
|
-
if (process.platform !== "darwin") return false;
|
|
565
|
-
let current = path;
|
|
566
|
-
while (!existsSync(current) && dirname(current) !== current) current = dirname(current);
|
|
567
|
-
let device: number;
|
|
568
|
-
try { device = statSync(current).dev; } catch { return false; }
|
|
569
|
-
const known = caseFoldingByDevice.get(device);
|
|
570
|
-
if (known !== undefined) return known;
|
|
571
|
-
for (let probe = current; dirname(probe) !== probe; probe = dirname(probe)) {
|
|
572
|
-
const name = basename(probe);
|
|
573
|
-
const swapped = [...name].map((c) => c === c.toLowerCase() ? c.toUpperCase() : c.toLowerCase()).join("");
|
|
574
|
-
if (swapped === name) continue;
|
|
575
|
-
try {
|
|
576
|
-
const original = statSync(probe);
|
|
577
|
-
if (original.dev !== device) break;
|
|
578
|
-
let folded = false;
|
|
579
|
-
try {
|
|
580
|
-
const other = statSync(join(dirname(probe), swapped));
|
|
581
|
-
folded = other.dev === original.dev && other.ino === original.ino;
|
|
582
|
-
} catch { /* The swapped name is absent: case-sensitive. */ }
|
|
583
|
-
caseFoldingByDevice.set(device, folded);
|
|
584
|
-
return folded;
|
|
585
|
-
} catch { return false; }
|
|
586
|
-
}
|
|
587
|
-
return false;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
555
|
/**
|
|
591
|
-
* Spell `path` the way the policy spells it when they differ only in case on
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
* the caller's case, and the kernel still treats it as the
|
|
595
|
-
*
|
|
596
|
-
*
|
|
556
|
+
* Spell `path` the way the policy spells it when they differ only in case, on
|
|
557
|
+
* macOS. `canonicalizePath` fixes the case of every existing component on a
|
|
558
|
+
* case-insensitive volume, but a missing tail (`~/.NPMRC` while `~/.npmrc` is
|
|
559
|
+
* absent) keeps the caller's case, and the kernel still treats it as the
|
|
560
|
+
* protected name. Seatbelt matches paths case-insensitively on every volume,
|
|
561
|
+
* case-sensitive APFS included, so this folds on darwin without probing the
|
|
562
|
+
* volume: the pre-check decides as the kernel does (#354, #358). The longest
|
|
563
|
+
* policy path that matches case-insensitively wins. Other platforms never fold.
|
|
597
564
|
*/
|
|
598
|
-
function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy): string {
|
|
565
|
+
function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy, seams: SandboxSeams): string {
|
|
566
|
+
if (currentPlatform(seams) !== "darwin") return path;
|
|
599
567
|
const broad = policy.broad;
|
|
600
568
|
const candidates = [policy.writableRoot, ...policy.denyWrite, ...(policy.credentialPaths ?? []),
|
|
601
569
|
...(policy.runtimeWrite ?? []), ...(policy.compatibilityWrite ?? []),
|
|
@@ -608,8 +576,7 @@ function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy): st
|
|
|
608
576
|
if (folded.length !== candidate.length || !contains(folded, lower)) continue;
|
|
609
577
|
if (!best || candidate.length > best.length) best = candidate;
|
|
610
578
|
}
|
|
611
|
-
|
|
612
|
-
if (!best || path.startsWith(best) || !caseInsensitiveVolume(path)) return path;
|
|
579
|
+
if (!best || path.startsWith(best)) return path;
|
|
613
580
|
return `${best}${path.slice(best.length)}`;
|
|
614
581
|
}
|
|
615
582
|
|
|
@@ -627,7 +594,7 @@ export function evaluateReadAccess(
|
|
|
627
594
|
policy: CompiledSandboxWritePolicy,
|
|
628
595
|
seams: SandboxSeams = {},
|
|
629
596
|
): ReadAccessDecision {
|
|
630
|
-
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy);
|
|
597
|
+
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy, seams);
|
|
631
598
|
const permissions = policy.permissions;
|
|
632
599
|
if (!permissions) return { allowed: true, path };
|
|
633
600
|
if (policy.broad) {
|
|
@@ -654,7 +621,7 @@ export function evaluateWriteAccess(
|
|
|
654
621
|
policy: CompiledSandboxWritePolicy,
|
|
655
622
|
seams: SandboxSeams = {},
|
|
656
623
|
): WriteAccessDecision {
|
|
657
|
-
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy);
|
|
624
|
+
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy, seams);
|
|
658
625
|
if (policy.broad) return evaluateBroadWrite(path, policy, policy.broad, seams);
|
|
659
626
|
if (!policy.permissions && !contains(policy.writableRoot, path)) {
|
|
660
627
|
return { allowed: false, path, reason: "outside-writable-root" };
|
|
@@ -1582,7 +1549,7 @@ function buildLinuxBroadCommand(
|
|
|
1582
1549
|
fileArgs: [...fileArgs, ...(!permissions.network ? ["--unshare-net"] : []),
|
|
1583
1550
|
"--", args.execPath, ...args.execArgs],
|
|
1584
1551
|
...(created.length && { notices: [
|
|
1585
|
-
`Sandbox: created an empty read-only placeholder where a protected symlink points at nothing, and left it in place: ${created.join(", ")}`,
|
|
1552
|
+
`Sandbox: created an empty read-only placeholder where a protected symlink points at nothing, and left it in place (remove it before creating the real target): ${created.join(", ")}`,
|
|
1586
1553
|
] }),
|
|
1587
1554
|
};
|
|
1588
1555
|
}
|
|
@@ -121,9 +121,9 @@ the sandbox is enabled again.
|
|
|
121
121
|
|
|
122
122
|
| | macOS (Seatbelt) | Linux (Bubblewrap) | Windows |
|
|
123
123
|
| - | - | - | - |
|
|
124
|
-
| Outside project = Write | Writes across home and temp. Removal and renaming refused outside the disposable places. | Fallback: ordinary top-level home folders (`~/projects`, `~/Documents`, …) and home itself are **read-only**. Dot entries, temp, worktree folders (found within three levels of home), and the workspace are writable, removal included. A directory holding a protected symlink (typically `~/.config` with a stow link such as `~/.config/git`) keeps its existing entries writable but refuses new top-level entries. | No backend: confined launches fail closed. |
|
|
124
|
+
| Outside project = Write | Writes across home and temp. Removal and renaming refused outside the disposable places. | Fallback: ordinary top-level home folders (`~/projects`, `~/Documents`, …) and home itself are **read-only**. Dot entries, temp, worktree folders (found within three levels of home), and the workspace are writable, removal included. A directory holding a protected symlink (typically `~/.config` with a stow link such as `~/.config/git`) keeps its existing entries writable but refuses new top-level entries. A protected symlink pointing at nothing can get an empty read-only placeholder that stays on disk; the launch says so, and you remove the placeholder before creating the real target. | No backend: confined launches fail closed. |
|
|
125
125
|
| Project files = Write | Enforced. | **Refused at launch** (bind mounts cannot separate removal from writing). Use Write & delete or Read. | No backend. |
|
|
126
|
-
| Deny list | Path rules. | Credentials masked by empty mode-000 mounts. Code that runs later is bound read-only. | No backend. |
|
|
126
|
+
| Deny list | Path rules, matched ignoring case on every volume (as Seatbelt does). | Credentials masked by empty mode-000 mounts. Code that runs later is bound read-only. | No backend. |
|
|
127
127
|
|
|
128
128
|
Linux could separate removal from writing with Landlock (5.19+), but nothing
|
|
129
129
|
here can apply it: Node has no binding and `bwrap` does not expose it. Windows
|
|
@@ -552,50 +552,18 @@ function contains(root: string, target: string): boolean {
|
|
|
552
552
|
return target.startsWith(root.endsWith(sep) ? root : `${root}${sep}`);
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
-
/** Per-device answer to "does this volume fold case?" (APFS/HFS+ default). */
|
|
556
|
-
const caseFoldingByDevice = new Map<number, boolean>();
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Whether the volume holding `path` (its nearest existing ancestor) ignores
|
|
560
|
-
* case: an existing component whose case-swapped spelling names the same
|
|
561
|
-
* inode proves it. macOS only; false whenever it cannot be shown.
|
|
562
|
-
*/
|
|
563
|
-
function caseInsensitiveVolume(path: string): boolean {
|
|
564
|
-
if (process.platform !== "darwin") return false;
|
|
565
|
-
let current = path;
|
|
566
|
-
while (!existsSync(current) && dirname(current) !== current) current = dirname(current);
|
|
567
|
-
let device: number;
|
|
568
|
-
try { device = statSync(current).dev; } catch { return false; }
|
|
569
|
-
const known = caseFoldingByDevice.get(device);
|
|
570
|
-
if (known !== undefined) return known;
|
|
571
|
-
for (let probe = current; dirname(probe) !== probe; probe = dirname(probe)) {
|
|
572
|
-
const name = basename(probe);
|
|
573
|
-
const swapped = [...name].map((c) => c === c.toLowerCase() ? c.toUpperCase() : c.toLowerCase()).join("");
|
|
574
|
-
if (swapped === name) continue;
|
|
575
|
-
try {
|
|
576
|
-
const original = statSync(probe);
|
|
577
|
-
if (original.dev !== device) break;
|
|
578
|
-
let folded = false;
|
|
579
|
-
try {
|
|
580
|
-
const other = statSync(join(dirname(probe), swapped));
|
|
581
|
-
folded = other.dev === original.dev && other.ino === original.ino;
|
|
582
|
-
} catch { /* The swapped name is absent: case-sensitive. */ }
|
|
583
|
-
caseFoldingByDevice.set(device, folded);
|
|
584
|
-
return folded;
|
|
585
|
-
} catch { return false; }
|
|
586
|
-
}
|
|
587
|
-
return false;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
555
|
/**
|
|
591
|
-
* Spell `path` the way the policy spells it when they differ only in case on
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
* the caller's case, and the kernel still treats it as the
|
|
595
|
-
*
|
|
596
|
-
*
|
|
556
|
+
* Spell `path` the way the policy spells it when they differ only in case, on
|
|
557
|
+
* macOS. `canonicalizePath` fixes the case of every existing component on a
|
|
558
|
+
* case-insensitive volume, but a missing tail (`~/.NPMRC` while `~/.npmrc` is
|
|
559
|
+
* absent) keeps the caller's case, and the kernel still treats it as the
|
|
560
|
+
* protected name. Seatbelt matches paths case-insensitively on every volume,
|
|
561
|
+
* case-sensitive APFS included, so this folds on darwin without probing the
|
|
562
|
+
* volume: the pre-check decides as the kernel does (#354, #358). The longest
|
|
563
|
+
* policy path that matches case-insensitively wins. Other platforms never fold.
|
|
597
564
|
*/
|
|
598
|
-
function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy): string {
|
|
565
|
+
function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy, seams: SandboxSeams): string {
|
|
566
|
+
if (currentPlatform(seams) !== "darwin") return path;
|
|
599
567
|
const broad = policy.broad;
|
|
600
568
|
const candidates = [policy.writableRoot, ...policy.denyWrite, ...(policy.credentialPaths ?? []),
|
|
601
569
|
...(policy.runtimeWrite ?? []), ...(policy.compatibilityWrite ?? []),
|
|
@@ -608,8 +576,7 @@ function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy): st
|
|
|
608
576
|
if (folded.length !== candidate.length || !contains(folded, lower)) continue;
|
|
609
577
|
if (!best || candidate.length > best.length) best = candidate;
|
|
610
578
|
}
|
|
611
|
-
|
|
612
|
-
if (!best || path.startsWith(best) || !caseInsensitiveVolume(path)) return path;
|
|
579
|
+
if (!best || path.startsWith(best)) return path;
|
|
613
580
|
return `${best}${path.slice(best.length)}`;
|
|
614
581
|
}
|
|
615
582
|
|
|
@@ -627,7 +594,7 @@ export function evaluateReadAccess(
|
|
|
627
594
|
policy: CompiledSandboxWritePolicy,
|
|
628
595
|
seams: SandboxSeams = {},
|
|
629
596
|
): ReadAccessDecision {
|
|
630
|
-
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy);
|
|
597
|
+
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy, seams);
|
|
631
598
|
const permissions = policy.permissions;
|
|
632
599
|
if (!permissions) return { allowed: true, path };
|
|
633
600
|
if (policy.broad) {
|
|
@@ -654,7 +621,7 @@ export function evaluateWriteAccess(
|
|
|
654
621
|
policy: CompiledSandboxWritePolicy,
|
|
655
622
|
seams: SandboxSeams = {},
|
|
656
623
|
): WriteAccessDecision {
|
|
657
|
-
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy);
|
|
624
|
+
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy, seams);
|
|
658
625
|
if (policy.broad) return evaluateBroadWrite(path, policy, policy.broad, seams);
|
|
659
626
|
if (!policy.permissions && !contains(policy.writableRoot, path)) {
|
|
660
627
|
return { allowed: false, path, reason: "outside-writable-root" };
|
|
@@ -1582,7 +1549,7 @@ function buildLinuxBroadCommand(
|
|
|
1582
1549
|
fileArgs: [...fileArgs, ...(!permissions.network ? ["--unshare-net"] : []),
|
|
1583
1550
|
"--", args.execPath, ...args.execArgs],
|
|
1584
1551
|
...(created.length && { notices: [
|
|
1585
|
-
`Sandbox: created an empty read-only placeholder where a protected symlink points at nothing, and left it in place: ${created.join(", ")}`,
|
|
1552
|
+
`Sandbox: created an empty read-only placeholder where a protected symlink points at nothing, and left it in place (remove it before creating the real target): ${created.join(", ")}`,
|
|
1586
1553
|
] }),
|
|
1587
1554
|
};
|
|
1588
1555
|
}
|
|
@@ -552,50 +552,18 @@ function contains(root: string, target: string): boolean {
|
|
|
552
552
|
return target.startsWith(root.endsWith(sep) ? root : `${root}${sep}`);
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
-
/** Per-device answer to "does this volume fold case?" (APFS/HFS+ default). */
|
|
556
|
-
const caseFoldingByDevice = new Map<number, boolean>();
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Whether the volume holding `path` (its nearest existing ancestor) ignores
|
|
560
|
-
* case: an existing component whose case-swapped spelling names the same
|
|
561
|
-
* inode proves it. macOS only; false whenever it cannot be shown.
|
|
562
|
-
*/
|
|
563
|
-
function caseInsensitiveVolume(path: string): boolean {
|
|
564
|
-
if (process.platform !== "darwin") return false;
|
|
565
|
-
let current = path;
|
|
566
|
-
while (!existsSync(current) && dirname(current) !== current) current = dirname(current);
|
|
567
|
-
let device: number;
|
|
568
|
-
try { device = statSync(current).dev; } catch { return false; }
|
|
569
|
-
const known = caseFoldingByDevice.get(device);
|
|
570
|
-
if (known !== undefined) return known;
|
|
571
|
-
for (let probe = current; dirname(probe) !== probe; probe = dirname(probe)) {
|
|
572
|
-
const name = basename(probe);
|
|
573
|
-
const swapped = [...name].map((c) => c === c.toLowerCase() ? c.toUpperCase() : c.toLowerCase()).join("");
|
|
574
|
-
if (swapped === name) continue;
|
|
575
|
-
try {
|
|
576
|
-
const original = statSync(probe);
|
|
577
|
-
if (original.dev !== device) break;
|
|
578
|
-
let folded = false;
|
|
579
|
-
try {
|
|
580
|
-
const other = statSync(join(dirname(probe), swapped));
|
|
581
|
-
folded = other.dev === original.dev && other.ino === original.ino;
|
|
582
|
-
} catch { /* The swapped name is absent: case-sensitive. */ }
|
|
583
|
-
caseFoldingByDevice.set(device, folded);
|
|
584
|
-
return folded;
|
|
585
|
-
} catch { return false; }
|
|
586
|
-
}
|
|
587
|
-
return false;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
555
|
/**
|
|
591
|
-
* Spell `path` the way the policy spells it when they differ only in case on
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
* the caller's case, and the kernel still treats it as the
|
|
595
|
-
*
|
|
596
|
-
*
|
|
556
|
+
* Spell `path` the way the policy spells it when they differ only in case, on
|
|
557
|
+
* macOS. `canonicalizePath` fixes the case of every existing component on a
|
|
558
|
+
* case-insensitive volume, but a missing tail (`~/.NPMRC` while `~/.npmrc` is
|
|
559
|
+
* absent) keeps the caller's case, and the kernel still treats it as the
|
|
560
|
+
* protected name. Seatbelt matches paths case-insensitively on every volume,
|
|
561
|
+
* case-sensitive APFS included, so this folds on darwin without probing the
|
|
562
|
+
* volume: the pre-check decides as the kernel does (#354, #358). The longest
|
|
563
|
+
* policy path that matches case-insensitively wins. Other platforms never fold.
|
|
597
564
|
*/
|
|
598
|
-
function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy): string {
|
|
565
|
+
function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy, seams: SandboxSeams): string {
|
|
566
|
+
if (currentPlatform(seams) !== "darwin") return path;
|
|
599
567
|
const broad = policy.broad;
|
|
600
568
|
const candidates = [policy.writableRoot, ...policy.denyWrite, ...(policy.credentialPaths ?? []),
|
|
601
569
|
...(policy.runtimeWrite ?? []), ...(policy.compatibilityWrite ?? []),
|
|
@@ -608,8 +576,7 @@ function alignCaseToPolicy(path: string, policy: CompiledSandboxWritePolicy): st
|
|
|
608
576
|
if (folded.length !== candidate.length || !contains(folded, lower)) continue;
|
|
609
577
|
if (!best || candidate.length > best.length) best = candidate;
|
|
610
578
|
}
|
|
611
|
-
|
|
612
|
-
if (!best || path.startsWith(best) || !caseInsensitiveVolume(path)) return path;
|
|
579
|
+
if (!best || path.startsWith(best)) return path;
|
|
613
580
|
return `${best}${path.slice(best.length)}`;
|
|
614
581
|
}
|
|
615
582
|
|
|
@@ -627,7 +594,7 @@ export function evaluateReadAccess(
|
|
|
627
594
|
policy: CompiledSandboxWritePolicy,
|
|
628
595
|
seams: SandboxSeams = {},
|
|
629
596
|
): ReadAccessDecision {
|
|
630
|
-
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy);
|
|
597
|
+
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy, seams);
|
|
631
598
|
const permissions = policy.permissions;
|
|
632
599
|
if (!permissions) return { allowed: true, path };
|
|
633
600
|
if (policy.broad) {
|
|
@@ -654,7 +621,7 @@ export function evaluateWriteAccess(
|
|
|
654
621
|
policy: CompiledSandboxWritePolicy,
|
|
655
622
|
seams: SandboxSeams = {},
|
|
656
623
|
): WriteAccessDecision {
|
|
657
|
-
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy);
|
|
624
|
+
const path = alignCaseToPolicy(canonicalizePath(target, seams), policy, seams);
|
|
658
625
|
if (policy.broad) return evaluateBroadWrite(path, policy, policy.broad, seams);
|
|
659
626
|
if (!policy.permissions && !contains(policy.writableRoot, path)) {
|
|
660
627
|
return { allowed: false, path, reason: "outside-writable-root" };
|
|
@@ -1582,7 +1549,7 @@ function buildLinuxBroadCommand(
|
|
|
1582
1549
|
fileArgs: [...fileArgs, ...(!permissions.network ? ["--unshare-net"] : []),
|
|
1583
1550
|
"--", args.execPath, ...args.execArgs],
|
|
1584
1551
|
...(created.length && { notices: [
|
|
1585
|
-
`Sandbox: created an empty read-only placeholder where a protected symlink points at nothing, and left it in place: ${created.join(", ")}`,
|
|
1552
|
+
`Sandbox: created an empty read-only placeholder where a protected symlink points at nothing, and left it in place (remove it before creating the real target): ${created.join(", ")}`,
|
|
1586
1553
|
] }),
|
|
1587
1554
|
};
|
|
1588
1555
|
}
|
|
@@ -420,7 +420,7 @@ export function subagentResultTool(Type: TypeModule, baseSession: SubagentToolSe
|
|
|
420
420
|
name: "subagent_result",
|
|
421
421
|
label: "Subagent Result",
|
|
422
422
|
description:
|
|
423
|
-
"Read a subagent's final output if it has finished. NEVER waits. Default current session. Ordinary answers are preserved verbatim in a
|
|
423
|
+
"Read a subagent's final output if it has finished. NEVER waits. Default current session. Ordinary answers are preserved verbatim in a 2048-byte UTF-8 page (max_bytes up to 8192 bytes, optional lines cap); pass cursor to reconstruct the rest. Pass all:true for a foreign-session id. mode=raw pages retained log bytes (16 KiB default, 64 KiB cap). Failures and exceptional lifecycle facts come before progress. No tool-name histories or default cost lines; pass include:[\"cost\",\"tools\"] to opt in.",
|
|
424
424
|
promptSnippet: "Read a finished subagent's final result (never waits)",
|
|
425
425
|
promptGuidelines: [
|
|
426
426
|
"Use subagent_result to collect a finished run's output. If it reports the run is still going, stop — do not poll; you'll be notified when it finishes.",
|
|
@@ -429,7 +429,7 @@ export function subagentResultTool(Type: TypeModule, baseSession: SubagentToolSe
|
|
|
429
429
|
parameters: Type.Object({
|
|
430
430
|
id: Type.String({ description: "Run id from subagent_spawn." }),
|
|
431
431
|
cursor: Type.Optional(Type.String({ description: "Caller-owned answer page or status cursor from a previous response." })),
|
|
432
|
-
max_bytes: Type.Optional(Type.Number({ description: "UTF-8 byte budget for this page (default
|
|
432
|
+
max_bytes: Type.Optional(Type.Number({ description: "UTF-8 byte budget for this page (default 2048 bytes, max 8192 bytes)." })),
|
|
433
433
|
maxBytes: Type.Optional(Type.Number({ description: MAX_BYTES_ALIAS_DESCRIPTION })),
|
|
434
434
|
lines: Type.Optional(Type.Number({ description: "Optional line cap for each answer page (default: bytes only). nextCursor continues after the last line shown." })),
|
|
435
435
|
tail_lines: Type.Optional(Type.Number({ description: "Deprecated alias for lines. lines wins when both are given." })),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-harness",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Pi extension bundle for a write sandbox, subagents, background tasks, SSH, goals, and structured plans.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"test": "node --test test/*.test.mjs"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"pi-better-background-tasks": "0.6.
|
|
53
|
+
"pi-better-background-tasks": "0.6.1",
|
|
54
54
|
"pi-better-goal": "0.5.0",
|
|
55
55
|
"pi-better-plan": "0.5.0",
|
|
56
|
-
"pi-better-sandbox": "0.7.
|
|
56
|
+
"pi-better-sandbox": "0.7.2",
|
|
57
57
|
"pi-better-ssh": "0.1.1",
|
|
58
|
-
"pi-better-subagents": "0.9.
|
|
58
|
+
"pi-better-subagents": "0.9.2",
|
|
59
59
|
"smol-toml": "1.9.0",
|
|
60
60
|
"yaml": "^2.9.1"
|
|
61
61
|
},
|