@preapexis/piexis 0.2.7 → 0.2.9
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/README.md +25 -6
- package/apps/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/apps/web/dist/assets/index-BWVf-tvl.js +78 -0
- package/apps/web/dist/assets/index-CtxsUlF6.css +1 -0
- package/apps/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/apps/web/dist/fonts/Geist-OFL.txt +93 -0
- package/apps/web/dist/fonts/JetBrainsMono-OFL.txt +93 -0
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/piexis-icon-16.png +0 -0
- package/apps/web/dist/piexis-icon-192.png +0 -0
- package/apps/web/dist/piexis-icon-32.png +0 -0
- package/apps/web/dist/piexis-icon-512.png +0 -0
- package/apps/web/dist/piexis-icon.ico +0 -0
- package/apps/web/dist/piexis-icon.svg +7 -0
- package/apps/web/dist/piexis-mark.svg +5 -0
- package/extensions/agents.ts +222 -68
- package/extensions/ui.ts +18 -7
- package/lib/agent-config.ts +5 -1
- package/lib/agents.ts +35 -2
- package/lib/recommended-tools.ts +1 -0
- package/lib/subagent-runner.ts +4 -1
- package/lib/subagent-scheduler.ts +221 -0
- package/package.json +11 -1
- package/schemas/agent.schema.json +4 -0
- package/apps/web/dist/assets/index-D4n3_lAw.js +0 -10
- package/apps/web/dist/assets/index-igiK0FPY.css +0 -1
package/extensions/agents.ts
CHANGED
|
@@ -5,81 +5,153 @@ import { Key } from "@earendil-works/pi-tui";
|
|
|
5
5
|
import { getAgentDir, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import * as piSdk from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import {
|
|
8
|
-
agentNames, childResourceArgs, completeProjectAgentConfig, isAgentName, loadAgentConfig, profiles, routeSubagent, subagentTools,
|
|
9
|
-
type AgentName, type Effort,
|
|
8
|
+
agentNames, childResourceArgs, completeProjectAgentConfig, detectProfileIntent, isAgentName, loadAgentConfig, profiles, routeSubagent, subagentTools,
|
|
9
|
+
type AgentConfig, type AgentName, type Effort,
|
|
10
10
|
} from "../lib/agents.ts";
|
|
11
11
|
import { limitOutput, piInvocation, runSubagent } from "../lib/subagent-runner.ts";
|
|
12
|
+
import { normalizeSubagentScope, SubagentScheduler, type SchedulerStatus } from "../lib/subagent-scheduler.ts";
|
|
12
13
|
|
|
13
14
|
const AgentSchema = Type.Union(agentNames.map((name) => Type.Literal(name)));
|
|
14
15
|
const parameters = Type.Object({
|
|
15
16
|
agent: AgentSchema,
|
|
16
17
|
purpose: Type.Optional(Type.Literal("explore", { description: "Use the configured small_model for read-only codebase exploration; available only for ask when delegate_exploration is enabled." })),
|
|
18
|
+
mode: Type.Optional(Type.Union([Type.Literal("read"), Type.Literal("write")], { description: "Declared work mode. Defaults to write, except read-only plan/ask and exploration. Read mode selects only read tools." })),
|
|
19
|
+
paths: Type.Optional(Type.Array(Type.String({ minLength: 1 }), { description: "Repository-relative files or directories the task may modify. Missing paths mean broad write ownership. Overlaps and shared configuration serialize writes." })),
|
|
17
20
|
task: Type.String({ minLength: 1, maxLength: 50_000, description: "Self-contained task including needed context; conversation history is not inherited." }),
|
|
18
21
|
});
|
|
19
22
|
const stateType = "piexis-agent";
|
|
23
|
+
const profileChoices = [...agentNames, "auto"];
|
|
20
24
|
|
|
21
25
|
export default function agentsExtension(pi: ExtensionAPI) {
|
|
22
26
|
let active: AgentName = "code";
|
|
27
|
+
let pinned = false;
|
|
28
|
+
let activationOrigin: "manual" | "automatic" | undefined;
|
|
29
|
+
let autoProfileEnabled = false;
|
|
23
30
|
let explorationEnabled = false;
|
|
24
31
|
let switching = false;
|
|
32
|
+
let startupHandled = false;
|
|
25
33
|
const depthText = process.env.PIEXIS_SUBAGENT_DEPTH ?? "0";
|
|
26
34
|
const depth = /^[0-2]$/.test(depthText) ? Number(depthText) : 2;
|
|
27
35
|
const jobs = new Map<AbortController, Promise<unknown>>();
|
|
36
|
+
let scheduler = new SubagentScheduler();
|
|
37
|
+
const childStatuses = new Map<AbortController, { agent: AgentName; status: "queued" | "running" }>();
|
|
38
|
+
|
|
39
|
+
function childStatus(ctx: ExtensionContext) {
|
|
40
|
+
if (!ctx.hasUI) return;
|
|
41
|
+
const groups = (["running", "queued"] as const).map((state) => {
|
|
42
|
+
const names = [...childStatuses.values()].filter((job) => job.status === state).map((job) => job.agent);
|
|
43
|
+
return names.length ? `${names.length} ${state}: ${names.join(", ")}` : "";
|
|
44
|
+
}).filter(Boolean);
|
|
45
|
+
ctx.ui.setStatus("piexis-subagents", groups.length ? `subagents · ${groups.join(" · ")}` : undefined);
|
|
46
|
+
}
|
|
47
|
+
const inheritedScope = process.env.PIEXIS_SUBAGENT_SCOPE ? JSON.parse(process.env.PIEXIS_SUBAGENT_SCOPE) : undefined;
|
|
28
48
|
const extensionPath = fileURLToPath(import.meta.url);
|
|
29
49
|
|
|
30
50
|
pi.registerFlag("agent", { description: `Agent profile: ${agentNames.join(", ")}`, type: "string" });
|
|
31
51
|
|
|
32
52
|
function status(ctx: ExtensionContext) {
|
|
33
|
-
if (ctx.hasUI)
|
|
53
|
+
if (!ctx.hasUI) return;
|
|
54
|
+
ctx.ui.setStatus("piexis-agent", `agent:${active}`);
|
|
55
|
+
ctx.ui.setStatus("piexis-agent-mode", autoProfileEnabled ? (pinned ? "manual" : "auto") : "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function persist() {
|
|
59
|
+
pi.appendEntry(stateType, { agent: active, origin: activationOrigin, pinned });
|
|
34
60
|
}
|
|
35
61
|
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
function canSwitch(ctx: ExtensionContext) {
|
|
63
|
+
return !switching && ctx.isIdle() && jobs.size === 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function activate(name: AgentName, ctx: ExtensionContext, origin: "manual" | "automatic", config?: AgentConfig): Promise<boolean> {
|
|
67
|
+
if (!canSwitch(ctx)) {
|
|
38
68
|
ctx.ui.notify("Wait for the current agent to finish before switching profiles.", "warning");
|
|
39
|
-
return;
|
|
69
|
+
return false;
|
|
40
70
|
}
|
|
41
71
|
switching = true;
|
|
72
|
+
const previousModel = ctx.model;
|
|
73
|
+
const previousEffort = (ctx.thinkingLevel ?? pi.getThinkingLevel()) as Effort;
|
|
74
|
+
const previousState = { active, activationOrigin, pinned };
|
|
75
|
+
let modelChanged = false;
|
|
76
|
+
let effortChanged = false;
|
|
42
77
|
try {
|
|
43
|
-
|
|
78
|
+
config ??= await loadAgentConfig(ctx.cwd, getAgentDir(), ctx.isProjectTrusted());
|
|
44
79
|
const override = config.subagent_variant_overrides?.[name];
|
|
45
80
|
const modelId = override?.model ?? config.model;
|
|
46
81
|
if (modelId) {
|
|
47
82
|
const slash = modelId.indexOf("/");
|
|
48
83
|
const model = ctx.modelRegistry.find(modelId.slice(0, slash), modelId.slice(slash + 1));
|
|
49
84
|
if (!model || !(await pi.setModel(model))) throw new Error(`Configured model unavailable: ${modelId}. Check /model and /login.`);
|
|
85
|
+
modelChanged = true;
|
|
50
86
|
}
|
|
51
|
-
if (override?.effort !== undefined) pi.setThinkingLevel(override.effort);
|
|
87
|
+
if (override?.effort !== undefined) { effortChanged = true; pi.setThinkingLevel(override.effort); }
|
|
52
88
|
active = name;
|
|
53
|
-
|
|
89
|
+
activationOrigin = origin;
|
|
90
|
+
pinned = origin === "manual";
|
|
91
|
+
persist();
|
|
92
|
+
autoProfileEnabled = config.auto_profile === true;
|
|
54
93
|
status(ctx);
|
|
55
|
-
|
|
94
|
+
if (origin === "manual" || name !== previousState.active) {
|
|
95
|
+
ctx.ui.notify(`Agent: ${active}${origin === "automatic" ? " (automatic)" : ""}`, "info");
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
56
98
|
} catch (error) {
|
|
99
|
+
// setModel can succeed before a later setting throws; keep a failed route atomic.
|
|
100
|
+
active = previousState.active;
|
|
101
|
+
activationOrigin = previousState.activationOrigin;
|
|
102
|
+
pinned = previousState.pinned;
|
|
103
|
+
try {
|
|
104
|
+
if (modelChanged && previousModel && !(await pi.setModel(previousModel))) throw new Error("Previous model unavailable");
|
|
105
|
+
if (modelChanged || effortChanged) pi.setThinkingLevel(previousEffort);
|
|
106
|
+
} catch (rollbackError) {
|
|
107
|
+
ctx.ui.notify(`Agents: could not restore previous settings: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, "warning");
|
|
108
|
+
}
|
|
57
109
|
ctx.ui.notify(`Agents: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
110
|
+
return false;
|
|
58
111
|
} finally {
|
|
59
112
|
switching = false;
|
|
60
113
|
}
|
|
61
114
|
}
|
|
62
115
|
|
|
63
|
-
function restore(ctx: ExtensionContext
|
|
116
|
+
function restore(ctx: ExtensionContext) {
|
|
64
117
|
active = "code";
|
|
118
|
+
pinned = false;
|
|
119
|
+
activationOrigin = undefined;
|
|
65
120
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
66
121
|
if (entry.type !== "custom" || entry.customType !== stateType) continue;
|
|
67
|
-
const
|
|
68
|
-
if (isAgentName(agent))
|
|
122
|
+
const data = entry.data as { agent?: unknown; origin?: unknown; pinned?: unknown } | undefined;
|
|
123
|
+
if (!isAgentName(data?.agent)) continue;
|
|
124
|
+
active = data.agent;
|
|
125
|
+
activationOrigin = data.origin === "automatic" ? "automatic" : "manual";
|
|
126
|
+
pinned = typeof data.pinned === "boolean" ? data.pinned : true;
|
|
69
127
|
}
|
|
70
|
-
const flag = applyFlag ? pi.getFlag("agent") : undefined;
|
|
71
|
-
if (isAgentName(flag)) active = flag;
|
|
72
|
-
else if (flag !== undefined && flag !== "") ctx.ui.notify(`Unknown agent: ${flag}. Choose ${agentNames.join(", ")}.`, "warning");
|
|
73
128
|
status(ctx);
|
|
74
129
|
}
|
|
75
130
|
|
|
76
|
-
pi.on("session_start", async (
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
|
|
131
|
+
pi.on("session_start", async (event, ctx) => {
|
|
132
|
+
if (jobs.size === 0) scheduler = new SubagentScheduler();
|
|
133
|
+
try {
|
|
134
|
+
const config = await loadAgentConfig(ctx.cwd, getAgentDir(), ctx.isProjectTrusted());
|
|
135
|
+
explorationEnabled = Boolean(config.delegate_exploration);
|
|
136
|
+
autoProfileEnabled = config.auto_profile === true;
|
|
137
|
+
} catch { explorationEnabled = false; autoProfileEnabled = false; }
|
|
138
|
+
restore(ctx);
|
|
139
|
+
childStatus(ctx);
|
|
140
|
+
if (!startupHandled && (!event.reason || event.reason === "startup")) {
|
|
141
|
+
const flag = pi.getFlag("agent");
|
|
142
|
+
if (isAgentName(flag)) {
|
|
143
|
+
active = flag;
|
|
144
|
+
activationOrigin = "manual";
|
|
145
|
+
// Child processes receive --agent as their fixed role; only main branches persist a pin.
|
|
146
|
+
if (depth === 0) { pinned = true; persist(); }
|
|
147
|
+
} else if (flag !== undefined && flag !== "") ctx.ui.notify(`Unknown agent: ${flag}. Choose ${agentNames.join(", ")}.`, "warning");
|
|
148
|
+
}
|
|
149
|
+
startupHandled = true;
|
|
150
|
+
status(ctx);
|
|
80
151
|
if (depth >= 2) pi.setActiveTools(pi.getActiveTools().filter((name) => name !== "subagent"));
|
|
81
152
|
// Only initialize a new main session; explicit CLI choices and resumed models remain authoritative.
|
|
82
|
-
if (depth !== 0 ||
|
|
153
|
+
if (depth !== 0 || (event.reason && event.reason !== "startup" && event.reason !== "new") ||
|
|
154
|
+
ctx.sessionManager.getBranch().some((entry) => entry.type === "message")) return;
|
|
83
155
|
const args = process.argv.slice(2).slice(0, process.argv.indexOf("--") < 0 ? undefined : process.argv.indexOf("--") - 2);
|
|
84
156
|
if (args.some((arg) => arg === "--model" || arg === "--models" || arg.startsWith("--model=") || arg.startsWith("--models="))) return;
|
|
85
157
|
try {
|
|
@@ -93,7 +165,30 @@ export default function agentsExtension(pi: ExtensionAPI) {
|
|
|
93
165
|
});
|
|
94
166
|
pi.on("session_tree", (_event, ctx) => restore(ctx));
|
|
95
167
|
|
|
96
|
-
pi.
|
|
168
|
+
pi.on("input", async (event, ctx) => {
|
|
169
|
+
if (depth !== 0 || switching || jobs.size > 0 || !ctx.isIdle() ||
|
|
170
|
+
(event.source !== "interactive" && event.source !== "rpc") || event.streamingBehavior === "steer" || event.streamingBehavior === "followUp" ||
|
|
171
|
+
event.text.trimStart().startsWith("/")) return { action: "continue" as const };
|
|
172
|
+
let config;
|
|
173
|
+
try { config = await loadAgentConfig(ctx.cwd, getAgentDir(), ctx.isProjectTrusted()); }
|
|
174
|
+
catch (error) {
|
|
175
|
+
ctx.ui.notify(`Agents: ${error instanceof Error ? error.message : String(error)}. Request was not submitted.`, "warning");
|
|
176
|
+
return { action: "handled" as const };
|
|
177
|
+
}
|
|
178
|
+
autoProfileEnabled = config.auto_profile === true;
|
|
179
|
+
status(ctx);
|
|
180
|
+
if (!autoProfileEnabled || pinned) return { action: "continue" as const };
|
|
181
|
+
const name = detectProfileIntent(event.text);
|
|
182
|
+
// The initial code profile has not applied a variant yet; an explicit first Execute must do so.
|
|
183
|
+
if (!name || (name === active && activationOrigin !== undefined)) return { action: "continue" as const };
|
|
184
|
+
if (!(await activate(name, ctx, "automatic", config))) {
|
|
185
|
+
ctx.ui.notify("Automatic profile activation failed; request was not submitted. Fix the configuration or model and retry.", "warning");
|
|
186
|
+
return { action: "handled" as const };
|
|
187
|
+
}
|
|
188
|
+
return { action: "continue" as const };
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
pi.registerCommand("setup-agents", {
|
|
97
192
|
description: "Fill missing project agent.json settings with defaults without replacing configured values",
|
|
98
193
|
handler: async (_args, ctx) => {
|
|
99
194
|
await ctx.waitForIdle();
|
|
@@ -111,18 +206,34 @@ export default function agentsExtension(pi: ExtensionAPI) {
|
|
|
111
206
|
},
|
|
112
207
|
});
|
|
113
208
|
|
|
114
|
-
pi.registerCommand("
|
|
115
|
-
description: "Switch agent profile (plan, orchestrator, debug, ask, code)",
|
|
209
|
+
pi.registerCommand("profile", {
|
|
210
|
+
description: "Switch agent profile (plan, orchestrator, debug, ask, code) or resume automatic routing (auto)",
|
|
211
|
+
getArgumentCompletions: (prefix) => {
|
|
212
|
+
const matches = profileChoices.filter((choice) => choice.startsWith(prefix));
|
|
213
|
+
return matches.length > 0 ? matches.map((choice) => ({ value: choice, label: choice })) : null;
|
|
214
|
+
},
|
|
116
215
|
handler: async (args, ctx) => {
|
|
117
216
|
await ctx.waitForIdle();
|
|
118
217
|
let name: string | undefined = args.trim() || undefined;
|
|
119
218
|
if (!name) {
|
|
120
|
-
if (!ctx.hasUI) { ctx.ui.notify(`Current agent: ${active}. Use /
|
|
121
|
-
name = await ctx.ui.select(`Agent profile (current: ${active})`,
|
|
219
|
+
if (!ctx.hasUI) { ctx.ui.notify(`Current agent: ${active}. Use /profile <${profileChoices.join("|")}>.`, "info"); return; }
|
|
220
|
+
name = await ctx.ui.select(`Agent profile (current: ${active})`, profileChoices);
|
|
122
221
|
if (!name) return;
|
|
123
222
|
}
|
|
124
|
-
if (
|
|
125
|
-
|
|
223
|
+
if (name === "auto") {
|
|
224
|
+
let config;
|
|
225
|
+
try { config = await loadAgentConfig(ctx.cwd, getAgentDir(), ctx.isProjectTrusted()); }
|
|
226
|
+
catch (error) { ctx.ui.notify(`Agents: ${error instanceof Error ? error.message : String(error)}`, "warning"); return; }
|
|
227
|
+
if (!config.auto_profile) { ctx.ui.notify("Enable auto_profile in agent.json before using /profile auto.", "warning"); return; }
|
|
228
|
+
if (!canSwitch(ctx)) { ctx.ui.notify("Wait for the current agent to finish before switching profiles.", "warning"); return; }
|
|
229
|
+
autoProfileEnabled = true;
|
|
230
|
+
if (pinned) { pinned = false; persist(); }
|
|
231
|
+
status(ctx);
|
|
232
|
+
ctx.ui.notify("Agent profile routing is automatic.", "info");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (!isAgentName(name)) { ctx.ui.notify(`Unknown agent: ${name}. Choose ${profileChoices.join(", ")}.`, "error"); return; }
|
|
236
|
+
await activate(name, ctx, "manual");
|
|
126
237
|
},
|
|
127
238
|
});
|
|
128
239
|
|
|
@@ -130,13 +241,13 @@ export default function agentsExtension(pi: ExtensionAPI) {
|
|
|
130
241
|
description: "Cycle agent profile (plan → orchestrator → debug → ask → code)",
|
|
131
242
|
handler: async (ctx) => {
|
|
132
243
|
if (ctx.mode !== "tui") return;
|
|
133
|
-
await activate(agentNames[(agentNames.indexOf(active) + 1) % agentNames.length], ctx);
|
|
244
|
+
await activate(agentNames[(agentNames.indexOf(active) + 1) % agentNames.length], ctx, "manual");
|
|
134
245
|
},
|
|
135
246
|
});
|
|
136
247
|
|
|
137
248
|
pi.on("before_agent_start", (event) => ({
|
|
138
249
|
systemPrompt: `${event.systemPrompt}\n\nPiexis agent profile: ${active}\n${profiles[active]}\n${depth > 0
|
|
139
|
-
?
|
|
250
|
+
? `You are a delegated subagent. Work only on the supplied task.${inheritedScope ? ` Declared ownership: ${inheritedScope.mode}; paths: ${JSON.stringify(inheritedScope.paths)}. Modify only these paths (an empty write list means broad ownership); keep nested delegation within this ownership.` : ""} If user input is needed, return the question to the parent; no terminal interaction is available. Report changed files, results, and unexecuted checks. If the subagent tool is available, use purpose: explore only for bounded read-only codebase exploration and return concise file paths, line numbers, and uncertainties.`
|
|
140
251
|
: explorationEnabled
|
|
141
252
|
? "All five profiles are callable through subagent. Delegate only when a separate context helps; supply a focused task and all relevant context. For bounded codebase exploration involving discovery, several files, or a summary, delegate to subagent with agent: ask and purpose: explore. It uses the configured small model and must return concise findings with file paths, line numbers, and uncertainties. Keep planning, judgment, edits, and final verification here; do not delegate a trivial single read."
|
|
142
253
|
: "All five profiles are callable through subagent. Delegate only when a separate context helps; supply a focused task and all relevant context."}`,
|
|
@@ -145,61 +256,104 @@ export default function agentsExtension(pi: ExtensionAPI) {
|
|
|
145
256
|
pi.registerTool({
|
|
146
257
|
name: "subagent",
|
|
147
258
|
label: "Subagent",
|
|
148
|
-
description: "Delegate a self-contained task to plan, orchestrator, debug, ask, or code in an isolated Pi process in this workspace. With delegate_exploration enabled, ask plus purpose: explore always uses the configured small_model with read-only tools. Other small tasks can automatically use small_model; variant overrides win. Model, effort, and routing reason are returned.
|
|
149
|
-
executionMode: "
|
|
259
|
+
description: "Delegate a self-contained task to plan, orchestrator, debug, ask, or code in an isolated Pi process in this workspace. With delegate_exploration enabled, ask plus purpose: explore always uses the configured small_model with read-only tools. Other small tasks can automatically use small_model; variant overrides win. Model, effort, and routing reason are returned. Orchestrator calls can run concurrently, up to two, only for non-conflicting declared scopes. Other profiles remain sequential. Declare mode and precise paths; broad writes and shared configuration serialize writes. Scopes are cooperative, not sandboxing; child questions return to the parent.",
|
|
260
|
+
executionMode: "parallel",
|
|
150
261
|
parameters,
|
|
151
262
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
152
|
-
|
|
153
|
-
if (!isAgentName(params.agent) || !params.task.trim() || params.task.length > 50_000) throw new Error("A valid agent and non-empty task of at most 50000 characters are required");
|
|
154
|
-
if ((active === "plan" || active === "ask") && params.agent !== "plan" && params.agent !== "ask") {
|
|
155
|
-
throw new Error(`${active} may delegate only to plan or ask; switch to code, debug, or orchestrator for changes`);
|
|
156
|
-
}
|
|
157
|
-
const exploration = params.purpose === "explore";
|
|
158
|
-
if (exploration && params.agent !== "ask") throw new Error("Exploration delegation requires agent: ask");
|
|
159
|
-
if (!ctx.model) throw new Error("Select a main model before delegating");
|
|
160
|
-
const config = await loadAgentConfig(ctx.cwd, getAgentDir(), ctx.isProjectTrusted());
|
|
161
|
-
if (exploration && !config.delegate_exploration) throw new Error("Enable delegate_exploration in agent.json before delegating exploration");
|
|
162
|
-
const route = routeSubagent(config, params.agent, params.task, `${ctx.model.provider}/${ctx.model.id}`, (ctx.thinkingLevel ?? pi.getThinkingLevel()) as Effort, exploration);
|
|
163
|
-
const slash = route.model.indexOf("/");
|
|
164
|
-
const selected = ctx.modelRegistry.find(route.model.slice(0, slash), route.model.slice(slash + 1));
|
|
165
|
-
if (!selected || !ctx.modelRegistry.hasConfiguredAuth(selected)) throw new Error(`Subagent model unavailable: ${route.model}. Check /model and /login.`);
|
|
166
|
-
const tools = subagentTools(params.agent, pi.getActiveTools(), depth + 1, config.delegate_exploration && !exploration);
|
|
263
|
+
const parentProfile = active;
|
|
167
264
|
const controller = new AbortController();
|
|
168
265
|
const abort = () => controller.abort(signal?.reason);
|
|
169
266
|
signal?.addEventListener("abort", abort, { once: true });
|
|
170
267
|
if (signal?.aborted) abort();
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
"--thinking", route.effort, "--agent", params.agent,
|
|
174
|
-
ctx.isProjectTrusted() ? "--approve" : "--no-approve", ...childResourceArgs(process.argv.slice(2)),
|
|
175
|
-
"--extension", resolve(extensionPath), "--tools", tools.join(",")];
|
|
176
|
-
if (tools.length === 0) args.push("--no-tools");
|
|
177
|
-
// Callable MCP tools must not escape a read-only child's explicit built-in loadout.
|
|
178
|
-
if (params.agent === "plan" || params.agent === "ask") {
|
|
179
|
-
if ("createMcpExtension" in piSdk) args.push("--no-mcp");
|
|
180
|
-
args.push("--exclude-tools", "mcp__*");
|
|
181
|
-
}
|
|
182
|
-
const job = runSubagent({ invocation: piInvocation(), args, cwd: ctx.cwd, depth: depth + 1,
|
|
183
|
-
task: params.task, signal: controller.signal,
|
|
184
|
-
onProgress: (text) => onUpdate?.({ content: [{ type: "text", text: `${params.agent} · ${route.model} · ${route.effort} (${route.reason})\n${text}` }], details }),
|
|
185
|
-
});
|
|
268
|
+
// Track preparation as well as queued/running work so shutdown and switching cannot race it.
|
|
269
|
+
const job = executeJob();
|
|
186
270
|
jobs.set(controller, job);
|
|
187
|
-
try {
|
|
188
|
-
|
|
271
|
+
try { return await job; }
|
|
272
|
+
finally {
|
|
273
|
+
jobs.delete(controller);
|
|
274
|
+
childStatuses.delete(controller);
|
|
275
|
+
childStatus(ctx);
|
|
276
|
+
signal?.removeEventListener("abort", abort);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function executeJob() {
|
|
280
|
+
controller.signal.throwIfAborted();
|
|
281
|
+
if (depth >= 2) throw new Error("Subagent delegation depth limit (2) reached");
|
|
282
|
+
if (!isAgentName(params.agent) || !params.task.trim() || params.task.length > 50_000) throw new Error("A valid agent and non-empty task of at most 50000 characters are required");
|
|
283
|
+
if ((parentProfile === "plan" || parentProfile === "ask") && params.agent !== "plan" && params.agent !== "ask") {
|
|
284
|
+
throw new Error(`${parentProfile} may delegate only to plan or ask; switch to code, debug, or orchestrator for changes`);
|
|
285
|
+
}
|
|
286
|
+
const exploration = params.purpose === "explore";
|
|
287
|
+
if (exploration && params.agent !== "ask") throw new Error("Exploration delegation requires agent: ask");
|
|
288
|
+
const readOnly = exploration || params.agent === "plan" || params.agent === "ask";
|
|
289
|
+
const parentScope = inheritedScope ? await normalizeSubagentScope(ctx.cwd, inheritedScope) : undefined;
|
|
290
|
+
const scope = await normalizeSubagentScope(ctx.cwd, {
|
|
291
|
+
mode: params.mode,
|
|
292
|
+
paths: params.paths ?? (parentScope?.mode === "write" && !readOnly && params.mode !== "read" ? parentScope.paths : undefined),
|
|
293
|
+
}, readOnly);
|
|
294
|
+
if (scope.mode === "write" && parentScope) {
|
|
295
|
+
if (parentScope.mode === "read" || (parentScope.paths.length > 0 && !parentScope.paths.includes(".") &&
|
|
296
|
+
(scope.paths.length === 0 || scope.paths.some((path) => !parentScope.paths.some((owner) => path === owner || path.startsWith(`${owner}/`)))))) {
|
|
297
|
+
throw new Error("Nested write scope must stay within the parent's declared ownership");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!ctx.model) throw new Error("Select a main model before delegating");
|
|
301
|
+
const config = await loadAgentConfig(ctx.cwd, getAgentDir(), ctx.isProjectTrusted());
|
|
302
|
+
if (exploration && !config.delegate_exploration) throw new Error("Enable delegate_exploration in agent.json before delegating exploration");
|
|
303
|
+
const route = routeSubagent(config, params.agent, params.task, `${ctx.model.provider}/${ctx.model.id}`, (ctx.thinkingLevel ?? pi.getThinkingLevel()) as Effort, exploration);
|
|
304
|
+
const slash = route.model.indexOf("/");
|
|
305
|
+
const selected = ctx.modelRegistry.find(route.model.slice(0, slash), route.model.slice(slash + 1));
|
|
306
|
+
if (!selected || !ctx.modelRegistry.hasConfiguredAuth(selected)) throw new Error(`Subagent model unavailable: ${route.model}. Check /model and /login.`);
|
|
307
|
+
const tools = subagentTools(scope.mode === "read" ? "ask" : params.agent, pi.getActiveTools(), depth + 1, config.delegate_exploration && !exploration);
|
|
308
|
+
let scheduling: SchedulerStatus = { status: "queued" };
|
|
309
|
+
const details = () => ({ agent: params.agent, ...route, mode: scope.mode, paths: scope.paths, broad: scope.broad, ...scheduling });
|
|
310
|
+
const update = (text: string) => onUpdate?.({ content: [{ type: "text" as const, text: `${params.agent} · ${route.model} · ${route.effort} (${route.reason})\n${text}` }], details: details() });
|
|
311
|
+
const args = ["--mode", "json", "--print", "--no-session", "--model", route.model,
|
|
312
|
+
"--thinking", route.effort, "--agent", params.agent,
|
|
313
|
+
ctx.isProjectTrusted() ? "--approve" : "--no-approve", ...childResourceArgs(process.argv.slice(2)),
|
|
314
|
+
"--extension", resolve(extensionPath), "--tools", tools.join(",")];
|
|
315
|
+
if (tools.length === 0) args.push("--no-tools");
|
|
316
|
+
// Callable MCP tools must not escape a read-only child's explicit built-in loadout.
|
|
317
|
+
if (scope.mode === "read") {
|
|
318
|
+
if ("createMcpExtension" in piSdk) args.push("--no-mcp");
|
|
319
|
+
args.push("--exclude-tools", "mcp__*");
|
|
320
|
+
}
|
|
321
|
+
const reportStatus = (state: SchedulerStatus) => {
|
|
322
|
+
scheduling = state;
|
|
323
|
+
if (state.status === "queued" || state.status === "running") {
|
|
324
|
+
childStatuses.set(controller, { agent: params.agent, status: state.status });
|
|
325
|
+
} else childStatuses.delete(controller);
|
|
326
|
+
childStatus(ctx);
|
|
327
|
+
update(`${state.status}${state.reason ? ` (${state.reason})` : ""} · ${scope.mode}${scope.paths.length ? ` · ${scope.paths.join(", ")}` : ""}`);
|
|
328
|
+
};
|
|
329
|
+
controller.signal.throwIfAborted();
|
|
330
|
+
const result = await scheduler.schedule({
|
|
331
|
+
scope, controller, serial: parentProfile !== "orchestrator",
|
|
332
|
+
run: async () => {
|
|
333
|
+
// A previous writer may have created a symlink while this request was queued.
|
|
334
|
+
await normalizeSubagentScope(ctx.cwd, scope);
|
|
335
|
+
return runSubagent({ invocation: piInvocation(), args, cwd: ctx.cwd, depth: depth + 1,
|
|
336
|
+
task: params.task, scope, signal: controller.signal, onProgress: update });
|
|
337
|
+
},
|
|
338
|
+
onStatus: (state) => {
|
|
339
|
+
// Runner results can resolve with provider errors; classify before reporting completion.
|
|
340
|
+
if (state.status !== "completed") reportStatus(state);
|
|
341
|
+
},
|
|
342
|
+
});
|
|
189
343
|
const failed = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted" || Boolean(result.error);
|
|
344
|
+
reportStatus({ status: result.stopReason === "aborted" ? "aborted" : failed ? "failed" : "completed" });
|
|
190
345
|
return {
|
|
191
346
|
content: [{ type: "text" as const, text: `${params.agent} · ${route.model} · ${route.effort} (${route.reason})\n${limitOutput(failed ? result.error || result.stderr || result.output || "Subagent failed" : result.output)}` }],
|
|
192
|
-
details: { ...details, ...result }, usage: result.usage, isError: failed,
|
|
347
|
+
details: { ...details(), ...result }, usage: result.usage, isError: failed,
|
|
193
348
|
};
|
|
194
|
-
} finally {
|
|
195
|
-
jobs.delete(controller);
|
|
196
|
-
signal?.removeEventListener("abort", abort);
|
|
197
349
|
}
|
|
198
350
|
},
|
|
199
351
|
});
|
|
200
352
|
|
|
201
353
|
pi.on("session_shutdown", async () => {
|
|
354
|
+
const closing = scheduler.shutdown("Session shutdown");
|
|
202
355
|
for (const controller of jobs.keys()) controller.abort("Session shutdown");
|
|
356
|
+
await closing;
|
|
203
357
|
await Promise.allSettled(jobs.values());
|
|
204
358
|
});
|
|
205
359
|
}
|
package/extensions/ui.ts
CHANGED
|
@@ -169,7 +169,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
169
169
|
|
|
170
170
|
let input = 0, output = 0, cacheRead = 0, cacheWrite = 0, cost = 0;
|
|
171
171
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
172
|
-
if (entry.type !== "message" || entry.message.role !== "assistant") continue;
|
|
172
|
+
if (entry.type !== "message" || (entry.message.role !== "assistant" && entry.message.role !== "toolResult")) continue;
|
|
173
173
|
const u = entry.message.usage;
|
|
174
174
|
if (!u) continue;
|
|
175
175
|
input += u.input;
|
|
@@ -183,14 +183,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
183
183
|
const activity = state === "tool" ? `${frame} ${activeTool}`
|
|
184
184
|
: state === "thinking" ? `${frame} thinking` : "● ready";
|
|
185
185
|
const agent = footerData.getExtensionStatuses?.().get("piexis-agent");
|
|
186
|
+
const agentMode = footerData.getExtensionStatuses?.().get("piexis-agent-mode");
|
|
186
187
|
const branch = footerData.getGitBranch();
|
|
187
188
|
// Reserve a small model slot; long tool names must not displace the profile.
|
|
188
189
|
const modelReserve = width >= 32 ? 9 : 0;
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
const fullProfile = agent && agentMode ? `${agent} (${agentMode})` : agent ?? "";
|
|
191
|
+
// Mode is optional; the profile itself remains ahead of activity and right-side fields.
|
|
192
|
+
const profileText = agent && width >= 48 && visibleWidth(fullProfile) + 6 + modelReserve <= width
|
|
193
|
+
? fullProfile
|
|
194
|
+
: agent ?? "";
|
|
195
|
+
const profileLimit = Math.max(0, width - 1);
|
|
196
|
+
const profile = profileText
|
|
197
|
+
? theme.fg("accent", truncateToWidth(profileText, profileLimit, "…")) + (profileLimit > 0 ? sep : "")
|
|
198
|
+
: "";
|
|
199
|
+
const activityWidth = Math.max(0, Math.min(24, width - visibleWidth(profile) - modelReserve));
|
|
200
|
+
const left1 = profile + (activityWidth ? theme.fg(state === "ready" ? "success" : "accent",
|
|
201
|
+
truncateToWidth(activity, activityWidth, "…")) : "");
|
|
194
202
|
const row1 = justify(left1, [
|
|
195
203
|
{ text: ctx.model?.id ?? "—", priority: 5 },
|
|
196
204
|
{ text: muted(`think ${pi.getThinkingLevel()}`), priority: 4 },
|
|
@@ -224,7 +232,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
224
232
|
{ text: muted(costText), priority: 3 }
|
|
225
233
|
], sep, width);
|
|
226
234
|
|
|
227
|
-
|
|
235
|
+
const children = footerData.getExtensionStatuses?.().get("piexis-subagents");
|
|
236
|
+
return children
|
|
237
|
+
? [row1, row2, theme.fg("accent", truncateToWidth(children, width, "…"))]
|
|
238
|
+
: [row1, row2];
|
|
228
239
|
},
|
|
229
240
|
|
|
230
241
|
dispose() {
|
package/lib/agent-config.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface AgentConfig {
|
|
|
11
11
|
small_model?: string;
|
|
12
12
|
subagent_model?: string;
|
|
13
13
|
delegate_exploration?: boolean;
|
|
14
|
+
auto_profile?: boolean;
|
|
14
15
|
subagent_variant_overrides?: Partial<Record<AgentName, { model?: string; effort?: Effort }>>;
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -21,7 +22,8 @@ export interface AgentConfig {
|
|
|
21
22
|
export const initialAgentConfigDefaults = {
|
|
22
23
|
"$schema": agentConfigSchemaUrl,
|
|
23
24
|
delegate_exploration: false,
|
|
24
|
-
|
|
25
|
+
auto_profile: false,
|
|
26
|
+
} satisfies Pick<AgentConfig, "$schema" | "delegate_exploration" | "auto_profile">;
|
|
25
27
|
|
|
26
28
|
/** Header copy for incomplete project agent configuration. */
|
|
27
29
|
export type AgentConfigSetting = Exclude<keyof AgentConfig, "$schema">;
|
|
@@ -31,6 +33,7 @@ export const agentConfigCapabilities = {
|
|
|
31
33
|
small_model: "use a cheaper model for eligible small tasks",
|
|
32
34
|
subagent_model: "choose the normal model for delegated work",
|
|
33
35
|
delegate_exploration: "opt in to cheap, read-only codebase exploration",
|
|
36
|
+
auto_profile: "automatically select a profile for clear requests",
|
|
34
37
|
subagent_variant_overrides: "customize model and effort for each agent profile",
|
|
35
38
|
} satisfies Record<AgentConfigSetting, string>;
|
|
36
39
|
|
|
@@ -50,6 +53,7 @@ export function createInitialAgentConfig(selected: string, inherited: AgentConfi
|
|
|
50
53
|
small_model: inherited.small_model ?? selected,
|
|
51
54
|
subagent_model: inherited.subagent_model ?? selected,
|
|
52
55
|
delegate_exploration: inherited.delegate_exploration ?? initialAgentConfigDefaults.delegate_exploration,
|
|
56
|
+
auto_profile: inherited.auto_profile ?? initialAgentConfigDefaults.auto_profile,
|
|
53
57
|
subagent_variant_overrides: overrides,
|
|
54
58
|
};
|
|
55
59
|
}
|
package/lib/agents.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { agentNames, type AgentConfig, type AgentName, effortLevels, type Effort
|
|
|
8
8
|
|
|
9
9
|
export const profiles: Record<AgentName, string> = {
|
|
10
10
|
plan: "Explore the request and relevant code, clarify important unknowns, and return an actionable plan with acceptance checks. Do not implement or delegate implementation. Stop after the plan.",
|
|
11
|
-
orchestrator: "Break work into focused tasks and delegate to plan, debug, ask, or code using subagent. Include all necessary context in each task; children do not inherit the conversation.
|
|
11
|
+
orchestrator: "Break work into focused tasks and delegate to plan, debug, ask, or code using subagent. Include all necessary context in each task; children do not inherit the conversation. Declare mode: read for read-only work and mode: write with precise repository-relative paths for edits. Divide parallel work by non-overlapping ownership; at most two jobs can run concurrently. Leave uncertain, overlapping, or shared-configuration writes sequential. Do not edit the workspace yourself while write children are running. Inspect results, reconcile integration issues, and run final verification after all children settle. Do not delegate the same orchestration task recursively.",
|
|
12
12
|
debug: "Reproduce or establish evidence for the failure, trace its cause before editing, and make the smallest justified fix. Verify with a regression check. Separate confirmed facts from hypotheses and report any unexecuted checks.",
|
|
13
13
|
ask: "Answer the user's question using relevant code or documentation as evidence. Do not change files or delegate changes. When context is missing, ask rather than invent facts. Stop when the question is answered.",
|
|
14
14
|
code: "Implement the requested scope using existing repository patterns. Preserve unrelated changes, add focused coverage for behavior changes, run relevant checks, and report results. Stop when acceptance conditions are met.",
|
|
@@ -34,7 +34,7 @@ export function parseAgentConfig(value: unknown): AgentConfig {
|
|
|
34
34
|
const source = object(value, "Agent configuration");
|
|
35
35
|
const result: AgentConfig = {};
|
|
36
36
|
for (const key of Object.keys(source)) {
|
|
37
|
-
if (!["$schema", "model", "small_model", "subagent_model", "delegate_exploration", "subagent_variant_overrides"].includes(key)) {
|
|
37
|
+
if (!["$schema", "model", "small_model", "subagent_model", "delegate_exploration", "auto_profile", "subagent_variant_overrides"].includes(key)) {
|
|
38
38
|
throw new Error(`Unknown agent configuration key: ${key}`);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -49,6 +49,10 @@ export function parseAgentConfig(value: unknown): AgentConfig {
|
|
|
49
49
|
if (typeof source.delegate_exploration !== "boolean") throw new Error("delegate_exploration must be a boolean");
|
|
50
50
|
result.delegate_exploration = source.delegate_exploration;
|
|
51
51
|
}
|
|
52
|
+
if (source.auto_profile !== undefined) {
|
|
53
|
+
if (typeof source.auto_profile !== "boolean") throw new Error("auto_profile must be a boolean");
|
|
54
|
+
result.auto_profile = source.auto_profile;
|
|
55
|
+
}
|
|
52
56
|
if (source.subagent_variant_overrides !== undefined) {
|
|
53
57
|
result.subagent_variant_overrides = {};
|
|
54
58
|
for (const [name, value] of Object.entries(object(source.subagent_variant_overrides, "subagent_variant_overrides"))) {
|
|
@@ -166,6 +170,35 @@ export async function completeProjectAgentConfig(cwd: string, agentDir: string,
|
|
|
166
170
|
});
|
|
167
171
|
}
|
|
168
172
|
|
|
173
|
+
const profileIntentRules: [AgentName, RegExp][] = [
|
|
174
|
+
["plan", /^(?:let'?s\s+plan|(?:make|create)\s+(?:me\s+)?(?:a\s+)?plan|help\s+me\s+plan|plan)\b/i],
|
|
175
|
+
["debug", /^(?:help\s+me\s+)?(?:debug|investigate|fix\s+(?:a\s+|the\s+|this\s+)?(?:bug|error|crash|failure))\b/i],
|
|
176
|
+
["orchestrator", /^(?:orchestrate|coordinate|delegate)\b/i],
|
|
177
|
+
["code", /^(?:implement|build|write|execute)\b/i],
|
|
178
|
+
["ask", /^(?:explain|answer(?:\s+(?:this\s+)?question)?|what|how|why|where|when|who)\b/i],
|
|
179
|
+
];
|
|
180
|
+
const politePrefix = /^(?:(?:please|kindly|hey)[,\s]+|(?:could|would|can)\s+you\s+)+/i;
|
|
181
|
+
|
|
182
|
+
export function detectProfileIntent(text: string): AgentName | undefined {
|
|
183
|
+
const input = text.trim().replace(/\u2019/g, "'").replace(politePrefix, "");
|
|
184
|
+
if (!input || /^[/`>~"'\u201c\u2018]/.test(input)) return undefined;
|
|
185
|
+
const match = profileIntentRules.find(([, expression]) => expression.test(input));
|
|
186
|
+
if (!match) return undefined;
|
|
187
|
+
const [profile, expression] = match;
|
|
188
|
+
const rest = input.slice(input.match(expression)![0].length);
|
|
189
|
+
if (profile !== "ask" && /^\s+(?:is|means|refers\s+to)\b/i.test(rest)) return undefined;
|
|
190
|
+
// shortcut: bounded English outer-action rules; expand only with evaluated intent examples.
|
|
191
|
+
const nested = /^(?:\s+(?:how|why|what|when|where)\b)|\bto\s+(?:debug|investigate|implement|build|write|execute)\b/i.test(rest);
|
|
192
|
+
if (!nested) {
|
|
193
|
+
const ownText = rest.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`]*`|"[^"]*"|“[^”]*”|'[^']*'|‘[^’]*’/g, "");
|
|
194
|
+
for (const clause of ownText.split(/\b(?:and(?:\s+then)?|then|but|also)\s+|[.!?]\s+|\n+/i).slice(1)) {
|
|
195
|
+
const other = profileIntentRules.find(([, rule]) => rule.test(clause.trim().replace(politePrefix, "")));
|
|
196
|
+
if (other && other[0] !== profile) return undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return profile;
|
|
200
|
+
}
|
|
201
|
+
|
|
169
202
|
export function isSmallTask(agent: AgentName, task: string): boolean {
|
|
170
203
|
// shortcut: conservative text heuristic, replace with evaluated routing if real tasks outgrow it.
|
|
171
204
|
if (task.length > 400 || task.split(/\s+/).length > 65) return false;
|
package/lib/recommended-tools.ts
CHANGED
|
@@ -94,6 +94,7 @@ export async function saveSetupChoice(settings: SetupSettings, choice: "enabled"
|
|
|
94
94
|
}
|
|
95
95
|
signal?.throwIfAborted();
|
|
96
96
|
const next = { ...project.settings };
|
|
97
|
+
if (choice === "enabled" && next.quietStartup === undefined) next.quietStartup = true;
|
|
97
98
|
const piexis = object(next.piexis) ? next.piexis : {};
|
|
98
99
|
next.piexis = { ...piexis,
|
|
99
100
|
...(tools.length ? { recommendedTools: choice } : {}),
|
package/lib/subagent-runner.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { basename, dirname, join } from "node:path";
|
|
5
5
|
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { SubagentScope } from "./subagent-scheduler.ts";
|
|
6
7
|
|
|
7
8
|
type Usage = NonNullable<Awaited<ReturnType<ToolDefinition["execute"]>>["usage"]>;
|
|
8
9
|
|
|
@@ -50,6 +51,7 @@ export async function runSubagent(options: {
|
|
|
50
51
|
cwd: string;
|
|
51
52
|
depth: number;
|
|
52
53
|
task: string;
|
|
54
|
+
scope?: SubagentScope;
|
|
53
55
|
signal: AbortSignal;
|
|
54
56
|
onProgress?: (text: string) => void;
|
|
55
57
|
}): Promise<ChildResult> {
|
|
@@ -60,7 +62,8 @@ export async function runSubagent(options: {
|
|
|
60
62
|
const child = spawn(options.invocation.command, [...options.invocation.args, ...options.args], {
|
|
61
63
|
cwd: options.cwd, shell: false, detached: grouped,
|
|
62
64
|
stdio: ["pipe", "pipe", "pipe"],
|
|
63
|
-
env: { ...process.env, PIEXIS_SUBAGENT_DEPTH: String(options.depth)
|
|
65
|
+
env: { ...process.env, PIEXIS_SUBAGENT_DEPTH: String(options.depth),
|
|
66
|
+
...(options.scope ? { PIEXIS_SUBAGENT_SCOPE: JSON.stringify(options.scope) } : {}) },
|
|
64
67
|
});
|
|
65
68
|
let buffer = "", bytes = 0, timer: NodeJS.Timeout | undefined, failure: Error | undefined;
|
|
66
69
|
let closed = false;
|