@leing2021/super-pi 0.22.0 → 0.23.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.
Files changed (101) hide show
  1. package/README.md +38 -209
  2. package/extensions/ce-core/index.ts +190 -7
  3. package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
  4. package/extensions/ce-core/tools/subagent.ts +2 -2
  5. package/package.json +4 -18
  6. package/rules/common/naming.md +154 -0
  7. package/skills/02-plan/SKILL.md +3 -2
  8. package/skills/03-work/SKILL.md +9 -6
  9. package/skills/04-review/SKILL.md +3 -2
  10. package/skills/06-next/SKILL.md +38 -10
  11. package/skills/06-next/references/recommendation-logic.md +2 -2
  12. package/skills/07-worktree/SKILL.md +9 -8
  13. package/skills/{09-help → 08-help}/SKILL.md +3 -3
  14. package/skills/{09-help → 08-help}/references/workflow-sequence.md +2 -1
  15. package/skills/references/language-detection.md +44 -0
  16. package/extensions/subagent/agent-management.ts +0 -595
  17. package/extensions/subagent/agent-manager-chain-detail.ts +0 -162
  18. package/extensions/subagent/agent-manager-detail.ts +0 -231
  19. package/extensions/subagent/agent-manager-edit.ts +0 -390
  20. package/extensions/subagent/agent-manager-list.ts +0 -278
  21. package/extensions/subagent/agent-manager-parallel.ts +0 -304
  22. package/extensions/subagent/agent-manager.ts +0 -705
  23. package/extensions/subagent/agent-scope.ts +0 -8
  24. package/extensions/subagent/agent-selection.ts +0 -25
  25. package/extensions/subagent/agent-serializer.ts +0 -123
  26. package/extensions/subagent/agent-templates.ts +0 -62
  27. package/extensions/subagent/agents/context-builder.md +0 -37
  28. package/extensions/subagent/agents/delegate.md +0 -9
  29. package/extensions/subagent/agents/oracle.md +0 -73
  30. package/extensions/subagent/agents/planner.md +0 -52
  31. package/extensions/subagent/agents/researcher.md +0 -50
  32. package/extensions/subagent/agents/reviewer.md +0 -38
  33. package/extensions/subagent/agents/scout.md +0 -48
  34. package/extensions/subagent/agents/worker.md +0 -52
  35. package/extensions/subagent/agents.ts +0 -761
  36. package/extensions/subagent/artifacts.ts +0 -100
  37. package/extensions/subagent/async-execution.ts +0 -520
  38. package/extensions/subagent/async-job-tracker.ts +0 -216
  39. package/extensions/subagent/async-status.ts +0 -241
  40. package/extensions/subagent/chain-clarify.ts +0 -1364
  41. package/extensions/subagent/chain-execution.ts +0 -853
  42. package/extensions/subagent/chain-serializer.ts +0 -126
  43. package/extensions/subagent/completion-dedupe.ts +0 -65
  44. package/extensions/subagent/doctor.ts +0 -200
  45. package/extensions/subagent/execution.ts +0 -738
  46. package/extensions/subagent/file-coalescer.ts +0 -42
  47. package/extensions/subagent/fork-context.ts +0 -63
  48. package/extensions/subagent/formatters.ts +0 -122
  49. package/extensions/subagent/frontmatter.ts +0 -31
  50. package/extensions/subagent/index.ts +0 -585
  51. package/extensions/subagent/intercom-bridge.ts +0 -240
  52. package/extensions/subagent/jsonl-writer.ts +0 -83
  53. package/extensions/subagent/model-fallback.ts +0 -108
  54. package/extensions/subagent/notify.ts +0 -110
  55. package/extensions/subagent/parallel-utils.ts +0 -108
  56. package/extensions/subagent/pi-args.ts +0 -138
  57. package/extensions/subagent/pi-spawn.ts +0 -100
  58. package/extensions/subagent/post-exit-stdio-guard.ts +0 -87
  59. package/extensions/subagent/prompt-template-bridge.ts +0 -399
  60. package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
  61. package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
  62. package/extensions/subagent/prompts/parallel-research.md +0 -50
  63. package/extensions/subagent/prompts/parallel-review.md +0 -40
  64. package/extensions/subagent/render-helpers.ts +0 -82
  65. package/extensions/subagent/render.ts +0 -836
  66. package/extensions/subagent/result-intercom.ts +0 -237
  67. package/extensions/subagent/result-watcher.ts +0 -171
  68. package/extensions/subagent/run-history.ts +0 -57
  69. package/extensions/subagent/run-status.ts +0 -136
  70. package/extensions/subagent/schemas.ts +0 -164
  71. package/extensions/subagent/session-tokens.ts +0 -50
  72. package/extensions/subagent/settings.ts +0 -367
  73. package/extensions/subagent/single-output.ts +0 -97
  74. package/extensions/subagent/skills.ts +0 -626
  75. package/extensions/subagent/slash-bridge.ts +0 -176
  76. package/extensions/subagent/slash-commands.ts +0 -303
  77. package/extensions/subagent/slash-live-state.ts +0 -294
  78. package/extensions/subagent/subagent-control.ts +0 -150
  79. package/extensions/subagent/subagent-executor.ts +0 -1899
  80. package/extensions/subagent/subagent-prompt-runtime.ts +0 -75
  81. package/extensions/subagent/subagent-runner.ts +0 -1470
  82. package/extensions/subagent/subagents-status.ts +0 -472
  83. package/extensions/subagent/text-editor.ts +0 -272
  84. package/extensions/subagent/top-level-async.ts +0 -15
  85. package/extensions/subagent/types.ts +0 -623
  86. package/extensions/subagent/utils.ts +0 -456
  87. package/extensions/subagent/worktree.ts +0 -579
  88. package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
  89. package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
  90. package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
  91. package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
  92. package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
  93. package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
  94. package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
  95. package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
  96. package/extensions/super-pi-extension/index.ts +0 -129
  97. package/extensions/super-pi-extension/model-sync.ts +0 -204
  98. package/skills/08-status/SKILL.md +0 -41
  99. package/skills/08-status/references/artifact-locations.md +0 -10
  100. package/skills/10-rules/SKILL.md +0 -76
  101. package/skills/pi-subagents/SKILL.md +0 -566
