pi-better-subagents 0.9.3 → 0.11.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 +7 -3
- package/agent-commands.ts +1 -1
- package/batch.mjs +58 -0
- package/catalog-runtime.ts +3 -3
- package/catalog-schema.ts +3 -3
- package/config.ts +29 -4
- package/docs/agent-catalog-operations.md +1 -1
- package/docs/agent-catalog.md +4 -4
- package/docs/agent-model-resolution.md +1 -1
- package/docs/usage.md +705 -0
- package/health-surface.mjs +1 -0
- package/index.ts +125 -31
- package/navigator.mjs +3 -15
- package/package.json +2 -1
- package/roles/role.developer.md +1 -1
- package/roles/role.product-manager.md +1 -1
- package/roles/role.researcher.md +1 -1
- package/shared-navigator.ts +22 -17
- package/tier-policy.ts +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ Use `pi-better-subagents` when you want Pi to launch independent agent work with
|
|
|
12
12
|
|
|
13
13
|
## Core Features
|
|
14
14
|
- Non-blocking subagent launches, with an optional role and named-agent catalog (`docs/agent-catalog.md`, `docs/agent-catalog-lifecycle.md`).
|
|
15
|
+
- Typed foreground `subagent_spawn_batch` launch receipts for native Pi codemode (Pi 0.99.1+), preserving direct tool calls, per-run callbacks, and batch capacity/catalog guarantees. See [codemode batch launches](docs/usage.md#codemode-batch-launches).
|
|
15
16
|
- Default OS write sandboxing on macOS and Linux.
|
|
16
17
|
- Explicit tool allowlists for child sessions.
|
|
17
18
|
- Durable logs, result retrieval, and [failure observations](docs/failure-observations.md) independent of lifecycle status. A live child handles its own tool errors; the parent is woken only for actionable incidents, and children can classify handled failures with `failure_disposition`.
|
|
@@ -37,21 +38,24 @@ Linux confinement requires a usable `bubblewrap` backend and Pi SDK 0.82.1 or ne
|
|
|
37
38
|
`config.json` sets `delegationMode` to `manual`, `adaptive` (default), or
|
|
38
39
|
`coordinator`. `/subagents` displays the active mode, and
|
|
39
40
|
`/subagents mode manual|adaptive|coordinator` changes it for the current
|
|
40
|
-
session without changing config.
|
|
41
|
+
session without changing config. `/subagents save` writes the current mode as
|
|
42
|
+
the next session's default. The navigator's `main` row opens the same control:
|
|
43
|
+
`m` cycles the session mode, `ctrl+s` saves that default, and `x`
|
|
44
|
+
does not stop it. Manual delegates only on explicit user or workflow request, even with a
|
|
41
45
|
plan. Adaptive delegates substantial independent work when useful. Coordinator
|
|
42
46
|
uses `agents_catalog` to discover current role descriptions and delegates every
|
|
43
47
|
nontrivial role-owned task, while the foreground coordinates, integrates, and
|
|
44
48
|
verifies. See [usage notes](docs/usage.md#delegation-mode).
|
|
45
49
|
|
|
46
50
|
`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
|
|
51
|
+
such as `role developer "Developer" … default openai/gpt-6.1-sol@high`. Roles are
|
|
48
52
|
listed and passed by short name (`role: "developer"`); the stored id
|
|
49
53
|
`role.developer` also works, and named agents keep their full id
|
|
50
54
|
(`agent: "agent.payments"`). To launch on that default, omit
|
|
51
55
|
`model` and `thinking` on a role or agent spawn; name one only for a stated
|
|
52
56
|
reason. When a launch's model or effort differs from the default, its launch
|
|
53
57
|
line says so, for example
|
|
54
|
-
`model openai/gpt-6-astra@high (role developer default openai/gpt-6-sol@high)`.
|
|
58
|
+
`model openai/gpt-6-astra@high (role developer default openai/gpt-6.1-sol@high)`.
|
|
55
59
|
|
|
56
60
|
## When To Use
|
|
57
61
|
|
package/agent-commands.ts
CHANGED
|
@@ -123,7 +123,7 @@ interface ParsedArgs {
|
|
|
123
123
|
|
|
124
124
|
export function registerAgentCommands(pi: Pick<ExtensionAPI, "registerCommand">, deps: AgentCommandDeps = {}): void {
|
|
125
125
|
pi.registerCommand("agents", {
|
|
126
|
-
description: "List, inspect, create, reload, or import
|
|
126
|
+
description: "[list|show|create|reload|import-codex] — List, inspect, create, reload, or import agent definitions",
|
|
127
127
|
getArgumentCompletions(prefix: string) {
|
|
128
128
|
const commands = ["list", "inspect", "show", "create", "reload", "import-codex", "help"];
|
|
129
129
|
const first = prefix.trim().split(/\s+/)[0] ?? "";
|
package/batch.mjs
CHANGED
|
@@ -14,6 +14,64 @@ import { assertTimingParams } from "./timing.ts";
|
|
|
14
14
|
|
|
15
15
|
const VALID_CAPACITY_MODES = new Set(["reject", "launch-available"]);
|
|
16
16
|
|
|
17
|
+
/** Public codemode receipt; details alone are not returned to nested callers. */
|
|
18
|
+
export const batchLaunchOutputSchema = {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
status: { type: "string", enum: ["launched", "partial", "not-launched", "clarification-needed"] },
|
|
22
|
+
batchId: { type: "string" },
|
|
23
|
+
batchName: { type: "string" },
|
|
24
|
+
launched: {
|
|
25
|
+
type: "array",
|
|
26
|
+
items: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: { job: { type: "integer", minimum: 1 }, name: { type: "string" }, id: { type: "string" }, modelNote: { type: "string" } },
|
|
29
|
+
required: ["job", "name", "id"],
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
failed: {
|
|
34
|
+
type: "array",
|
|
35
|
+
items: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: { job: { type: "integer", minimum: 1 }, name: { type: "string" }, reason: { type: "string" } },
|
|
38
|
+
required: ["job", "name", "reason"],
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
skipped: {
|
|
43
|
+
type: "array",
|
|
44
|
+
items: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: { job: { type: "integer", minimum: 1 }, name: { type: "string" } },
|
|
47
|
+
required: ["job", "name"],
|
|
48
|
+
additionalProperties: false,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
message: { type: "string" },
|
|
52
|
+
choices: { type: "array", items: { type: "string" } },
|
|
53
|
+
},
|
|
54
|
+
required: ["status", "launched", "failed", "skipped"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Launch success is not run completion; partial receipts retain every created run. */
|
|
59
|
+
export function batchLaunchResult({ batchId, batchName, launched, skipped, failed }) {
|
|
60
|
+
const receipt = {
|
|
61
|
+
status: launched.length === 0 ? "not-launched" : failed.length || skipped.length ? "partial" : "launched",
|
|
62
|
+
batchId,
|
|
63
|
+
...(batchName !== undefined ? { batchName } : {}),
|
|
64
|
+
launched,
|
|
65
|
+
failed,
|
|
66
|
+
skipped,
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: formatBatchLaunchResponse(receipt) }],
|
|
70
|
+
details: receipt,
|
|
71
|
+
structuredContent: receipt,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
17
75
|
/**
|
|
18
76
|
* Overlay per-job options on top of shared options. Only defined keys are
|
|
19
77
|
* inherited; booleans keep their false values.
|
package/catalog-runtime.ts
CHANGED
|
@@ -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
|
*/
|
package/catalog-schema.ts
CHANGED
|
@@ -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;
|
package/config.ts
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* `defaultTools` absent → the built-in SAFE_DEFAULT_TOOLS.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { SELF_SPEC } from "./extensions.ts";
|
|
16
|
-
import type
|
|
16
|
+
import { isDelegationMode, type DelegationMode } from "./delegation.ts";
|
|
17
17
|
import type { TimingSettings } from "./timing.ts";
|
|
18
18
|
|
|
19
19
|
export interface SubagentConfig extends TimingSettings {
|
|
@@ -80,24 +80,49 @@ export const SAFE_DEFAULT_TOOLS = "read, bash, edit, write, web_search, web_fetc
|
|
|
80
80
|
export const SAFE_CLEAN_TOOLS = "read, bash";
|
|
81
81
|
|
|
82
82
|
let cached: SubagentConfig | undefined;
|
|
83
|
+
let configPathForTests: string | undefined;
|
|
83
84
|
|
|
84
85
|
/** Test seam. Pass undefined to reload config.json on the next loadConfig call. */
|
|
85
86
|
export function setConfigForTests(next: SubagentConfig | undefined): void {
|
|
86
87
|
cached = next;
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/** Test seam. Save and load use this file instead of the package config.json. */
|
|
91
|
+
export function setConfigPathForTests(path: string | undefined): void {
|
|
92
|
+
configPathForTests = path;
|
|
93
|
+
cached = undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function configPath(): string {
|
|
97
|
+
return configPathForTests ?? join(dirname(fileURLToPath(import.meta.url)), "config.json");
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
/** Load config.json from the extension directory. Missing/invalid → {}. */
|
|
90
101
|
export function loadConfig(): SubagentConfig {
|
|
91
102
|
if (cached) return cached;
|
|
92
103
|
try {
|
|
93
|
-
|
|
94
|
-
cached = JSON.parse(readFileSync(join(dir, "config.json"), "utf-8")) as SubagentConfig;
|
|
104
|
+
cached = JSON.parse(readFileSync(configPath(), "utf-8")) as SubagentConfig;
|
|
95
105
|
} catch {
|
|
96
106
|
cached = {};
|
|
97
107
|
}
|
|
98
108
|
return cached;
|
|
99
109
|
}
|
|
100
110
|
|
|
111
|
+
/** Write delegationMode into config.json, preserving every other key. */
|
|
112
|
+
export function writeDelegationMode(mode: DelegationMode, path = configPath()): void {
|
|
113
|
+
if (!isDelegationMode(mode)) throw new Error(`Invalid delegation mode: ${String(mode)}`);
|
|
114
|
+
let current: SubagentConfig = {};
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
|
|
117
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) current = parsed as SubagentConfig;
|
|
118
|
+
} catch { /* missing file starts as an empty config */ }
|
|
119
|
+
current.delegationMode = mode;
|
|
120
|
+
const pending = `${path}.${process.pid}.tmp`;
|
|
121
|
+
writeFileSync(pending, `${JSON.stringify(current, null, 2)}\n`);
|
|
122
|
+
renameSync(pending, path);
|
|
123
|
+
if (path === configPath()) cached = current;
|
|
124
|
+
}
|
|
125
|
+
|
|
101
126
|
/** Normalize a comma/space tool list to pi's bare comma form: "a, b" → "a,b". */
|
|
102
127
|
export function normalizeTools(list: string): string {
|
|
103
128
|
return list.split(",").map((t) => t.trim()).filter(Boolean).join(",");
|
|
@@ -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
|
|
package/docs/agent-catalog.md
CHANGED
|
@@ -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
|
|