pi-subagents 0.21.0 → 0.21.2
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 +31 -0
- package/README.md +49 -18
- package/agents/context-builder.md +6 -3
- package/package.json +3 -4
- package/prompts/parallel-context-build.md +53 -0
- package/prompts/parallel-handoff-plan.md +59 -0
- package/prompts/parallel-review.md +4 -0
- package/skills/pi-subagents/SKILL.md +71 -3
- package/{agent-management.ts → src/agents/agent-management.ts} +1 -5
- package/{agents.ts → src/agents/agents.ts} +1 -1
- package/{doctor.ts → src/extension/doctor.ts} +5 -5
- package/{index.ts → src/extension/index.ts} +21 -20
- package/{schemas.ts → src/extension/schemas.ts} +25 -39
- package/{intercom-bridge.ts → src/intercom/intercom-bridge.ts} +19 -10
- package/{result-intercom.ts → src/intercom/result-intercom.ts} +33 -5
- package/{agent-manager-chain-detail.ts → src/manager-ui/agent-manager-chain-detail.ts} +3 -3
- package/{agent-manager-detail.ts → src/manager-ui/agent-manager-detail.ts} +7 -7
- package/{agent-manager-edit.ts → src/manager-ui/agent-manager-edit.ts} +4 -4
- package/{agent-manager-list.ts → src/manager-ui/agent-manager-list.ts} +12 -5
- package/{agent-manager-parallel.ts → src/manager-ui/agent-manager-parallel.ts} +3 -3
- package/{agent-manager.ts → src/manager-ui/agent-manager.ts} +19 -16
- package/{async-execution.ts → src/runs/background/async-execution.ts} +11 -11
- package/{async-job-tracker.ts → src/runs/background/async-job-tracker.ts} +29 -6
- package/src/runs/background/async-resume.ts +305 -0
- package/{async-status.ts → src/runs/background/async-status.ts} +14 -12
- package/{notify.ts → src/runs/background/notify.ts} +1 -1
- package/{result-watcher.ts → src/runs/background/result-watcher.ts} +93 -23
- package/{run-status.ts → src/runs/background/run-status.ts} +63 -28
- package/src/runs/background/stale-run-reconciler.ts +275 -0
- package/{subagent-runner.ts → src/runs/background/subagent-runner.ts} +43 -55
- package/{chain-clarify.ts → src/runs/foreground/chain-clarify.ts} +7 -7
- package/{chain-execution.ts → src/runs/foreground/chain-execution.ts} +10 -10
- package/{execution.ts → src/runs/foreground/execution.ts} +26 -22
- package/{subagent-executor.ts → src/runs/foreground/subagent-executor.ts} +155 -24
- package/{long-running-guard.ts → src/runs/shared/long-running-guard.ts} +3 -3
- package/{model-fallback.ts → src/runs/shared/model-fallback.ts} +1 -1
- package/{subagent-control.ts → src/runs/shared/subagent-control.ts} +4 -8
- package/src/shared/atomic-json.ts +16 -0
- package/{settings.ts → src/shared/settings.ts} +21 -14
- package/{types.ts → src/shared/types.ts} +10 -2
- package/{utils.ts → src/shared/utils.ts} +1 -15
- package/{slash-bridge.ts → src/slash/slash-bridge.ts} +2 -2
- package/{slash-commands.ts → src/slash/slash-commands.ts} +13 -10
- package/{slash-live-state.ts → src/slash/slash-live-state.ts} +2 -2
- package/{render.ts → src/tui/render.ts} +3 -3
- package/{subagents-status.ts → src/tui/subagents-status.ts} +34 -14
- /package/{agent-scope.ts → src/agents/agent-scope.ts} +0 -0
- /package/{agent-selection.ts → src/agents/agent-selection.ts} +0 -0
- /package/{agent-serializer.ts → src/agents/agent-serializer.ts} +0 -0
- /package/{agent-templates.ts → src/agents/agent-templates.ts} +0 -0
- /package/{chain-serializer.ts → src/agents/chain-serializer.ts} +0 -0
- /package/{frontmatter.ts → src/agents/frontmatter.ts} +0 -0
- /package/{skills.ts → src/agents/skills.ts} +0 -0
- /package/{completion-dedupe.ts → src/runs/background/completion-dedupe.ts} +0 -0
- /package/{top-level-async.ts → src/runs/background/top-level-async.ts} +0 -0
- /package/{completion-guard.ts → src/runs/shared/completion-guard.ts} +0 -0
- /package/{parallel-utils.ts → src/runs/shared/parallel-utils.ts} +0 -0
- /package/{pi-args.ts → src/runs/shared/pi-args.ts} +0 -0
- /package/{pi-spawn.ts → src/runs/shared/pi-spawn.ts} +0 -0
- /package/{run-history.ts → src/runs/shared/run-history.ts} +0 -0
- /package/{single-output.ts → src/runs/shared/single-output.ts} +0 -0
- /package/{subagent-prompt-runtime.ts → src/runs/shared/subagent-prompt-runtime.ts} +0 -0
- /package/{worktree.ts → src/runs/shared/worktree.ts} +0 -0
- /package/{artifacts.ts → src/shared/artifacts.ts} +0 -0
- /package/{file-coalescer.ts → src/shared/file-coalescer.ts} +0 -0
- /package/{fork-context.ts → src/shared/fork-context.ts} +0 -0
- /package/{formatters.ts → src/shared/formatters.ts} +0 -0
- /package/{jsonl-writer.ts → src/shared/jsonl-writer.ts} +0 -0
- /package/{post-exit-stdio-guard.ts → src/shared/post-exit-stdio-guard.ts} +0 -0
- /package/{session-tokens.ts → src/shared/session-tokens.ts} +0 -0
- /package/{prompt-template-bridge.ts → src/slash/prompt-template-bridge.ts} +0 -0
- /package/{render-helpers.ts → src/tui/render-helpers.ts} +0 -0
- /package/{text-editor.ts → src/tui/text-editor.ts} +0 -0
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Type } from "typebox";
|
|
6
|
+
import { SUBAGENT_ACTIONS } from "../shared/types.ts";
|
|
6
7
|
|
|
7
8
|
const SkillOverride = Type.Unsafe({
|
|
8
|
-
type: ["string", "array", "boolean"],
|
|
9
9
|
anyOf: [
|
|
10
10
|
{ type: "array", items: { type: "string" } },
|
|
11
11
|
{ type: "boolean" },
|
|
@@ -15,12 +15,14 @@ const SkillOverride = Type.Unsafe({
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
const OutputOverride = Type.Unsafe({
|
|
18
|
-
|
|
18
|
+
anyOf: [
|
|
19
|
+
{ type: "string" },
|
|
20
|
+
{ type: "boolean" },
|
|
21
|
+
],
|
|
19
22
|
description: "Output filename/path (string), or false to disable file output",
|
|
20
23
|
});
|
|
21
24
|
|
|
22
25
|
const ReadsOverride = Type.Unsafe({
|
|
23
|
-
type: ["array", "boolean"],
|
|
24
26
|
anyOf: [
|
|
25
27
|
{ type: "array", items: { type: "string" } },
|
|
26
28
|
{ type: "boolean" },
|
|
@@ -40,20 +42,6 @@ const TaskItem = Type.Object({
|
|
|
40
42
|
skill: Type.Optional(SkillOverride),
|
|
41
43
|
});
|
|
42
44
|
|
|
43
|
-
// Sequential chain step (single agent)
|
|
44
|
-
const SequentialStepSchema = Type.Object({
|
|
45
|
-
agent: Type.String(),
|
|
46
|
-
task: Type.Optional(Type.String({
|
|
47
|
-
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."
|
|
48
|
-
})),
|
|
49
|
-
cwd: Type.Optional(Type.String()),
|
|
50
|
-
output: Type.Optional(OutputOverride),
|
|
51
|
-
reads: Type.Optional(ReadsOverride),
|
|
52
|
-
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
53
|
-
skill: Type.Optional(SkillOverride),
|
|
54
|
-
model: Type.Optional(Type.String({ description: "Override model for this step" })),
|
|
55
|
-
});
|
|
56
|
-
|
|
57
45
|
// Parallel task item (within a parallel step)
|
|
58
46
|
const ParallelTaskSchema = Type.Object({
|
|
59
47
|
agent: Type.String(),
|
|
@@ -67,17 +55,7 @@ const ParallelTaskSchema = Type.Object({
|
|
|
67
55
|
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
68
56
|
});
|
|
69
57
|
|
|
70
|
-
//
|
|
71
|
-
const ParallelStepSchema = Type.Object({
|
|
72
|
-
parallel: Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" }),
|
|
73
|
-
concurrency: Type.Optional(Type.Number({ description: "Max concurrent tasks (default: 4)" })),
|
|
74
|
-
failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure (default: false)" })),
|
|
75
|
-
worktree: Type.Optional(Type.Boolean({
|
|
76
|
-
description: "Create isolated git worktrees for each parallel task."
|
|
77
|
-
})),
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Flattened so providers that reject anyOf/oneOf can still accept either sequential or parallel steps.
|
|
58
|
+
// Flattened so chain steps do not need an object-shape anyOf/oneOf union.
|
|
81
59
|
const ChainItem = Type.Object({
|
|
82
60
|
agent: Type.Optional(Type.String({ description: "Sequential step agent name" })),
|
|
83
61
|
task: Type.Optional(Type.String({
|
|
@@ -100,9 +78,9 @@ const ChainItem = Type.Object({
|
|
|
100
78
|
const ControlOverrides = Type.Object({
|
|
101
79
|
enabled: Type.Optional(Type.Boolean({ description: "Enable/disable subagent control attention tracking for this run" })),
|
|
102
80
|
needsAttentionAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "No-observed-activity window before a run needs attention" })),
|
|
103
|
-
activeNoticeAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "Active-long-running notice threshold by elapsed ms (default:
|
|
104
|
-
activeNoticeAfterTurns: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
105
|
-
activeNoticeAfterTokens: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
81
|
+
activeNoticeAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "Active-long-running notice threshold by elapsed ms (default: 240000)" })),
|
|
82
|
+
activeNoticeAfterTurns: Type.Optional(Type.Integer({ minimum: 1, description: "Optional active-long-running notice threshold by assistant turns (disabled by default)" })),
|
|
83
|
+
activeNoticeAfterTokens: Type.Optional(Type.Integer({ minimum: 1, description: "Optional active-long-running notice threshold by total tokens (disabled by default)" })),
|
|
106
84
|
failedToolAttemptsBeforeAttention: Type.Optional(Type.Integer({ minimum: 1, description: "Consecutive mutating-tool failures before escalating to needs_attention (default: 3)" })),
|
|
107
85
|
notifyOn: Type.Optional(Type.Array(Type.String({ enum: ["active_long_running", "needs_attention"] }), {
|
|
108
86
|
description: "Control event types that should notify the parent/orchestrator. Defaults to active_long_running and needs_attention.",
|
|
@@ -117,26 +95,31 @@ export const SubagentParams = Type.Object({
|
|
|
117
95
|
task: Type.Optional(Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })),
|
|
118
96
|
// Management action (when present, tool operates in management mode)
|
|
119
97
|
action: Type.Optional(Type.String({
|
|
120
|
-
|
|
98
|
+
enum: [...SUBAGENT_ACTIONS],
|
|
99
|
+
description: "Management/control action. Omit for execution mode."
|
|
121
100
|
})),
|
|
122
101
|
id: Type.Optional(Type.String({
|
|
123
|
-
description: "Run id or prefix for action='status' or action='
|
|
102
|
+
description: "Run id or prefix for action='status', action='interrupt', or action='resume'."
|
|
124
103
|
})),
|
|
125
104
|
runId: Type.Optional(Type.String({
|
|
126
|
-
description: "Target run ID for action='interrupt'. Defaults to the most recently active controllable run
|
|
105
|
+
description: "Target run ID for action='interrupt' or action='resume'. Defaults to the most recently active controllable run for interrupt. Prefer id for new calls."
|
|
127
106
|
})),
|
|
128
107
|
dir: Type.Optional(Type.String({
|
|
129
|
-
description: "Async run directory for action='status'."
|
|
108
|
+
description: "Async run directory for action='status' or action='resume'."
|
|
130
109
|
})),
|
|
110
|
+
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child." })),
|
|
111
|
+
message: Type.Optional(Type.String({ description: "Follow-up message for action='resume'." })),
|
|
131
112
|
// Chain identifier for management (can't reuse 'chain' — that's the execution array)
|
|
132
113
|
chainName: Type.Optional(Type.String({
|
|
133
114
|
description: "Chain name for get/update/delete management actions"
|
|
134
115
|
})),
|
|
135
116
|
// Agent/chain configuration for create/update (nested to avoid conflicts with execution fields)
|
|
136
117
|
config: Type.Optional(Type.Unsafe({
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
118
|
+
anyOf: [
|
|
119
|
+
{ type: "object", additionalProperties: true },
|
|
120
|
+
{ type: "string" },
|
|
121
|
+
],
|
|
122
|
+
description: "Agent or chain config for create/update. Agent: name, description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext ('fresh'|'fork'), 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?, skill?, progress?}). Presence of 'steps' creates a chain instead of an agent. String values must be valid JSON."
|
|
140
123
|
})),
|
|
141
124
|
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?, output?, reads?, progress?}, ...]" })),
|
|
142
125
|
concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or 4." })),
|
|
@@ -165,7 +148,10 @@ export const SubagentParams = Type.Object({
|
|
|
165
148
|
control: Type.Optional(ControlOverrides),
|
|
166
149
|
// Solo agent overrides
|
|
167
150
|
output: Type.Optional(Type.Unsafe({
|
|
168
|
-
|
|
151
|
+
anyOf: [
|
|
152
|
+
{ type: "string" },
|
|
153
|
+
{ type: "boolean" },
|
|
154
|
+
],
|
|
169
155
|
description: "Output file for single agent (string), or false to disable. Relative paths resolve against cwd.",
|
|
170
156
|
})),
|
|
171
157
|
skill: Type.Optional(SkillOverride),
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import type { AgentConfig } from "
|
|
5
|
-
import type { ExtensionConfig, IntercomBridgeConfig, IntercomBridgeMode } from "
|
|
4
|
+
import type { AgentConfig } from "../agents/agents.ts";
|
|
5
|
+
import type { ExtensionConfig, IntercomBridgeConfig, IntercomBridgeMode } from "../shared/types.ts";
|
|
6
|
+
|
|
7
|
+
function defaultIntercomExtensionDir(): string {
|
|
8
|
+
return path.join(os.homedir(), ".pi", "agent", "extensions", "pi-intercom");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function defaultIntercomConfigPath(): string {
|
|
12
|
+
return path.join(os.homedir(), ".pi", "agent", "intercom", "config.json");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function defaultSubagentConfigDir(): string {
|
|
16
|
+
return path.join(os.homedir(), ".pi", "agent", "extensions", "subagent");
|
|
17
|
+
}
|
|
6
18
|
|
|
7
|
-
const DEFAULT_INTERCOM_EXTENSION_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", "pi-intercom");
|
|
8
|
-
const DEFAULT_INTERCOM_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "intercom", "config.json");
|
|
9
|
-
const DEFAULT_SUBAGENT_CONFIG_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent");
|
|
10
19
|
const DEFAULT_INTERCOM_TARGET_PREFIX = "subagent-chat";
|
|
11
20
|
export const INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:";
|
|
12
21
|
const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only. Do not continue that conversation or send questions, status updates, or completion handoffs to the orchestrator in normal assistant text.
|
|
@@ -135,9 +144,9 @@ ${instruction}`;
|
|
|
135
144
|
export function diagnoseIntercomBridge(input: ResolveIntercomBridgeInput): IntercomBridgeDiagnostic {
|
|
136
145
|
const config = resolveIntercomBridgeConfig(input.config);
|
|
137
146
|
const mode = config.mode;
|
|
138
|
-
const extensionDir = path.resolve(input.extensionDir ??
|
|
147
|
+
const extensionDir = path.resolve(input.extensionDir ?? defaultIntercomExtensionDir());
|
|
139
148
|
const orchestratorTarget = input.orchestratorTarget?.trim();
|
|
140
|
-
const configPath = path.resolve(input.configPath ??
|
|
149
|
+
const configPath = path.resolve(input.configPath ?? defaultIntercomConfigPath());
|
|
141
150
|
const wantsIntercom = mode !== "off" && !(mode === "fork-only" && input.context !== "fork");
|
|
142
151
|
const piIntercomAvailable = fs.existsSync(extensionDir);
|
|
143
152
|
let configStatus: ReturnType<typeof intercomConfigStatus> | undefined;
|
|
@@ -173,9 +182,9 @@ export function diagnoseIntercomBridge(input: ResolveIntercomBridgeInput): Inter
|
|
|
173
182
|
export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): IntercomBridgeState {
|
|
174
183
|
const config = resolveIntercomBridgeConfig(input.config);
|
|
175
184
|
const mode = config.mode;
|
|
176
|
-
const extensionDir = path.resolve(input.extensionDir ??
|
|
185
|
+
const extensionDir = path.resolve(input.extensionDir ?? defaultIntercomExtensionDir());
|
|
177
186
|
const orchestratorTarget = input.orchestratorTarget?.trim();
|
|
178
|
-
const settingsDir = path.resolve(input.settingsDir ??
|
|
187
|
+
const settingsDir = path.resolve(input.settingsDir ?? defaultSubagentConfigDir());
|
|
179
188
|
const defaultInstruction = buildIntercomBridgeInstruction(
|
|
180
189
|
orchestratorTarget || "{orchestratorTarget}",
|
|
181
190
|
DEFAULT_INTERCOM_BRIDGE_TEMPLATE,
|
|
@@ -194,7 +203,7 @@ export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): Interc
|
|
|
194
203
|
return { active: false, mode, extensionDir, instruction: defaultInstruction };
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
const configPath = path.resolve(input.configPath ??
|
|
206
|
+
const configPath = path.resolve(input.configPath ?? defaultIntercomConfigPath());
|
|
198
207
|
const intercomStatus = intercomConfigStatus(configPath);
|
|
199
208
|
if (intercomStatus.error) console.warn(`Failed to parse intercom config at '${configPath}'. Assuming enabled.`, intercomStatus.error);
|
|
200
209
|
if (!intercomStatus.enabled) {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type SubagentResultStatus,
|
|
9
9
|
SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT,
|
|
10
10
|
SUBAGENT_RESULT_INTERCOM_EVENT,
|
|
11
|
-
} from "
|
|
11
|
+
} from "../shared/types.ts";
|
|
12
12
|
|
|
13
13
|
export function resolveSubagentResultStatus(input: {
|
|
14
14
|
exitCode?: number;
|
|
@@ -69,10 +69,24 @@ interface GroupedResultIntercomMessageInput {
|
|
|
69
69
|
chainSteps?: number;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
function asyncResumeGuidance(input: {
|
|
73
|
+
source: "foreground" | "async";
|
|
74
|
+
children: SubagentResultIntercomChild[];
|
|
75
|
+
asyncId?: string;
|
|
76
|
+
}): string | undefined {
|
|
77
|
+
if (input.source !== "async" || !input.asyncId) return undefined;
|
|
78
|
+
if (input.children.length === 1 && typeof input.children[0]?.sessionPath === "string") {
|
|
79
|
+
return `Revive: subagent({ action: "resume", id: "${input.asyncId}", message: "..." })`;
|
|
80
|
+
}
|
|
81
|
+
if (input.children.length > 1) return "Resume: unsupported for multi-child async runs until per-child session files are persisted.";
|
|
82
|
+
return "Resume: unavailable; no single child session file was persisted.";
|
|
83
|
+
}
|
|
84
|
+
|
|
72
85
|
function formatSubagentResultIntercomMessage(input: {
|
|
73
86
|
runId: string;
|
|
74
87
|
mode: "single" | "parallel" | "chain";
|
|
75
88
|
status: SubagentResultStatus;
|
|
89
|
+
source: "foreground" | "async";
|
|
76
90
|
children: SubagentResultIntercomChild[];
|
|
77
91
|
asyncId?: string;
|
|
78
92
|
asyncDir?: string;
|
|
@@ -92,16 +106,20 @@ function formatSubagentResultIntercomMessage(input: {
|
|
|
92
106
|
}
|
|
93
107
|
if (input.asyncId) lines.push(`Async id: ${input.asyncId}`);
|
|
94
108
|
if (input.asyncDir) lines.push(`Async dir: ${input.asyncDir}`);
|
|
109
|
+
const resumeGuidance = asyncResumeGuidance(input);
|
|
110
|
+
if (resumeGuidance) lines.push(resumeGuidance);
|
|
95
111
|
if (input.children.some((child) => child.intercomTarget)) {
|
|
96
112
|
lines.push("");
|
|
97
|
-
lines.push(
|
|
113
|
+
lines.push(input.source === "async"
|
|
114
|
+
? "Previous intercom targets below identify child sessions used while they were running. Inspect artifacts or session logs if resume is unavailable."
|
|
115
|
+
: "Intercom targets below identify child sessions used while they were running; completed child sessions may no longer be reachable. Inspect artifacts or session logs for follow-up.");
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
for (let index = 0; index < input.children.length; index++) {
|
|
101
119
|
const child = input.children[index]!;
|
|
102
120
|
lines.push("");
|
|
103
121
|
lines.push(`${index + 1}. ${child.agent} — ${child.status}`);
|
|
104
|
-
if (child.intercomTarget) lines.push(
|
|
122
|
+
if (child.intercomTarget) lines.push(`${input.source === "async" ? "Previous intercom target" : "Run intercom target"}: ${child.intercomTarget}`);
|
|
105
123
|
if (child.artifactPath) lines.push(`Output artifact: ${child.artifactPath}`);
|
|
106
124
|
if (child.sessionPath) lines.push(`Session: ${child.sessionPath}`);
|
|
107
125
|
lines.push("Summary:");
|
|
@@ -144,9 +162,19 @@ export async function deliverSubagentResultIntercomEvent(
|
|
|
144
162
|
events: IntercomEventBus,
|
|
145
163
|
payload: SubagentResultIntercomPayload,
|
|
146
164
|
timeoutMs = 500,
|
|
165
|
+
): Promise<boolean> {
|
|
166
|
+
return deliverSubagentIntercomMessageEvent(events, payload.to, payload.message, timeoutMs, payload);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function deliverSubagentIntercomMessageEvent(
|
|
170
|
+
events: IntercomEventBus,
|
|
171
|
+
to: string,
|
|
172
|
+
message: string,
|
|
173
|
+
timeoutMs = 500,
|
|
174
|
+
extra: Record<string, unknown> = {},
|
|
147
175
|
): Promise<boolean> {
|
|
148
176
|
if (typeof events.on !== "function" || typeof events.emit !== "function") return false;
|
|
149
|
-
const requestId =
|
|
177
|
+
const requestId = typeof extra.requestId === "string" ? extra.requestId : randomUUID();
|
|
150
178
|
return new Promise((resolve) => {
|
|
151
179
|
let settled = false;
|
|
152
180
|
let unsubscribe: (() => void) | undefined;
|
|
@@ -166,7 +194,7 @@ export async function deliverSubagentResultIntercomEvent(
|
|
|
166
194
|
});
|
|
167
195
|
timer = setTimeout(() => finish(false), timeoutMs);
|
|
168
196
|
try {
|
|
169
|
-
events.emit(SUBAGENT_RESULT_INTERCOM_EVENT, { ...
|
|
197
|
+
events.emit(SUBAGENT_RESULT_INTERCOM_EVENT, { ...extra, to, message, requestId });
|
|
170
198
|
} catch {
|
|
171
199
|
finish(false);
|
|
172
200
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type { ChainConfig, ChainStepConfig } from "
|
|
4
|
-
import { row, renderFooter, renderHeader, formatPath, formatScrollInfo } from "
|
|
5
|
-
import { isParallelStep, type ChainStep } from "
|
|
3
|
+
import type { ChainConfig, ChainStepConfig } from "../agents/agents.ts";
|
|
4
|
+
import { row, renderFooter, renderHeader, formatPath, formatScrollInfo } from "../tui/render-helpers.ts";
|
|
5
|
+
import { isParallelStep, type ChainStep } from "../shared/settings.ts";
|
|
6
6
|
|
|
7
7
|
export interface ChainDetailState {
|
|
8
8
|
scrollOffset: number;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type { AgentConfig } from "
|
|
4
|
-
import { formatDuration } from "
|
|
5
|
-
import type { RunEntry } from "
|
|
6
|
-
import { buildSkillInjection, resolveSkills } from "
|
|
7
|
-
import { ensureCursorVisible, getCursorDisplayPos, renderEditor, wrapText } from "
|
|
8
|
-
import type { TextEditorState } from "
|
|
9
|
-
import { pad, row, renderHeader, renderFooter, formatPath, formatScrollInfo } from "
|
|
3
|
+
import type { AgentConfig } from "../agents/agents.ts";
|
|
4
|
+
import { formatDuration } from "../shared/formatters.ts";
|
|
5
|
+
import type { RunEntry } from "../runs/shared/run-history.ts";
|
|
6
|
+
import { buildSkillInjection, resolveSkills } from "../agents/skills.ts";
|
|
7
|
+
import { ensureCursorVisible, getCursorDisplayPos, renderEditor, wrapText } from "../tui/text-editor.ts";
|
|
8
|
+
import type { TextEditorState } from "../tui/text-editor.ts";
|
|
9
|
+
import { pad, row, renderHeader, renderFooter, formatPath, formatScrollInfo } from "../tui/render-helpers.ts";
|
|
10
10
|
|
|
11
11
|
export interface DetailState {
|
|
12
12
|
resolved: boolean;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import { defaultSystemPromptMode, type AgentConfig, type AgentDefaultContext, type BuiltinAgentOverrideBase } from "
|
|
4
|
-
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "
|
|
5
|
-
import type { TextEditorState } from "
|
|
6
|
-
import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "
|
|
3
|
+
import { defaultSystemPromptMode, type AgentConfig, type AgentDefaultContext, type BuiltinAgentOverrideBase } from "../agents/agents.ts";
|
|
4
|
+
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "../tui/text-editor.ts";
|
|
5
|
+
import type { TextEditorState } from "../tui/text-editor.ts";
|
|
6
|
+
import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "../tui/render-helpers.ts";
|
|
7
7
|
|
|
8
8
|
export interface ModelInfo { provider: string; id: string; fullId: string; }
|
|
9
9
|
export interface SkillInfo { name: string; source: string; description?: string; }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import type { AgentSource } from "
|
|
2
|
+
import type { AgentSource } from "../agents/agents.ts";
|
|
3
3
|
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
4
|
-
import { pad, row, renderHeader, renderFooter, fuzzyFilter, formatScrollInfo } from "
|
|
4
|
+
import { pad, row, renderHeader, renderFooter, fuzzyFilter, formatScrollInfo } from "../tui/render-helpers.ts";
|
|
5
5
|
|
|
6
6
|
export interface ListAgent {
|
|
7
7
|
id: string;
|
|
@@ -22,6 +22,12 @@ export interface ListState {
|
|
|
22
22
|
selected: string[];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export interface ListShortcuts {
|
|
26
|
+
newShortcut: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_AGENT_MANAGER_NEW_SHORTCUT = "shift+ctrl+n";
|
|
30
|
+
|
|
25
31
|
export type ListAction =
|
|
26
32
|
| { type: "open-detail"; id: string }
|
|
27
33
|
| { type: "clone"; id: string }
|
|
@@ -57,7 +63,7 @@ function clampCursor(state: ListState, filtered: ListAgent[]): void {
|
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
export function handleListInput(state: ListState, agents: ListAgent[], data: string): ListAction | undefined {
|
|
66
|
+
export function handleListInput(state: ListState, agents: ListAgent[], data: string, shortcuts: ListShortcuts = { newShortcut: DEFAULT_AGENT_MANAGER_NEW_SHORTCUT }): ListAction | undefined {
|
|
61
67
|
const filtered = fuzzyFilter(agents, state.filterQuery);
|
|
62
68
|
|
|
63
69
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
@@ -98,7 +104,7 @@ export function handleListInput(state: ListState, agents: ListAgent[], data: str
|
|
|
98
104
|
return;
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
if (matchesKey(data,
|
|
107
|
+
if (matchesKey(data, shortcuts.newShortcut)) {
|
|
102
108
|
return { type: "new" };
|
|
103
109
|
}
|
|
104
110
|
|
|
@@ -160,6 +166,7 @@ export function renderList(
|
|
|
160
166
|
width: number,
|
|
161
167
|
theme: Theme,
|
|
162
168
|
statusMessage?: { text: string; type: "error" | "info" },
|
|
169
|
+
shortcuts: ListShortcuts = { newShortcut: DEFAULT_AGENT_MANAGER_NEW_SHORTCUT },
|
|
163
170
|
): string[] {
|
|
164
171
|
const lines: string[] = [];
|
|
165
172
|
const filtered = fuzzyFilter(agents, state.filterQuery);
|
|
@@ -269,7 +276,7 @@ export function renderList(
|
|
|
269
276
|
? ` [ctrl+r] chain [ctrl+p] parallel [tab] add [shift+tab] remove [esc] clear (${selCount}) `
|
|
270
277
|
: selCount === 1
|
|
271
278
|
? " [ctrl+r] run [ctrl+p] parallel [tab] add more [shift+tab] remove [esc] clear "
|
|
272
|
-
:
|
|
279
|
+
: ` [enter] view [ctrl+r] run [tab] select [${shortcuts.newShortcut}] new [esc] close `;
|
|
273
280
|
lines.push(renderFooter(footerText, width, theme));
|
|
274
281
|
|
|
275
282
|
return lines;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type { TextEditorState } from "
|
|
4
|
-
import { createEditorState, handleEditorInput, renderEditor, wrapText, getCursorDisplayPos, ensureCursorVisible } from "
|
|
5
|
-
import { pad, row, renderHeader, renderFooter, fuzzyFilter } from "
|
|
3
|
+
import type { TextEditorState } from "../tui/text-editor.ts";
|
|
4
|
+
import { createEditorState, handleEditorInput, renderEditor, wrapText, getCursorDisplayPos, ensureCursorVisible } from "../tui/text-editor.ts";
|
|
5
|
+
import { pad, row, renderHeader, renderFooter, fuzzyFilter } from "../tui/render-helpers.ts";
|
|
6
6
|
|
|
7
7
|
interface ParallelSlot {
|
|
8
8
|
agentName: string;
|
|
@@ -14,20 +14,20 @@ import {
|
|
|
14
14
|
type AgentConfig,
|
|
15
15
|
type BuiltinAgentOverrideBase,
|
|
16
16
|
type ChainConfig,
|
|
17
|
-
} from "
|
|
18
|
-
import { serializeAgent } from "
|
|
19
|
-
import { TEMPLATE_ITEMS, type AgentTemplate, type TemplateItem } from "
|
|
20
|
-
import { parseChain, serializeChain } from "
|
|
21
|
-
import { renderList, handleListInput, type ListAgent, type ListState, type ListAction } from "./agent-manager-list.ts";
|
|
17
|
+
} from "../agents/agents.ts";
|
|
18
|
+
import { serializeAgent } from "../agents/agent-serializer.ts";
|
|
19
|
+
import { TEMPLATE_ITEMS, type AgentTemplate, type TemplateItem } from "../agents/agent-templates.ts";
|
|
20
|
+
import { parseChain, serializeChain } from "../agents/chain-serializer.ts";
|
|
21
|
+
import { DEFAULT_AGENT_MANAGER_NEW_SHORTCUT, renderList, handleListInput, type ListAgent, type ListShortcuts, type ListState, type ListAction } from "./agent-manager-list.ts";
|
|
22
22
|
import { createParallelState, handleParallelInput, renderParallel, formatParallelTitle, type ParallelState, type AgentOption } from "./agent-manager-parallel.ts";
|
|
23
23
|
import { renderDetail, handleDetailInput, renderTaskInput, type DetailState, type DetailAction, type LaunchToggleState } from "./agent-manager-detail.ts";
|
|
24
24
|
import { renderChainDetail, handleChainDetailInput, type ChainDetailAction, type ChainDetailState } from "./agent-manager-chain-detail.ts";
|
|
25
25
|
import { createEditState, handleEditInput, renderEdit, type EditField, type EditScreen, type EditState, type ModelInfo, type SkillInfo } from "./agent-manager-edit.ts";
|
|
26
|
-
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "
|
|
27
|
-
import type { TextEditorState } from "
|
|
28
|
-
import { loadRunsForAgent } from "
|
|
29
|
-
import { pad, row, renderHeader, renderFooter } from "
|
|
30
|
-
import { isParallelStep, type ChainStep } from "
|
|
26
|
+
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "../tui/text-editor.ts";
|
|
27
|
+
import type { TextEditorState } from "../tui/text-editor.ts";
|
|
28
|
+
import { loadRunsForAgent } from "../runs/shared/run-history.ts";
|
|
29
|
+
import { pad, row, renderHeader, renderFooter } from "../tui/render-helpers.ts";
|
|
30
|
+
import { isParallelStep, type ChainStep } from "../shared/settings.ts";
|
|
31
31
|
|
|
32
32
|
export type ManagerResult =
|
|
33
33
|
| { action: "launch"; agent: string; task: string; skipClarify?: boolean; fork?: boolean; background?: boolean }
|
|
@@ -43,6 +43,7 @@ interface ChainEntry { id: string; kind: "chain"; config: ChainConfig; }
|
|
|
43
43
|
interface NameInputState { mode: "new-agent" | "clone-agent" | "clone-chain" | "new-chain"; editor: TextEditorState; scope: "user" | "project"; allowProject: boolean; sourceId?: string; template?: AgentTemplate; error?: string; }
|
|
44
44
|
interface StatusMessage { text: string; type: "error" | "info"; }
|
|
45
45
|
interface OverrideScopeState { selectedScope: "user" | "project"; allowProject: boolean; }
|
|
46
|
+
export interface AgentManagerOptions { newShortcut?: string; }
|
|
46
47
|
|
|
47
48
|
const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "defaultContext", "disabled", "tools", "skills", "prompt"];
|
|
48
49
|
|
|
@@ -131,14 +132,16 @@ export class AgentManagerComponent implements Component {
|
|
|
131
132
|
private models: ModelInfo[];
|
|
132
133
|
private skills: SkillInfo[];
|
|
133
134
|
private done: (result: ManagerResult) => void;
|
|
135
|
+
private shortcuts: ListShortcuts;
|
|
134
136
|
|
|
135
|
-
constructor(tui: TUI, theme: Theme, agentData: AgentData, models: ModelInfo[], skills: SkillInfo[], done: (result: ManagerResult) => void) {
|
|
137
|
+
constructor(tui: TUI, theme: Theme, agentData: AgentData, models: ModelInfo[], skills: SkillInfo[], done: (result: ManagerResult) => void, options: AgentManagerOptions = {}) {
|
|
136
138
|
this.tui = tui;
|
|
137
139
|
this.theme = theme;
|
|
138
140
|
this.agentData = agentData;
|
|
139
141
|
this.models = models;
|
|
140
142
|
this.skills = skills;
|
|
141
143
|
this.done = done;
|
|
144
|
+
this.shortcuts = { newShortcut: options.newShortcut?.trim() || DEFAULT_AGENT_MANAGER_NEW_SHORTCUT };
|
|
142
145
|
this.loadEntries();
|
|
143
146
|
}
|
|
144
147
|
|
|
@@ -518,7 +521,7 @@ export class AgentManagerComponent implements Component {
|
|
|
518
521
|
if (this.screen === "list" && this.statusMessage) this.clearStatus();
|
|
519
522
|
if (this.screen.startsWith("edit") && this.editState?.error) this.editState.error = undefined;
|
|
520
523
|
switch (this.screen) {
|
|
521
|
-
case "list": { const action = handleListInput(this.listState, this.listAgents(), data); if (action) this.handleListAction(action); this.tui.requestRender(); return; }
|
|
524
|
+
case "list": { const action = handleListInput(this.listState, this.listAgents(), data, this.shortcuts); if (action) this.handleListAction(action); this.tui.requestRender(); return; }
|
|
522
525
|
case "template-select": this.handleTemplateSelectInput(data); return;
|
|
523
526
|
case "override-scope": this.handleOverrideScopeInput(data); return;
|
|
524
527
|
case "detail": {
|
|
@@ -671,14 +674,14 @@ export class AgentManagerComponent implements Component {
|
|
|
671
674
|
render(width: number): string[] {
|
|
672
675
|
this.overlayWidth = width; const w = this.overlayWidth;
|
|
673
676
|
switch (this.screen) {
|
|
674
|
-
case "list": return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage);
|
|
677
|
+
case "list": return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage, this.shortcuts);
|
|
675
678
|
case "template-select": return this.renderTemplateSelect(w);
|
|
676
679
|
case "override-scope": return this.renderOverrideScope(w);
|
|
677
|
-
case "detail": { const entry = this.getAgentEntry(this.currentAgentId); if (!entry) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage); return renderDetail(this.detailState, entry.config, this.agentData.cwd, w, this.theme); }
|
|
678
|
-
case "chain-detail": { const entry = this.getChainEntry(this.currentChainId); if (!entry) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage); return renderChainDetail(this.chainDetailState, entry.config, w, this.theme); }
|
|
680
|
+
case "detail": { const entry = this.getAgentEntry(this.currentAgentId); if (!entry) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage, this.shortcuts); return renderDetail(this.detailState, entry.config, this.agentData.cwd, w, this.theme); }
|
|
681
|
+
case "chain-detail": { const entry = this.getChainEntry(this.currentChainId); if (!entry) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage, this.shortcuts); return renderChainDetail(this.chainDetailState, entry.config, w, this.theme); }
|
|
679
682
|
case "edit": case "edit-field": case "edit-prompt": return this.editState ? renderEdit(this.screen as EditScreen, this.editState, w, this.theme) : [];
|
|
680
683
|
case "parallel-builder": {
|
|
681
|
-
if (!this.parallelState) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage);
|
|
684
|
+
if (!this.parallelState) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage, this.shortcuts);
|
|
682
685
|
const agentOptions: AgentOption[] = this.agents.map((e) => ({ name: e.config.name, description: e.config.description, model: e.config.model }));
|
|
683
686
|
return renderParallel(this.parallelState, agentOptions, w, this.theme);
|
|
684
687
|
}
|
|
@@ -9,16 +9,16 @@ import * as path from "node:path";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
12
|
-
import type { AgentConfig } from "
|
|
13
|
-
import { applyThinkingSuffix } from "
|
|
14
|
-
import { injectSingleOutputInstruction, resolveSingleOutputPath } from "
|
|
15
|
-
import { buildChainInstructions, isParallelStep, resolveStepBehavior, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "
|
|
16
|
-
import type { RunnerStep } from "
|
|
17
|
-
import { resolvePiPackageRoot } from "
|
|
18
|
-
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "
|
|
19
|
-
import { resolveChildCwd } from "
|
|
20
|
-
import { buildModelCandidates, resolveModelCandidate, type AvailableModelInfo } from "
|
|
21
|
-
import { resolveExpectedWorktreeAgentCwd } from "
|
|
12
|
+
import type { AgentConfig } from "../../agents/agents.ts";
|
|
13
|
+
import { applyThinkingSuffix } from "../shared/pi-args.ts";
|
|
14
|
+
import { injectSingleOutputInstruction, resolveSingleOutputPath } from "../shared/single-output.ts";
|
|
15
|
+
import { buildChainInstructions, isParallelStep, resolveStepBehavior, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
16
|
+
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
17
|
+
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
18
|
+
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
19
|
+
import { resolveChildCwd } from "../../shared/utils.ts";
|
|
20
|
+
import { buildModelCandidates, resolveModelCandidate, type AvailableModelInfo } from "../shared/model-fallback.ts";
|
|
21
|
+
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
22
22
|
import {
|
|
23
23
|
type ArtifactConfig,
|
|
24
24
|
type Details,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
TEMP_ROOT_DIR,
|
|
31
31
|
getAsyncConfigPath,
|
|
32
32
|
resolveChildMaxSubagentDepth,
|
|
33
|
-
} from "
|
|
33
|
+
} from "../../shared/types.ts";
|
|
34
34
|
|
|
35
35
|
const require = createRequire(import.meta.url);
|
|
36
36
|
const piPackageRoot = resolvePiPackageRoot();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { renderWidget } from "
|
|
5
|
-
import { formatControlNoticeMessage } from "
|
|
4
|
+
import { renderWidget } from "../../tui/render.ts";
|
|
5
|
+
import { formatControlNoticeMessage } from "../shared/subagent-control.ts";
|
|
6
6
|
import {
|
|
7
7
|
type AsyncJobState,
|
|
8
8
|
type AsyncParallelGroupStatus,
|
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
type ControlEvent,
|
|
11
11
|
type SubagentState,
|
|
12
12
|
POLL_INTERVAL_MS,
|
|
13
|
+
RESULTS_DIR,
|
|
13
14
|
SUBAGENT_CONTROL_EVENT,
|
|
14
15
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
15
|
-
} from "
|
|
16
|
-
import { readStatus } from "
|
|
16
|
+
} from "../../shared/types.ts";
|
|
17
|
+
import { readStatus } from "../../shared/utils.ts";
|
|
18
|
+
import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
function isValidParallelGroup(group: AsyncParallelGroupStatus, stepCount: number, chainStepCount: number): boolean {
|
|
@@ -35,6 +37,9 @@ function normalizeParallelGroups(groups: AsyncParallelGroupStatus[] | undefined,
|
|
|
35
37
|
interface AsyncJobTrackerOptions {
|
|
36
38
|
completionRetentionMs?: number;
|
|
37
39
|
pollIntervalMs?: number;
|
|
40
|
+
resultsDir?: string;
|
|
41
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
42
|
+
now?: () => number;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: SubagentState, asyncDirRoot: string, options: AsyncJobTrackerOptions = {}): {
|
|
@@ -45,6 +50,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
45
50
|
} {
|
|
46
51
|
const completionRetentionMs = options.completionRetentionMs ?? 10000;
|
|
47
52
|
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
53
|
+
const resultsDir = options.resultsDir ?? RESULTS_DIR;
|
|
48
54
|
const rerenderWidget = (ctx: ExtensionContext, jobs = Array.from(state.asyncJobs.values())) => {
|
|
49
55
|
renderWidget(ctx, jobs);
|
|
50
56
|
ctx.ui.requestRender?.();
|
|
@@ -132,7 +138,20 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
132
138
|
for (const job of state.asyncJobs.values()) {
|
|
133
139
|
try {
|
|
134
140
|
emitNewControlEvents(job);
|
|
135
|
-
const
|
|
141
|
+
const reconciliation = reconcileAsyncRun(job.asyncDir, {
|
|
142
|
+
resultsDir,
|
|
143
|
+
kill: options.kill,
|
|
144
|
+
now: options.now,
|
|
145
|
+
startedRun: {
|
|
146
|
+
runId: job.asyncId,
|
|
147
|
+
pid: job.pid,
|
|
148
|
+
mode: job.mode,
|
|
149
|
+
agents: job.agents,
|
|
150
|
+
startedAt: job.startedAt,
|
|
151
|
+
sessionFile: job.sessionFile,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
const status = reconciliation.status ?? readStatus(job.asyncDir);
|
|
136
155
|
if (status) {
|
|
137
156
|
const previousStatus = job.status;
|
|
138
157
|
job.status = status.state;
|
|
@@ -167,7 +186,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
167
186
|
job.outputFile = status.outputFile ?? job.outputFile;
|
|
168
187
|
job.totalTokens = status.totalTokens ?? job.totalTokens;
|
|
169
188
|
job.sessionFile = status.sessionFile ?? job.sessionFile;
|
|
170
|
-
if ((job.status === "complete" || job.status === "failed" || job.status === "paused") && previousStatus !== job.status) {
|
|
189
|
+
if ((job.status === "complete" || job.status === "failed" || job.status === "paused") && (previousStatus !== job.status || !state.cleanupTimers.has(job.asyncId))) {
|
|
171
190
|
scheduleCleanup(job.asyncId);
|
|
172
191
|
}
|
|
173
192
|
continue;
|
|
@@ -178,6 +197,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
178
197
|
console.error(`Failed to read async status for '${job.asyncDir}':`, error);
|
|
179
198
|
job.status = "failed";
|
|
180
199
|
job.updatedAt = Date.now();
|
|
200
|
+
if (!state.cleanupTimers.has(job.asyncId)) {
|
|
201
|
+
scheduleCleanup(job.asyncId);
|
|
202
|
+
}
|
|
181
203
|
}
|
|
182
204
|
}
|
|
183
205
|
|
|
@@ -202,6 +224,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
202
224
|
asyncId: info.id,
|
|
203
225
|
asyncDir,
|
|
204
226
|
status: "queued",
|
|
227
|
+
pid: typeof info.pid === "number" ? info.pid : undefined,
|
|
205
228
|
mode: info.chain ? "chain" : "single",
|
|
206
229
|
agents,
|
|
207
230
|
stepsTotal: firstGroupCount ?? agents?.length,
|