@@ -1,164 +0,0 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
3
- /**
4
- * TypeBox schemas for subagent tool parameters
5
- */
6
-
7
- import { Type } from "typebox";
8
-
9
- const SkillOverride = Type.Unsafe({
10
- type: ["string", "array", "boolean"],
11
- items: { type: "string" },
12
- description: "Skill name(s) to inject (comma-separated), array of strings, or boolean (false disables, true uses default)",
13
- });
14
-
15
- const OutputOverride = Type.Unsafe({
16
- type: ["string", "boolean"],
17
- description: "Output filename/path (string), or false to disable file output",
18
- });
19
-
20
- const ReadsOverride = Type.Unsafe({
21
- type: ["array", "boolean"],
22
- items: { type: "string" },
23
- description: "Files to read before running (array of filenames), or false to disable",
24
- });
25
-
26
- export const TaskItem = Type.Object({
27
- agent: Type.String(),
28
- task: Type.String(),
29
- cwd: Type.Optional(Type.String()),
30
- count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),
31
- output: Type.Optional(OutputOverride),
32
- reads: Type.Optional(ReadsOverride),
33
- progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking for this task" })),
34
- model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
35
- skill: Type.Optional(SkillOverride),
36
- });
37
-
38
- // Sequential chain step (single agent)
39
- export const SequentialStepSchema = Type.Object({
40
- agent: Type.String(),
41
- task: Type.Optional(Type.String({
42
- description: "Task template with variables: {task}=original request, {previous}=prior step's text response, {chain_dir}=shared folder. Required for first step, defaults to '{previous}' for subsequent steps."
43
- })),
44
- cwd: Type.Optional(Type.String()),
45
- output: Type.Optional(OutputOverride),
46
- reads: Type.Optional(ReadsOverride),
47
- progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
48
- skill: Type.Optional(SkillOverride),
49
- model: Type.Optional(Type.String({ description: "Override model for this step" })),
50
- });
51
-
52
- // Parallel task item (within a parallel step)
53
- export const ParallelTaskSchema = Type.Object({
54
- agent: Type.String(),
55
- task: Type.Optional(Type.String({ description: "Task template with {task}, {previous}, {chain_dir} variables. Defaults to {previous}." })),
56
- cwd: Type.Optional(Type.String()),
57
- count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),
58
- output: Type.Optional(OutputOverride),
59
- reads: Type.Optional(ReadsOverride),
60
- progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
61
- skill: Type.Optional(SkillOverride),
62
- model: Type.Optional(Type.String({ description: "Override model for this task" })),
63
- });
64
-
65
- // Parallel chain step (multiple agents running concurrently)
66
- export const ParallelStepSchema = Type.Object({
67
- parallel: Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" }),
68
- concurrency: Type.Optional(Type.Number({ description: "Max concurrent tasks (default: 4)" })),
69
- failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure (default: false)" })),
70
- worktree: Type.Optional(Type.Boolean({
71
- description: "Create isolated git worktrees for each parallel task."
72
- })),
73
- });
74
-
75
- // Flattened so providers that reject anyOf/oneOf can still accept either sequential or parallel steps.
76
- export const ChainItem = Type.Object({
77
- agent: Type.Optional(Type.String({ description: "Sequential step agent name" })),
78
- task: Type.Optional(Type.String({
79
- description: "Task template with variables: {task}=original request, {previous}=prior step's text response, {chain_dir}=shared folder. Required for first step, defaults to '{previous}' for subsequent steps."
80
- })),
81
- cwd: Type.Optional(Type.String()),
82
- output: Type.Optional(OutputOverride),
83
- reads: Type.Optional(ReadsOverride),
84
- progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
85
- skill: Type.Optional(SkillOverride),
86
- model: Type.Optional(Type.String({ description: "Override model for this step" })),
87
- parallel: Type.Optional(Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" })),
88
- concurrency: Type.Optional(Type.Number({ description: "Max concurrent tasks (default: 4)" })),
89
- failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure (default: false)" })),
90
- worktree: Type.Optional(Type.Boolean({
91
- description: "Create isolated git worktrees for each parallel task."
92
- })),
93
- }, { description: "Chain step: use {agent, task?, ...} for sequential or {parallel: [...]} for concurrent execution" });
94
-
95
- export const ControlOverrides = Type.Object({
96
- enabled: Type.Optional(Type.Boolean({ description: "Enable/disable subagent control attention tracking for this run" })),
97
- needsAttentionAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "No-observed-activity window before a run needs attention" })),
98
- notifyOn: Type.Optional(Type.Array(Type.String({ enum: ["needs_attention"] }), {
99
- description: "Control event types that should notify the parent/orchestrator. Defaults to needs_attention.",
100
- })),
101
- notifyChannels: Type.Optional(Type.Array(Type.String({ enum: ["event", "async", "intercom"] }), {
102
- description: "Notification channels to use when available. Defaults to event, async, and intercom.",
103
- })),
104
- });
105
-
106
- export const SubagentParams = Type.Object({
107
- agent: Type.Optional(Type.String({ description: "Agent name (SINGLE mode) or target for management get/update/delete" })),
108
- task: Type.Optional(Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })),
109
- // Management action (when present, tool operates in management mode)
110
- action: Type.Optional(Type.String({
111
- description: "Action: 'list', 'get', 'create', 'update', 'delete', 'status', 'interrupt', or 'doctor'. Omit for execution mode."
112
- })),
113
- id: Type.Optional(Type.String({
114
- description: "Run id or prefix for action='status' or action='interrupt'."
115
- })),
116
- runId: Type.Optional(Type.String({
117
- description: "Target run ID for action='interrupt'. Defaults to the most recently active controllable run in this session. Prefer id for new calls."
118
- })),
119
- dir: Type.Optional(Type.String({
120
- description: "Async run directory for action='status'."
121
- })),
122
- // Chain identifier for management (can't reuse 'chain' — that's the execution array)
123
- chainName: Type.Optional(Type.String({
124
- description: "Chain name for get/update/delete management actions"
125
- })),
126
- // Agent/chain configuration for create/update (nested to avoid conflicts with execution fields)
127
- config: Type.Optional(Type.Unsafe({
128
- type: ["object", "string"],
129
- additionalProperties: true,
130
- description: "Agent or chain config for create/update. Agent: name, description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, model, tools (comma-separated), extensions (comma-separated), skills (comma-separated), thinking, output, reads, progress, maxSubagentDepth. Chain: name, description, scope, steps (array of {agent, task?, output?, reads?, model?, skills?, progress?}). Presence of 'steps' creates a chain instead of an agent. String values must be valid JSON."
131
- })),
132
- tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?, output?, reads?, progress?}, ...]" })),
133
- concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or 4." })),
134
- worktree: Type.Optional(Type.Boolean({
135
- description: "Create isolated git worktrees for each parallel task. " +
136
- "Prevents filesystem conflicts. Requires clean git state. " +
137
- "Per-worktree diffs included in output."
138
- })),
139
- chain: Type.Optional(Type.Array(ChainItem, { description: "CHAIN mode: sequential pipeline where each step's response becomes {previous} for the next. Use {task}, {previous}, {chain_dir} in task templates." })),
140
- context: Type.Optional(Type.String({
141
- enum: ["fresh", "fork"],
142
- description: "'fresh' (default) or 'fork' to branch from parent session",
143
- })),
144
- chainDir: Type.Optional(Type.String({ description: "Persistent directory for chain artifacts. Default: a user-scoped temp directory under <tmpdir>/ (auto-cleaned after 24h)" })),
145
- async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
146
- agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)" })),
147
- cwd: Type.Optional(Type.String()),
148
- artifacts: Type.Optional(Type.Boolean({ description: "Write debug artifacts (default: true)" })),
149
- includeProgress: Type.Optional(Type.Boolean({ description: "Include full progress in result (default: false)" })),
150
- share: Type.Optional(Type.Boolean({ description: "Upload session to GitHub Gist for sharing (default: false)" })),
151
- sessionDir: Type.Optional(
152
- Type.String({ description: "Directory to store session logs (default: temp; enables sessions even if share=false)" }),
153
- ),
154
- // Clarification TUI
155
- clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution (default: true for chains, false for single/parallel). Implies sync mode." })),
156
- control: Type.Optional(ControlOverrides),
157
- // Solo agent overrides
158
- output: Type.Optional(Type.Unsafe({
159
- type: ["string", "boolean"],
160
- description: "Output file for single agent (string), or false to disable. Relative paths resolve against cwd.",
161
- })),
162
- skill: Type.Optional(SkillOverride),
163
- model: Type.Optional(Type.String({ description: "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')" })),
164
- });
@@ -1,50 +0,0 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
3
- import * as fs from "node:fs";
4
- import * as path from "node:path";
5
-
6
- export interface TokenUsage {
7
- input: number;
8
- output: number;
9
- total: number;
10
- }
11
-
12
- function findLatestSessionFile(sessionDir: string): string | null {
13
- try {
14
- const files = fs.readdirSync(sessionDir)
15
- .filter((f) => f.endsWith(".jsonl"))
16
- .map((f) => path.join(sessionDir, f));
17
- if (files.length === 0) return null;
18
- files.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
19
- return files[0] ?? null;
20
- } catch {
21
- return null;
22
- }
23
- }
24
-
25
- export function parseSessionTokens(sessionDir: string): TokenUsage | null {
26
- const sessionFile = findLatestSessionFile(sessionDir);
27
- if (!sessionFile) return null;
28
- try {
29
- const content = fs.readFileSync(sessionFile, "utf-8");
30
- let input = 0;
31
- let output = 0;
32
- for (const line of content.split("\n")) {
33
- if (!line.trim()) continue;
34
- try {
35
- const entry = JSON.parse(line);
36
- const usage = entry.usage ?? entry.message?.usage;
37
- if (usage) {
38
- input += usage.inputTokens ?? usage.input ?? 0;
39
- output += usage.outputTokens ?? usage.output ?? 0;
40
- }
41
- } catch {
42
- // Ignore malformed lines while scanning usage entries.
43
- }
44
- }
45
- return { input, output, total: input + output };
46
- } catch {
47
- // Usage extraction should not fail the run.
48
- return null;
49
- }
50
- }
@@ -1,367 +0,0 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
3
- /**
4
- * Chain behavior, template resolution, and directory management
5
- */
6
-
7
- import * as fs from "node:fs";
8
- import * as path from "node:path";
9
- import type { AgentConfig } from "./agents.ts";
10
- import { normalizeSkillInput } from "./skills.ts";
11
- import { CHAIN_RUNS_DIR } from "./types.ts";
12
- const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
13
- const INITIAL_PROGRESS_CONTENT = "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n";
14
-
15
- // =============================================================================
16
- // Behavior Resolution Types
17
- // =============================================================================
18
-
19
- export interface ResolvedStepBehavior {
20
- output: string | false;
21
- reads: string[] | false;
22
- progress: boolean;
23
- skills: string[] | false;
24
- model?: string;
25
- }
26
-
27
- export interface StepOverrides {
28
- output?: string | false;
29
- reads?: string[] | false;
30
- progress?: boolean;
31
- skills?: string[] | false;
32
- model?: string;
33
- }
34
-
35
- // =============================================================================
36
- // Chain Step Types
37
- // =============================================================================
38
-
39
- /** Sequential step: single agent execution */
40
- export interface SequentialStep {
41
- agent: string;
42
- task?: string;
43
- cwd?: string;
44
- output?: string | false;
45
- reads?: string[] | false;
46
- progress?: boolean;
47
- skill?: string | string[] | false;
48
- model?: string;
49
- }
50
-
51
- /** Parallel task item within a parallel step */
52
- export interface ParallelTaskItem {
53
- agent: string;
54
- task?: string;
55
- cwd?: string;
56
- count?: number;
57
- output?: string | false;
58
- reads?: string[] | false;
59
- progress?: boolean;
60
- skill?: string | string[] | false;
61
- model?: string;
62
- }
63
-
64
- /** Parallel step: multiple agents running concurrently */
65
- export interface ParallelStep {
66
- parallel: ParallelTaskItem[];
67
- concurrency?: number;
68
- failFast?: boolean;
69
- worktree?: boolean;
70
- }
71
-
72
- /** Union type for chain steps */
73
- export type ChainStep = SequentialStep | ParallelStep;
74
-
75
- // =============================================================================
76
- // Type Guards
77
- // =============================================================================
78
-
79
- export function isParallelStep(step: ChainStep): step is ParallelStep {
80
- return "parallel" in step && Array.isArray((step as ParallelStep).parallel);
81
- }
82
-
83
- /** Get all agent names in a step (single for sequential, multiple for parallel) */
84
- export function getStepAgents(step: ChainStep): string[] {
85
- if (isParallelStep(step)) {
86
- return step.parallel.map((t) => t.agent);
87
- }
88
- return [step.agent];
89
- }
90
-
91
- // =============================================================================
92
- // Chain Directory Management
93
- // =============================================================================
94
-
95
- export function createChainDir(runId: string, baseDir?: string): string {
96
- const chainDir = path.join(baseDir ? path.resolve(baseDir) : CHAIN_RUNS_DIR, runId);
97
- fs.mkdirSync(chainDir, { recursive: true });
98
- return chainDir;
99
- }
100
-
101
- export function removeChainDir(chainDir: string): void {
102
- try {
103
- fs.rmSync(chainDir, { recursive: true });
104
- } catch {
105
- // Chain cleanup is best-effort. Runs can already have cleaned their temp dir.
106
- }
107
- }
108
-
109
- export function cleanupOldChainDirs(): void {
110
- if (!fs.existsSync(CHAIN_RUNS_DIR)) return;
111
- const now = Date.now();
112
- let dirs: string[];
113
- try {
114
- dirs = fs.readdirSync(CHAIN_RUNS_DIR);
115
- } catch {
116
- // Startup cleanup is best-effort. If the scoped temp root is unreadable,
117
- // skip cleanup instead of failing extension startup.
118
- return;
119
- }
120
-
121
- for (const dir of dirs) {
122
- try {
123
- const dirPath = path.join(CHAIN_RUNS_DIR, dir);
124
- const stat = fs.statSync(dirPath);
125
- if (stat.isDirectory() && now - stat.mtimeMs > CHAIN_DIR_MAX_AGE_MS) {
126
- fs.rmSync(dirPath, { recursive: true });
127
- }
128
- } catch {
129
- // Skip directories that can't be processed; continue with others
130
- }
131
- }
132
- }
133
-
134
- // =============================================================================
135
- // Template Resolution
136
- // =============================================================================
137
-
138
- /** Resolved templates for a chain - string for sequential, string[] for parallel */
139
- export type ResolvedTemplates = (string | string[])[];
140
-
141
- /**
142
- * Resolve templates for a chain with parallel step support.
143
- * Returns string for sequential steps, string[] for parallel steps.
144
- */
145
- export function resolveChainTemplates(
146
- steps: ChainStep[],
147
- ): ResolvedTemplates {
148
- return steps.map((step, i) => {
149
- if (isParallelStep(step)) {
150
- // Parallel step: resolve each task's template
151
- return step.parallel.map((task) => {
152
- if (task.task) return task.task;
153
- // Default for parallel tasks is {previous}
154
- return "{previous}";
155
- });
156
- }
157
- // Sequential step: existing logic
158
- const seq = step as SequentialStep;
159
- if (seq.task) return seq.task;
160
- // Default: first step uses {task}, others use {previous}
161
- return i === 0 ? "{task}" : "{previous}";
162
- });
163
- }
164
-
165
- // =============================================================================
166
- // Behavior Resolution
167
- // =============================================================================
168
-
169
- /**
170
- * Resolve effective chain behavior per step.
171
- * Priority: step override > agent frontmatter > false (disabled)
172
- */
173
- export function resolveStepBehavior(
174
- agentConfig: AgentConfig,
175
- stepOverrides: StepOverrides,
176
- chainSkills?: string[],
177
- ): ResolvedStepBehavior {
178
- // Output: step override > frontmatter > false (no output)
179
- const output =
180
- stepOverrides.output !== undefined
181
- ? stepOverrides.output
182
- : agentConfig.output ?? false;
183
-
184
- // Reads: step override > frontmatter defaultReads > false (no reads)
185
- const reads =
186
- stepOverrides.reads !== undefined
187
- ? stepOverrides.reads
188
- : agentConfig.defaultReads ?? false;
189
-
190
- // Progress: step override > frontmatter defaultProgress > false
191
- const progress =
192
- stepOverrides.progress !== undefined
193
- ? stepOverrides.progress
194
- : agentConfig.defaultProgress ?? false;
195
-
196
- let skills: string[] | false;
197
- if (stepOverrides.skills === false) {
198
- skills = false;
199
- } else if (stepOverrides.skills !== undefined) {
200
- skills = [...stepOverrides.skills];
201
- if (chainSkills && chainSkills.length > 0) {
202
- skills = [...new Set([...skills, ...chainSkills])];
203
- }
204
- } else {
205
- skills = agentConfig.skills ? [...agentConfig.skills] : [];
206
- if (chainSkills && chainSkills.length > 0) {
207
- skills = [...new Set([...skills, ...chainSkills])];
208
- }
209
- }
210
-
211
- const model = stepOverrides.model ?? agentConfig.model;
212
- return { output, reads, progress, skills, model };
213
- }
214
-
215
- // =============================================================================
216
- // Chain Instruction Injection
217
- // =============================================================================
218
-
219
- /**
220
- * Resolve a file path: absolute paths pass through, relative paths get chainDir prepended.
221
- */
222
- function resolveChainPath(filePath: string, chainDir: string): string {
223
- return path.isAbsolute(filePath) ? filePath : path.join(chainDir, filePath);
224
- }
225
-
226
- /**
227
- * Build chain instructions from resolved behavior.
228
- * These are appended to the task to tell the agent what to read/write.
229
- */
230
- export function writeInitialProgressFile(progressDir: string): void {
231
- fs.writeFileSync(path.join(progressDir, "progress.md"), INITIAL_PROGRESS_CONTENT);
232
- }
233
-
234
- export function buildChainInstructions(
235
- behavior: ResolvedStepBehavior,
236
- chainDir: string,
237
- isFirstProgressAgent: boolean,
238
- previousSummary?: string,
239
- ): { prefix: string; suffix: string } {
240
- const prefixParts: string[] = [];
241
- const suffixParts: string[] = [];
242
-
243
- // READS - prepend to override any hardcoded filenames in task text
244
- if (behavior.reads && behavior.reads.length > 0) {
245
- const files = behavior.reads.map((f) => resolveChainPath(f, chainDir));
246
- prefixParts.push(`[Read from: ${files.join(", ")}]`);
247
- }
248
-
249
- // OUTPUT - prepend so agent knows where to write
250
- if (behavior.output) {
251
- const outputPath = resolveChainPath(behavior.output, chainDir);
252
- prefixParts.push(`[Write to: ${outputPath}]`);
253
- }
254
-
255
- // Progress instructions in suffix (less critical)
256
- if (behavior.progress) {
257
- const progressPath = path.join(chainDir, "progress.md");
258
- if (isFirstProgressAgent) {
259
- suffixParts.push(`Create and maintain progress at: ${progressPath}`);
260
- } else {
261
- suffixParts.push(`Update progress at: ${progressPath}`);
262
- }
263
- }
264
-
265
- // Include previous step's summary in suffix if available
266
- if (previousSummary && previousSummary.trim()) {
267
- suffixParts.push(`Previous step output:\n${previousSummary.trim()}`);
268
- }
269
-
270
- const prefix = prefixParts.length > 0
271
- ? prefixParts.join("\n") + "\n\n"
272
- : "";
273
-
274
- const suffix = suffixParts.length > 0
275
- ? "\n\n---\n" + suffixParts.join("\n")
276
- : "";
277
-
278
- return { prefix, suffix };
279
- }
280
-
281
- // =============================================================================
282
- // Parallel Step Support
283
- // =============================================================================
284
-
285
- /**
286
- * Resolve behaviors for all tasks in a parallel step.
287
- * Creates namespaced output paths to avoid collisions.
288
- */
289
- export function resolveParallelBehaviors(
290
- tasks: ParallelTaskItem[],
291
- agentConfigs: AgentConfig[],
292
- stepIndex: number,
293
- chainSkills?: string[],
294
- ): ResolvedStepBehavior[] {
295
- return tasks.map((task, taskIndex) => {
296
- const config = agentConfigs.find((a) => a.name === task.agent);
297
- if (!config) {
298
- throw new Error(`Unknown agent: ${task.agent}`);
299
- }
300
-
301
- // Build subdirectory path for this parallel task
302
- const subdir = path.join(`parallel-${stepIndex}`, `${taskIndex}-${task.agent}`);
303
-
304
- // Output: task override > agent default (namespaced) > false
305
- // Absolute paths pass through unchanged; relative paths get namespaced under subdir
306
- let output: string | false = false;
307
- if (task.output !== undefined) {
308
- if (task.output === false) {
309
- output = false;
310
- } else if (path.isAbsolute(task.output)) {
311
- output = task.output; // Absolute path: use as-is
312
- } else {
313
- output = path.join(subdir, task.output); // Relative: namespace under subdir
314
- }
315
- } else if (config.output) {
316
- // Agent defaults are always relative, so namespace them
317
- output = path.join(subdir, config.output);
318
- }
319
-
320
- // Reads: task override > agent default > false
321
- const reads =
322
- task.reads !== undefined ? task.reads : config.defaultReads ?? false;
323
-
324
- // Progress: task override > agent default > false
325
- const progress =
326
- task.progress !== undefined
327
- ? task.progress
328
- : config.defaultProgress ?? false;
329
-
330
- const taskSkillInput = normalizeSkillInput(task.skill);
331
- let skills: string[] | false;
332
- if (taskSkillInput === false) {
333
- skills = false;
334
- } else if (taskSkillInput !== undefined) {
335
- skills = [...taskSkillInput];
336
- if (chainSkills && chainSkills.length > 0) {
337
- skills = [...new Set([...skills, ...chainSkills])];
338
- }
339
- } else {
340
- skills = config.skills ? [...config.skills] : [];
341
- if (chainSkills && chainSkills.length > 0) {
342
- skills = [...new Set([...skills, ...chainSkills])];
343
- }
344
- }
345
-
346
- const model = task.model ?? config.model;
347
- return { output, reads, progress, skills, model };
348
- });
349
- }
350
-
351
- /**
352
- * Create subdirectories for parallel step outputs
353
- */
354
- export function createParallelDirs(
355
- chainDir: string,
356
- stepIndex: number,
357
- taskCount: number,
358
- agentNames: string[],
359
- ): void {
360
- for (let i = 0; i < taskCount; i++) {
361
- const subdir = path.join(chainDir, `parallel-${stepIndex}`, `${i}-${agentNames[i]}`);
362
- fs.mkdirSync(subdir, { recursive: true });
363
- }
364
- }
365
-
366
- export type { ParallelTaskResult } from "./parallel-utils.ts";
367
- export { aggregateParallelOutputs } from "./parallel-utils.ts";
@@ -1,97 +0,0 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
3
- import * as fs from "node:fs";
4
- import * as path from "node:path";
5
-
6
- export interface SingleOutputSnapshot {
7
- exists: boolean;
8
- mtimeMs?: number;
9
- size?: number;
10
- }
11
-
12
- export function resolveSingleOutputPath(
13
- output: string | false | undefined,
14
- runtimeCwd: string,
15
- requestedCwd?: string,
16
- ): string | undefined {
17
- if (typeof output !== "string" || !output) return undefined;
18
- if (path.isAbsolute(output)) return output;
19
- const baseCwd = requestedCwd
20
- ? (path.isAbsolute(requestedCwd) ? requestedCwd : path.resolve(runtimeCwd, requestedCwd))
21
- : runtimeCwd;
22
- return path.resolve(baseCwd, output);
23
- }
24
-
25
- export function injectSingleOutputInstruction(task: string, outputPath: string | undefined): string {
26
- if (!outputPath) return task;
27
- return `${task}\n\n---\n**Output:** Write your findings to: ${outputPath}`;
28
- }
29
-
30
- export function captureSingleOutputSnapshot(outputPath: string | undefined): SingleOutputSnapshot | undefined {
31
- if (!outputPath) return undefined;
32
- try {
33
- const stat = fs.statSync(outputPath);
34
- return { exists: true, mtimeMs: stat.mtimeMs, size: stat.size };
35
- } catch {
36
- return { exists: false };
37
- }
38
- }
39
-
40
- export function persistSingleOutput(
41
- outputPath: string | undefined,
42
- fullOutput: string,
43
- ): { savedPath?: string; error?: string } {
44
- if (!outputPath) return {};
45
- try {
46
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
47
- fs.writeFileSync(outputPath, fullOutput, "utf-8");
48
- return { savedPath: outputPath };
49
- } catch (err) {
50
- return { error: err instanceof Error ? err.message : String(err) };
51
- }
52
- }
53
-
54
- export function resolveSingleOutput(
55
- outputPath: string | undefined,
56
- fallbackOutput: string,
57
- beforeRun: SingleOutputSnapshot | undefined,
58
- ): { fullOutput: string; savedPath?: string; saveError?: string } {
59
- if (!outputPath) return { fullOutput: fallbackOutput };
60
-
61
- try {
62
- const stat = fs.statSync(outputPath);
63
- const changedSinceStart = !beforeRun?.exists
64
- || stat.mtimeMs !== beforeRun.mtimeMs
65
- || stat.size !== beforeRun.size;
66
- if (changedSinceStart) {
67
- return {
68
- fullOutput: fs.readFileSync(outputPath, "utf-8"),
69
- savedPath: outputPath,
70
- };
71
- }
72
- } catch {}
73
-
74
- const save = persistSingleOutput(outputPath, fallbackOutput);
75
- if (save.savedPath) return { fullOutput: fallbackOutput, savedPath: save.savedPath };
76
- return { fullOutput: fallbackOutput, saveError: save.error };
77
- }
78
-
79
- export function finalizeSingleOutput(params: {
80
- fullOutput: string;
81
- truncatedOutput?: string;
82
- outputPath?: string;
83
- exitCode: number;
84
- savedPath?: string;
85
- saveError?: string;
86
- }): { displayOutput: string; savedPath?: string; saveError?: string } {
87
- let displayOutput = params.truncatedOutput || params.fullOutput;
88
- if (params.exitCode === 0 && params.savedPath) {
89
- displayOutput += `\n\nOutput saved to: ${params.savedPath}`;
90
- return { displayOutput, savedPath: params.savedPath };
91
- }
92
- if (params.exitCode === 0 && params.saveError && params.outputPath) {
93
- displayOutput += `\n\nFailed to save output to: ${params.outputPath}\n${params.saveError}`;
94
- return { displayOutput, saveError: params.saveError };
95
- }
96
- return { displayOutput };
97
- }