pi-subagents 0.24.2 → 0.24.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 +26 -0
- package/README.md +13 -5
- package/package.json +4 -8
- package/prompts/review-loop.md +41 -0
- package/skills/pi-subagents/SKILL.md +51 -21
- package/src/agents/agent-management.ts +5 -0
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +30 -6
- package/src/agents/skills.ts +25 -23
- package/src/extension/config.ts +16 -0
- package/src/extension/index.ts +8 -24
- package/src/extension/schemas.ts +1 -1
- package/src/intercom/intercom-bridge.ts +2 -1
- package/src/runs/background/async-execution.ts +16 -5
- package/src/runs/background/async-job-tracker.ts +16 -8
- package/src/runs/background/async-status.ts +5 -2
- package/src/runs/background/run-status.ts +4 -1
- package/src/runs/background/subagent-runner.ts +34 -7
- package/src/runs/foreground/execution.ts +17 -5
- package/src/runs/foreground/subagent-executor.ts +6 -7
- package/src/runs/shared/completion-guard.ts +23 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +5 -0
- package/src/runs/shared/run-history.ts +12 -7
- package/src/runs/shared/single-output.ts +12 -2
- package/src/shared/artifacts.ts +2 -2
- package/src/shared/formatters.ts +13 -0
- package/src/shared/model-info.ts +10 -0
- package/src/shared/types.ts +1 -0
- package/src/shared/utils.ts +11 -1
- package/src/tui/render.ts +160 -147
package/src/agents/skills.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { execSync } from "node:child_process";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import { getAgentDir } from "../shared/utils.ts";
|
|
9
10
|
|
|
10
11
|
export type SkillSource =
|
|
11
12
|
| "project"
|
|
@@ -46,11 +47,10 @@ interface SkillSearchPath {
|
|
|
46
47
|
const skillCache = new Map<string, SkillCacheEntry>();
|
|
47
48
|
const MAX_CACHE_SIZE = 50;
|
|
48
49
|
|
|
49
|
-
let loadSkillsCache: { cwd: string; skills: CachedSkillEntry[]; timestamp: number } | null = null;
|
|
50
|
+
let loadSkillsCache: { cwd: string; agentDir: string; skills: CachedSkillEntry[]; timestamp: number } | null = null;
|
|
50
51
|
const LOAD_SKILLS_CACHE_TTL_MS = 5000;
|
|
51
52
|
|
|
52
53
|
const CONFIG_DIR = ".pi";
|
|
53
|
-
const AGENT_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
54
54
|
const SUBAGENT_ORCHESTRATION_SKILL = "pi-subagents";
|
|
55
55
|
|
|
56
56
|
const SOURCE_PRIORITY: Record<SkillSource, number> = {
|
|
@@ -133,10 +133,10 @@ function getGlobalNpmRoot(): string | null {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
function collectInstalledPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
136
|
+
function collectInstalledPackageSkillPaths(cwd: string, agentDir: string): SkillSearchPath[] {
|
|
137
137
|
const dirs: SkillSearchPath[] = [
|
|
138
138
|
{ path: path.join(cwd, CONFIG_DIR, "npm", "node_modules"), source: "project-package" },
|
|
139
|
-
{ path: path.join(
|
|
139
|
+
{ path: path.join(agentDir, "npm", "node_modules"), source: "user-package" },
|
|
140
140
|
];
|
|
141
141
|
|
|
142
142
|
const globalRoot = getGlobalNpmRoot();
|
|
@@ -184,11 +184,11 @@ function collectInstalledPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
|
184
184
|
return results;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
function collectSettingsSkillPaths(cwd: string): SkillSearchPath[] {
|
|
187
|
+
function collectSettingsSkillPaths(cwd: string, agentDir: string): SkillSearchPath[] {
|
|
188
188
|
const results: SkillSearchPath[] = [];
|
|
189
189
|
const settingsFiles = [
|
|
190
190
|
{ file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-settings" as const },
|
|
191
|
-
{ file: path.join(
|
|
191
|
+
{ file: path.join(agentDir, "settings.json"), base: agentDir, source: "user-settings" as const },
|
|
192
192
|
];
|
|
193
193
|
|
|
194
194
|
for (const { file, base, source } of settingsFiles) {
|
|
@@ -285,10 +285,10 @@ function resolveSettingsPackageRoot(source: string, baseDir: string): string | u
|
|
|
285
285
|
return undefined;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
function collectSettingsPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
288
|
+
function collectSettingsPackageSkillPaths(cwd: string, agentDir: string): SkillSearchPath[] {
|
|
289
289
|
const settingsFiles = [
|
|
290
290
|
{ file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-package" as const },
|
|
291
|
-
{ file: path.join(
|
|
291
|
+
{ file: path.join(agentDir, "settings.json"), base: agentDir, source: "user-package" as const },
|
|
292
292
|
];
|
|
293
293
|
const results: SkillSearchPath[] = [];
|
|
294
294
|
|
|
@@ -315,16 +315,16 @@ function collectSettingsPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
|
315
315
|
return results;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
function buildSkillPaths(cwd: string): SkillSearchPath[] {
|
|
318
|
+
function buildSkillPaths(cwd: string, agentDir: string): SkillSearchPath[] {
|
|
319
319
|
const skillPaths: SkillSearchPath[] = [
|
|
320
320
|
{ path: path.join(cwd, CONFIG_DIR, "skills"), source: "project" },
|
|
321
321
|
{ path: path.join(cwd, ".agents", "skills"), source: "project" },
|
|
322
|
-
{ path: path.join(
|
|
322
|
+
{ path: path.join(agentDir, "skills"), source: "user" },
|
|
323
323
|
{ path: path.join(os.homedir(), ".agents", "skills"), source: "user" },
|
|
324
|
-
...collectInstalledPackageSkillPaths(cwd),
|
|
325
|
-
...collectSettingsPackageSkillPaths(cwd),
|
|
324
|
+
...collectInstalledPackageSkillPaths(cwd, agentDir),
|
|
325
|
+
...collectSettingsPackageSkillPaths(cwd, agentDir),
|
|
326
326
|
...extractSkillPathsFromPackageRoot(cwd, "project-package"),
|
|
327
|
-
...collectSettingsSkillPaths(cwd),
|
|
327
|
+
...collectSettingsSkillPaths(cwd, agentDir),
|
|
328
328
|
];
|
|
329
329
|
|
|
330
330
|
const deduped = new Map<string, SkillSearchPath>();
|
|
@@ -337,15 +337,16 @@ function buildSkillPaths(cwd: string): SkillSearchPath[] {
|
|
|
337
337
|
return [...deduped.values()];
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
function inferSkillSource(filePath: string, cwd: string, sourceHint?: SkillSource): SkillSource {
|
|
340
|
+
function inferSkillSource(filePath: string, cwd: string, agentDir: string, sourceHint?: SkillSource): SkillSource {
|
|
341
341
|
if (sourceHint) return sourceHint;
|
|
342
342
|
|
|
343
343
|
const projectConfigRoot = path.resolve(cwd, CONFIG_DIR);
|
|
344
344
|
const projectSkillsRoot = path.resolve(cwd, CONFIG_DIR, "skills");
|
|
345
345
|
const projectPackagesRoot = path.resolve(cwd, CONFIG_DIR, "npm", "node_modules");
|
|
346
346
|
const projectAgentsRoot = path.resolve(cwd, ".agents");
|
|
347
|
-
const userSkillsRoot = path.resolve(
|
|
348
|
-
const userPackagesRoot = path.resolve(
|
|
347
|
+
const userSkillsRoot = path.resolve(agentDir, "skills");
|
|
348
|
+
const userPackagesRoot = path.resolve(agentDir, "npm", "node_modules");
|
|
349
|
+
const userAgentRoot = path.resolve(agentDir);
|
|
349
350
|
const userAgentsRoot = path.resolve(os.homedir(), ".agents");
|
|
350
351
|
|
|
351
352
|
if (isWithinPath(filePath, projectPackagesRoot)) return "project-package";
|
|
@@ -354,7 +355,7 @@ function inferSkillSource(filePath: string, cwd: string, sourceHint?: SkillSourc
|
|
|
354
355
|
|
|
355
356
|
if (isWithinPath(filePath, userPackagesRoot)) return "user-package";
|
|
356
357
|
if (isWithinPath(filePath, userSkillsRoot) || isWithinPath(filePath, userAgentsRoot)) return "user";
|
|
357
|
-
if (isWithinPath(filePath,
|
|
358
|
+
if (isWithinPath(filePath, userAgentRoot)) return "user-settings";
|
|
358
359
|
|
|
359
360
|
const globalRoot = getGlobalNpmRoot();
|
|
360
361
|
if (globalRoot && isWithinPath(filePath, globalRoot)) return "user-package";
|
|
@@ -390,7 +391,7 @@ function maybeReadSkillDescription(filePath: string): string | undefined {
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
|
|
393
|
-
function collectFilesystemSkills(cwd: string, skillPaths: SkillSearchPath[]): CachedSkillEntry[] {
|
|
394
|
+
function collectFilesystemSkills(cwd: string, agentDir: string, skillPaths: SkillSearchPath[]): CachedSkillEntry[] {
|
|
394
395
|
const entries: CachedSkillEntry[] = [];
|
|
395
396
|
const seen = new Set<string>();
|
|
396
397
|
let order = 0;
|
|
@@ -403,7 +404,7 @@ function collectFilesystemSkills(cwd: string, skillPaths: SkillSearchPath[]): Ca
|
|
|
403
404
|
entries.push({
|
|
404
405
|
name,
|
|
405
406
|
filePath: resolvedFile,
|
|
406
|
-
source: inferSkillSource(resolvedFile, cwd, sourceHint),
|
|
407
|
+
source: inferSkillSource(resolvedFile, cwd, agentDir, sourceHint),
|
|
407
408
|
description: maybeReadSkillDescription(resolvedFile),
|
|
408
409
|
order: order++,
|
|
409
410
|
});
|
|
@@ -464,12 +465,13 @@ function collectFilesystemSkills(cwd: string, skillPaths: SkillSearchPath[]): Ca
|
|
|
464
465
|
|
|
465
466
|
function getCachedSkills(cwd: string): CachedSkillEntry[] {
|
|
466
467
|
const now = Date.now();
|
|
467
|
-
|
|
468
|
+
const agentDir = getAgentDir();
|
|
469
|
+
if (loadSkillsCache && loadSkillsCache.cwd === cwd && loadSkillsCache.agentDir === agentDir && now - loadSkillsCache.timestamp < LOAD_SKILLS_CACHE_TTL_MS) {
|
|
468
470
|
return loadSkillsCache.skills;
|
|
469
471
|
}
|
|
470
472
|
|
|
471
|
-
const skillPaths = buildSkillPaths(cwd);
|
|
472
|
-
const loaded = collectFilesystemSkills(cwd, skillPaths);
|
|
473
|
+
const skillPaths = buildSkillPaths(cwd, agentDir);
|
|
474
|
+
const loaded = collectFilesystemSkills(cwd, agentDir, skillPaths);
|
|
473
475
|
const dedupedByName = new Map<string, CachedSkillEntry>();
|
|
474
476
|
|
|
475
477
|
for (const entry of loaded) {
|
|
@@ -478,7 +480,7 @@ function getCachedSkills(cwd: string): CachedSkillEntry[] {
|
|
|
478
480
|
}
|
|
479
481
|
|
|
480
482
|
const skills = [...dedupedByName.values()].sort((a, b) => a.order - b.order);
|
|
481
|
-
loadSkillsCache = { cwd, skills, timestamp: now };
|
|
483
|
+
loadSkillsCache = { cwd, agentDir, skills, timestamp: now };
|
|
482
484
|
return skills;
|
|
483
485
|
}
|
|
484
486
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ExtensionConfig } from "../shared/types.ts";
|
|
4
|
+
import { getAgentDir } from "../shared/utils.ts";
|
|
5
|
+
|
|
6
|
+
export function loadConfig(): ExtensionConfig {
|
|
7
|
+
const configPath = path.join(getAgentDir(), "extensions", "subagent", "config.json");
|
|
8
|
+
try {
|
|
9
|
+
if (fs.existsSync(configPath)) {
|
|
10
|
+
return JSON.parse(fs.readFileSync(configPath, "utf-8")) as ExtensionConfig;
|
|
11
|
+
}
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(`Failed to load subagent config from '${configPath}':`, error);
|
|
14
|
+
}
|
|
15
|
+
return {};
|
|
16
|
+
}
|
package/src/extension/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { discoverAgents } from "../agents/agents.ts";
|
|
|
22
22
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
23
23
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
24
24
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
25
|
-
import { renderWidget, renderSubagentResult
|
|
25
|
+
import { clearLegacyResultAnimationTimer, renderWidget, renderSubagentResult } from "../tui/render.ts";
|
|
26
26
|
import { SubagentParams } from "./schemas.ts";
|
|
27
27
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
28
28
|
import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
|
|
@@ -35,9 +35,9 @@ import { inspectSubagentStatus } from "../runs/background/run-status.ts";
|
|
|
35
35
|
import registerSubagentNotify, { type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
36
36
|
import { SUBAGENT_CHILD_ENV } from "../runs/shared/pi-args.ts";
|
|
37
37
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
38
|
+
import { loadConfig } from "./config.ts";
|
|
38
39
|
import {
|
|
39
40
|
type Details,
|
|
40
|
-
type ExtensionConfig,
|
|
41
41
|
type SubagentState,
|
|
42
42
|
ASYNC_DIR,
|
|
43
43
|
DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -56,6 +56,8 @@ import {
|
|
|
56
56
|
type SubagentControlMessageDetails,
|
|
57
57
|
} from "./control-notices.ts";
|
|
58
58
|
|
|
59
|
+
export { loadConfig } from "./config.ts";
|
|
60
|
+
|
|
59
61
|
/**
|
|
60
62
|
* Derive subagent session base directory from parent session file.
|
|
61
63
|
* If parent session is ~/.pi/agent/sessions/abc123.jsonl,
|
|
@@ -72,18 +74,6 @@ function getSubagentSessionRoot(parentSessionFile: string | null): string {
|
|
|
72
74
|
return fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-session-"));
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
function loadConfig(): ExtensionConfig {
|
|
76
|
-
const configPath = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent", "config.json");
|
|
77
|
-
try {
|
|
78
|
-
if (fs.existsSync(configPath)) {
|
|
79
|
-
return JSON.parse(fs.readFileSync(configPath, "utf-8")) as ExtensionConfig;
|
|
80
|
-
}
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error(`Failed to load subagent config from '${configPath}':`, error);
|
|
83
|
-
}
|
|
84
|
-
return {};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
77
|
function expandTilde(p: string): string {
|
|
88
78
|
return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
|
|
89
79
|
}
|
|
@@ -142,14 +132,11 @@ function createSlashResultComponent(
|
|
|
142
132
|
details: SlashMessageDetails,
|
|
143
133
|
options: { expanded: boolean },
|
|
144
134
|
theme: ExtensionContext["ui"]["theme"],
|
|
145
|
-
requestRender: () => void,
|
|
146
135
|
): Container {
|
|
147
136
|
const container = new Container();
|
|
148
|
-
const animationState: { subagentResultAnimationTimer?: ReturnType<typeof setInterval> } = {};
|
|
149
137
|
let lastVersion = -1;
|
|
150
138
|
container.render = (width: number): string[] => {
|
|
151
139
|
const snapshot = getSlashRenderableSnapshot(details);
|
|
152
|
-
syncResultAnimation(snapshot.result, { state: animationState, invalidate: requestRender });
|
|
153
140
|
if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {
|
|
154
141
|
lastVersion = snapshot.version;
|
|
155
142
|
rebuildSlashResultContainer(container, snapshot.result, options, theme);
|
|
@@ -271,8 +258,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
271
258
|
primeExistingResults();
|
|
272
259
|
|
|
273
260
|
const runtimeCleanup = () => {
|
|
274
|
-
stopWidgetAnimation();
|
|
275
|
-
stopResultAnimations();
|
|
276
261
|
stopResultWatcher();
|
|
277
262
|
clearPendingForegroundControlNotices(state);
|
|
278
263
|
if (state.poller) {
|
|
@@ -297,7 +282,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
297
282
|
pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
|
|
298
283
|
const details = resolveSlashMessageDetails(message.details);
|
|
299
284
|
if (!details) return undefined;
|
|
300
|
-
return createSlashResultComponent(details, options, theme
|
|
285
|
+
return createSlashResultComponent(details, options, theme);
|
|
301
286
|
});
|
|
302
287
|
|
|
303
288
|
pi.registerMessageRenderer<SubagentNotifyDetails>("subagent-notify", (message, options, theme) => {
|
|
@@ -445,7 +430,7 @@ DIAGNOSTICS:
|
|
|
445
430
|
}
|
|
446
431
|
const isParallel = (args.tasks?.length ?? 0) > 0;
|
|
447
432
|
const parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);
|
|
448
|
-
const asyncLabel = args.async === true && !isParallel ? theme.fg("warning", " [async]") : "";
|
|
433
|
+
const asyncLabel = args.async === true && args.clarify !== true && !isParallel ? theme.fg("warning", " [async]") : "";
|
|
449
434
|
if (args.chain?.length)
|
|
450
435
|
return new Text(
|
|
451
436
|
`${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})${asyncLabel}`,
|
|
@@ -466,7 +451,7 @@ DIAGNOSTICS:
|
|
|
466
451
|
},
|
|
467
452
|
|
|
468
453
|
renderResult(result, options, theme, context) {
|
|
469
|
-
|
|
454
|
+
clearLegacyResultAnimationTimer(context);
|
|
470
455
|
return renderSubagentResult(result, options, theme);
|
|
471
456
|
},
|
|
472
457
|
|
|
@@ -514,6 +499,7 @@ DIAGNOSTICS:
|
|
|
514
499
|
state.lastUiContext = ctx;
|
|
515
500
|
if (state.asyncJobs.size > 0) {
|
|
516
501
|
renderWidget(ctx, Array.from(state.asyncJobs.values()));
|
|
502
|
+
ctx.ui.requestRender?.();
|
|
517
503
|
ensurePoller();
|
|
518
504
|
}
|
|
519
505
|
});
|
|
@@ -569,8 +555,6 @@ DIAGNOSTICS:
|
|
|
569
555
|
slashBridge.dispose();
|
|
570
556
|
promptTemplateBridge.cancelAll();
|
|
571
557
|
promptTemplateBridge.dispose();
|
|
572
|
-
stopWidgetAnimation();
|
|
573
|
-
stopResultAnimations();
|
|
574
558
|
if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
|
|
575
559
|
delete globalStore[runtimeCleanupStoreKey];
|
|
576
560
|
}
|
package/src/extension/schemas.ts
CHANGED
|
@@ -152,7 +152,7 @@ export const SubagentParams = Type.Object({
|
|
|
152
152
|
Type.String({ description: "Directory to store session logs (default: temp; enables sessions even if share=false)" }),
|
|
153
153
|
),
|
|
154
154
|
// Clarification TUI
|
|
155
|
-
clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution
|
|
155
|
+
clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution. Explicit clarify: true keeps the run foreground for the clarify UI; omitted clarify can still run in the background when async: true is set." })),
|
|
156
156
|
control: Type.Optional(ControlOverrides),
|
|
157
157
|
// Solo agent overrides
|
|
158
158
|
output: Type.Optional(Type.Unsafe({
|
|
@@ -4,12 +4,13 @@ import * as os from "node:os";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import type { AgentConfig } from "../agents/agents.ts";
|
|
6
6
|
import type { ExtensionConfig, IntercomBridgeConfig, IntercomBridgeMode } from "../shared/types.ts";
|
|
7
|
+
import { getAgentDir } from "../shared/utils.ts";
|
|
7
8
|
|
|
8
9
|
const PI_INTERCOM_PACKAGE_NAME = "pi-intercom";
|
|
9
10
|
const CONFIG_DIR = ".pi";
|
|
10
11
|
|
|
11
12
|
function defaultAgentDir(): string {
|
|
12
|
-
return
|
|
13
|
+
return getAgentDir();
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function defaultIntercomExtensionDir(agentDir = defaultAgentDir()): string {
|
|
@@ -11,13 +11,14 @@ import { createRequire } from "node:module";
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
13
13
|
import { applyThinkingSuffix } from "../shared/pi-args.ts";
|
|
14
|
-
import { injectSingleOutputInstruction, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
14
|
+
import { injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
15
15
|
import { buildChainInstructions, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
16
16
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
17
17
|
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
18
18
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
19
19
|
import { resolveChildCwd } from "../../shared/utils.ts";
|
|
20
20
|
import { buildModelCandidates, resolveModelCandidate, type AvailableModelInfo } from "../shared/model-fallback.ts";
|
|
21
|
+
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
21
22
|
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
22
23
|
import {
|
|
23
24
|
type ArtifactConfig,
|
|
@@ -125,7 +126,7 @@ interface AsyncSingleParams {
|
|
|
125
126
|
sessionRoot?: string;
|
|
126
127
|
sessionFile?: string;
|
|
127
128
|
skills?: string[];
|
|
128
|
-
output?: string |
|
|
129
|
+
output?: string | boolean;
|
|
129
130
|
outputMode?: "inline" | "file-only";
|
|
130
131
|
modelOverride?: string;
|
|
131
132
|
availableModels?: AvailableModelInfo[];
|
|
@@ -309,17 +310,20 @@ export function executeAsyncChain(
|
|
|
309
310
|
const task = injectSingleOutputInstruction(`${readInstructions.prefix}${s.task ?? "{previous}"}${progressInstructions.suffix}`, outputPath);
|
|
310
311
|
|
|
311
312
|
const primaryModel = resolveModelCandidate(behavior.model ?? a.model, availableModels, ctx.currentModelProvider);
|
|
313
|
+
const model = applyThinkingSuffix(primaryModel, a.thinking);
|
|
312
314
|
return {
|
|
313
315
|
agent: s.agent,
|
|
314
316
|
task,
|
|
315
317
|
cwd: stepCwd,
|
|
316
|
-
model
|
|
318
|
+
model,
|
|
319
|
+
thinking: resolveEffectiveThinking(model, a.thinking),
|
|
317
320
|
modelCandidates: buildModelCandidates(behavior.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
|
|
318
321
|
applyThinkingSuffix(candidate, a.thinking),
|
|
319
322
|
),
|
|
320
323
|
tools: a.tools,
|
|
321
324
|
extensions: a.extensions,
|
|
322
325
|
mcpDirectTools: a.mcpDirectTools,
|
|
326
|
+
completionGuard: a.completionGuard,
|
|
323
327
|
systemPrompt,
|
|
324
328
|
systemPromptMode: a.systemPromptMode,
|
|
325
329
|
inheritProjectContext: a.inheritProjectContext,
|
|
@@ -520,11 +524,16 @@ export function executeAsyncSingle(
|
|
|
520
524
|
};
|
|
521
525
|
}
|
|
522
526
|
|
|
523
|
-
const
|
|
527
|
+
const effectiveOutput = normalizeSingleOutputOverride(params.output, agentConfig.output);
|
|
528
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, runnerCwd);
|
|
524
529
|
const outputMode = params.outputMode ?? "inline";
|
|
525
530
|
const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
|
|
526
531
|
if (validationError) return formatAsyncStartError("single", validationError);
|
|
527
532
|
const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath);
|
|
533
|
+
const model = applyThinkingSuffix(
|
|
534
|
+
resolveModelCandidate(params.modelOverride ?? agentConfig.model, availableModels, ctx.currentModelProvider),
|
|
535
|
+
agentConfig.thinking,
|
|
536
|
+
);
|
|
528
537
|
let spawnResult: { pid?: number; error?: string } = {};
|
|
529
538
|
try {
|
|
530
539
|
spawnResult = spawnRunner(
|
|
@@ -535,13 +544,15 @@ export function executeAsyncSingle(
|
|
|
535
544
|
agent,
|
|
536
545
|
task: taskWithOutputInstruction,
|
|
537
546
|
cwd: runnerCwd,
|
|
538
|
-
model
|
|
547
|
+
model,
|
|
548
|
+
thinking: resolveEffectiveThinking(model, agentConfig.thinking),
|
|
539
549
|
modelCandidates: buildModelCandidates(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
|
|
540
550
|
applyThinkingSuffix(candidate, agentConfig.thinking),
|
|
541
551
|
),
|
|
542
552
|
tools: agentConfig.tools,
|
|
543
553
|
extensions: agentConfig.extensions,
|
|
544
554
|
mcpDirectTools: agentConfig.mcpDirectTools,
|
|
555
|
+
completionGuard: agentConfig.completionGuard,
|
|
545
556
|
systemPrompt,
|
|
546
557
|
systemPromptMode: agentConfig.systemPromptMode,
|
|
547
558
|
inheritProjectContext: agentConfig.inheritProjectContext,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { renderWidget } from "../../tui/render.ts";
|
|
4
|
+
import { renderWidget, widgetRenderKey } from "../../tui/render.ts";
|
|
5
5
|
import { formatControlNoticeMessage } from "../shared/subagent-control.ts";
|
|
6
6
|
import {
|
|
7
7
|
type AsyncJobState,
|
|
@@ -118,7 +118,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
let widgetChanged = false;
|
|
121
122
|
for (const job of state.asyncJobs.values()) {
|
|
123
|
+
const widgetStateBefore = widgetRenderKey(job);
|
|
122
124
|
try {
|
|
123
125
|
emitNewControlEvents(job);
|
|
124
126
|
const reconciliation = reconcileAsyncRun(job.asyncDir, {
|
|
@@ -153,7 +155,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
153
155
|
job.currentStep = status.currentStep ?? job.currentStep;
|
|
154
156
|
job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
|
|
155
157
|
job.startedAt = status.startedAt ?? job.startedAt;
|
|
156
|
-
job.updatedAt = status.lastUpdate
|
|
158
|
+
if (status.lastUpdate !== undefined) job.updatedAt = status.lastUpdate;
|
|
157
159
|
if (status.steps?.length) {
|
|
158
160
|
const groups = normalizeParallelGroups(status.parallelGroups, status.steps.length, status.chainStepCount ?? status.steps.length);
|
|
159
161
|
job.parallelGroups = groups.length ? groups : job.parallelGroups;
|
|
@@ -179,21 +181,27 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
179
181
|
if ((job.status === "complete" || job.status === "failed" || job.status === "paused") && (previousStatus !== job.status || !state.cleanupTimers.has(job.asyncId))) {
|
|
180
182
|
scheduleCleanup(job.asyncId);
|
|
181
183
|
}
|
|
184
|
+
if (widgetRenderKey(job) !== widgetStateBefore) widgetChanged = true;
|
|
182
185
|
continue;
|
|
183
186
|
}
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
if (job.status === "queued") {
|
|
188
|
+
job.status = "running";
|
|
189
|
+
job.updatedAt = Date.now();
|
|
190
|
+
}
|
|
186
191
|
} catch (error) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
192
|
+
if (job.status !== "failed") {
|
|
193
|
+
console.error(`Failed to read async status for '${job.asyncDir}':`, error);
|
|
194
|
+
job.status = "failed";
|
|
195
|
+
job.updatedAt = Date.now();
|
|
196
|
+
}
|
|
190
197
|
if (!state.cleanupTimers.has(job.asyncId)) {
|
|
191
198
|
scheduleCleanup(job.asyncId);
|
|
192
199
|
}
|
|
193
200
|
}
|
|
201
|
+
if (widgetRenderKey(job) !== widgetStateBefore) widgetChanged = true;
|
|
194
202
|
}
|
|
195
203
|
|
|
196
|
-
if (state.lastUiContext?.hasUI) rerenderWidget(state.lastUiContext);
|
|
204
|
+
if (widgetChanged && state.lastUiContext?.hasUI) rerenderWidget(state.lastUiContext);
|
|
197
205
|
}, pollIntervalMs);
|
|
198
206
|
state.poller.unref?.();
|
|
199
207
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { formatDuration, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
3
|
+
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
4
|
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
5
5
|
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type SubagentRunMode, type TokenUsage } from "../../shared/types.ts";
|
|
6
6
|
import { readStatus } from "../../shared/utils.ts";
|
|
@@ -25,6 +25,7 @@ interface AsyncRunStepSummary {
|
|
|
25
25
|
tokens?: TokenUsage;
|
|
26
26
|
skills?: string[];
|
|
27
27
|
model?: string;
|
|
28
|
+
thinking?: string;
|
|
28
29
|
attemptedModels?: string[];
|
|
29
30
|
error?: string;
|
|
30
31
|
}
|
|
@@ -160,6 +161,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
160
161
|
...(step.tokens ? { tokens: step.tokens } : {}),
|
|
161
162
|
...(step.skills ? { skills: step.skills } : {}),
|
|
162
163
|
...(step.model ? { model: step.model } : {}),
|
|
164
|
+
...(step.thinking ? { thinking: step.thinking } : {}),
|
|
163
165
|
...(step.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
|
|
164
166
|
...(step.error ? { error: step.error } : {}),
|
|
165
167
|
};
|
|
@@ -235,7 +237,8 @@ function formatStepLine(step: AsyncRunStepSummary): string {
|
|
|
235
237
|
const parts = [`${step.index + 1}. ${step.agent}`, step.status];
|
|
236
238
|
const activity = formatActivityFacts(step);
|
|
237
239
|
if (activity) parts.push(activity);
|
|
238
|
-
|
|
240
|
+
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
241
|
+
if (modelThinking) parts.push(modelThinking);
|
|
239
242
|
if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
|
|
240
243
|
if (step.tokens) parts.push(`${formatTokens(step.tokens.total)} tok`);
|
|
241
244
|
return parts.join(" | ");
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
4
|
import { formatAsyncRunList, formatAsyncRunOutputPath, formatAsyncRunProgressLabel, listAsyncRuns } from "./async-status.ts";
|
|
5
|
+
import { formatModelThinking } from "../../shared/formatters.ts";
|
|
5
6
|
import { formatActivityLabel } from "../../shared/status-format.ts";
|
|
6
7
|
import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details } from "../../shared/types.ts";
|
|
7
8
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
@@ -142,8 +143,10 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
142
143
|
].filter((line): line is string => Boolean(line));
|
|
143
144
|
for (const [index, step] of (status.steps ?? []).entries()) {
|
|
144
145
|
const stepActivityText = step.status === "running" ? formatActivityLabel(step.lastActivityAt, step.activityState) : undefined;
|
|
146
|
+
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
147
|
+
const modelText = modelThinking ? ` (${modelThinking})` : "";
|
|
145
148
|
const errorText = step.error ? `, error: ${step.error}` : "";
|
|
146
|
-
lines.push(`${stepLineLabel(status, index)}: ${step.agent} ${step.status}${stepActivityText ? `, ${stepActivityText}` : ""}${errorText}`);
|
|
149
|
+
lines.push(`${stepLineLabel(status, index)}: ${step.agent} ${step.status}${modelText}${stepActivityText ? `, ${stepActivityText}` : ""}${errorText}`);
|
|
147
150
|
const stepOutputPath = path.join(asyncDir, `output-${index}.log`);
|
|
148
151
|
if (stepOutputPath !== outputPath && fs.existsSync(stepOutputPath)) lines.push(` Output: ${stepOutputPath}`);
|
|
149
152
|
if (step.status === "running") {
|