@hcflabs/pi-subagent 27.10.8 → 28.0.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/CONTEXT.md +1 -1
- package/README.md +38 -3
- package/dist/full-suite.d.ts +34 -0
- package/dist/full-suite.js +142 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +37 -0
- package/dist/runner.d.ts +6 -2
- package/dist/runner.js +27 -31
- package/dist/schema.d.ts +38 -3
- package/dist/schema.js +23 -1
- package/dist/store.d.ts +1 -1
- package/dist/store.js +9 -7
- package/docs/orchestration.md +4 -2
- package/extensions/admission.ts +57 -18
- package/extensions/isolated.ts +14 -2
- package/extensions/repos.ts +40 -0
- package/extensions/subagent.ts +55 -19
- package/extensions/workflow.ts +26 -3
- package/package.json +2 -1
- package/skills/pi-subagent/SKILL.md +1 -1
package/extensions/workflow.ts
CHANGED
|
@@ -33,6 +33,11 @@ const AccessSchema = StringEnum(DIRECT_ACCESS, { description: ACCESS_GUIDANCE })
|
|
|
33
33
|
const DryRunSchema = Type.Boolean({ description: "Optional. When true, run every launch validation and return the resolved plan without starting workers, saving state, or reserving the checkout." });
|
|
34
34
|
const ContinueFromSchema = Type.String({ pattern: "^D[1-9][0-9]*\\.[1-9][0-9]*$", description: "Optional direct worker reference such as D7.1. Continues that finished worker's Pi session in a new tab with this task; role must equal the recorded Role." });
|
|
35
35
|
|
|
36
|
+
const RepoSchema = Type.String({
|
|
37
|
+
minLength: 1,
|
|
38
|
+
description: "Optional Repository target: a child Git repository as a path relative to the Pi working directory, such as team/service. The worker runs there, and admission reserves that checkout. A task value overrides the request value. Omit it to use the Pi working directory. Use subagent_repos to list targets.",
|
|
39
|
+
});
|
|
40
|
+
|
|
36
41
|
const PathsSchema = Type.Array(Type.String({ minLength: 1 }), {
|
|
37
42
|
minItems: 1,
|
|
38
43
|
maxItems: 32,
|
|
@@ -46,6 +51,7 @@ export const DelegationSchema = Type.Object({
|
|
|
46
51
|
access: AccessSchema,
|
|
47
52
|
paths: Type.Optional(PathsSchema),
|
|
48
53
|
continueFrom: Type.Optional(ContinueFromSchema),
|
|
54
|
+
repo: Type.Optional(RepoSchema),
|
|
49
55
|
kind: Type.Optional(DirectKindSchema),
|
|
50
56
|
model: Type.Optional(ModelSchema),
|
|
51
57
|
modelClass: Type.Optional(ModelClassSchema),
|
|
@@ -59,6 +65,7 @@ export const DirectWorkflowSchema = Type.Object({
|
|
|
59
65
|
access: Type.Optional(AccessSchema),
|
|
60
66
|
paths: Type.Optional(PathsSchema),
|
|
61
67
|
continueFrom: Type.Optional(ContinueFromSchema),
|
|
68
|
+
repo: Type.Optional(RepoSchema),
|
|
62
69
|
kind: Type.Optional(DirectKindSchema),
|
|
63
70
|
model: Type.Optional(ModelSchema),
|
|
64
71
|
modelClass: Type.Optional(ModelClassSchema),
|
|
@@ -114,8 +121,18 @@ function normalizePaths(value: Static<typeof DelegationSchema>, path: string): s
|
|
|
114
121
|
});
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
|
|
124
|
+
/** Check the selector shape here. Admission and launch resolve it against the Pi working directory. */
|
|
125
|
+
function normalizeRepo(value: string, path: string): string {
|
|
126
|
+
const normalized = text(value, path);
|
|
127
|
+
if (/[\u0000-\u001f\u007f-\u009f]/.test(normalized) || normalized.startsWith("/") || normalized.split(/[\\/]/).includes("..")) {
|
|
128
|
+
throw new Error(`${path} must be a relative path without control characters or .. components.`);
|
|
129
|
+
}
|
|
130
|
+
return normalized;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function normalizeDelegation(value: Static<typeof DelegationSchema>, path: string, requestRepo?: string): Delegation {
|
|
118
134
|
const paths = normalizePaths(value, path);
|
|
135
|
+
const repo = value.repo === undefined ? requestRepo : normalizeRepo(value.repo, `${path}.repo`);
|
|
119
136
|
return {
|
|
120
137
|
role: text(value.role, `${path}.role`),
|
|
121
138
|
name: normalizeTaskName(value.name, `${path}.name`),
|
|
@@ -123,6 +140,7 @@ function normalizeDelegation(value: Static<typeof DelegationSchema>, path: strin
|
|
|
123
140
|
access: value.access,
|
|
124
141
|
...(paths === undefined ? {} : { paths }),
|
|
125
142
|
...(value.continueFrom === undefined ? {} : { continueFrom: value.continueFrom }),
|
|
143
|
+
...(repo === undefined ? {} : { repo }),
|
|
126
144
|
kind: "text",
|
|
127
145
|
...(value.model === undefined ? {} : { model: text(value.model, `${path}.model`) }),
|
|
128
146
|
...(value.modelClass === undefined ? {} : { modelClass: value.modelClass }),
|
|
@@ -164,9 +182,10 @@ export function parseWorkflow(value: unknown): ParsedWorkflow {
|
|
|
164
182
|
if (!mode) throw new Error("direct workflow must select exactly one of single, tasks, or chain.");
|
|
165
183
|
const input = value as DirectInput;
|
|
166
184
|
if (mode === "single" && !hasDelegation(input)) throw new Error("direct workflow requires role, name, task, and access.");
|
|
185
|
+
const repo = input.repo === undefined ? undefined : normalizeRepo(input.repo, "workflow.repo");
|
|
167
186
|
const workflow: ParsedWorkflow = mode === "single" ? { mode, delegations: [normalizeDelegation(input as Static<typeof DelegationSchema>, "workflow")] }
|
|
168
|
-
: mode === "parallel" ? { mode, delegations: input.tasks!.map((item, index) => normalizeDelegation(item, `tasks[${index}]
|
|
169
|
-
: { mode, delegations: input.chain!.map((item, index) => normalizeDelegation(item, `chain[${index}]
|
|
187
|
+
: mode === "parallel" ? { mode, delegations: input.tasks!.map((item, index) => normalizeDelegation(item, `tasks[${index}]`, repo)) }
|
|
188
|
+
: { mode, delegations: input.chain!.map((item, index) => normalizeDelegation(item, `chain[${index}]`, repo)) };
|
|
170
189
|
const continued = workflow.delegations.flatMap(({ continueFrom }) => continueFrom === undefined ? [] : [continueFrom]);
|
|
171
190
|
if (new Set(continued).size !== continued.length) throw new Error("A direct request may continue each worker at most once.");
|
|
172
191
|
return workflow;
|
|
@@ -175,6 +194,9 @@ export function parseWorkflow(value: unknown): ParsedWorkflow {
|
|
|
175
194
|
/** A direct request is a writer request when any task declares write access. */
|
|
176
195
|
export const writesCheckout = (workflow: ParsedWorkflow): boolean => workflow.delegations.some(({ access }) => access === "write");
|
|
177
196
|
|
|
197
|
+
/** The Repository target of each task. `undefined` means the Pi working directory. */
|
|
198
|
+
export const workflowRepos = (workflow: ParsedWorkflow): Array<string | undefined> => workflow.delegations.map(({ repo }) => repo);
|
|
199
|
+
|
|
178
200
|
/** `dryRun` is a tool-call option, not part of the direct workflow or the durable isolated request. */
|
|
179
201
|
export function parseDelegateTask(value: unknown): ParsedDelegateTask {
|
|
180
202
|
let dryRun = false;
|
|
@@ -185,6 +207,7 @@ export function parseDelegateTask(value: unknown): ParsedDelegateTask {
|
|
|
185
207
|
value = rest;
|
|
186
208
|
}
|
|
187
209
|
if (value && typeof value === "object" && !Array.isArray(value) && (value as { mode?: unknown }).mode === "isolated") {
|
|
210
|
+
if (Object.hasOwn(value, "repo")) throw new Error("mode isolated does not accept repo yet. Start Pi in the repository, or use mode direct.");
|
|
188
211
|
return { mode: "isolated", request: parseExecuteRequest(value), dryRun };
|
|
189
212
|
}
|
|
190
213
|
return { mode: "direct", workflow: parseWorkflow(value), dryRun };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hcflabs/pi-subagent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "28.0.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,6 +74,7 @@
|
|
|
74
74
|
"@hcflabs/pi-herdr": "^0.5.0",
|
|
75
75
|
"@hcflabs/pi-multi-codex": "^3.0.0",
|
|
76
76
|
"@hcflabs/pi-process": "^0.2.0",
|
|
77
|
+
"@hcflabs/pi-repo": "^0.1.0",
|
|
77
78
|
"@hcflabs/pi-task-models": "^9.0.0",
|
|
78
79
|
"proper-lockfile": "^4.1.2"
|
|
79
80
|
},
|
|
@@ -30,7 +30,7 @@ To build on a finished direct worker, add `continueFrom` with its reference (suc
|
|
|
30
30
|
|
|
31
31
|
# Isolated requests
|
|
32
32
|
|
|
33
|
-
Give the request a unique ID and goal. Give each task a unique ID, kind, Role, model class, bounded requirements, deliverable, `dependsOn`, and `contextFrom`. Changesets require focused task checks, and a graph containing changesets requires final checks.
|
|
33
|
+
Give the request a unique ID and goal. Give each task a unique ID, kind, Role, model class, bounded requirements, deliverable, `dependsOn`, and `contextFrom`. Changesets require focused task checks, and a graph containing changesets requires final checks. Admission adds the repository's Full suite to final checks if missing: the `repoSuites` config entry, `pnpm test` for a root `package.json` test script, or `make test` for a Makefile `test` target. The suite runs against the combined tip before Main promotion. If no suite exists, tell the user to add one or configure `repoSuites`. Text context may name only completed text tasks, preserves declared order, and implies a scheduling dependency; do not repeat an edge in `dependsOn` and `contextFrom`.
|
|
34
34
|
|
|
35
35
|
Every changeset becomes ready automatically after preliminary checks pass. While a locally owned changeset task is actively working, the user may open `/subagent` to queue bounded same-worker revisions or withdraw all still-pending instructions into the native editor and submit one replacement. Cancellation after withdrawal leaves the originals removed; already-claimed instructions cannot be recalled. Each revision must produce a new clean commit and rerun preliminary checks. If a worker's turn ends with uncommitted changes, the runner sends it one commit reminder per attempt; this uses no correction, and a worktree still dirty afterwards needs attention. When no queued revision remains, the runner seals and durably records the exact ready candidate without waiting for sibling workers. There is no guaranteed post-completion editing window, and late follow-ups fail visibly.
|
|
36
36
|
|