pi-better-harness 0.12.3 → 0.13.0
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 +21 -0
- package/extensions/minimal-output/hook.ts +149 -0
- package/extensions/minimal-output/index.ts +85 -0
- package/node_modules/pi-better-sandbox/README.md +21 -8
- package/node_modules/pi-better-sandbox/package.json +1 -1
- package/node_modules/pi-better-sandbox/permissions-page.ts +94 -41
- package/node_modules/pi-better-subagents/README.md +2 -2
- package/node_modules/pi-better-subagents/catalog-runtime.ts +3 -3
- package/node_modules/pi-better-subagents/catalog-schema.ts +3 -3
- package/node_modules/pi-better-subagents/docs/agent-catalog-operations.md +1 -1
- package/node_modules/pi-better-subagents/docs/agent-catalog.md +4 -4
- package/node_modules/pi-better-subagents/docs/agent-model-resolution.md +1 -1
- package/node_modules/pi-better-subagents/roles/role.developer.md +1 -1
- package/node_modules/pi-better-subagents/roles/role.product-manager.md +1 -1
- package/node_modules/pi-better-subagents/roles/role.researcher.md +1 -1
- package/node_modules/pi-better-subagents/tier-policy.ts +2 -2
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -60,6 +60,27 @@ Sandbox state is human-only: `/sandbox`, `/sandbox on`, `/sandbox off`,
|
|
|
60
60
|
commands with no tool equivalent. `/sandbox off` and `/sandbox default off`
|
|
61
61
|
need interactive confirmation. Full policy: [pi-better-sandbox](https://github.com/1aboveio/pi-better-harness/tree/main/packages/pi-better-sandbox#readme).
|
|
62
62
|
|
|
63
|
+
## Minimal Tool Output
|
|
64
|
+
|
|
65
|
+
The bundled harness also provides `/tool-output minimal`, `/tool-output normal`,
|
|
66
|
+
and `/tool-output` (toggle). Minimal mode hides every collapsed result body,
|
|
67
|
+
including built-in, extension, and MCP text/images, without changing execution,
|
|
68
|
+
sandboxing, or agent-facing payloads. Tool headers and error coloring remain;
|
|
69
|
+
Ctrl+O reveals results, and fullscreen Pi versions with clickable tool rows
|
|
70
|
+
also support clicking headers.
|
|
71
|
+
|
|
72
|
+
Normal mode is the default. The preference is saved in the current session,
|
|
73
|
+
including resume/reload. This is a version-sensitive internal TUI adapter,
|
|
74
|
+
tested with Pi 0.82.1 and 0.99.1; incompatible APIs produce a warning and leave
|
|
75
|
+
ordinary output enabled. Print/RPC output and exported transcripts are unchanged.
|
|
76
|
+
|
|
77
|
+
The standalone-package installer does not install this bundled extension.
|
|
78
|
+
Load the bundled harness or run it directly from a checkout:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
pi -e ./packages/pi-better-harness/extensions/minimal-output/index.ts
|
|
82
|
+
```
|
|
83
|
+
|
|
63
84
|
## When To Use
|
|
64
85
|
|
|
65
86
|
Use the installer when you want every core extension with standalone package identities. Install an individual package instead when you only need the sandbox, subagents, shell task supervision, synchronous SSH, goal tracking, or plans.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
interface ToolComponent {
|
|
7
|
+
expanded: boolean;
|
|
8
|
+
showImages: boolean;
|
|
9
|
+
resultRendererComponent?: unknown;
|
|
10
|
+
ui: { requestRender(): void };
|
|
11
|
+
invalidate(): void;
|
|
12
|
+
setExpanded(expanded: boolean): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Method = (this: ToolComponent, ...args: unknown[]) => unknown;
|
|
16
|
+
interface ToolPrototype {
|
|
17
|
+
updateDisplay: Method;
|
|
18
|
+
getResultRenderer: Method;
|
|
19
|
+
getTextOutput: Method;
|
|
20
|
+
setExpanded: Method;
|
|
21
|
+
invalidate: Method;
|
|
22
|
+
[HOOK]?: HookState;
|
|
23
|
+
}
|
|
24
|
+
interface HookState {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
owners: number;
|
|
27
|
+
redraw(collapse?: boolean): void;
|
|
28
|
+
restore(): void;
|
|
29
|
+
}
|
|
30
|
+
export interface MinimalOutputHook {
|
|
31
|
+
setEnabled(enabled: boolean): void;
|
|
32
|
+
dispose(): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const HOOK = Symbol.for("pi-better-harness.minimal-output-hook");
|
|
36
|
+
const emptyResult = () => ({ render: () => [] as string[], invalidate() {} });
|
|
37
|
+
|
|
38
|
+
/** Resolve the running host's SDK, not a second SDK bundled beside the extension. */
|
|
39
|
+
export async function loadToolPrototype(): Promise<ToolPrototype> {
|
|
40
|
+
let require = createRequire(import.meta.url);
|
|
41
|
+
if (process.argv[1]) {
|
|
42
|
+
try {
|
|
43
|
+
const hostRequire = createRequire(realpathSync(process.argv[1]));
|
|
44
|
+
sdkEntry(hostRequire);
|
|
45
|
+
require = hostRequire;
|
|
46
|
+
} catch { /* SDK embedding or a test runner: use the extension's SDK. */ }
|
|
47
|
+
}
|
|
48
|
+
const entry = sdkEntry(require);
|
|
49
|
+
const path = join(dirname(entry), "modes/interactive/components/tool-execution.js");
|
|
50
|
+
const module = await import(pathToFileURL(path).href);
|
|
51
|
+
return module.ToolExecutionComponent.prototype;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Pi's ESM-only export cannot be resolved with require.resolve on older SDKs.
|
|
55
|
+
function sdkEntry(require: ReturnType<typeof createRequire>): string {
|
|
56
|
+
for (const directory of require.resolve.paths("@earendil-works/pi-coding-agent") ?? []) {
|
|
57
|
+
const root = join(directory, "@earendil-works/pi-coding-agent");
|
|
58
|
+
try {
|
|
59
|
+
const metadata = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
60
|
+
const entry = metadata.exports?.["."]?.import ?? metadata.main;
|
|
61
|
+
if (metadata.name === "@earendil-works/pi-coding-agent" && typeof entry === "string") return join(root, entry);
|
|
62
|
+
} catch { /* Continue through Node's module search paths. */ }
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Could not locate the running Pi SDK. Normal output remains enabled.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Internal TUI adapter: no tools are replaced and no result content is changed. */
|
|
68
|
+
export function installMinimalOutputHook(prototype: ToolPrototype): MinimalOutputHook {
|
|
69
|
+
for (const name of ["updateDisplay", "getResultRenderer", "getTextOutput", "setExpanded", "invalidate"] as const) {
|
|
70
|
+
if (typeof prototype[name] !== "function") {
|
|
71
|
+
throw new Error(`Pi's tool display API is incompatible: missing ${name}. Normal output remains enabled.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let state = prototype[HOOK];
|
|
75
|
+
if (!state) {
|
|
76
|
+
const originals = {
|
|
77
|
+
updateDisplay: prototype.updateDisplay,
|
|
78
|
+
getResultRenderer: prototype.getResultRenderer,
|
|
79
|
+
getTextOutput: prototype.getTextOutput,
|
|
80
|
+
};
|
|
81
|
+
const seen = new WeakSet<ToolComponent>();
|
|
82
|
+
const components = new Set<WeakRef<ToolComponent>>();
|
|
83
|
+
state = {
|
|
84
|
+
enabled: false,
|
|
85
|
+
owners: 0,
|
|
86
|
+
redraw(collapse = false) {
|
|
87
|
+
for (const reference of components) {
|
|
88
|
+
const component = reference.deref();
|
|
89
|
+
if (!component) { components.delete(reference); continue; }
|
|
90
|
+
if (collapse) component.setExpanded(false);
|
|
91
|
+
else component.invalidate();
|
|
92
|
+
component.ui.requestRender();
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
restore() {
|
|
96
|
+
for (const name of Object.keys(originals) as Array<keyof typeof originals>) {
|
|
97
|
+
// Do not remove another extension's subsequently installed wrapper.
|
|
98
|
+
if (prototype[name] === wrappers[name]) prototype[name] = originals[name];
|
|
99
|
+
}
|
|
100
|
+
delete prototype[HOOK];
|
|
101
|
+
components.clear();
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const current = state;
|
|
105
|
+
const wrappers = {
|
|
106
|
+
updateDisplay(this: ToolComponent, ...args: unknown[]) {
|
|
107
|
+
if (!seen.has(this)) { seen.add(this); components.add(new WeakRef(this)); }
|
|
108
|
+
if (!current.enabled || this.expanded) return originals.updateDisplay.apply(this, args);
|
|
109
|
+
const showImages = this.showImages;
|
|
110
|
+
const lastRenderer = this.resultRendererComponent;
|
|
111
|
+
this.showImages = false;
|
|
112
|
+
try {
|
|
113
|
+
return originals.updateDisplay.apply(this, args);
|
|
114
|
+
} finally {
|
|
115
|
+
this.showImages = showImages;
|
|
116
|
+
// Expanded mode must get the original renderer's previous component, not our empty one.
|
|
117
|
+
this.resultRendererComponent = lastRenderer;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
getResultRenderer(this: ToolComponent, ...args: unknown[]) {
|
|
121
|
+
return current.enabled && !this.expanded ? emptyResult : originals.getResultRenderer.apply(this, args);
|
|
122
|
+
},
|
|
123
|
+
getTextOutput(this: ToolComponent, ...args: unknown[]) {
|
|
124
|
+
return current.enabled && !this.expanded ? "" : originals.getTextOutput.apply(this, args);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
Object.assign(prototype, wrappers);
|
|
128
|
+
prototype[HOOK] = state;
|
|
129
|
+
}
|
|
130
|
+
const current = state;
|
|
131
|
+
current.owners += 1;
|
|
132
|
+
let disposed = false;
|
|
133
|
+
return {
|
|
134
|
+
setEnabled(enabled) {
|
|
135
|
+
if (disposed) return;
|
|
136
|
+
current.enabled = enabled;
|
|
137
|
+
current.redraw(enabled);
|
|
138
|
+
},
|
|
139
|
+
dispose() {
|
|
140
|
+
if (disposed) return;
|
|
141
|
+
disposed = true;
|
|
142
|
+
current.owners -= 1;
|
|
143
|
+
if (current.owners > 0) return;
|
|
144
|
+
current.enabled = false;
|
|
145
|
+
current.redraw();
|
|
146
|
+
current.restore();
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { installMinimalOutputHook, loadToolPrototype, type MinimalOutputHook } from "./hook.ts";
|
|
3
|
+
|
|
4
|
+
const ENTRY = "pi-better-harness-tool-output";
|
|
5
|
+
|
|
6
|
+
export default function minimalOutputExtension(pi: ExtensionAPI): void {
|
|
7
|
+
let hook: MinimalOutputHook | undefined;
|
|
8
|
+
let enabled = false;
|
|
9
|
+
|
|
10
|
+
function status(ctx: ExtensionContext): void {
|
|
11
|
+
ctx.ui.setStatus(ENTRY, enabled ? "tools: minimal" : undefined);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function collapseTools(ctx: ExtensionContext): void {
|
|
15
|
+
// setToolsExpanded is a no-op when already false; force a redraw of restored transcript rows.
|
|
16
|
+
if (!ctx.ui.getToolsExpanded()) ctx.ui.setToolsExpanded(true);
|
|
17
|
+
ctx.ui.setToolsExpanded(false);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function ensureHook(ctx: ExtensionContext): Promise<boolean> {
|
|
21
|
+
if (ctx.mode !== "tui") {
|
|
22
|
+
ctx.ui.notify("Minimal tool output is available only in Pi's interactive TUI.", "warning");
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (hook) return true;
|
|
26
|
+
try {
|
|
27
|
+
hook = installMinimalOutputHook(await loadToolPrototype());
|
|
28
|
+
return true;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
ctx.ui.notify(`Minimal tool output is unavailable: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
36
|
+
if (ctx.mode !== "tui") return;
|
|
37
|
+
enabled = false;
|
|
38
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
39
|
+
if (entry.type === "custom" && entry.customType === ENTRY) {
|
|
40
|
+
const data = entry.data as { enabled?: unknown } | undefined;
|
|
41
|
+
if (typeof data?.enabled === "boolean") enabled = data.enabled;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Observe transcript components from startup, so the toggle also redraws existing rows.
|
|
45
|
+
if (await ensureHook(ctx)) {
|
|
46
|
+
if (enabled) collapseTools(ctx);
|
|
47
|
+
hook!.setEnabled(enabled);
|
|
48
|
+
} else enabled = false;
|
|
49
|
+
status(ctx);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
pi.on("session_shutdown", () => {
|
|
53
|
+
hook?.dispose();
|
|
54
|
+
hook = undefined;
|
|
55
|
+
enabled = false;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
pi.registerCommand("tool-output", {
|
|
59
|
+
description: "Tool output: minimal | normal (no argument toggles). Display only; agent results are unchanged.",
|
|
60
|
+
getArgumentCompletions: (argumentPrefix) => {
|
|
61
|
+
const prefix = argumentPrefix.trimStart().toLowerCase();
|
|
62
|
+
const matches = [
|
|
63
|
+
{ value: "minimal", label: "minimal", description: "Hide collapsed tool result bodies" },
|
|
64
|
+
{ value: "normal", label: "normal", description: "Restore ordinary tool output" },
|
|
65
|
+
].filter((option) => option.value.startsWith(prefix));
|
|
66
|
+
return matches.length > 0 ? matches : null;
|
|
67
|
+
},
|
|
68
|
+
handler: async (args, ctx) => {
|
|
69
|
+
const mode = args.trim().toLowerCase();
|
|
70
|
+
if (mode && mode !== "minimal" && mode !== "normal") {
|
|
71
|
+
ctx.ui.notify("Usage: /tool-output [minimal|normal]", "warning");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!await ensureHook(ctx)) return;
|
|
75
|
+
enabled = mode ? mode === "minimal" : !enabled;
|
|
76
|
+
if (enabled) collapseTools(ctx);
|
|
77
|
+
hook!.setEnabled(enabled);
|
|
78
|
+
pi.appendEntry(ENTRY, { version: 1, enabled });
|
|
79
|
+
status(ctx);
|
|
80
|
+
ctx.ui.notify(enabled
|
|
81
|
+
? "Minimal tool output on. Click a tool in fullscreen mode or use Ctrl+O to reveal results."
|
|
82
|
+
: "Normal tool output restored.", "info");
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -28,11 +28,10 @@ Network access - On
|
|
|
28
28
|
Guarded (follows the file rules)
|
|
29
29
|
[x] apply_patch harness adapter
|
|
30
30
|
Trusted (runs outside the file rules)
|
|
31
|
-
[x]
|
|
32
|
-
[
|
|
33
|
-
[ ] <other installed tool> <its package>
|
|
31
|
+
> [x] @juicesharp/rpiv-web-tools 2/2
|
|
32
|
+
> [ ] <other installed package> 0/1
|
|
34
33
|
|
|
35
|
-
↑↓ Select row ←→ Select column Space Change
|
|
34
|
+
↑↓ Select row ←→ Select column / expand / collapse Enter Fold Space Change
|
|
36
35
|
Save as defaults
|
|
37
36
|
```
|
|
38
37
|
|
|
@@ -44,10 +43,22 @@ Save as defaults
|
|
|
44
43
|
full before any file changes.
|
|
45
44
|
- **Trusted** tools are the other tools your Pi has loaded, listed with their
|
|
46
45
|
package. A ticked one is loaded into the subagent and runs in its Pi process,
|
|
47
|
-
**outside the file rules**.
|
|
46
|
+
**outside the file rules**. One Space toggles it immediately. It is admitted
|
|
48
47
|
only from the package you ticked. Known network tool names are refused while
|
|
49
48
|
Network access is Off: `web_fetch`, `web_search`, `firecrawl_scrape`, `firecrawl_extract`, `mcp`, `mcpScript`, `remote_bash`, and any `mcp__*` name. That is a name list, not a network sandbox.
|
|
50
49
|
|
|
50
|
+
Trusted tools use two levels: a package/provider group, then its individual tools.
|
|
51
|
+
Non-MCP tools are grouped by exact owning package; `mcp__<provider>__...` tools
|
|
52
|
+
are grouped by provider and owning package together. Groups start collapsed.
|
|
53
|
+
`>` means collapsed and `v` means expanded. Their checkbox shows `[x]` when
|
|
54
|
+
all children are selected, `[-]` when some are selected, and `[ ]` when none
|
|
55
|
+
are; the count is selected/total. Right expands a group. Left collapses a group,
|
|
56
|
+
or moves from a child to its parent and collapses it. Enter folds the selected
|
|
57
|
+
group. Space on a group selects all children when none or only some are selected,
|
|
58
|
+
or deselects them all when all are selected. Every toggle applies on one Space;
|
|
59
|
+
saving looser defaults still needs a second Enter. Only individual tool
|
|
60
|
+
entries are saved, never a package/provider wildcard or the open/closed state.
|
|
61
|
+
|
|
51
62
|
Defaults: `apply_patch`, `web_fetch`, and `web_search` on; everything else off.
|
|
52
63
|
See [ADR 0009](../../docs/adr/0009-guarded-and-trusted-subagent-tools.md).
|
|
53
64
|
|
|
@@ -109,9 +120,11 @@ repository's `.git/hooks` or `.git/config` (`core.hooksPath`, `core.fsmonitor`,
|
|
|
109
120
|
`~/.gitconfig`, `~/.config/git`, and `~/.git-templates` themselves stay protected. Add your own paths to the deny list with `/sandbox deny add <path>`. Under
|
|
110
121
|
Write, those paths are also protected from removal and renaming.
|
|
111
122
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
second Enter
|
|
123
|
+
Space applies every change immediately, including higher permission levels,
|
|
124
|
+
capabilities switched on, and a sandbox switched off. Saving looser defaults
|
|
125
|
+
still needs a second Enter, with a prompt listing what would loosen. The focused
|
|
126
|
+
row has a full-width background and bold text; the selected Main/Subagents cell
|
|
127
|
+
also uses inverse styling. No model
|
|
115
128
|
tool can change these settings. Other rows toggle Off/On. Detail cells under an
|
|
116
129
|
Off sandbox display a dimmed `-` and cannot be changed; their values return when
|
|
117
130
|
the sandbox is enabled again.
|
|
@@ -2,7 +2,7 @@ import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { Key, matchesKey, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
import { isNetworkTool, packageLabel, type DiscoveredTool } from "./shared-task-tools.ts";
|
|
5
|
-
import { defaultSandboxPermissions,
|
|
5
|
+
import { defaultSandboxPermissions, type CredentialAccess, type FileAccess, type SandboxPermissionProfile as PermissionProfile, type SandboxPermissionSettings as PermissionSettings } from "./permissions.ts";
|
|
6
6
|
export type { PermissionProfile, PermissionSettings };
|
|
7
7
|
|
|
8
8
|
export interface PermissionPageHandlers {
|
|
@@ -15,9 +15,12 @@ export interface PermissionPageHandlers {
|
|
|
15
15
|
discoverTools?(): readonly Pick<DiscoveredTool, "name" | "package">[];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
type TrustedItem = { kind: "trusted"; name: string; package: string; loaded: boolean; group: string };
|
|
19
|
+
type ToolGroup = { kind: "group"; id: string; label: string; tools: TrustedItem[] };
|
|
18
20
|
type ToolItem =
|
|
19
21
|
| { kind: "guarded"; name: "apply_patch" }
|
|
20
|
-
|
|
|
22
|
+
| TrustedItem
|
|
23
|
+
| ToolGroup;
|
|
21
24
|
|
|
22
25
|
const GUARDED_HINT = "apply_patch goes through the guarded file operations: Project files and Outside project apply to it.";
|
|
23
26
|
const TRUSTED_HINT = "Trusted tools run in the subagent's Pi process, outside the file rules. Tick only tools you trust.";
|
|
@@ -66,7 +69,7 @@ function cell(text: string, width: number): string {
|
|
|
66
69
|
return cut + " ".repeat(Math.max(0, width - visibleWidth(cut)));
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
/**
|
|
72
|
+
/** Keyboard-driven profiles and a foldable package/provider tool tree. */
|
|
70
73
|
export function createPermissionsPage(
|
|
71
74
|
theme: Theme,
|
|
72
75
|
handlers: PermissionPageHandlers,
|
|
@@ -91,24 +94,41 @@ export function createPermissionsPage(
|
|
|
91
94
|
}
|
|
92
95
|
const isTicked = (item: { name: string; package: string }) =>
|
|
93
96
|
!!settings?.subagentTools.trusted.some((tool) => tool.name === item.name && tool.package === item.package);
|
|
94
|
-
|
|
95
|
-
function
|
|
96
|
-
const trusted = new Map<string,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
97
|
+
const expanded = new Set<string>();
|
|
98
|
+
function toolGroups(): ToolGroup[] {
|
|
99
|
+
const trusted = new Map<string, TrustedItem>();
|
|
100
|
+
const add = (tool: { name: string; package: string }, loaded: boolean) => {
|
|
101
|
+
const provider = /^mcp__(.+?)__/.exec(tool.name)?.[1];
|
|
102
|
+
const group = JSON.stringify([tool.package, provider ?? ""]);
|
|
103
|
+
const key = JSON.stringify([tool.name, tool.package]);
|
|
104
|
+
if (!trusted.has(key)) trusted.set(key, { ...tool, kind: "trusted", loaded, group });
|
|
105
|
+
};
|
|
106
|
+
for (const tool of discovered) add(tool, true);
|
|
107
|
+
for (const tool of settings?.subagentTools.trusted ?? []) add(tool, false);
|
|
108
|
+
const groups = new Map<string, ToolGroup>();
|
|
109
|
+
for (const tool of trusted.values()) {
|
|
110
|
+
if (!groups.has(tool.group)) {
|
|
111
|
+
const provider = JSON.parse(tool.group)[1] as string;
|
|
112
|
+
groups.set(tool.group, { kind: "group", id: tool.group,
|
|
113
|
+
label: provider ? `${provider} (${packageLabel(tool.package)})` : packageLabel(tool.package), tools: [] });
|
|
114
|
+
}
|
|
115
|
+
groups.get(tool.group)!.tools.push(tool);
|
|
101
116
|
}
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
for (const group of groups.values()) group.tools.sort((a, b) => a.name.localeCompare(b.name));
|
|
118
|
+
return [...groups.values()].sort((a, b) => a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
|
|
119
|
+
}
|
|
120
|
+
function toolItems(): ToolItem[] {
|
|
121
|
+
return [{ kind: "guarded", name: "apply_patch" }, ...toolGroups().flatMap((group): ToolItem[] =>
|
|
122
|
+
[group, ...(expanded.has(group.id) ? group.tools : [])])];
|
|
104
123
|
}
|
|
124
|
+
const itemKey = (item: ToolItem) => item.kind === "group" ? item.id
|
|
125
|
+
: item.kind === "guarded" ? "apply_patch" : JSON.stringify([item.name, item.package]);
|
|
105
126
|
let items = toolItems();
|
|
106
127
|
const saveRow = () => rows.length + items.length;
|
|
107
128
|
let row = 0;
|
|
108
129
|
let column = 0;
|
|
109
130
|
let busy = false;
|
|
110
131
|
let pendingConfirmation: string | undefined;
|
|
111
|
-
let pendingChange: string | undefined;
|
|
112
132
|
|
|
113
133
|
function report(error: unknown): void {
|
|
114
134
|
message = errorText(error);
|
|
@@ -122,7 +142,14 @@ export function createPermissionsPage(
|
|
|
122
142
|
if (row >= rows.length) {
|
|
123
143
|
const item = items[row - rows.length]!;
|
|
124
144
|
if (item.kind === "guarded") next.subagentTools.applyPatch = !next.subagentTools.applyPatch;
|
|
125
|
-
else if (
|
|
145
|
+
else if (item.kind === "group") {
|
|
146
|
+
const all = item.tools.every(isTicked);
|
|
147
|
+
for (const tool of item.tools) {
|
|
148
|
+
if (all) next.subagentTools.trusted = next.subagentTools.trusted.filter((entry) =>
|
|
149
|
+
!(entry.name === tool.name && entry.package === tool.package));
|
|
150
|
+
else if (!isTicked(tool)) next.subagentTools.trusted.push({ name: tool.name, package: tool.package });
|
|
151
|
+
}
|
|
152
|
+
} else if (isTicked(item)) {
|
|
126
153
|
next.subagentTools.trusted = next.subagentTools.trusted.filter((tool) => !(tool.name === item.name && tool.package === item.package));
|
|
127
154
|
} else next.subagentTools.trusted.push({ name: item.name, package: item.package });
|
|
128
155
|
return apply(next);
|
|
@@ -144,22 +171,18 @@ export function createPermissionsPage(
|
|
|
144
171
|
|
|
145
172
|
async function apply(next: PermissionSettings): Promise<void> {
|
|
146
173
|
if (!settings) return;
|
|
147
|
-
// A looser value is applied only on a second Space, like a looser Save.
|
|
148
|
-
const loosened = describeLoosening(settings, next);
|
|
149
|
-
const nextKey = JSON.stringify(next);
|
|
150
|
-
if (loosened.length && pendingChange !== nextKey) {
|
|
151
|
-
pendingChange = nextKey;
|
|
152
|
-
message = `Looser (${loosened.join("; ")}). Press Space again to apply.`;
|
|
153
|
-
isError = false;
|
|
154
|
-
requestRender();
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
pendingChange = undefined;
|
|
158
174
|
busy = true;
|
|
159
175
|
try {
|
|
160
176
|
await handlers.change(snapshot(next));
|
|
177
|
+
const selected = row >= rows.length && row < saveRow() ? items[row - rows.length] : undefined;
|
|
178
|
+
const wasSave = row === saveRow();
|
|
179
|
+
const successors = selected ? items.slice(row - rows.length + 1).map(itemKey) : [];
|
|
161
180
|
settings = next;
|
|
162
181
|
items = toolItems();
|
|
182
|
+
const index = selected ? items.findIndex((item) => itemKey(item) === itemKey(selected)) : -1;
|
|
183
|
+
const successor = successors.map((key) => items.findIndex((item) => itemKey(item) === key)).find((at) => at >= 0);
|
|
184
|
+
row = wasSave ? saveRow() : index >= 0 ? rows.length + index
|
|
185
|
+
: successor !== undefined ? rows.length + successor : Math.min(row, saveRow());
|
|
163
186
|
message = "";
|
|
164
187
|
isError = false;
|
|
165
188
|
requestRender();
|
|
@@ -201,11 +224,21 @@ export function createPermissionsPage(
|
|
|
201
224
|
}
|
|
202
225
|
}
|
|
203
226
|
|
|
227
|
+
function fold(open?: boolean): void {
|
|
228
|
+
const item = row >= rows.length && row < saveRow() ? items[row - rows.length] : undefined;
|
|
229
|
+
const id = item?.kind === "group" ? item.id : item?.kind === "trusted" && open === false ? item.group : undefined;
|
|
230
|
+
if (!id) return;
|
|
231
|
+
if (open ?? !expanded.has(id)) expanded.add(id);
|
|
232
|
+
else expanded.delete(id);
|
|
233
|
+
items = toolItems();
|
|
234
|
+
row = rows.length + items.findIndex((entry) => entry.kind === "group" && entry.id === id);
|
|
235
|
+
requestRender();
|
|
236
|
+
}
|
|
237
|
+
|
|
204
238
|
return {
|
|
205
239
|
invalidate() {},
|
|
206
240
|
handleInput(data: string) {
|
|
207
241
|
if (!matchesKey(data, Key.enter)) pendingConfirmation = undefined;
|
|
208
|
-
if (!matchesKey(data, Key.space)) pendingChange = undefined;
|
|
209
242
|
if (matchesKey(data, Key.escape)) {
|
|
210
243
|
close();
|
|
211
244
|
} else if (matchesKey(data, Key.up)) {
|
|
@@ -215,15 +248,19 @@ export function createPermissionsPage(
|
|
|
215
248
|
row = Math.min(saveRow(), row + 1);
|
|
216
249
|
requestRender();
|
|
217
250
|
} else if (matchesKey(data, Key.left)) {
|
|
218
|
-
|
|
251
|
+
if (row >= rows.length && row < saveRow()) fold(false);
|
|
252
|
+
else column = 0;
|
|
219
253
|
requestRender();
|
|
220
254
|
} else if (matchesKey(data, Key.right)) {
|
|
221
|
-
|
|
255
|
+
if (row >= rows.length && row < saveRow()) fold(true);
|
|
256
|
+
else column = 1;
|
|
222
257
|
requestRender();
|
|
223
258
|
} else if (matchesKey(data, Key.space)) {
|
|
224
259
|
void change();
|
|
225
260
|
} else if (matchesKey(data, Key.enter) && row === saveRow()) {
|
|
226
261
|
void save();
|
|
262
|
+
} else if (matchesKey(data, Key.enter)) {
|
|
263
|
+
fold();
|
|
227
264
|
}
|
|
228
265
|
},
|
|
229
266
|
render(width: number): string[] {
|
|
@@ -235,14 +272,16 @@ export function createPermissionsPage(
|
|
|
235
272
|
const available = Math.max(0, w - prefix - labelWidth - gap);
|
|
236
273
|
const mainWidth = Math.ceil(available / 2);
|
|
237
274
|
const subWidth = available - mainWidth;
|
|
275
|
+
const highlight = (text: string, selected: boolean): string => selected
|
|
276
|
+
? theme.bg("selectedBg", theme.bold(cell(text, w))) : text;
|
|
238
277
|
const line = (label: string, main: string, sub: string, selected: boolean, dimMain = false, dimSub = false) => {
|
|
239
278
|
const marker = prefix ? (selected ? "> " : " ") : "";
|
|
240
279
|
const labelPart = cell(label, labelWidth);
|
|
241
|
-
const mainPart = cell(main, mainWidth);
|
|
242
|
-
const subPart = cell(sub, subWidth);
|
|
243
|
-
return theme.fg(selected ? "accent" : "text", marker + labelPart) + " ".repeat(gap) +
|
|
244
|
-
|
|
245
|
-
|
|
280
|
+
const mainPart = theme.fg(dimMain ? "dim" : "text", cell(main, mainWidth));
|
|
281
|
+
const subPart = theme.fg(dimSub ? "dim" : "text", cell(sub, subWidth));
|
|
282
|
+
return highlight(theme.fg(selected ? "accent" : "text", marker + labelPart) + " ".repeat(gap) +
|
|
283
|
+
(selected && column === 0 ? theme.inverse(mainPart) : mainPart) +
|
|
284
|
+
(selected && column === 1 ? theme.inverse(subPart) : subPart), selected);
|
|
246
285
|
};
|
|
247
286
|
const output = [line("Sandbox permissions", "Main", "Subagents", false), ""];
|
|
248
287
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -261,16 +300,25 @@ export function createPermissionsPage(
|
|
|
261
300
|
}
|
|
262
301
|
// Subagents · Tools: which extension tools a confined subagent may use.
|
|
263
302
|
output.push("", theme.fg("text", truncateToWidth(`${prefix ? " " : ""}Subagents · Tools`, w, "")));
|
|
264
|
-
const
|
|
303
|
+
const toolNames = items.flatMap((item) => item.kind === "group" ? [] : [visibleWidth(item.name)]);
|
|
304
|
+
const nameWidth = Math.min(24, Math.max(...toolNames), Math.max(0, w - prefix - 10));
|
|
265
305
|
items.forEach((item, index) => {
|
|
266
306
|
if (index === 0) output.push(theme.fg("dim", truncateToWidth(`${prefix ? " " : ""} Guarded (follows the file rules)`, w, "")));
|
|
267
307
|
if (index === 1) output.push(theme.fg("dim", truncateToWidth(`${prefix ? " " : ""} Trusted (runs outside the file rules)`, w, "")));
|
|
268
308
|
const selected = row === rows.length + index;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
309
|
+
let text: string;
|
|
310
|
+
if (item.kind === "group") {
|
|
311
|
+
const count = item.tools.filter(isTicked).length;
|
|
312
|
+
const tick = count === item.tools.length ? "x" : count ? "-" : " ";
|
|
313
|
+
text = ` ${expanded.has(item.id) ? "v" : ">"} [${tick}] ${item.label} ${count}/${item.tools.length}`;
|
|
314
|
+
} else {
|
|
315
|
+
const ticked = item.kind === "guarded" ? !!settings?.subagentTools.applyPatch : isTicked(item);
|
|
316
|
+
const detail = item.kind === "guarded" ? "harness adapter" : [
|
|
317
|
+
...(isNetworkTool(item.name) ? ["needs Network On"] : []), ...(item.loaded ? [] : ["not loaded"])].join(" · ");
|
|
318
|
+
text = `${item.kind === "guarded" ? " " : " "}[${ticked ? "x" : " "}] ${cell(item.name, nameWidth)} ${detail}`;
|
|
319
|
+
}
|
|
320
|
+
output.push(highlight(theme.fg(selected ? "accent" : settings ? "text" : "dim",
|
|
321
|
+
truncateToWidth(`${prefix ? (selected ? "> " : " ") : ""}${text}`, w, "")), selected));
|
|
274
322
|
});
|
|
275
323
|
if (items.length === 1) {
|
|
276
324
|
output.push(theme.fg("dim", truncateToWidth(`${prefix ? " " : ""} Trusted (runs outside the file rules)`, w, "")),
|
|
@@ -282,13 +330,18 @@ export function createPermissionsPage(
|
|
|
282
330
|
const contextual = tool ? (tool.kind === "guarded" ? GUARDED_HINT : TRUSTED_HINT)
|
|
283
331
|
: selected && settings ? cellHint(selected, settings[columns[column]!]) : undefined;
|
|
284
332
|
for (const hint of [
|
|
285
|
-
"↑↓ Select
|
|
333
|
+
"↑↓ Select · ←→ Column/fold · Space Toggle tool/group · Enter Fold/Save · Esc Back",
|
|
286
334
|
"Changes apply to new launches. Background tasks follow their launcher.",
|
|
287
335
|
"Stored credentials: known files only; excludes OS vaults and environment tokens.",
|
|
288
336
|
"Trusted tools run outside the file rules.",
|
|
289
337
|
...(contextual ? [contextual] : []),
|
|
290
338
|
]) output.push(theme.fg("dim", truncateToWidth(hint, w, "")));
|
|
291
|
-
if (message)
|
|
339
|
+
if (message) {
|
|
340
|
+
const clean = message.replace(/[\r\n]+/g, " ");
|
|
341
|
+
const confirmation = /^(.*) (Press Enter again to save\.)$/.exec(clean);
|
|
342
|
+
const messages = confirmation && visibleWidth(clean) > w ? [confirmation[1]!, confirmation[2]!] : [clean];
|
|
343
|
+
for (const text of messages) output.push(theme.fg(isError ? "error" : "muted", truncateToWidth(text, w, "")));
|
|
344
|
+
}
|
|
292
345
|
return output;
|
|
293
346
|
},
|
|
294
347
|
};
|
|
@@ -44,14 +44,14 @@ nontrivial role-owned task, while the foreground coordinates, integrates, and
|
|
|
44
44
|
verifies. See [usage notes](docs/usage.md#delegation-mode).
|
|
45
45
|
|
|
46
46
|
`agents_catalog` shows each role's and named agent's default model and effort,
|
|
47
|
-
such as `role developer "Developer" … default openai/gpt-6-sol@high`. Roles are
|
|
47
|
+
such as `role developer "Developer" … default openai/gpt-6.1-sol@high`. Roles are
|
|
48
48
|
listed and passed by short name (`role: "developer"`); the stored id
|
|
49
49
|
`role.developer` also works, and named agents keep their full id
|
|
50
50
|
(`agent: "agent.payments"`). To launch on that default, omit
|
|
51
51
|
`model` and `thinking` on a role or agent spawn; name one only for a stated
|
|
52
52
|
reason. When a launch's model or effort differs from the default, its launch
|
|
53
53
|
line says so, for example
|
|
54
|
-
`model openai/gpt-6-astra@high (role developer default openai/gpt-6-sol@high)`.
|
|
54
|
+
`model openai/gpt-6-astra@high (role developer default openai/gpt-6.1-sol@high)`.
|
|
55
55
|
|
|
56
56
|
## When To Use
|
|
57
57
|
|
|
@@ -256,9 +256,9 @@ export async function clarifyCatalogRequest(
|
|
|
256
256
|
* Launch-line note for a catalog run whose effective model or effort differs
|
|
257
257
|
* from the role or agent default. The wording names the cause, so a fallback
|
|
258
258
|
* or a capped effort is not mistaken for a caller override:
|
|
259
|
-
* - override: `model openai/gpt-6-astra@high (role developer default openai/gpt-6-sol@high)`
|
|
260
|
-
* - fallback: `model xai/grok-4.7@high (role developer default openai/gpt-6-sol@high unavailable; foreground fallback)`
|
|
261
|
-
* - capped effort: `model openai/gpt-6-sol@medium (role developer default openai/gpt-6-sol@high; effort capped at medium by the model)`
|
|
259
|
+
* - override: `model openai/gpt-6-astra@high (role developer default openai/gpt-6.1-sol@high)`
|
|
260
|
+
* - fallback: `model xai/grok-4.7@high (role developer default openai/gpt-6.1-sol@high unavailable; foreground fallback)`
|
|
261
|
+
* - capped effort: `model openai/gpt-6.1-sol@medium (role developer default openai/gpt-6.1-sol@high; effort capped at medium by the model)`
|
|
262
262
|
* Undefined for a non-catalog run, a definition with no default, or a launch
|
|
263
263
|
* that matches the default. Only the fields the definition sets are compared.
|
|
264
264
|
*/
|
|
@@ -72,10 +72,10 @@ export const PARSER_LIMITS = {
|
|
|
72
72
|
* runtime already uses. Tiers are catalog policy, not benchmark rankings.
|
|
73
73
|
*/
|
|
74
74
|
export const APPROVED_ROLE_DEFAULTS = [
|
|
75
|
-
{ id: "role.researcher", name: "Researcher", model: "openai/gpt-6-sol", effort: "medium", tier: "balanced" },
|
|
75
|
+
{ id: "role.researcher", name: "Researcher", model: "openai/gpt-6.1-sol", effort: "medium", tier: "balanced" },
|
|
76
76
|
{ id: "role.explorer", name: "Explorer", model: "openai/gpt-6-luna", effort: "medium", tier: "efficient" },
|
|
77
|
-
{ id: "role.product-manager", name: "Product Manager", model: "openai/gpt-6-sol", effort: "medium", tier: "balanced" },
|
|
78
|
-
{ id: "role.developer", name: "Developer", model: "openai/gpt-6-sol", effort: "high", tier: "balanced" },
|
|
77
|
+
{ id: "role.product-manager", name: "Product Manager", model: "openai/gpt-6.1-sol", effort: "medium", tier: "balanced" },
|
|
78
|
+
{ id: "role.developer", name: "Developer", model: "openai/gpt-6.1-sol", effort: "high", tier: "balanced" },
|
|
79
79
|
{ id: "role.reviewer", name: "Reviewer", model: "openai/gpt-6-astra", effort: "medium", tier: "frontier" },
|
|
80
80
|
{ id: "role.architect", name: "Architect", model: "openai/gpt-6-astra", effort: "high", tier: "frontier" },
|
|
81
81
|
] as const;
|
|
@@ -52,7 +52,7 @@ Codex applies the agent file's model and `model_reasoning_effort` ahead of the c
|
|
|
52
52
|
|
|
53
53
|
`agents_catalog` accepts `list` and `inspect` only. It uses the same inspection view as `/agents show`. It has no write path. Launch remains `subagent_spawn` or the batch tool, with at most one `agent` or `role` selector. Those selectors are lifecycle's wiring, not this tool.
|
|
54
54
|
|
|
55
|
-
A list line names a role by its short name (`role developer "Developer" …`), the form the `role` field takes; the inspect header, `identity.id`, and the structured `id` keep the stored id `role.developer`. Named agents show their full `agent.<slug>` id in both. Each list line and the inspect header end with the definition's default model and effort when one is set, such as `default openai/gpt-6-sol@high`: a role's own default, or a named agent's override or inherited role default. The structured view carries the same value as `defaults: { model, effort, label }`, or `null` when neither is set. A spawn that omits `model` and `thinking` uses it. When a catalog launch's model or effort differs from it, the launch line (and each batch job line) adds a note such as `model openai/gpt-6-astra@high (role developer default openai/gpt-6-sol@high)`; a named agent's note reads `agent default …`. Only the fields the definition sets are compared. The note names the cause: a fallback from an unavailable default reads `(role developer default openai/gpt-6-sol@high unavailable; foreground fallback)` (or `same-tier` / `configured-default`), and an effort the model cannot run adds `effort capped at <level> by the model`.
|
|
55
|
+
A list line names a role by its short name (`role developer "Developer" …`), the form the `role` field takes; the inspect header, `identity.id`, and the structured `id` keep the stored id `role.developer`. Named agents show their full `agent.<slug>` id in both. Each list line and the inspect header end with the definition's default model and effort when one is set, such as `default openai/gpt-6.1-sol@high`: a role's own default, or a named agent's override or inherited role default. The structured view carries the same value as `defaults: { model, effort, label }`, or `null` when neither is set. A spawn that omits `model` and `thinking` uses it. When a catalog launch's model or effort differs from it, the launch line (and each batch job line) adds a note such as `model openai/gpt-6-astra@high (role developer default openai/gpt-6.1-sol@high)`; a named agent's note reads `agent default …`. Only the fields the definition sets are compared. The note names the cause: a fallback from an unavailable default reads `(role developer default openai/gpt-6.1-sol@high unavailable; foreground fallback)` (or `same-tier` / `configured-default`), and an effort the model cannot run adds `effort capped at <level> by the model`.
|
|
56
56
|
|
|
57
57
|
## Lifecycle attachment
|
|
58
58
|
|
|
@@ -16,7 +16,7 @@ id: role.developer
|
|
|
16
16
|
name: Developer
|
|
17
17
|
description: Implements a requested change in the existing system.
|
|
18
18
|
defaults:
|
|
19
|
-
model: openai/gpt-6-sol
|
|
19
|
+
model: openai/gpt-6.1-sol
|
|
20
20
|
effort: high
|
|
21
21
|
tier: balanced
|
|
22
22
|
---
|
|
@@ -100,10 +100,10 @@ Model availability, same-tier fallback, and nearest-effort adjustment are not de
|
|
|
100
100
|
|
|
101
101
|
| Role | Stored id | Name | Model | Effort | Tier |
|
|
102
102
|
| --- | --- | --- | --- | --- | --- |
|
|
103
|
-
| `researcher` | `role.researcher` | Researcher | `openai/gpt-6-sol` | medium | balanced |
|
|
103
|
+
| `researcher` | `role.researcher` | Researcher | `openai/gpt-6.1-sol` | medium | balanced |
|
|
104
104
|
| `explorer` | `role.explorer` | Explorer | `openai/gpt-6-luna` | medium | efficient |
|
|
105
|
-
| `product-manager` | `role.product-manager` | Product Manager | `openai/gpt-6-sol` | medium | balanced |
|
|
106
|
-
| `developer` | `role.developer` | Developer | `openai/gpt-6-sol` | high | balanced |
|
|
105
|
+
| `product-manager` | `role.product-manager` | Product Manager | `openai/gpt-6.1-sol` | medium | balanced |
|
|
106
|
+
| `developer` | `role.developer` | Developer | `openai/gpt-6.1-sol` | high | balanced |
|
|
107
107
|
| `reviewer` | `role.reviewer` | Reviewer | `openai/gpt-6-astra` | medium | frontier |
|
|
108
108
|
| `architect` | `role.architect` | Architect | `openai/gpt-6-astra` | high | frontier |
|
|
109
109
|
|
|
@@ -10,7 +10,7 @@ Normative rules are R3, R4, R8, and the resolver half of R10. Prose parsing and
|
|
|
10
10
|
- It does not grant tools, sandbox modes, extensions, or presets. Inspection copies the catalog `capabilities` object (`grantedByCatalog: false`).
|
|
11
11
|
- It does not call a provider. Availability is one `getAvailable()` snapshot from the foreground registry. `find()` only explains that a known model is outside that set.
|
|
12
12
|
- It does not retry, and it does not substitute after a child has started. `automaticRetry` and `substitutionAfterStart` are false. `startupFailure.action` is `surface-error-no-retry-no-substitution`. A later provider initialization failure is a lifecycle error.
|
|
13
|
-
- It does not invent tier candidates. Built-in tiers name the approved models and have empty candidate lists. `openai/gpt-6-sol`, `openai/gpt-6-luna`, and `openai/gpt-6-astra` are not substitutes.
|
|
13
|
+
- It does not invent tier candidates. Built-in tiers name the approved models and have empty candidate lists. `openai/gpt-6.1-sol`, `openai/gpt-6-luna`, and `openai/gpt-6-astra` are not substitutes.
|
|
14
14
|
|
|
15
15
|
## Precedence
|
|
16
16
|
|
|
@@ -5,7 +5,7 @@ id: role.product-manager
|
|
|
5
5
|
name: Product Manager
|
|
6
6
|
description: Owns user-visible requirements and acceptance criteria; excludes technical design and implementation.
|
|
7
7
|
defaults:
|
|
8
|
-
model: openai/gpt-6-sol
|
|
8
|
+
model: openai/gpt-6.1-sol
|
|
9
9
|
effort: medium
|
|
10
10
|
tier: balanced
|
|
11
11
|
---
|
|
@@ -49,13 +49,13 @@ export interface NormalizedTierPolicy {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Approved model membership only. `openai/gpt-6-sol`, `openai/gpt-6-luna`, and
|
|
52
|
+
* Approved model membership only. `openai/gpt-6.1-sol`, `openai/gpt-6-luna`, and
|
|
53
53
|
* `openai/gpt-6-astra` are not fallbacks for each other.
|
|
54
54
|
*/
|
|
55
55
|
export const DEFAULT_TIER_POLICY = {
|
|
56
56
|
tiers: {
|
|
57
57
|
efficient: { members: ["openai/gpt-6-luna"], candidates: [] },
|
|
58
|
-
balanced: { members: ["openai/gpt-6-sol"], candidates: [] },
|
|
58
|
+
balanced: { members: ["openai/gpt-6.1-sol"], candidates: [] },
|
|
59
59
|
frontier: { members: ["openai/gpt-6-astra"], candidates: [] },
|
|
60
60
|
},
|
|
61
61
|
} as const satisfies TierPolicy;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"extensions/background-tasks/index.ts",
|
|
24
24
|
"extensions/ssh/index.ts",
|
|
25
25
|
"extensions/goal/index.ts",
|
|
26
|
-
"extensions/plan/index.ts"
|
|
26
|
+
"extensions/plan/index.ts",
|
|
27
|
+
"extensions/minimal-output/index.ts"
|
|
27
28
|
],
|
|
28
29
|
"image": "https://raw.githubusercontent.com/1aboveio/pi-better-harness/main/docs/images/package-gallery/pi-better-harness.png"
|
|
29
30
|
},
|
|
@@ -47,13 +48,14 @@
|
|
|
47
48
|
],
|
|
48
49
|
"scripts": {
|
|
49
50
|
"prepack": "node ../../scripts/stage-harness-dependencies.mjs",
|
|
51
|
+
"typecheck": "tsc --noEmit --allowImportingTsExtensions --module NodeNext --moduleResolution NodeNext --target ES2022 --strict --skipLibCheck --types node extensions/minimal-output/index.ts extensions/minimal-output/hook.ts",
|
|
50
52
|
"test": "node --test test/*.test.mjs"
|
|
51
53
|
},
|
|
52
54
|
"dependencies": {
|
|
53
55
|
"pi-better-background-tasks": "0.6.2",
|
|
54
56
|
"pi-better-goal": "0.5.0",
|
|
55
57
|
"pi-better-plan": "0.5.1",
|
|
56
|
-
"pi-better-sandbox": "0.
|
|
58
|
+
"pi-better-sandbox": "0.8.0",
|
|
57
59
|
"pi-better-ssh": "0.1.1",
|
|
58
60
|
"pi-better-subagents": "0.9.3",
|
|
59
61
|
"smol-toml": "1.9.0",
|