@hcflabs/pi-subagent 28.3.3 → 28.4.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 +3 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +24 -6
- package/docs/orchestration.md +8 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Commands, prompts, and UI are for you. Tools and the packaged skill are for Main
|
|
|
46
46
|
| Direct status widget (`D`) | ui | Show direct workflows as a tree under `Main` with references, names, Role badges, status, routes, elapsed time, measured task tokens, cost, and tool activity. In fullscreen mode, a click on a working or finished worker row focuses that worker's verified Herdr pane. |
|
|
47
47
|
| Isolated status widget (`I`) | ui | Show recorded task state, Role/model-class badges, and retained workspaces in the TUI. Readiness does not mean promotion. |
|
|
48
48
|
|
|
49
|
-
`delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only. Main must make these decisions visibly; `codemode` scripts can call only `subagent_status`.
|
|
49
|
+
`delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only. Main must make these decisions visibly; `codemode` scripts can call only `subagent_status` and `subagent_repos`.
|
|
50
50
|
|
|
51
51
|
### Direct workflow
|
|
52
52
|
|
|
@@ -269,6 +269,8 @@ Role Markdown files in `~/.pi/agent/config/pi-subagent/` require frontmatter and
|
|
|
269
269
|
| `mcps` | Optional exact global server names from `~/.pi/agent/mcp.json`, using native `mcpServers` format. Omitted or `[]` denies access to `mcp.json` servers. Servers that Role extensions register always connect. |
|
|
270
270
|
| body | Required system instructions |
|
|
271
271
|
|
|
272
|
+
A Role's `modelClass` selects a Task Profile. When the profile has a primary and a fallback that resolve in Main, the worker selects the Task Profile Model `task/<profile>`. A retryable error on the primary then moves the worker to the fallback. The launch record names the primary route. A designated `model` uses no fallback.
|
|
273
|
+
|
|
272
274
|
The package ships `implementer`, `reviewer`, and `scout`; same-name user Roles override built-ins. Roles define capabilities, not access or isolation. Unknown frontmatter keys invalidate that file and report its path and key. Other valid Roles load; duplicate names become unavailable. Delegation to an invalid file's claimed Role fails with that file's error. Inspect `/subagent roles`.
|
|
273
275
|
|
|
274
276
|
The built-in reviewer answers exactly `PASS` when it finds no actionable correctness risk. Otherwise, each finding starts with a priority and a confidence (`high`, `medium`, or `low`), for example `[P1] confidence: high`. Then it gives file and line evidence, the trigger, the impact, and the smallest fix. Each finding blocks approval.
|
package/dist/index.d.ts
CHANGED
|
@@ -73,6 +73,11 @@ export interface PreparedRoleLaunch extends ResolvedRoleLaunch {
|
|
|
73
73
|
export interface CreateRoleLaunchInput {
|
|
74
74
|
role: Role;
|
|
75
75
|
route: ResolvedTaskRoute;
|
|
76
|
+
/**
|
|
77
|
+
* Task Profile of the route. When set, the child selects the Task Profile Model `task/<profile>`,
|
|
78
|
+
* so a retryable error on the primary route moves the child to the fallback route.
|
|
79
|
+
*/
|
|
80
|
+
failoverProfile?: ProfileName;
|
|
76
81
|
extensions?: readonly string[];
|
|
77
82
|
tools?: readonly string[];
|
|
78
83
|
env?: Readonly<Record<string, string>>;
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createConfigStore, extensionConfigDir, field, formatConfigIssues, parse
|
|
|
6
6
|
import { hasDisplayControlCharacters } from "./display-text.js";
|
|
7
7
|
import { DEFAULT_MAX_TURNS } from "./ephemeral.js";
|
|
8
8
|
import { loadRoleMcpConfig, parseRoleMcpAllowlist } from "./mcp-role.js";
|
|
9
|
-
import { loadTaskModelsConfig, modelReference, orderedProfileRoutes, PROFILE_NAMES, resolveConfiguredTaskRoute, resolveTaskModelRoute, } from "@hcflabs/pi-task-models";
|
|
9
|
+
import { loadTaskModelsConfig, modelReference, orderedProfileRoutes, PROFILE_NAMES, resolveConfiguredTaskRoute, resolveTaskModelRoute, TASK_PROFILE_MODELS_EXTENSION, taskProfileModelReference, } from "@hcflabs/pi-task-models";
|
|
10
10
|
export { DISPLAY_TEXT_CONTRACT, hasDisplayControlCharacters } from "./display-text.js";
|
|
11
11
|
export { loadRoleMcpConfig, parseRoleMcpAllowlist } from "./mcp-role.js";
|
|
12
12
|
export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, } from "./ephemeral.js";
|
|
@@ -450,6 +450,14 @@ function resolveConfiguredRoute(ctx, profileName, profile) {
|
|
|
450
450
|
}
|
|
451
451
|
throw new Error(`No usable ${profileName} task model route. Run /task-models.`);
|
|
452
452
|
}
|
|
453
|
+
/** Return the profile when it has a primary and a fallback route that resolve in this session, so failover can help. */
|
|
454
|
+
function failoverProfile(ctx, profileName, agentDir = getAgentDir()) {
|
|
455
|
+
const profile = loadTaskModelsConfig(agentDir).value.profiles[profileName];
|
|
456
|
+
if (!profile)
|
|
457
|
+
return undefined;
|
|
458
|
+
const usable = orderedProfileRoutes(profile).filter((route) => resolveTaskModelRoute(ctx, route) !== undefined);
|
|
459
|
+
return usable.length > 1 ? profileName : undefined;
|
|
460
|
+
}
|
|
453
461
|
export function resolveRoleSkills(pi, role) {
|
|
454
462
|
const skills = new Map(pi.getCommands()
|
|
455
463
|
.filter((command) => command.source === "skill")
|
|
@@ -512,6 +520,8 @@ function prepareRoleLaunchFromSkills(ctx, input, skills) {
|
|
|
512
520
|
ROLE_MCP_EXTENSION,
|
|
513
521
|
...(role.tools.includes("git_read") ? [GIT_READ_EXTENSION] : []),
|
|
514
522
|
ROLE_TOOLS_EXTENSION,
|
|
523
|
+
// The child runs with --no-extensions, so it loads the Task Profile Models only from this path.
|
|
524
|
+
...(input.failoverProfile ? [TASK_PROFILE_MODELS_EXTENSION] : []),
|
|
515
525
|
];
|
|
516
526
|
const env = Object.fromEntries(Object.entries(input.env ?? {}).map(([key, value]) => {
|
|
517
527
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
@@ -527,7 +537,7 @@ function prepareRoleLaunchFromSkills(ctx, input, skills) {
|
|
|
527
537
|
for (const skill of skills.paths)
|
|
528
538
|
args.push("--skill", skill);
|
|
529
539
|
args.push(`--${ROLE_TOOL_POLICY_FLAG}`, JSON.stringify(tools));
|
|
530
|
-
args.push("--model", modelReference(input.route.model));
|
|
540
|
+
args.push("--model", input.failoverProfile ? taskProfileModelReference(input.failoverProfile) : modelReference(input.route.model));
|
|
531
541
|
if (input.route.thinkingLevel)
|
|
532
542
|
args.push("--thinking", input.route.thinkingLevel);
|
|
533
543
|
args.push(ctx.isProjectTrusted() ? "--approve" : "--no-approve");
|
|
@@ -546,11 +556,16 @@ function prepareRoleLaunchFromSkills(ctx, input, skills) {
|
|
|
546
556
|
function prepareResolvedRoleLaunch(pi, ctx, input) {
|
|
547
557
|
const { task, modelClass, agentDir, ...launchInput } = input;
|
|
548
558
|
const selectedClass = modelClass ?? input.role.modelClass;
|
|
559
|
+
const route = selectedClass === undefined
|
|
560
|
+
? resolveConfiguredTaskRoute(ctx, task, agentDir)
|
|
561
|
+
: resolveTaskRoute(ctx, selectedClass, agentDir);
|
|
562
|
+
// The route resolved, so the config loads. A task without a class uses its assigned or declared profile.
|
|
563
|
+
const profileName = selectedClass ?? loadTaskModelsConfig(agentDir).value.tasks[task.id] ?? task.defaultProfile;
|
|
564
|
+
const failover = failoverProfile(ctx, profileName, agentDir);
|
|
549
565
|
return prepareRoleLaunchFromSkills(ctx, {
|
|
550
566
|
...launchInput,
|
|
551
|
-
route
|
|
552
|
-
|
|
553
|
-
: resolveTaskRoute(ctx, selectedClass, agentDir),
|
|
567
|
+
route,
|
|
568
|
+
...(failover ? { failoverProfile: failover } : {}),
|
|
554
569
|
}, resolveRoleSkills(pi, input.role));
|
|
555
570
|
}
|
|
556
571
|
export function resolveRoleLaunch(pi, ctx, input) {
|
|
@@ -589,9 +604,12 @@ export async function resolveConfiguredRoleLaunch(pi, ctx, input) {
|
|
|
589
604
|
...namedSkills,
|
|
590
605
|
paths: [...new Set([...namedSkills.paths, ...resources.skills])],
|
|
591
606
|
};
|
|
607
|
+
const route = resolveTaskRoute(ctx, input.modelClass);
|
|
608
|
+
const failover = failoverProfile(ctx, input.modelClass);
|
|
592
609
|
const prepared = prepareRoleLaunchFromSkills(ctx, {
|
|
593
610
|
role: effectiveRole,
|
|
594
|
-
route
|
|
611
|
+
route,
|
|
612
|
+
...(failover ? { failoverProfile: failover } : {}),
|
|
595
613
|
}, skills);
|
|
596
614
|
assertNoMissingRoleSkills(effectiveRole, prepared);
|
|
597
615
|
const args = [
|
package/docs/orchestration.md
CHANGED
|
@@ -81,7 +81,7 @@ Direct `changeset` is rejected; use isolated mode for checked integration. Resou
|
|
|
81
81
|
|
|
82
82
|
### Dry run
|
|
83
83
|
|
|
84
|
-
Set `"dryRun": true` on a direct or isolated `delegate_task` request to check it without a launch. The dry run does the same checks as a launch: request shape, Role lookup, route and model with thinking level, Skills, tools, MCP allowlist, Role extensions, config, and `continueFrom` eligibility. For an isolated request, it also checks the task graph and
|
|
84
|
+
Set `"dryRun": true` on a direct or isolated `delegate_task` request to check it without a launch. The dry run does the same checks as a launch: request shape, Role lookup, route and model with thinking level, Skills, tools, MCP allowlist, Role extensions, config, and `continueFrom` eligibility. For an isolated request, it also checks the task graph and pins Main's committed branch, HEAD, and tree. Dirty files in Main do not block the dry run or admission. If the request has a changeset task, it also selects the Full suite, adds it to the final checks, and shows it. A failed check gives the same error as a launch.
|
|
85
85
|
|
|
86
86
|
The result is a bounded plan. It shows each task's Role, resolved `provider/model`, thinking level, access or kind, dependencies, and checks. An isolated plan also shows the final checks. The dry run makes no Herdr call, Herdr tab, session record, saved state, worktree, lease, checkout admission, or follow-up. The plan lists each check it cannot do without such an effect as `Not checked: <reason>`. Examples are the Herdr host preflight, checkout admission, and the stop of a continued agent.
|
|
87
87
|
|
|
@@ -281,7 +281,7 @@ Every task declares `id`, `kind`, `role`, `modelClass`, `requirements`, `deliver
|
|
|
281
281
|
|
|
282
282
|
Admission and worker-wave dispatch pin Main's exact committed branch, HEAD, and tree without inspecting or modifying its staged, unstaged, or untracked edits. Worker allocation verifies the same committed identity around worktree creation, including resumed waves; staged-dependent waves verify their clean integration snapshot. A dirty Main can therefore produce a checked retained candidate, but branch/HEAD drift stops new waves. The runner executes ready worker tasks concurrently, inspects clean committed candidates, runs preliminary task checks and optional preliminary judgment, then records ready candidates for Main. **It does not automatically integrate them.**
|
|
283
283
|
|
|
284
|
-
The extension waits up to about 10 seconds for the shell of a new worker pane to finish startup
|
|
284
|
+
The extension waits up to about 10 seconds for the shell of a new worker pane to finish startup. Then it waits up to about 20 more seconds for that shell to own the pane foreground, before the readiness probe and the Pi start.
|
|
285
285
|
|
|
286
286
|
Main selects exact candidates through `subagent_stage` into an owned integration checkout in explicit order. Each stage is a committed merge of the candidate, not a mutation of Main. Conflicts retain the checkout for Main to resolve; `resolve` verifies the exact two-parent merge instead of replaying an uncertain merge. Main can explicitly run dependent tasks from an exact staged tip with `subagent_integrate advance` and stage their results afterward. Independent candidates may be staged without re-running workers.
|
|
287
287
|
|
|
@@ -396,13 +396,13 @@ If you removed resources outside Pi Subagent, abort can reconcile their absence.
|
|
|
396
396
|
|
|
397
397
|
### Recovery and state
|
|
398
398
|
|
|
399
|
-
State version
|
|
399
|
+
State version 6 lives privately under:
|
|
400
400
|
|
|
401
401
|
```text
|
|
402
402
|
<agent-dir>/config/pi-subagent/state/<repository-hash>/<request-id>.json
|
|
403
403
|
```
|
|
404
404
|
|
|
405
|
-
Parsing rejects unknown properties, malformed evidence, invalid lineage, and
|
|
405
|
+
Parsing rejects unknown properties, malformed evidence, invalid lineage, and v5 or older state without migration. Preserve old files and recover work manually. Writes are atomic. A productive lease prevents concurrent execution or resumption but permits status inspection and abort.
|
|
406
406
|
|
|
407
407
|
`delegate_task`, `subagent_resume`, and `subagent_integrate advance` return a durable request ID after saving the wave. Work continues asynchronously. A saved worker attention state sends a follow-up to the launching session, including while siblings run. The wave also reports its final result.
|
|
408
408
|
|
|
@@ -600,4 +600,7 @@ Worktree finalization returns one of:
|
|
|
600
600
|
- `retained`: exact commit and dirty measurements are known and work remains; or
|
|
601
601
|
- `recovery`: cleanup or measurement needs deliberate action and reports only proved fields.
|
|
602
602
|
|
|
603
|
-
No cleanup path uses `git clean`, force deletion, hidden fallback to Main, or deletion of unproved work.
|
|
603
|
+
No cleanup path uses `git clean`, force deletion, hidden fallback to Main, or deletion of unproved work. There are two exceptions, and each needs user confirmation:
|
|
604
|
+
|
|
605
|
+
- After the user selects **Close and delete worktrees (discard work)** in `/subagent` and confirms, the extension force-removes the reviewed owned worktrees.
|
|
606
|
+
- After a separate confirmation in `/subagent prune`, the command force-removes the reviewed items that it marked as forceable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hcflabs/pi-subagent",
|
|
3
|
-
"version": "28.
|
|
3
|
+
"version": "28.4.0",
|
|
4
4
|
"description": "Delegate bounded direct work or durable checked isolated task graphs to Pi roles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@hcflabs/pi-herdr": "^0.7.0",
|
|
75
75
|
"@hcflabs/pi-process": "^0.3.0",
|
|
76
76
|
"@hcflabs/pi-repo": "^0.1.0",
|
|
77
|
-
"@hcflabs/pi-task-models": "^9.
|
|
77
|
+
"@hcflabs/pi-task-models": "^9.3.0",
|
|
78
78
|
"proper-lockfile": "^4.1.2"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|