@selesai/code 0.9.2 → 0.9.4
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/CHANGELOG.md +12 -0
- package/dist/defaults/models.json +20 -4
- package/dist/defaults/settings.json +7 -7
- package/dist/extensions/model-prompt-injector/config.json +1 -1
- package/dist/extensions/workflow/extension.ts +19 -19
- package/dist/extensions/workflow/modes.ts +34 -14
- package/docs/workflows.md +37 -230
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@selesai/code` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.9.4] - 2026-08-22
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **Extensible pi-subagents workflows.** The bundled `/workflow-*` extension is now a thin registry-based adapter over pi-subagents orchestration. Modes can use ordered runs, parallel discovery, or scripted conditional loops without adding command plumbing. The prototype workflow now runs external research and codebase exploration in parallel.
|
|
9
|
+
- **Workflow completion contract.** Build/review/fix loops now finish only when the reviewer reports `clean` with no remaining work, preventing a clean review of one slice from ending an incomplete plan.
|
|
10
|
+
- **Workflow documentation.** Replaced stale durable-state-machine documentation with the current pi-subagents launch, recovery, and extension model.
|
|
11
|
+
|
|
12
|
+
## [0.9.3] - 2026-08-22
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **Token-In model identifiers.** Replace the provider's removed `auto`/`auto-premium` identifiers with the bundled `celestial-pro`, `celestial-max`, and `celestial-ultra` models, and point factory defaults and model-specific prompt injection at the supported Celestial IDs.
|
|
16
|
+
|
|
5
17
|
## [0.9.2] - 2026-08-20
|
|
6
18
|
|
|
7
19
|
### Fixed
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"models": [
|
|
13
13
|
{
|
|
14
|
-
"id": "
|
|
15
|
-
"name": "
|
|
14
|
+
"id": "celestial-pro",
|
|
15
|
+
"name": "Celestial Pro",
|
|
16
16
|
"reasoning": true,
|
|
17
17
|
"input": ["text"],
|
|
18
18
|
"contextWindow": 393216,
|
|
@@ -27,8 +27,24 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
"id": "
|
|
31
|
-
"name": "
|
|
30
|
+
"id": "celestial-max",
|
|
31
|
+
"name": "Celestial Max",
|
|
32
|
+
"reasoning": true,
|
|
33
|
+
"input": ["text"],
|
|
34
|
+
"contextWindow": 393216,
|
|
35
|
+
"maxTokens": 64000,
|
|
36
|
+
"thinkingLevelMap": {
|
|
37
|
+
"minimal": null,
|
|
38
|
+
"low": null,
|
|
39
|
+
"medium": null,
|
|
40
|
+
"high": null,
|
|
41
|
+
"xhigh": null,
|
|
42
|
+
"max": "max"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "celestial-ultra",
|
|
47
|
+
"name": "Celestial Ultra",
|
|
32
48
|
"reasoning": true,
|
|
33
49
|
"input": ["text"],
|
|
34
50
|
"contextWindow": 393216,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"keepRecentTokens": 32000,
|
|
10
10
|
"reserveTokens": 64000
|
|
11
11
|
},
|
|
12
|
-
"defaultModel": "
|
|
12
|
+
"defaultModel": "celestial-pro",
|
|
13
13
|
"defaultProvider": "tokenin",
|
|
14
14
|
"defaultThinkingLevel": "max",
|
|
15
15
|
"doubleEscapeAction": "tree",
|
|
@@ -44,22 +44,22 @@
|
|
|
44
44
|
"subagents": {
|
|
45
45
|
"agentOverrides": {
|
|
46
46
|
"architect": {
|
|
47
|
-
"model": "tokenin/
|
|
47
|
+
"model": "tokenin/celestial-pro:max"
|
|
48
48
|
},
|
|
49
49
|
"builder": {
|
|
50
|
-
"model": "tokenin/
|
|
50
|
+
"model": "tokenin/celestial-pro:max"
|
|
51
51
|
},
|
|
52
52
|
"commentator": {
|
|
53
|
-
"model": "tokenin/
|
|
53
|
+
"model": "tokenin/celestial-pro:max"
|
|
54
54
|
},
|
|
55
55
|
"explorer": {
|
|
56
|
-
"model": "tokenin/
|
|
56
|
+
"model": "tokenin/celestial-pro:max"
|
|
57
57
|
},
|
|
58
58
|
"recapper": {
|
|
59
|
-
"model": "tokenin/
|
|
59
|
+
"model": "tokenin/celestial-pro:max"
|
|
60
60
|
},
|
|
61
61
|
"researcher": {
|
|
62
|
-
"model": "tokenin/
|
|
62
|
+
"model": "tokenin/celestial-pro:max"
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"enabled": true
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
|
-
"match": ["
|
|
10
|
+
"match": ["celestial-*"],
|
|
11
11
|
"mode": "prepend",
|
|
12
12
|
"prompt": "text: You are a helpful software engineer assistant.\n\ndescription: Bootstrap with shell/read, then expose the full Standard tool catalog after the first durable tool call.\n\nwhen you thought, start with `we need...`",
|
|
13
13
|
"enabled": true
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
// ponytail: thin
|
|
2
|
-
// each mode's scripted workflow through pi-subagents' launchSlashSubagent.
|
|
1
|
+
// ponytail: thin slash-command adapter for the pi-subagents workflow runtime.
|
|
3
2
|
|
|
4
3
|
import type { ExtensionAPI } from "@selesai/code";
|
|
5
4
|
import { launchSlashSubagent } from "../pi-subagents/src/slash/slash-commands.ts";
|
|
6
|
-
import {
|
|
5
|
+
import { WORKFLOW_MODES, type WorkflowMode } from "./modes.ts";
|
|
7
6
|
|
|
8
7
|
export default function workflowModesExtension(pi: ExtensionAPI): void {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
const register = (mode: WorkflowMode) => pi.registerCommand(mode.command, {
|
|
9
|
+
description: mode.description,
|
|
10
|
+
handler: async (args, ctx) => {
|
|
11
|
+
const goal = args.trim();
|
|
12
|
+
if (!goal) {
|
|
13
|
+
ctx.ui.notify(`${mode.description}\nUsage: /${mode.command} <goal>`, "info");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
launchSlashSubagent(pi, ctx, {
|
|
17
|
+
...mode.launch(goal),
|
|
18
|
+
async: true,
|
|
19
|
+
agentScope: "both",
|
|
20
|
+
mission: { title: goal },
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
});
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
register("workflow-prototype", "Run the full prototype workflow (research → plan → reuse → handoff → auto loop → audit) as a scripted workflow.", buildPrototypeScript);
|
|
24
|
-
register("workflow-quicktype", "Run the quicker prototype workflow without research (plan → reuse → handoff → auto loop → audit) as a scripted workflow.", buildQuicktypeScript);
|
|
25
|
-
register("workflow-loop", "Run a direct auto build→review→fix loop for an already-agreed plan as a scripted workflow.", buildLoopScript);
|
|
25
|
+
for (const mode of WORKFLOW_MODES) register(mode);
|
|
26
26
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
// ponytail:
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// ponytail: workflow mode registry over pi-subagents' public workflowScript seam.
|
|
2
|
+
// A mode returns launch parameters; the extension owns slash-command plumbing.
|
|
3
|
+
|
|
4
|
+
import type { SubagentParamsLike } from "../pi-subagents/src/runs/foreground/subagent-executor.ts";
|
|
5
|
+
|
|
6
|
+
export interface WorkflowMode {
|
|
7
|
+
command: string;
|
|
8
|
+
description: string;
|
|
9
|
+
launch(goal: string): Pick<SubagentParamsLike, "workflowScript" | "chain" | "tasks" | "concurrency">;
|
|
10
|
+
}
|
|
5
11
|
|
|
6
12
|
function js(value: string): string {
|
|
7
|
-
|
|
13
|
+
return JSON.stringify(value);
|
|
8
14
|
}
|
|
9
15
|
|
|
16
|
+
// A loop depends on the previous review's output, so it belongs in pi-subagents'
|
|
17
|
+
// scripted workflow runtime rather than a fixed native chain.
|
|
10
18
|
const AUTO_LOOP = String.raw`
|
|
11
19
|
const autoLoop = async (goal, context, progressFile) => {
|
|
12
20
|
emit({ phase: 'start', goal });
|
|
@@ -25,14 +33,15 @@ const autoLoop = async (goal, context, progressFile) => {
|
|
|
25
33
|
timeoutMs: 15 * 60 * 1000,
|
|
26
34
|
task: 'Independently review the builder work for this round and report concrete evidence (what you inspected and what you ran). Do not modify the workspace.\n\nAcceptance criteria (source of truth):\n' + context + '\n\nProgress file (scope your review to its latest round entry; also re-check the files from the immediately preceding fix entry if one exists; fall back to the full uncommitted diff if it is missing or empty):\n' + progressFile + '\n\nBuilder completion summary:\n' + build.output + '\n\nIf the plan is not yet complete, add a "Remaining work:" section listing the next concrete step(s). End with exactly one line: WORKFLOW_REVIEW_STATUS: clean OR WORKFLOW_REVIEW_STATUS: blocking.',
|
|
27
35
|
});
|
|
28
|
-
|
|
36
|
+
const hasRemainingWork = /Remaining work\s*:\s*\S/i.test(review.output);
|
|
37
|
+
if (/WORKFLOW_REVIEW_STATUS\s*:\s*clean/i.test(review.output) && !hasRemainingWork) {
|
|
29
38
|
return { result: 'clean', rounds: completed + 1 };
|
|
30
39
|
}
|
|
31
40
|
previousReview = review.output;
|
|
32
41
|
await runs.run('fix-' + round, {
|
|
33
42
|
agent: 'builder',
|
|
34
43
|
timeoutMs: 45 * 60 * 1000,
|
|
35
|
-
task: 'Address ONLY the findings from the review below. The "Remaining work:" section (if present) is for the next round; do not act on it.\n\nProgress ledger: append a "## Round ' + round + ' fix" entry to the progress file at ' + progressFile + ' before finishing. List every file you changed and a short summary of the fixes.\n\nReviewer findings:\n' + review.output,
|
|
44
|
+
task: 'Address ONLY the findings from the review below. The "Remaining work:" section (if present) is for the next round; do not act on it. If the review is clean but has Remaining work, make no changes and record that fact.\n\nProgress ledger: append a "## Round ' + round + ' fix" entry to the progress file at ' + progressFile + ' before finishing. List every file you changed and a short summary of the fixes.\n\nReviewer findings:\n' + review.output,
|
|
36
45
|
});
|
|
37
46
|
completed += 1;
|
|
38
47
|
round += 1;
|
|
@@ -49,13 +58,13 @@ const autoLoop = async (goal, context, progressFile) => {
|
|
|
49
58
|
const PROGRESS_DIR = ".pi-subagents/progress/";
|
|
50
59
|
|
|
51
60
|
export function buildLoopScript(goal: string): string {
|
|
52
|
-
|
|
61
|
+
return String.raw`const goal = ${js(goal)};
|
|
53
62
|
${AUTO_LOOP}
|
|
54
63
|
return await autoLoop(goal, goal, ${js(PROGRESS_DIR + "loop.md")});`;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
export function buildTaskScript(goal: string): string {
|
|
58
|
-
|
|
67
|
+
return String.raw`const goal = ${js(goal)};
|
|
59
68
|
const plan = await runs.run('plan', { agent: 'architect', task: 'Produce a concrete implementation plan for: ' + goal + '. Cover what to build, how, in what order, which files and components, and the finished result. Return inline.' });
|
|
60
69
|
const reuse = await runs.run('reuse', { agent: 'explorer', task: 'Explore the codebase for reusable patterns relevant to: ' + plan.output + '. Point at relevant areas and dependencies; skip cleanly if wholly new. Return inline.' });
|
|
61
70
|
const handoff = await runs.run('handoff', { agent: 'recapper', task: 'Compile a self-contained handoff from the plan and reuse findings so fresh agents understand the goal, constraints, and acceptance criteria without re-planning.\n\nPlan:\n' + plan.output + '\n\nReuse findings:\n' + reuse.output + '\n\nReturn inline.' });
|
|
@@ -64,10 +73,14 @@ return await autoLoop(goal, handoff.output, ${js(PROGRESS_DIR + "task.md")});`;
|
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
export function buildPrototypeScript(goal: string): string {
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
return String.raw`const goal = ${js(goal)};
|
|
77
|
+
const discovery = await runs.all([
|
|
78
|
+
{ key: 'research', agent: 'researcher', task: 'Research the external, fast-changing knowledge this task depends on (libraries, SDKs, APIs, unfamiliar alternatives). Task: ' + goal + '. Synthesize actionable findings with sources. Return inline.' },
|
|
79
|
+
{ key: 'explore', agent: 'explorer', task: 'Map existing code, dependencies, and reusable patterns relevant to: ' + goal + '. Return inline.' },
|
|
80
|
+
]);
|
|
81
|
+
const research = discovery.find(result => result.key === 'research');
|
|
82
|
+
const reuse = discovery.find(result => result.key === 'explore');
|
|
83
|
+
const plan = await runs.run('plan', { agent: 'architect', task: 'Produce a concrete build plan from the research and codebase findings.\n\nResearch:\n' + research.output + '\n\nCodebase findings:\n' + reuse.output + '\n\nRequest:\n' + goal + '\n\nReturn inline.' });
|
|
71
84
|
const handoff = await runs.run('handoff', { agent: 'recapper', task: 'Compile a self-contained handoff from the plan and reuse findings.\n\nPlan:\n' + plan.output + '\n\nReuse:\n' + reuse.output + '\n\nReturn inline.' });
|
|
72
85
|
${AUTO_LOOP}
|
|
73
86
|
const loop = await autoLoop(goal, handoff.output, ${js(PROGRESS_DIR + "prototype.md")});
|
|
@@ -76,7 +89,7 @@ return { ...loop, audited: true };`;
|
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
export function buildQuicktypeScript(goal: string): string {
|
|
79
|
-
|
|
92
|
+
return String.raw`const goal = ${js(goal)};
|
|
80
93
|
const plan = await runs.run('plan', { agent: 'architect', task: 'Produce a concrete build plan for: ' + goal + '. Cover what to build, how, in what order, which components, and the finished result. Return inline.' });
|
|
81
94
|
const reuse = await runs.run('reuse', { agent: 'explorer', task: 'Explore the codebase for reusable patterns relevant to: ' + plan.output + '. Return inline.' });
|
|
82
95
|
const handoff = await runs.run('handoff', { agent: 'recapper', task: 'Compile a self-contained handoff from the plan and reuse findings.\n\nPlan:\n' + plan.output + '\n\nReuse:\n' + reuse.output + '\n\nReturn inline.' });
|
|
@@ -85,3 +98,10 @@ const loop = await autoLoop(goal, handoff.output, ${js(PROGRESS_DIR + "quicktype
|
|
|
85
98
|
const audit = await runs.run('audit', { agent: 'commentator', task: 'Final audit of the uncommitted changes for correctness, plan adherence, and over-engineering (cut bloat, dead flexibility, reinvented stdlib). Plan:\n' + plan.output + '\n\nReport concrete evidence. Do not modify the workspace.' });
|
|
86
99
|
return { ...loop, audited: true };`;
|
|
87
100
|
}
|
|
101
|
+
|
|
102
|
+
export const WORKFLOW_MODES: readonly WorkflowMode[] = [
|
|
103
|
+
{ command: "workflow-task", description: "Run the task workflow (plan → reuse → handoff → build/review/fix loop).", launch: (goal) => ({ workflowScript: buildTaskScript(goal) }) },
|
|
104
|
+
{ command: "workflow-prototype", description: "Run the prototype workflow (parallel research/reuse → plan → handoff → loop → audit).", launch: (goal) => ({ workflowScript: buildPrototypeScript(goal) }) },
|
|
105
|
+
{ command: "workflow-quicktype", description: "Run the quicker prototype workflow (plan → reuse → handoff → loop → audit).", launch: (goal) => ({ workflowScript: buildQuicktypeScript(goal) }) },
|
|
106
|
+
{ command: "workflow-loop", description: "Run a direct build/review/fix loop for an already-agreed plan.", launch: (goal) => ({ workflowScript: buildLoopScript(goal) }) },
|
|
107
|
+
];
|
package/docs/workflows.md
CHANGED
|
@@ -1,250 +1,57 @@
|
|
|
1
1
|
# Workflows
|
|
2
2
|
|
|
3
|
-
Selesai
|
|
3
|
+
Selesai's built-in workflows are thin slash-command adapters over the **pi-subagents** orchestration runtime. They do not have a separate state machine, artifact protocol, or `workflow.json` format.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Run a workflow
|
|
6
6
|
|
|
7
|
+
```text
|
|
8
|
+
/workflow-task <goal>
|
|
9
|
+
/workflow-prototype <goal>
|
|
10
|
+
/workflow-quicktype <goal>
|
|
11
|
+
/workflow-loop <goal>
|
|
7
12
|
```
|
|
8
|
-
src/extensions/workflow/
|
|
9
|
-
package.json pi package manifest; loads ./extension.ts as the single entry
|
|
10
|
-
state-machine.ts pure phase state machine (no fs, no pi API)
|
|
11
|
-
adapter.ts pi wiring: tools, commands, events, fs, durable-state lifecycle
|
|
12
|
-
run-state.ts versioned atomic workflow.json load/save/discovery
|
|
13
|
-
extension.ts single pi extension that mounts every workflow mode
|
|
14
|
-
modes/
|
|
15
|
-
prototype.ts mode config + registration object (exported as `prototypeMode`)
|
|
16
|
-
quicktype.ts mode config + registration object (exported as `quicktypeMode`)
|
|
17
|
-
task.ts mode config + registration object (exported as `taskMode`)
|
|
18
|
-
loop.ts mode config + registration object (exported as `loopMode`)
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
- **`state-machine.ts`** is the deep module. It owns the phase graph, artifact gating, skip rules, the terminal close gate, and the reentrancy guard. It imports nothing external — no `node:fs`, no pi API, no `pi-tui`, no `typebox`. Every method returns a `WorkflowEffect` (a discriminated union in domain vocabulary) that the adapter pattern-matches on.
|
|
22
|
-
- **`adapter.ts`** is the thin glue. It owns Pi/fs wiring, durable state, explicit resume, loop review persistence, and the git-based `reuse` skip predicate. Parent-written artifacts advance durable phase state and queue hidden engine continuations; every built-in mode flows automatically.
|
|
23
|
-
- **`workflow.json`** in each artifact directory is the canonical, versioned run record. It is atomically replaced after state changes; session custom entries are only pointers for UI/history and never reconstruct an active run.
|
|
24
|
-
- **`extension.ts`** imports each mode's registration object and calls `createWorkflowExtension(config, options)(pi)` for each. One extension load registers the model-facing artifact writer and `end_workflow` tool. Starting and resuming are user-only actions exposed by each mode's slash command.
|
|
25
|
-
- **A mode file** is pure data: the phase list, per-phase artifact filenames, prompt generators, terminal close artifacts, and command/status/entry identities. Prompts receive `{ artifactDir, userPrompt }`. Each mode exports a `WorkflowModeRegistration` object (e.g. `prototypeMode`, `quicktypeMode`); it does not call `createWorkflowExtension` itself.
|
|
26
|
-
|
|
27
|
-
## To add a future mode
|
|
28
|
-
|
|
29
|
-
Copy `modes/quicktype.ts` (the smaller one) and change the config. That's the whole change — the engine never needs editing.
|
|
30
|
-
|
|
31
|
-
### 1. Create the mode file
|
|
32
|
-
|
|
33
|
-
`src/extensions/workflow/modes/rigorous.ts`:
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
import type {
|
|
37
|
-
Phase,
|
|
38
|
-
PromptContext,
|
|
39
|
-
WorkflowConfig,
|
|
40
|
-
WorkflowModeRegistration,
|
|
41
|
-
} from "../state-machine.ts";
|
|
42
|
-
|
|
43
|
-
const phases: Phase[] = [
|
|
44
|
-
"grilling",
|
|
45
|
-
"spec", // ← new phase, not in the built-in set
|
|
46
|
-
"research",
|
|
47
|
-
"plan",
|
|
48
|
-
"reuse",
|
|
49
|
-
"handoff",
|
|
50
|
-
"loop",
|
|
51
|
-
"audit",
|
|
52
|
-
"sign-off", // ← new terminal phase
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const prompts: Partial<Record<Phase, (ctx: PromptContext) => string>> = {
|
|
56
|
-
grilling: ({ artifactDir, userPrompt }) => `…grilling prompt…`,
|
|
57
|
-
spec: ({ artifactDir }) => `…spec prompt…`,
|
|
58
|
-
research: ({ artifactDir }) => `…research prompt…`,
|
|
59
|
-
plan: ({ artifactDir }) => `…plan prompt…`,
|
|
60
|
-
reuse: ({ artifactDir }) => `…reuse prompt…`,
|
|
61
|
-
handoff: ({ artifactDir }) => `…handoff prompt…`,
|
|
62
|
-
loop: ({ artifactDir }) => `…loop prompt…`,
|
|
63
|
-
audit: ({ artifactDir }) => `…audit prompt…`,
|
|
64
|
-
"sign-off": ({ artifactDir }) => `…sign-off prompt…`,
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const config: WorkflowConfig = {
|
|
68
|
-
mode: "rigorous",
|
|
69
|
-
phases,
|
|
70
|
-
phaseArtifacts: {
|
|
71
|
-
grilling: "requirements.md",
|
|
72
|
-
spec: "spec.md",
|
|
73
|
-
research: "research.md",
|
|
74
|
-
plan: "plan.md",
|
|
75
|
-
reuse: "reuse.md",
|
|
76
|
-
handoff: "handoff.md",
|
|
77
|
-
loop: "loop-complete.md",
|
|
78
|
-
audit: "review.md",
|
|
79
|
-
"sign-off": "acceptance.md",
|
|
80
|
-
},
|
|
81
|
-
prompts,
|
|
82
|
-
// Files that must exist before end() can close the workflow.
|
|
83
|
-
// Config-owned — declare whatever your terminal phase requires.
|
|
84
|
-
closeArtifacts: ["acceptance.md", "sign-off-report.md"],
|
|
85
|
-
statusKey: "rigorous",
|
|
86
|
-
entryType: "rigorous-phase",
|
|
87
|
-
footerLabel: "rigorous",
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
export const rigorousMode: WorkflowModeRegistration = {
|
|
91
|
-
config,
|
|
92
|
-
commandName: "rigorous",
|
|
93
|
-
commandDescription:
|
|
94
|
-
"Run the rigorous workflow (grill → spec → research → plan → reuse → handoff → loop → audit → sign-off)",
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export default rigorousMode;
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### 2. Register it in `extension.ts`
|
|
101
|
-
|
|
102
|
-
Add the mode to the `MODES` array in `src/extensions/workflow/extension.ts`:
|
|
103
13
|
|
|
104
|
-
|
|
105
|
-
import { rigorousMode } from "./modes/rigorous.ts";
|
|
106
|
-
|
|
107
|
-
const MODES = [prototypeMode, quicktypeMode, rigorousMode] as const;
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
That's it. The loader picks it up at boot (`package.json` loads only `./extension.ts`), and the `/rigorous` command is registered automatically. There is no model-facing start/resume or `next` tool — users start and resume through `/rigorous`, phases auto-advance as artifacts land, and only `end_workflow({ mode: "rigorous" })` completes the terminal phase.
|
|
14
|
+
Each command starts an async pi-subagents mission. Recover a completed, paused, or confusing run with the pi-subagents mission and status controls (`/subagents`, `/subagents-doctor`, or the corresponding `subagent` tool actions); there is no `/workflow-* resume` command.
|
|
111
15
|
|
|
112
16
|
## Built-in modes
|
|
113
17
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
### `task` — plan → codebase exploration → handoff → build/review loop
|
|
122
|
-
|
|
123
|
-
Task now follows the same phase shape as the other modes, minus grilling/research/audit: an architect subagent produces a validated `plan.md`, an optional explorer subagent produces `reuse.md`, a recapper subagent produces a validated `handoff.md`, and then a builder↔commentator review loop runs (max 3 blocking rounds). A clean review makes the workflow terminal-ready; `end_workflow({ mode: "task" })` completes it.
|
|
124
|
-
|
|
125
|
-
Lifecycle: `plan → reuse → handoff → loop (build ↔ review) → terminal-ready → end_workflow({ mode: "task" })`
|
|
126
|
-
|
|
127
|
-
- `/workflow-task <goal>` — start a new run
|
|
128
|
-
- `/workflow-task resume` — list and resume active runs
|
|
129
|
-
- `/workflow-task help` — show the lifecycle
|
|
130
|
-
- Valid phase artifacts automatically queue the next phase prompt (the workflow does not pause at artifact boundaries)
|
|
131
|
-
- No grilling, research, or audit phases
|
|
132
|
-
- `reuse.md` is optional; it is skipped automatically when the project has no git history
|
|
133
|
-
|
|
134
|
-
### `loop` — direct build/review loop
|
|
135
|
-
|
|
136
|
-
Use this after the plan was already agreed in the current conversation. It captures the agreed context into a parent-owned `handoff.md` artifact, then runs an engine-owned `loop` phase: builder changes workspace code, commentator independently validates the diff and relevant checks, then blocking feedback returns to the builder (max 3 blocking rounds). A clean review writes `loop-complete.md`, makes the run terminal-ready, and requires explicit completion.
|
|
137
|
-
|
|
138
|
-
Fresh subagents do not inherit the parent conversation. The parent forks a `recapper` subagent once to synthesize a concise, self-contained handoff document directly from the inherited conversation. The parent validates the handoff marker and writes `handoff.md` via `write_workflow_artifact`. After that, every builder and commentator call reads `handoff.md` instead of relying on the parent conversation. Persisted `loop-review-N.md` files feed blocking fixes back to the builder.
|
|
139
|
-
|
|
140
|
-
Lifecycle: `handoff → loop (build ↔ review) → terminal-ready → end_workflow({ mode: "loop" })`
|
|
141
|
-
|
|
142
|
-
- `/workflow-loop <goal>` — start a direct build/review run
|
|
143
|
-
- `/workflow-loop resume` / `/workflow-loop resume <id-or-artifact-dir-or-workflow.json>` — list or resume a run
|
|
144
|
-
- `/workflow-loop help` — show the lifecycle
|
|
145
|
-
|
|
146
|
-
## Config reference
|
|
18
|
+
| Command | Shape |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `/workflow-task` | plan → reuse → handoff → build/review/fix loop |
|
|
21
|
+
| `/workflow-prototype` | parallel research + codebase exploration → plan → handoff → build/review/fix loop → audit |
|
|
22
|
+
| `/workflow-quicktype` | plan → reuse → handoff → build/review/fix loop → audit |
|
|
23
|
+
| `/workflow-loop` | direct build/review/fix loop for an already-agreed plan |
|
|
147
24
|
|
|
148
|
-
|
|
149
|
-
|---|---|---|
|
|
150
|
-
| `mode` | `string` | Mode name, echoed in entry payloads and messages. |
|
|
151
|
-
| `phases` | `Phase[]` | Ordered phase list. `Phase` is `string` — new phase names are allowed. |
|
|
152
|
-
| `phaseArtifacts` | `Partial<Record<Phase, string>>` | The artifact file each phase must produce before advancing. Omit a phase to skip its gate. |
|
|
153
|
-
| `prompts` | `Partial<Record<Phase, (ctx) => string>>` | Prompt generator per phase. `ctx = { artifactDir, userPrompt }`. |
|
|
154
|
-
| `closeArtifacts` | `string[]` | Files that must exist before `end()` succeeds. Config-owned, no built-in default. |
|
|
155
|
-
| `skipRules?` | `{ phase, shouldSkip }[]` | Optional per-phase skip rules. `shouldSkip` is a boolean predicate; when true the engine skips to the next phase. Omit to use the adapter's default (skip `reuse` when the project has no git history). |
|
|
156
|
-
| `statusKey` | `string` | Footer status key. |
|
|
157
|
-
| `entryType` | `string` | Session-history custom-type. It stores a pointer only; `workflow.json` is canonical. |
|
|
158
|
-
| `footerLabel` | `string` | Label shown in the footer (`● label · step/total phase`). |
|
|
25
|
+
The prototype mode uses `runs.all` for its independent research and codebase-exploration work. All modes use `runs.run` for ordered handoffs. The build/review/fix loop uses `workflowScript` because its next step depends on the reviewer result; a blocking review gets a scoped fix round, while `clean` plus no remaining work ends the run.
|
|
159
26
|
|
|
160
|
-
|
|
27
|
+
## Extending workflows
|
|
161
28
|
|
|
162
|
-
The
|
|
29
|
+
The extension seam is `src/extensions/workflow/modes.ts`.
|
|
163
30
|
|
|
164
|
-
|
|
165
|
-
|---|---|
|
|
166
|
-
| `commandName` | The `/<command>` name users type to kick off the workflow. |
|
|
167
|
-
| `commandDescription` | Description shown in the command list. |
|
|
31
|
+
Add one `WorkflowMode` entry to `WORKFLOW_MODES`:
|
|
168
32
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
- `/workflow-prototype help`, `/workflow-quicktype help`, `/workflow-task help`, or `/workflow-loop help` shows the start, resume, continue, and explicit-completion lifecycle.
|
|
179
|
-
|
|
180
|
-
Resume validates the selected file is under the artifacts base, belongs to that mode, is active, and matches its containing directory. It reconciles the current expected artifact once before emitting the current prompt, covering a crash after `write_workflow_artifact` writes the file but before the phase-state write. Valid artifact writes queue one hidden engine-controlled continuation using `steer` and terminate the current parent turn; invalid writes stay in the current phase and do not terminate. Prompts injected by start, resume, and continue commands are hidden custom messages rather than visible synthetic user messages. Transition-capable calls (`write_workflow_artifact`, loop commentator transitions, and `end_workflow`) must be the sole tool call in their assistant batch; the adapter fails closed when that cannot be proven. Corrupt records are skipped during discovery. Reloads never auto-resume; the user must explicitly resume through a mode's slash command.
|
|
181
|
-
|
|
182
|
-
A valid terminal artifact makes a workflow **terminal-ready**; it does not complete the run. Call `end_workflow({ mode })` to write `status: "completed"`, append the done entry, and terminate. This is the only completion path.
|
|
183
|
-
|
|
184
|
-
## Artifact ownership
|
|
185
|
-
|
|
186
|
-
Workflow artifacts have one writer: the parent session's `write_workflow_artifact` tool. Every workflow child call uses `output: false` and returns inline. In artifact phases (`plan`, `reuse`, `handoff`, and `audit`), the parent inspects that result, validates any required marker, and immediately passes it to `write_workflow_artifact`. Child output paths and fallback persistence are deliberately disabled; a child result alone cannot create an artifact or advance a phase.
|
|
187
|
-
|
|
188
|
-
The implement/review loop is the explicit exception to parent persistence, not inline return: the engine persists `loop-review-<round>.md` and `loop-complete.md` from commentator results so it can manage review rounds. Builders only change workspace code.
|
|
189
|
-
|
|
190
|
-
Every state-machine method returns a `WorkflowEffect` — a discriminated union the adapter switches on:
|
|
191
|
-
|
|
192
|
-
| Effect | Meaning |
|
|
193
|
-
|---|---|
|
|
194
|
-
| `started` | `start()` succeeded; first phase prompt + entry + footer. |
|
|
195
|
-
| `alreadyActive` | `start()` called while a workflow is active. |
|
|
196
|
-
| `advanced` | Phase moved forward (optionally `skipped` a phase). |
|
|
197
|
-
| `blocked` | Current phase's artifact is missing. |
|
|
198
|
-
| `terminalNeedsArtifacts` | At the last phase; a close artifact is missing. |
|
|
199
|
-
| `terminalReady` | At the last phase; all close artifacts present — call `end()`. |
|
|
200
|
-
| `closed` | `end()` succeeded; workflow finished. |
|
|
201
|
-
| `endBlocked` | `end()` called from the wrong phase or with close artifacts missing. |
|
|
202
|
-
| `idle` | No active workflow. |
|
|
203
|
-
| `noOp` | Auto-advance checked, nothing to do (not active, not armed, artifact not present, or already advancing). |
|
|
204
|
-
|
|
205
|
-
The `tool_result` auto-advance hook is one line:
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
const eff = await sm.onArtifactMaybe(deps);
|
|
209
|
-
applyEffect(pi, ctx, config, eff);
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
The reentrancy guard lives inside `onArtifactMaybe` — concurrent calls return `noOp`, so a double `write` in one turn cannot double-advance the phase.
|
|
213
|
-
|
|
214
|
-
## Skip rules
|
|
215
|
-
|
|
216
|
-
By default the adapter skips the `reuse` phase when the project has no git history. To override, supply `skipRules` in your config:
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
skipRules: [
|
|
220
|
-
{ phase: "research", shouldSkip: async () => isWellUnderstoodDomain() },
|
|
221
|
-
{ phase: "reuse", shouldSkip: async () => isEmptyProject() },
|
|
222
|
-
],
|
|
33
|
+
```ts
|
|
34
|
+
{
|
|
35
|
+
command: "workflow-rigorous",
|
|
36
|
+
description: "Run the rigorous workflow.",
|
|
37
|
+
launch: (goal) => ({
|
|
38
|
+
workflowScript: `const goal = ${JSON.stringify(goal)};
|
|
39
|
+
return runs.run("plan", { agent: "architect", task: "Plan: " + goal });`,
|
|
40
|
+
}),
|
|
41
|
+
}
|
|
223
42
|
```
|
|
224
43
|
|
|
225
|
-
`
|
|
226
|
-
|
|
227
|
-
## Testing a mode
|
|
44
|
+
`launch(goal)` returns pi-subagents public execution fields. Prefer the native execution shapes where the mode is static:
|
|
228
45
|
|
|
229
|
-
|
|
46
|
+
- `chain` for a fixed ordered sequence, including human checkpoints.
|
|
47
|
+
- `tasks` for independent, read-only parallel work.
|
|
48
|
+
- `workflowScript` only when the orchestration is conditional, iterative, needs dynamic fan-out, or combines native run operations.
|
|
230
49
|
|
|
231
|
-
|
|
232
|
-
import { WorkflowStateMachine } from "../extensions/workflow/state-machine.ts";
|
|
50
|
+
`extension.ts` automatically registers every entry in `WORKFLOW_MODES`; no new command plumbing is needed. The mode owns task wording and execution shape. The extension owns only argument validation, async launch, agent scope, and mission creation.
|
|
233
51
|
|
|
234
|
-
|
|
235
|
-
const deps = {
|
|
236
|
-
async artifactExists(phase, dir) {
|
|
237
|
-
const file = config.phaseArtifacts[phase];
|
|
238
|
-
return file ? files.has(`${dir}/${file}`) : true;
|
|
239
|
-
},
|
|
240
|
-
async fileExists(path) { return files.has(path); },
|
|
241
|
-
async mkdirArtifactDir() {},
|
|
242
|
-
artifactPathFor: (goal) => `/fake/${goal}`,
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const sm = new WorkflowStateMachine(config);
|
|
246
|
-
const eff = await sm.start("build X", deps);
|
|
247
|
-
expect(eff.kind).toBe("started");
|
|
248
|
-
```
|
|
52
|
+
## Constraints
|
|
249
53
|
|
|
250
|
-
|
|
54
|
+
- `workflowScript`, `chain`, and `tasks` are alternative top-level pi-subagents execution modes. A mode that needs an auto-loop and preceding/following phases should use `workflowScript` and call `runs.run` / `runs.all` within it.
|
|
55
|
+
- Keep one writer at a time. Parallel lanes should be research or review unless they are isolated in worktrees.
|
|
56
|
+
- Workflow progress ledgers are under `.pi-subagents/progress/` and are local runtime artifacts, not durable workflow state.
|
|
57
|
+
- The outer mission and pi-subagents run artifacts are the recovery record.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selesai/code",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|