pi-subagents 0.11.1 → 0.11.3
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 +17 -0
- package/README.md +24 -4
- package/agents.ts +1 -30
- package/async-execution.ts +1 -1
- package/async-job-tracker.ts +123 -0
- package/chain-serializer.ts +1 -30
- package/execution.ts +17 -79
- package/frontmatter.ts +29 -0
- package/index.ts +73 -1164
- package/package.json +1 -1
- package/parallel-utils.ts +30 -8
- package/pi-args.ts +122 -0
- package/result-watcher.ts +92 -0
- package/settings.ts +2 -38
- package/skills.ts +15 -0
- package/slash-commands.ts +348 -0
- package/subagent-executor.ts +736 -0
- package/subagent-runner.ts +16 -60
- package/types.ts +19 -0
- package/utils.ts +1 -24
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.3] - 2026-03-17
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Decomposed `index.ts` (1,450 → ~350 lines) into focused modules: `subagent-executor.ts`, `async-job-tracker.ts`, `result-watcher.ts`, `slash-commands.ts`. Shared mutable state centralized in `SubagentState` interface. Three identical session handlers collapsed into one.
|
|
9
|
+
- Extracted shared pi CLI arg-builder (`pi-args.ts`) from duplicated logic in `execution.ts` and `subagent-runner.ts`.
|
|
10
|
+
- Consolidated `mapConcurrent` (canonical in `parallel-utils.ts`, re-exported from `utils.ts`), `aggregateParallelOutputs` (canonical in `parallel-utils.ts` with optional header formatter, re-exported from `settings.ts`), and `parseFrontmatter` (extracted to `frontmatter.ts`).
|
|
11
|
+
|
|
12
|
+
## [0.11.2] - 2026-03-11
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- `--no-skills` was missing from the async runner (`subagent-runner.ts`). PR #41 added skill scoping to the sync path but the async runner spawns pi through its own code path, so background subagents with explicit skills still got the full `<available_skills>` catalog injected.
|
|
16
|
+
- `defaultSessionDir` and `sessionDir` with `~` paths (e.g. `"~/.pi/agent/sessions/subagent/"`) were not expanded — `path.resolve("~/...")` treats `~` as a literal directory name. Added tilde expansion matching the existing pattern in `skills.ts`.
|
|
17
|
+
- Multiple subagent calls within a session would collide when `defaultSessionDir` was configured, since it wasn't appending a unique `runId`. Both `defaultSessionDir` and parent-session-derived paths now get `runId` appended.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
- Removed exported `resolveSessionRoot()` function and `SessionRootInput` interface. These were introduced by PR #46 but never called in production — the inline resolution logic diverged (always-on sessions, `runId` appended) making the function's contract misleading. Associated tests and dead code from PR #47 scaffolding also removed from `path-handling.test.ts`.
|
|
21
|
+
|
|
5
22
|
## [0.11.1] - 2026-03-08
|
|
6
23
|
|
|
7
24
|
### Changed
|
package/README.md
CHANGED
|
@@ -538,7 +538,7 @@ Notes:
|
|
|
538
538
|
| `artifacts` | boolean | true | Write debug artifacts |
|
|
539
539
|
| `includeProgress` | boolean | false | Include full progress in result |
|
|
540
540
|
| `share` | boolean | false | Upload session to GitHub Gist (see [Session Sharing](#session-sharing)) |
|
|
541
|
-
| `sessionDir` | string |
|
|
541
|
+
| `sessionDir` | string | - | Override session log directory (takes precedence over `defaultSessionDir` and parent-session-derived path) |
|
|
542
542
|
|
|
543
543
|
**ChainItem** can be either a sequential step or a parallel step:
|
|
544
544
|
|
|
@@ -604,11 +604,31 @@ Templates support three variables:
|
|
|
604
604
|
|
|
605
605
|
This aggregated output becomes `{previous}` for the next step.
|
|
606
606
|
|
|
607
|
-
##
|
|
607
|
+
## Extension Configuration
|
|
608
|
+
|
|
609
|
+
`pi-subagents` reads optional JSON config from `~/.pi/agent/extensions/subagent/config.json`.
|
|
610
|
+
|
|
611
|
+
### `defaultSessionDir`
|
|
608
612
|
|
|
613
|
+
`defaultSessionDir` sets the fallback directory used for session logs. Eg:
|
|
614
|
+
|
|
615
|
+
```json
|
|
616
|
+
{
|
|
617
|
+
"defaultSessionDir": "~/.pi/agent/sessions/subagent/"
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Session root resolution follows this precedence:
|
|
622
|
+
1. `params.sessionDir` from the `subagent` tool call
|
|
623
|
+
2. `config.defaultSessionDir`
|
|
624
|
+
3. Derived from parent session (stored alongside parent session file)
|
|
625
|
+
|
|
626
|
+
Sessions are always enabled — every subagent run gets a session directory for tracking.
|
|
627
|
+
|
|
628
|
+
## Chain Directory
|
|
609
629
|
Each chain run creates `<tmpdir>/pi-chain-runs/{runId}/` containing:
|
|
610
630
|
- `context.md` - Scout/context-builder output
|
|
611
|
-
- `plan.md` - Planner output
|
|
631
|
+
- `plan.md` - Planner output
|
|
612
632
|
- `progress.md` - Worker/reviewer shared progress
|
|
613
633
|
- `parallel-{stepIndex}/` - Subdirectories for parallel step outputs
|
|
614
634
|
- `0-{agent}/output.md` - First parallel task output
|
|
@@ -629,7 +649,7 @@ Files per task:
|
|
|
629
649
|
|
|
630
650
|
## Session Logs
|
|
631
651
|
|
|
632
|
-
Session files (JSONL) are stored under a per-run session
|
|
652
|
+
Session files (JSONL) are stored under a per-run session directory. Directory selection follows the same precedence as session root resolution: explicit `sessionDir` > `config.defaultSessionDir` > parent-session-derived path. The session file path is shown in output.
|
|
633
653
|
|
|
634
654
|
## Session Sharing
|
|
635
655
|
|
package/agents.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
import { KNOWN_FIELDS } from "./agent-serializer.js";
|
|
10
10
|
import { parseChain } from "./chain-serializer.js";
|
|
11
11
|
import { mergeAgentsForScope } from "./agent-selection.js";
|
|
12
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
12
13
|
|
|
13
14
|
export type AgentScope = "user" | "project" | "both";
|
|
14
15
|
|
|
@@ -58,36 +59,6 @@ export interface AgentDiscoveryResult {
|
|
|
58
59
|
projectAgentsDir: string | null;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
|
|
62
|
-
const frontmatter: Record<string, string> = {};
|
|
63
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
64
|
-
|
|
65
|
-
if (!normalized.startsWith("---")) {
|
|
66
|
-
return { frontmatter, body: normalized };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const endIndex = normalized.indexOf("\n---", 3);
|
|
70
|
-
if (endIndex === -1) {
|
|
71
|
-
return { frontmatter, body: normalized };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const frontmatterBlock = normalized.slice(4, endIndex);
|
|
75
|
-
const body = normalized.slice(endIndex + 4).trim();
|
|
76
|
-
|
|
77
|
-
for (const line of frontmatterBlock.split("\n")) {
|
|
78
|
-
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
79
|
-
if (match) {
|
|
80
|
-
let value = match[2].trim();
|
|
81
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
82
|
-
value = value.slice(1, -1);
|
|
83
|
-
}
|
|
84
|
-
frontmatter[match[1]] = value;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { frontmatter, body };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
62
|
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
92
63
|
const agents: AgentConfig[] = [];
|
|
93
64
|
|
package/async-execution.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
12
12
|
import type { AgentConfig } from "./agents.js";
|
|
13
|
-
import { applyThinkingSuffix } from "./
|
|
13
|
+
import { applyThinkingSuffix } from "./pi-args.js";
|
|
14
14
|
import { injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.js";
|
|
15
15
|
import { isParallelStep, resolveStepBehavior, type ChainStep, type ParallelStep, type SequentialStep, type StepOverrides } from "./settings.js";
|
|
16
16
|
import type { RunnerStep } from "./parallel-utils.js";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { renderWidget } from "./render.js";
|
|
4
|
+
import {
|
|
5
|
+
type SubagentState,
|
|
6
|
+
POLL_INTERVAL_MS,
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
import { readStatus } from "./utils.js";
|
|
9
|
+
|
|
10
|
+
export function createAsyncJobTracker(state: SubagentState, asyncDirRoot: string): {
|
|
11
|
+
ensurePoller: () => void;
|
|
12
|
+
handleStarted: (data: unknown) => void;
|
|
13
|
+
handleComplete: (data: unknown) => void;
|
|
14
|
+
resetJobs: (ctx?: ExtensionContext) => void;
|
|
15
|
+
} {
|
|
16
|
+
const ensurePoller = () => {
|
|
17
|
+
if (state.poller) return;
|
|
18
|
+
state.poller = setInterval(() => {
|
|
19
|
+
if (!state.lastUiContext || !state.lastUiContext.hasUI) return;
|
|
20
|
+
if (state.asyncJobs.size === 0) {
|
|
21
|
+
renderWidget(state.lastUiContext, []);
|
|
22
|
+
if (state.poller) {
|
|
23
|
+
clearInterval(state.poller);
|
|
24
|
+
state.poller = null;
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const job of state.asyncJobs.values()) {
|
|
30
|
+
if (job.status === "complete" || job.status === "failed") {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const status = readStatus(job.asyncDir);
|
|
34
|
+
if (status) {
|
|
35
|
+
job.status = status.state;
|
|
36
|
+
job.mode = status.mode;
|
|
37
|
+
job.currentStep = status.currentStep ?? job.currentStep;
|
|
38
|
+
job.stepsTotal = status.steps?.length ?? job.stepsTotal;
|
|
39
|
+
job.startedAt = status.startedAt ?? job.startedAt;
|
|
40
|
+
job.updatedAt = status.lastUpdate ?? Date.now();
|
|
41
|
+
if (status.steps?.length) {
|
|
42
|
+
job.agents = status.steps.map((step) => step.agent);
|
|
43
|
+
}
|
|
44
|
+
job.sessionDir = status.sessionDir ?? job.sessionDir;
|
|
45
|
+
job.outputFile = status.outputFile ?? job.outputFile;
|
|
46
|
+
job.totalTokens = status.totalTokens ?? job.totalTokens;
|
|
47
|
+
job.sessionFile = status.sessionFile ?? job.sessionFile;
|
|
48
|
+
} else {
|
|
49
|
+
job.status = job.status === "queued" ? "running" : job.status;
|
|
50
|
+
job.updatedAt = Date.now();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
renderWidget(state.lastUiContext, Array.from(state.asyncJobs.values()));
|
|
55
|
+
}, POLL_INTERVAL_MS);
|
|
56
|
+
state.poller.unref?.();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleStarted = (data: unknown) => {
|
|
60
|
+
const info = data as {
|
|
61
|
+
id?: string;
|
|
62
|
+
asyncDir?: string;
|
|
63
|
+
agent?: string;
|
|
64
|
+
chain?: string[];
|
|
65
|
+
};
|
|
66
|
+
if (!info.id) return;
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const asyncDir = info.asyncDir ?? path.join(asyncDirRoot, info.id);
|
|
69
|
+
const agents = info.chain && info.chain.length > 0 ? info.chain : info.agent ? [info.agent] : undefined;
|
|
70
|
+
state.asyncJobs.set(info.id, {
|
|
71
|
+
asyncId: info.id,
|
|
72
|
+
asyncDir,
|
|
73
|
+
status: "queued",
|
|
74
|
+
mode: info.chain ? "chain" : "single",
|
|
75
|
+
agents,
|
|
76
|
+
stepsTotal: agents?.length,
|
|
77
|
+
startedAt: now,
|
|
78
|
+
updatedAt: now,
|
|
79
|
+
});
|
|
80
|
+
if (state.lastUiContext) {
|
|
81
|
+
renderWidget(state.lastUiContext, Array.from(state.asyncJobs.values()));
|
|
82
|
+
ensurePoller();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleComplete = (data: unknown) => {
|
|
87
|
+
const result = data as { id?: string; success?: boolean; asyncDir?: string };
|
|
88
|
+
const asyncId = result.id;
|
|
89
|
+
if (!asyncId) return;
|
|
90
|
+
const job = state.asyncJobs.get(asyncId);
|
|
91
|
+
if (job) {
|
|
92
|
+
job.status = result.success ? "complete" : "failed";
|
|
93
|
+
job.updatedAt = Date.now();
|
|
94
|
+
if (result.asyncDir) job.asyncDir = result.asyncDir;
|
|
95
|
+
}
|
|
96
|
+
if (state.lastUiContext) {
|
|
97
|
+
renderWidget(state.lastUiContext, Array.from(state.asyncJobs.values()));
|
|
98
|
+
}
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
state.cleanupTimers.delete(asyncId);
|
|
101
|
+
state.asyncJobs.delete(asyncId);
|
|
102
|
+
if (state.lastUiContext) {
|
|
103
|
+
renderWidget(state.lastUiContext, Array.from(state.asyncJobs.values()));
|
|
104
|
+
}
|
|
105
|
+
}, 10000);
|
|
106
|
+
state.cleanupTimers.set(asyncId, timer);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const resetJobs = (ctx?: ExtensionContext) => {
|
|
110
|
+
for (const timer of state.cleanupTimers.values()) {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
}
|
|
113
|
+
state.cleanupTimers.clear();
|
|
114
|
+
state.asyncJobs.clear();
|
|
115
|
+
state.resultFileCoalescer.clear();
|
|
116
|
+
if (ctx?.hasUI) {
|
|
117
|
+
state.lastUiContext = ctx;
|
|
118
|
+
renderWidget(ctx, []);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return { ensurePoller, handleStarted, handleComplete, resetJobs };
|
|
123
|
+
}
|
package/chain-serializer.ts
CHANGED
|
@@ -1,34 +1,5 @@
|
|
|
1
1
|
import type { ChainConfig, ChainStepConfig } from "./agents.js";
|
|
2
|
-
|
|
3
|
-
function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
|
|
4
|
-
const frontmatter: Record<string, string> = {};
|
|
5
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
6
|
-
|
|
7
|
-
if (!normalized.startsWith("---")) {
|
|
8
|
-
return { frontmatter, body: normalized };
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const endIndex = normalized.indexOf("\n---", 3);
|
|
12
|
-
if (endIndex === -1) {
|
|
13
|
-
return { frontmatter, body: normalized };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const frontmatterBlock = normalized.slice(4, endIndex);
|
|
17
|
-
const body = normalized.slice(endIndex + 4).trim();
|
|
18
|
-
|
|
19
|
-
for (const line of frontmatterBlock.split("\n")) {
|
|
20
|
-
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
21
|
-
if (match) {
|
|
22
|
-
let value = match[2].trim();
|
|
23
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
24
|
-
value = value.slice(1, -1);
|
|
25
|
-
}
|
|
26
|
-
frontmatter[match[1]] = value;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return { frontmatter, body };
|
|
31
|
-
}
|
|
2
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
32
3
|
|
|
33
4
|
function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
|
|
34
5
|
const lines = sectionBody.split("\n");
|
package/execution.ts
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
|
-
import * as os from "node:os";
|
|
8
|
-
import * as path from "node:path";
|
|
9
6
|
import type { Message } from "@mariozechner/pi-ai";
|
|
10
7
|
import type { AgentConfig } from "./agents.js";
|
|
11
8
|
import {
|
|
@@ -24,7 +21,6 @@ import {
|
|
|
24
21
|
getSubagentDepthEnv,
|
|
25
22
|
} from "./types.js";
|
|
26
23
|
import {
|
|
27
|
-
writePrompt,
|
|
28
24
|
getFinalOutput,
|
|
29
25
|
findLatestSessionFile,
|
|
30
26
|
detectSubagentError,
|
|
@@ -34,15 +30,7 @@ import {
|
|
|
34
30
|
import { buildSkillInjection, resolveSkills } from "./skills.js";
|
|
35
31
|
import { getPiSpawnCommand } from "./pi-spawn.js";
|
|
36
32
|
import { createJsonlWriter } from "./jsonl-writer.js";
|
|
37
|
-
|
|
38
|
-
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
39
|
-
|
|
40
|
-
export function applyThinkingSuffix(model: string | undefined, thinking: string | undefined): string | undefined {
|
|
41
|
-
if (!model || !thinking || thinking === "off") return model;
|
|
42
|
-
const colonIdx = model.lastIndexOf(":");
|
|
43
|
-
if (colonIdx !== -1 && THINKING_LEVELS.includes(model.substring(colonIdx + 1))) return model;
|
|
44
|
-
return `${model}:${thinking}`;
|
|
45
|
-
}
|
|
33
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "./pi-args.js";
|
|
46
34
|
|
|
47
35
|
/**
|
|
48
36
|
* Run a subagent synchronously (blocking until complete)
|
|
@@ -67,48 +55,10 @@ export async function runSync(
|
|
|
67
55
|
};
|
|
68
56
|
}
|
|
69
57
|
|
|
70
|
-
const args = ["--mode", "json", "-p"];
|
|
71
58
|
const shareEnabled = options.share === true;
|
|
72
59
|
const sessionEnabled = Boolean(options.sessionDir) || shareEnabled;
|
|
73
|
-
if (!sessionEnabled) {
|
|
74
|
-
args.push("--no-session");
|
|
75
|
-
}
|
|
76
|
-
if (options.sessionDir) {
|
|
77
|
-
try {
|
|
78
|
-
fs.mkdirSync(options.sessionDir, { recursive: true });
|
|
79
|
-
} catch {}
|
|
80
|
-
args.push("--session-dir", options.sessionDir);
|
|
81
|
-
}
|
|
82
60
|
const effectiveModel = modelOverride ?? agent.model;
|
|
83
61
|
const modelArg = applyThinkingSuffix(effectiveModel, agent.thinking);
|
|
84
|
-
// Use --models (not --model) because pi CLI silently ignores --model
|
|
85
|
-
// without a companion --provider flag. --models resolves the provider
|
|
86
|
-
// automatically via resolveModelScope. See: #8
|
|
87
|
-
if (modelArg) args.push("--models", modelArg);
|
|
88
|
-
const toolExtensionPaths: string[] = [];
|
|
89
|
-
if (agent.tools?.length) {
|
|
90
|
-
const builtinTools: string[] = [];
|
|
91
|
-
for (const tool of agent.tools) {
|
|
92
|
-
if (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")) {
|
|
93
|
-
toolExtensionPaths.push(tool);
|
|
94
|
-
} else {
|
|
95
|
-
builtinTools.push(tool);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (builtinTools.length > 0) {
|
|
99
|
-
args.push("--tools", builtinTools.join(","));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (agent.extensions !== undefined) {
|
|
103
|
-
args.push("--no-extensions");
|
|
104
|
-
for (const extPath of agent.extensions) {
|
|
105
|
-
args.push("--extension", extPath);
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
for (const extPath of toolExtensionPaths) {
|
|
109
|
-
args.push("--extension", extPath);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
62
|
|
|
113
63
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
114
64
|
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkills(skillNames, runtimeCwd);
|
|
@@ -119,26 +69,20 @@ export async function runSync(
|
|
|
119
69
|
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillInjection}` : skillInjection;
|
|
120
70
|
}
|
|
121
71
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const taskFilePath = path.join(tmpDir, "task.md");
|
|
137
|
-
fs.writeFileSync(taskFilePath, `Task: ${task}`, { mode: 0o600 });
|
|
138
|
-
args.push(`@${taskFilePath}`);
|
|
139
|
-
} else {
|
|
140
|
-
args.push(`Task: ${task}`);
|
|
141
|
-
}
|
|
72
|
+
const { args, env: sharedEnv, tempDir } = buildPiArgs({
|
|
73
|
+
baseArgs: ["--mode", "json", "-p"],
|
|
74
|
+
task,
|
|
75
|
+
sessionEnabled,
|
|
76
|
+
sessionDir: options.sessionDir,
|
|
77
|
+
model: effectiveModel,
|
|
78
|
+
thinking: agent.thinking,
|
|
79
|
+
tools: agent.tools,
|
|
80
|
+
extensions: agent.extensions,
|
|
81
|
+
skills: skillNames,
|
|
82
|
+
systemPrompt,
|
|
83
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
84
|
+
promptFileStem: agent.name,
|
|
85
|
+
});
|
|
142
86
|
|
|
143
87
|
const result: SingleResult = {
|
|
144
88
|
agent: agentName,
|
|
@@ -180,13 +124,7 @@ export async function runSync(
|
|
|
180
124
|
}
|
|
181
125
|
}
|
|
182
126
|
|
|
183
|
-
const spawnEnv = { ...process.env, ...getSubagentDepthEnv() };
|
|
184
|
-
const mcpDirect = agent.mcpDirectTools;
|
|
185
|
-
if (mcpDirect?.length) {
|
|
186
|
-
spawnEnv.MCP_DIRECT_TOOLS = mcpDirect.join(",");
|
|
187
|
-
} else {
|
|
188
|
-
spawnEnv.MCP_DIRECT_TOOLS = "__none__";
|
|
189
|
-
}
|
|
127
|
+
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv() };
|
|
190
128
|
|
|
191
129
|
let closeJsonlWriter: (() => Promise<void>) | undefined;
|
|
192
130
|
const exitCode = await new Promise<number>((resolve) => {
|
|
@@ -372,7 +310,7 @@ export async function runSync(
|
|
|
372
310
|
} catch {}
|
|
373
311
|
}
|
|
374
312
|
|
|
375
|
-
|
|
313
|
+
cleanupTempDir(tempDir);
|
|
376
314
|
result.exitCode = exitCode;
|
|
377
315
|
|
|
378
316
|
if (exitCode === 0 && !result.error) {
|
package/frontmatter.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
|
|
2
|
+
const frontmatter: Record<string, string> = {};
|
|
3
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
4
|
+
|
|
5
|
+
if (!normalized.startsWith("---")) {
|
|
6
|
+
return { frontmatter, body: normalized };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
10
|
+
if (endIndex === -1) {
|
|
11
|
+
return { frontmatter, body: normalized };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const frontmatterBlock = normalized.slice(4, endIndex);
|
|
15
|
+
const body = normalized.slice(endIndex + 4).trim();
|
|
16
|
+
|
|
17
|
+
for (const line of frontmatterBlock.split("\n")) {
|
|
18
|
+
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
19
|
+
if (match) {
|
|
20
|
+
let value = match[2].trim();
|
|
21
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
22
|
+
value = value.slice(1, -1);
|
|
23
|
+
}
|
|
24
|
+
frontmatter[match[1]] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { frontmatter, body };
|
|
29
|
+
}
|