pi-subagents-lite 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent-discovery.ts +23 -0
- package/src/agent-manager.ts +14 -44
- package/src/agent-runner.ts +20 -41
- package/src/config-io.ts +7 -4
- package/src/index.ts +8 -9
- package/src/menus.ts +17 -26
- package/src/model-precedence.ts +0 -1
- package/src/output-file.ts +2 -3
- package/src/prompts.ts +35 -1
- package/src/result-viewer.ts +6 -3
- package/src/skill-loader.ts +92 -16
- package/src/stop-agent-tool.ts +3 -2
- package/src/tool-execution.ts +8 -30
- package/src/types.ts +6 -1
package/package.json
CHANGED
package/src/agent-discovery.ts
CHANGED
|
@@ -28,6 +28,7 @@ export interface AgentConfigFromMd {
|
|
|
28
28
|
tools?: string[];
|
|
29
29
|
extensions?: boolean | string[];
|
|
30
30
|
skills?: boolean | string[];
|
|
31
|
+
preload_skills?: string[] | false;
|
|
31
32
|
model?: string;
|
|
32
33
|
thinking?: ThinkingLevel;
|
|
33
34
|
max_turns?: number;
|
|
@@ -163,6 +164,26 @@ export function parseExtensions(
|
|
|
163
164
|
return undefined;
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Parse the preload_skills field from frontmatter.
|
|
169
|
+
* Unlike parseExtensions, does NOT accept true/"true"/"all" —
|
|
170
|
+
* preload requires an explicit list of skill names.
|
|
171
|
+
*/
|
|
172
|
+
export function parsePreloadSkills(
|
|
173
|
+
raw: unknown,
|
|
174
|
+
): string[] | false | undefined {
|
|
175
|
+
if (raw === false || raw === "false" || raw === "none") {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
179
|
+
return splitCommaList(raw);
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(raw)) {
|
|
182
|
+
return raw.map(String);
|
|
183
|
+
}
|
|
184
|
+
return undefined; // true/"true"/"all" not supported
|
|
185
|
+
}
|
|
186
|
+
|
|
166
187
|
/* ------------------------------------------------------------------ */
|
|
167
188
|
/* Frontmatter value helpers */
|
|
168
189
|
/* ------------------------------------------------------------------ */
|
|
@@ -253,6 +274,7 @@ export function parseAgentFile(
|
|
|
253
274
|
tools: parseStringArray(frontmatter, "tools"),
|
|
254
275
|
extensions: parseExtensions(frontmatter.extensions),
|
|
255
276
|
skills: parseExtensions(frontmatter.skills),
|
|
277
|
+
preload_skills: parsePreloadSkills(frontmatter.preload_skills),
|
|
256
278
|
model: parseString(frontmatter, "model"),
|
|
257
279
|
thinking: parseThinkingLevel(parseString(frontmatter, "thinking")),
|
|
258
280
|
max_turns: parseNumber(frontmatter, "max_turns"),
|
|
@@ -377,6 +399,7 @@ function fromMd(md: AgentConfigFromMd): Partial<AgentConfig> {
|
|
|
377
399
|
builtinToolNames: md.tools,
|
|
378
400
|
extensions: md.extensions,
|
|
379
401
|
skills: md.skills,
|
|
402
|
+
preloadSkills: md.preload_skills,
|
|
380
403
|
model: md.model,
|
|
381
404
|
thinking: md.thinking,
|
|
382
405
|
maxTurns: md.max_turns,
|
package/src/agent-manager.ts
CHANGED
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
16
16
|
import type { Model } from "@earendil-works/pi-ai";
|
|
17
17
|
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import {
|
|
18
|
+
import { runAgent, type ToolActivity } from "./agent-runner.js";
|
|
19
19
|
import { createOutputFilePath, streamToOutputFile, writeInitialEntry } from "./output-file.js";
|
|
20
|
-
import
|
|
21
|
-
AgentInvocation,
|
|
22
|
-
AgentRecord,
|
|
23
|
-
CompactionInfo,
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
import {
|
|
21
|
+
type AgentInvocation,
|
|
22
|
+
type AgentRecord,
|
|
23
|
+
type CompactionInfo,
|
|
24
|
+
SHORT_ID_LENGTH,
|
|
25
|
+
type SubagentType,
|
|
26
|
+
type ThinkingLevel,
|
|
26
27
|
} from "./types.js";
|
|
27
28
|
import { addUsage, getLifetimeTotal, type LifetimeUsage } from "./usage.js";
|
|
28
29
|
import { errorMessage } from "./utils.js";
|
|
@@ -33,8 +34,10 @@ const CLEANUP_INTERVAL_MS = 60_000;
|
|
|
33
34
|
/** Age after which a completed agent record is evicted (milliseconds). */
|
|
34
35
|
const CLEANUP_AGE_CUTOFF_MS = 10 * 60_000;
|
|
35
36
|
|
|
36
|
-
/**
|
|
37
|
-
const
|
|
37
|
+
/** UUID prefix length for agent IDs stored in the agents map (uniqueness). */
|
|
38
|
+
const AGENT_ID_PREFIX_LENGTH = 17;
|
|
39
|
+
|
|
40
|
+
|
|
38
41
|
|
|
39
42
|
/** Default per-model concurrency limit when not specified in config. */
|
|
40
43
|
const DEFAULT_CONCURRENCY_LIMIT = 4;
|
|
@@ -233,7 +236,7 @@ export class AgentManager {
|
|
|
233
236
|
prompt: string,
|
|
234
237
|
options: SpawnOptions,
|
|
235
238
|
): string {
|
|
236
|
-
const id = randomUUID().slice(0,
|
|
239
|
+
const id = randomUUID().slice(0, AGENT_ID_PREFIX_LENGTH);
|
|
237
240
|
const abortController = new AbortController();
|
|
238
241
|
const args: SpawnArgs = { pi, ctx, type, prompt, options };
|
|
239
242
|
|
|
@@ -381,7 +384,7 @@ export class AgentManager {
|
|
|
381
384
|
}
|
|
382
385
|
|
|
383
386
|
/**
|
|
384
|
-
* Build common record-tracking callbacks shared by startAgent
|
|
387
|
+
* Build common record-tracking callbacks shared by startAgent.
|
|
385
388
|
* Updates the record's toolUses, lifetimeUsage, and compactionCount.
|
|
386
389
|
* When options are provided, also forwards events to the caller.
|
|
387
390
|
*/
|
|
@@ -434,39 +437,6 @@ export class AgentManager {
|
|
|
434
437
|
this.queue = this.queue.filter(e => !started.has(e.id));
|
|
435
438
|
}
|
|
436
439
|
|
|
437
|
-
/**
|
|
438
|
-
* Resume an existing agent session with a new prompt.
|
|
439
|
-
*/
|
|
440
|
-
async resume(
|
|
441
|
-
id: string,
|
|
442
|
-
prompt: string,
|
|
443
|
-
signal?: AbortSignal,
|
|
444
|
-
): Promise<AgentRecord | undefined> {
|
|
445
|
-
const record = this.agents.get(id);
|
|
446
|
-
if (!record?.session) return undefined;
|
|
447
|
-
|
|
448
|
-
record.status = "running";
|
|
449
|
-
record.startedAt = Date.now();
|
|
450
|
-
record.completedAt = undefined;
|
|
451
|
-
record.result = undefined;
|
|
452
|
-
record.error = undefined;
|
|
453
|
-
|
|
454
|
-
try {
|
|
455
|
-
const responseText = await resumeAgent(record.session, prompt, {
|
|
456
|
-
...this.createRecordCallbacks(record),
|
|
457
|
-
signal,
|
|
458
|
-
});
|
|
459
|
-
record.status = "completed";
|
|
460
|
-
record.result = responseText;
|
|
461
|
-
record.completedAt = Date.now();
|
|
462
|
-
} catch (err) {
|
|
463
|
-
record.status = "error";
|
|
464
|
-
record.error = errorMessage(err);
|
|
465
|
-
record.completedAt = Date.now();
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return record;
|
|
469
|
-
}
|
|
470
440
|
|
|
471
441
|
/**
|
|
472
442
|
* Send a steering message to a running agent.
|
package/src/agent-runner.ts
CHANGED
|
@@ -28,9 +28,9 @@ import { extractText } from "./context.js";
|
|
|
28
28
|
import type { LifetimeUsage } from "./usage.js";
|
|
29
29
|
import { findModelInRegistry } from "./utils.js";
|
|
30
30
|
import { DEFAULT_AGENTS } from "./default-agents.js";
|
|
31
|
-
import { buildAgentPrompt, type PromptExtras } from "./prompts.js";
|
|
32
|
-
import { preloadSkills } from "./skill-loader.js";
|
|
33
|
-
import type
|
|
31
|
+
import { buildAgentPrompt, type PromptExtras, type SkillMeta } from "./prompts.js";
|
|
32
|
+
import { preloadSkills, loadSkillMeta } from "./skill-loader.js";
|
|
33
|
+
import { type CompactionInfo, type EnvInfo, SHORT_ID_LENGTH, type SubagentType, type ThinkingLevel } from "./types.js";
|
|
34
34
|
|
|
35
35
|
/** Names of tools registered by this extension that subagents must NOT inherit. */
|
|
36
36
|
export const EXCLUDED_TOOL_NAMES = ["Agent"];
|
|
@@ -157,7 +157,7 @@ function usageFromAssistantMessage(msg: Record<string, unknown>): LifetimeUsage
|
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
159
|
* Subscribe to shared session events (tool activity, usage, compaction)
|
|
160
|
-
* used by
|
|
160
|
+
* used by runAgent. Returns an unsubscribe function.
|
|
161
161
|
*/
|
|
162
162
|
export function subscribeToSessionEvents(
|
|
163
163
|
session: AgentSession,
|
|
@@ -264,12 +264,18 @@ export async function runAgent(
|
|
|
264
264
|
const effectiveIsolated = options.isolated ?? agentConfig?.isolated;
|
|
265
265
|
const extensions = effectiveIsolated ? false : config.extensions;
|
|
266
266
|
const skills = effectiveIsolated ? false : config.skills;
|
|
267
|
+
const preloadSkillsList = effectiveIsolated ? false : agentConfig?.preloadSkills;
|
|
267
268
|
|
|
268
269
|
// Build prompt extras (no memoryBlock — skills only).
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
// - preloadSkills: force full content into system prompt
|
|
271
|
+
// - skills: metadata only (whitelist), agent reads on-demand
|
|
272
|
+
const extras: PromptExtras = {};
|
|
273
|
+
if (Array.isArray(preloadSkillsList)) {
|
|
274
|
+
extras.skillBlocks = preloadSkills(preloadSkillsList, effectiveCwd);
|
|
275
|
+
}
|
|
276
|
+
if (Array.isArray(skills)) {
|
|
277
|
+
extras.skillMetas = loadSkillMeta(skills, effectiveCwd);
|
|
278
|
+
}
|
|
273
279
|
|
|
274
280
|
const toolNames = getToolNamesForType(type);
|
|
275
281
|
|
|
@@ -284,9 +290,11 @@ export async function runAgent(
|
|
|
284
290
|
systemPrompt = buildAgentPrompt({ ...fallback, name: type }, effectiveCwd, env, extras);
|
|
285
291
|
}
|
|
286
292
|
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
|
|
293
|
+
// Skip the built-in skill loader when:
|
|
294
|
+
// - skills is false (no skills)
|
|
295
|
+
// - preloadSkills is string[] (we handle preloading ourselves)
|
|
296
|
+
// - skills is string[] (we handle metadata ourselves)
|
|
297
|
+
const skipSkillLoader = skills === false || Array.isArray(skills) || Array.isArray(preloadSkillsList);
|
|
290
298
|
|
|
291
299
|
const agentDir = getAgentDir();
|
|
292
300
|
|
|
@@ -330,7 +338,7 @@ export async function runAgent(
|
|
|
330
338
|
|
|
331
339
|
const baseSessionName = agentConfig?.name ?? type;
|
|
332
340
|
session.setSessionName(
|
|
333
|
-
options.agentId ? `${baseSessionName}#${options.agentId.slice(0,
|
|
341
|
+
options.agentId ? `${baseSessionName}#${options.agentId.slice(0, SHORT_ID_LENGTH)}` : baseSessionName,
|
|
334
342
|
);
|
|
335
343
|
|
|
336
344
|
// Filter active tools: remove our own tools to prevent nesting,
|
|
@@ -395,32 +403,3 @@ export async function runAgent(
|
|
|
395
403
|
const responseText = collector.getText().trim() || getLastAssistantText(session);
|
|
396
404
|
return { responseText, session, aborted, steered: softLimitReached };
|
|
397
405
|
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Send a new prompt to an existing session (resume).
|
|
401
|
-
*/
|
|
402
|
-
export async function resumeAgent(
|
|
403
|
-
session: AgentSession,
|
|
404
|
-
prompt: string,
|
|
405
|
-
options: Pick<RunOptions, "onToolActivity" | "onAssistantUsage" | "onCompaction"> & { signal?: AbortSignal } = {},
|
|
406
|
-
): Promise<string> {
|
|
407
|
-
const collector = collectResponseText(session);
|
|
408
|
-
const cleanupAbort = forwardAbortSignal(session, options.signal);
|
|
409
|
-
const unsubEvents = subscribeToSessionEvents(session, options);
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
await session.prompt(prompt);
|
|
413
|
-
} finally {
|
|
414
|
-
collector.unsubscribe();
|
|
415
|
-
unsubEvents();
|
|
416
|
-
cleanupAbort();
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return collector.getText().trim() || getLastAssistantText(session);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
package/src/config-io.ts
CHANGED
|
@@ -12,6 +12,12 @@ import type { SubagentsConfig } from "./model-precedence.js";
|
|
|
12
12
|
const CONFIG_DIR = path.join(process.env.HOME || "", ".pi", "agent");
|
|
13
13
|
const CONFIG_PATH = path.join(CONFIG_DIR, "subagents-lite.json");
|
|
14
14
|
|
|
15
|
+
/** Default configuration — used when config file doesn't exist or is invalid. */
|
|
16
|
+
export const DEFAULT_CONFIG: SubagentsConfig = {
|
|
17
|
+
agent: { default: null, forceBackground: false },
|
|
18
|
+
concurrency: { default: 4 },
|
|
19
|
+
};
|
|
20
|
+
|
|
15
21
|
/** Read config from disk. Returns defaults if file doesn't exist or is invalid. */
|
|
16
22
|
export function loadConfig(): SubagentsConfig {
|
|
17
23
|
try {
|
|
@@ -21,10 +27,7 @@ export function loadConfig(): SubagentsConfig {
|
|
|
21
27
|
// File doesn't exist or is invalid — return defaults
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
return {
|
|
25
|
-
agent: { default: null, forceBackground: false },
|
|
26
|
-
concurrency: { default: 4 },
|
|
27
|
-
};
|
|
30
|
+
return { ...DEFAULT_CONFIG, agent: { ...DEFAULT_CONFIG.agent }, concurrency: { ...DEFAULT_CONFIG.concurrency } };
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/** Write config to disk with atomic rename. */
|
package/src/index.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { scanAgentFilesInDir, mergeAgents } from "./agent-discovery.js";
|
|
|
39
39
|
import { AgentManager } from "./agent-manager.js";
|
|
40
40
|
import { AgentWidget, buildStatsParts, formatMs, getDisplayName, type AgentActivity, type UICtx } from "./ui/agent-widget.js";
|
|
41
41
|
import { showAgentsMainMenu } from "./menus.js";
|
|
42
|
-
import { loadConfig } from "./config-io.js";
|
|
42
|
+
import { loadConfig, DEFAULT_CONFIG } from "./config-io.js";
|
|
43
43
|
import { executeAgentTool, toolCallListener, backgroundAgentIds, scheduleNudge } from "./tool-execution.js";
|
|
44
44
|
import { executeStopAgentTool } from "./stop-agent-tool.js";
|
|
45
45
|
|
|
@@ -51,10 +51,7 @@ import { executeStopAgentTool } from "./stop-agent-tool.js";
|
|
|
51
51
|
export let sessionOverrides: SessionModelOverrides = { default: null };
|
|
52
52
|
|
|
53
53
|
/** Config cache — loaded at session_start, updated by /agents menu mutations. */
|
|
54
|
-
export let __config: SubagentsConfig = {
|
|
55
|
-
agent: { default: null, forceBackground: false },
|
|
56
|
-
concurrency: { default: 4 },
|
|
57
|
-
};
|
|
54
|
+
export let __config: SubagentsConfig = { ...DEFAULT_CONFIG, agent: { ...DEFAULT_CONFIG.agent }, concurrency: { ...DEFAULT_CONFIG.concurrency } };
|
|
58
55
|
|
|
59
56
|
/** Agent manager singleton — module-level, no globalThis access. */
|
|
60
57
|
export let manager: AgentManager;
|
|
@@ -162,19 +159,21 @@ function buildStatsLine(d: Record<string, unknown>, theme: any): string {
|
|
|
162
159
|
*/
|
|
163
160
|
function registerAgentTool(pi: ExtensionAPI): void {
|
|
164
161
|
const types = getAvailableTypes();
|
|
162
|
+
// Use plain string to avoid verbose anyOf in prompt.
|
|
163
|
+
// Available types are listed in description for discoverability.
|
|
165
164
|
const agentParam = types.length > 0
|
|
166
|
-
? Type.Optional(Type.
|
|
165
|
+
? Type.Optional(Type.String({ description: types.join(",") }))
|
|
167
166
|
: Type.Optional(Type.String());
|
|
167
|
+
// @ts-expect-error — description removed to save prompt tokens
|
|
168
168
|
pi.registerTool({
|
|
169
169
|
name: "Agent",
|
|
170
170
|
label: "Agent",
|
|
171
|
-
description: ".",
|
|
172
171
|
parameters: Type.Object({
|
|
173
172
|
prompt: Type.String(),
|
|
174
173
|
description: Type.String(),
|
|
175
174
|
agent: agentParam,
|
|
176
175
|
run_in_background: Type.Optional(Type.Boolean()),
|
|
177
|
-
|
|
176
|
+
|
|
178
177
|
}),
|
|
179
178
|
execute: executeAgentTool,
|
|
180
179
|
|
|
@@ -241,10 +240,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
241
240
|
registerAgentTool(pi);
|
|
242
241
|
|
|
243
242
|
// StopAgent tool — stealth schema, stop a running agent by ID
|
|
243
|
+
// @ts-expect-error — description removed to save prompt tokens
|
|
244
244
|
pi.registerTool({
|
|
245
245
|
name: "StopAgent",
|
|
246
246
|
label: "StopAgent",
|
|
247
|
-
description: ".",
|
|
248
247
|
parameters: Type.Object({
|
|
249
248
|
agent_id: Type.String(),
|
|
250
249
|
}),
|
package/src/menus.ts
CHANGED
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { getAgentConfig, getAvailableTypes, getAllTypes } from "./agent-types.js";
|
|
10
10
|
import type { AgentRecord } from "./types.js";
|
|
11
|
+
import { SHORT_ID_LENGTH } from "./types.js";
|
|
11
12
|
import { ModelSelectorDialog, type ModelOption } from "./model-selector.js";
|
|
12
13
|
import { ResultViewer, type ResultViewerStats } from "./result-viewer.js";
|
|
13
14
|
import { getDisplayName } from "./ui/agent-widget.js";
|
|
14
15
|
import { buildSnapshotMarkdown } from "./context.js";
|
|
15
16
|
|
|
16
|
-
import { parseModelKey
|
|
17
|
+
import { parseModelKey } from "./utils.js";
|
|
17
18
|
import {
|
|
18
19
|
__config,
|
|
19
20
|
sessionOverrides,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
piInstance,
|
|
22
23
|
} from "./index.js";
|
|
23
24
|
import { resolveModel } from "./model-precedence.js";
|
|
24
|
-
import { saveConfigAtomic } from "./config-io.js";
|
|
25
|
+
import { saveConfigAtomic, DEFAULT_CONFIG } from "./config-io.js";
|
|
25
26
|
|
|
26
27
|
// ============================================================================
|
|
27
28
|
// Helpers
|
|
@@ -334,9 +335,9 @@ export async function showModelSettingsMenu(
|
|
|
334
335
|
overridden.sort((a, b) => a.effectiveModel.localeCompare(b.effectiveModel));
|
|
335
336
|
const padLen = Math.max(...types.map(t => t.length));
|
|
336
337
|
for (const { typeName, cfg, sessionOverride, configOverride, hasSession, effectiveModel } of overridden) {
|
|
338
|
+
const frontmatterHint = !hasSession && configOverride && cfg?.model ? `${cfg.model} → ` : "";
|
|
337
339
|
const displayModel = hasSession ? `${sessionOverride} [session]` : effectiveModel;
|
|
338
|
-
|
|
339
|
-
items.push(`${typeName.padEnd(padLen)} · ${displayModel}${frontmatterHint}`);
|
|
340
|
+
items.push(`${typeName.padEnd(padLen)} · ${frontmatterHint}${displayModel}`);
|
|
340
341
|
|
|
341
342
|
const currentValue = hasSession ? sessionOverride! : effectiveModel;
|
|
342
343
|
actions.push(buildOverrideAction(typeName, typeName, currentValue, !!configOverride));
|
|
@@ -473,7 +474,6 @@ async function handleAgentBriefing(ctx: ExtensionCommandContext): Promise<void>
|
|
|
473
474
|
lines.push("| `agent` | Which agent type to use (default: general-purpose) |");
|
|
474
475
|
lines.push("| `thinking` | Optional thinking mode override (e.g., `off`, `minimal`, `low`, `medium`, `high`, `xhigh`) |");
|
|
475
476
|
lines.push("| `run_in_background` | When `true`, result is auto-delivered — do NOT poll. Continue working while waiting. |");
|
|
476
|
-
lines.push("| `resume` | Agent ID to resume from; when set, `prompt` is appended to the previous conversation |");
|
|
477
477
|
lines.push("");
|
|
478
478
|
|
|
479
479
|
// Usage guidelines
|
|
@@ -481,7 +481,6 @@ async function handleAgentBriefing(ctx: ExtensionCommandContext): Promise<void>
|
|
|
481
481
|
lines.push("- Agents start fresh with their config — they do NOT inherit the parent conversation");
|
|
482
482
|
lines.push("- For parallel tasks, spawn multiple `run_in_background: true` agents in one turn");
|
|
483
483
|
lines.push(" → Results are auto-delivered — do NOT poll, the result will arrive when ready");
|
|
484
|
-
lines.push("- Use `resume` to continue an incomplete agent's conversation");
|
|
485
484
|
piInstance.sendUserMessage(lines.join("\n"));
|
|
486
485
|
ctx.ui.notify("Agent briefing sent to LLM", "info");
|
|
487
486
|
}
|
|
@@ -542,7 +541,7 @@ export async function showConcurrencySettingsMenu(
|
|
|
542
541
|
// Reset all to defaults
|
|
543
542
|
items.push("Reset all to defaults");
|
|
544
543
|
actions.push(async () => {
|
|
545
|
-
__config.concurrency = {
|
|
544
|
+
__config.concurrency = { ...DEFAULT_CONFIG.concurrency };
|
|
546
545
|
applyConcurrencyConfig();
|
|
547
546
|
ctx.ui.notify("Concurrency reset to defaults", "info");
|
|
548
547
|
});
|
|
@@ -672,7 +671,7 @@ async function showRunningAgentsMenu(
|
|
|
672
671
|
record.status === "queued" ? "⏳" :
|
|
673
672
|
record.status === "error" ? "✗" : "•";
|
|
674
673
|
items.push(
|
|
675
|
-
`${statusIcon} ${record.id.slice(0,
|
|
674
|
+
`${statusIcon} ${record.id.slice(0, SHORT_ID_LENGTH)} ${record.type} ${record.status} ${elapsed}s`,
|
|
676
675
|
);
|
|
677
676
|
|
|
678
677
|
actions.push(async () => {
|
|
@@ -710,9 +709,9 @@ async function showResultViewer(
|
|
|
710
709
|
text: string,
|
|
711
710
|
): Promise<void> {
|
|
712
711
|
const titleSuffix = kind === "result"
|
|
713
|
-
? record.id.slice(0,
|
|
712
|
+
? record.id.slice(0, SHORT_ID_LENGTH)
|
|
714
713
|
: kind === "snapshot"
|
|
715
|
-
? `snapshot \u00b7 ${record.id.slice(0,
|
|
714
|
+
? `snapshot \u00b7 ${record.id.slice(0, SHORT_ID_LENGTH)}`
|
|
716
715
|
: "Error";
|
|
717
716
|
const stats: ResultViewerStats = {
|
|
718
717
|
lifetimeUsage: record.lifetimeUsage,
|
|
@@ -753,19 +752,11 @@ async function steerAgentById(
|
|
|
753
752
|
const message = await ctx.ui.input(`Steer ${record.type}`);
|
|
754
753
|
if (!message?.trim()) return;
|
|
755
754
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
record.pendingSteers.push(message.trim());
|
|
762
|
-
ctx.ui.notify(`Steer message queued for ${record.id.slice(0, 8)}…`, "info");
|
|
763
|
-
} else {
|
|
764
|
-
await record.session.steer(message.trim());
|
|
765
|
-
ctx.ui.notify(`Steer sent to ${record.id.slice(0, 8)}…`, "info");
|
|
766
|
-
}
|
|
767
|
-
} catch (err) {
|
|
768
|
-
ctx.ui.notify(`Steer failed: ${errorMessage(err)}`, "error");
|
|
755
|
+
const sent = await manager.steer(agentId, message.trim());
|
|
756
|
+
if (sent) {
|
|
757
|
+
ctx.ui.notify(`Steer sent to ${record.id.slice(0, SHORT_ID_LENGTH)}…`, "info");
|
|
758
|
+
} else {
|
|
759
|
+
ctx.ui.notify(`Steer failed for ${record.id.slice(0, SHORT_ID_LENGTH)}`, "error");
|
|
769
760
|
}
|
|
770
761
|
}
|
|
771
762
|
|
|
@@ -819,12 +810,12 @@ export async function showAgentActions(
|
|
|
819
810
|
items.push("Stop");
|
|
820
811
|
actions.push(async () => {
|
|
821
812
|
manager?.abort(record.id);
|
|
822
|
-
ctx.ui.notify(`Stopped ${record.id.slice(0,
|
|
813
|
+
ctx.ui.notify(`Stopped ${record.id.slice(0, SHORT_ID_LENGTH)}`, "info");
|
|
823
814
|
});
|
|
824
815
|
}
|
|
825
816
|
|
|
826
817
|
if (items.length === 0) {
|
|
827
|
-
ctx.ui.notify(`Agent ${record.id.slice(0,
|
|
818
|
+
ctx.ui.notify(`Agent ${record.id.slice(0, SHORT_ID_LENGTH)} — no actions available`, "info");
|
|
828
819
|
return;
|
|
829
820
|
}
|
|
830
821
|
|
|
@@ -834,7 +825,7 @@ export async function showAgentActions(
|
|
|
834
825
|
items.push("Back");
|
|
835
826
|
actions.push(async () => {});
|
|
836
827
|
|
|
837
|
-
await runMenu(ctx, `Agent ${record.id.slice(0,
|
|
828
|
+
await runMenu(ctx, `Agent ${record.id.slice(0, SHORT_ID_LENGTH)}`, items, actions);
|
|
838
829
|
}
|
|
839
830
|
|
|
840
831
|
async function showAgentTypes(ctx: ExtensionCommandContext): Promise<void> {
|
package/src/model-precedence.ts
CHANGED
package/src/output-file.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { formatTokens } from "./usage.js";
|
|
15
16
|
|
|
16
17
|
/** Max length for a truncated command in tool arg summaries. */
|
|
17
18
|
const MAX_COMMAND_DISPLAY_LENGTH = 100;
|
|
@@ -254,9 +255,7 @@ export function streamToOutputFile(
|
|
|
254
255
|
|
|
255
256
|
// Write DONE line
|
|
256
257
|
const { turnCount = 0, toolUseCount = 0, totalTokens = 0, cost = 0 } = stats ?? {};
|
|
257
|
-
const tokensStr = totalTokens
|
|
258
|
-
? `${(totalTokens / 1000).toFixed(1)}k tokens`
|
|
259
|
-
: `${totalTokens} tokens`;
|
|
258
|
+
const tokensStr = `${formatTokens(totalTokens)} tokens`;
|
|
260
259
|
const costStr = `$${cost.toFixed(3)}`;
|
|
261
260
|
safeAppend(path, `${timestamp()} [DONE] ${turnCount} turns, ${toolUseCount} tool uses, ${tokensStr}, ${costStr}\n`);
|
|
262
261
|
|
package/src/prompts.ts
CHANGED
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { AgentConfig, EnvInfo } from "./types.js";
|
|
9
|
+
import type { SkillMeta } from "./skill-loader.js";
|
|
10
|
+
export type { SkillMeta };
|
|
9
11
|
|
|
10
12
|
/** Extra sections to inject into the system prompt (skills only — no memoryBlock). */
|
|
11
13
|
export interface PromptExtras {
|
|
12
|
-
/** Preloaded skill contents to inject. */
|
|
14
|
+
/** Preloaded skill contents to inject (full content). */
|
|
13
15
|
skillBlocks?: { name: string; content: string }[];
|
|
16
|
+
/** Skill metadata for whitelist display (name, description, location only). */
|
|
17
|
+
skillMetas?: SkillMeta[];
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
/**
|
|
@@ -43,11 +47,34 @@ export function buildAgentPrompt(
|
|
|
43
47
|
|
|
44
48
|
// Build optional extras suffix (skills only — no memoryBlock)
|
|
45
49
|
const extraSections: string[] = [];
|
|
50
|
+
|
|
51
|
+
// Skill metadata whitelist (like Pi's available_skills format)
|
|
52
|
+
if (extras?.skillMetas?.length) {
|
|
53
|
+
const lines = [
|
|
54
|
+
"The following skills provide specialized instructions for specific tasks.",
|
|
55
|
+
"Use the read tool to load a skill's file when the task matches its description.",
|
|
56
|
+
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
|
|
57
|
+
"",
|
|
58
|
+
"<available_skills>",
|
|
59
|
+
];
|
|
60
|
+
for (const skill of extras.skillMetas) {
|
|
61
|
+
lines.push(" <skill>");
|
|
62
|
+
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
63
|
+
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
64
|
+
lines.push(` <location>${escapeXml(skill.location)}</location>`);
|
|
65
|
+
lines.push(" </skill>");
|
|
66
|
+
}
|
|
67
|
+
lines.push("</available_skills>");
|
|
68
|
+
extraSections.push(lines.join("\n"));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Preloaded skill contents (full dump into system prompt)
|
|
46
72
|
if (extras?.skillBlocks?.length) {
|
|
47
73
|
for (const skill of extras.skillBlocks) {
|
|
48
74
|
extraSections.push(`\n# Preloaded Skill: ${skill.name}\n${skill.content}`);
|
|
49
75
|
}
|
|
50
76
|
}
|
|
77
|
+
|
|
51
78
|
const extrasSuffix = extraSections.length > 0 ? `\n\n${extraSections.join("\n")}` : "";
|
|
52
79
|
|
|
53
80
|
const header = `You are a pi coding agent sub-agent.
|
|
@@ -58,4 +85,11 @@ ${envBlock}`;
|
|
|
58
85
|
return `${activeAgentTag}${header}\n\n${config.systemPrompt}${extrasSuffix}`;
|
|
59
86
|
}
|
|
60
87
|
|
|
88
|
+
function escapeXml(value: string): string {
|
|
89
|
+
// Only escape < and > — enough for XML-like tags, keeps text readable for LLMs
|
|
90
|
+
return value
|
|
91
|
+
.replace(/</g, "<")
|
|
92
|
+
.replace(/>/g, ">");
|
|
93
|
+
}
|
|
94
|
+
|
|
61
95
|
|
package/src/result-viewer.ts
CHANGED
|
@@ -45,6 +45,9 @@ export interface ResultViewerStats {
|
|
|
45
45
|
/** Lines scrolled per PageUp/PageDown (kept at a fixed, comfortable amount). */
|
|
46
46
|
const PAGE_STEP = 14;
|
|
47
47
|
|
|
48
|
+
/** Render width for markdown content and separator line. */
|
|
49
|
+
const RENDER_WIDTH = 78;
|
|
50
|
+
|
|
48
51
|
/** Fixed non-viewport lines in the component (borders, title, spacers, hints, etc.). */
|
|
49
52
|
const BASE_OVERHEAD = 10;
|
|
50
53
|
|
|
@@ -141,7 +144,7 @@ export class ResultViewer extends Container implements Component {
|
|
|
141
144
|
// Build markdown renderer (pre-render to get total lines)
|
|
142
145
|
const mdTheme = buildMarkdownTheme(theme);
|
|
143
146
|
this.markdown = new Markdown(text, 0, 0, mdTheme);
|
|
144
|
-
this.renderedLines = this.markdown.render(
|
|
147
|
+
this.renderedLines = this.markdown.render(RENDER_WIDTH);
|
|
145
148
|
|
|
146
149
|
this.buildUI(title, stats);
|
|
147
150
|
this.updateViewport();
|
|
@@ -169,7 +172,7 @@ export class ResultViewer extends Container implements Component {
|
|
|
169
172
|
|
|
170
173
|
// Separator
|
|
171
174
|
this.addChild(
|
|
172
|
-
new Text(this.theme.fg("muted", "─".repeat(
|
|
175
|
+
new Text(this.theme.fg("muted", "─".repeat(RENDER_WIDTH)), 0, 0),
|
|
173
176
|
);
|
|
174
177
|
this.addChild(new Spacer(1));
|
|
175
178
|
|
|
@@ -272,7 +275,7 @@ export class ResultViewer extends Container implements Component {
|
|
|
272
275
|
this.textRef.text = newText;
|
|
273
276
|
const mdTheme = buildMarkdownTheme(this.theme);
|
|
274
277
|
this.markdown = new Markdown(newText, 0, 0, mdTheme);
|
|
275
|
-
this.renderedLines = this.markdown.render(
|
|
278
|
+
this.renderedLines = this.markdown.render(RENDER_WIDTH);
|
|
276
279
|
// Preserve scroll position, clamped to new content bounds
|
|
277
280
|
this.scrollOffset = Math.min(oldOffset, this.renderedLines.length - 1);
|
|
278
281
|
this.updateViewport();
|
package/src/skill-loader.ts
CHANGED
|
@@ -32,37 +32,73 @@ interface PreloadedSkill {
|
|
|
32
32
|
content: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface SkillMeta {
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
location: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Skill search roots in precedence order (project → user → legacy).
|
|
43
|
+
* Shared by preloadSkills and loadSkillMeta.
|
|
44
|
+
*/
|
|
45
|
+
function getSkillRoots(cwd: string): string[] {
|
|
46
|
+
return [
|
|
47
|
+
join(cwd, ".pi", "skills"), // project — Pi standard
|
|
48
|
+
join(cwd, ".agents", "skills"), // project — Agent Skills spec
|
|
49
|
+
join(getAgentDir(), "skills"), // user — Pi standard
|
|
50
|
+
join(homedir(), ".agents", "skills"), // user — Agent Skills spec
|
|
51
|
+
join(homedir(), ".pi", "skills"), // legacy global, pre-Pi
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
35
55
|
export function preloadSkills(skillNames: string[], cwd: string): PreloadedSkill[] {
|
|
36
56
|
return skillNames.map((name) => ({ name, content: loadSkillContent(name, cwd) }));
|
|
37
57
|
}
|
|
38
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Load skill metadata only (name, description, location) without full content.
|
|
61
|
+
* Used for the skills whitelist — agent can read full content on-demand.
|
|
62
|
+
*/
|
|
63
|
+
export function loadSkillMeta(skillNames: string[], cwd: string): SkillMeta[] {
|
|
64
|
+
return skillNames.map((name) => {
|
|
65
|
+
const location = findSkillLocation(name, cwd);
|
|
66
|
+
if (!location) {
|
|
67
|
+
return { name, description: `(Skill "${name}" not found)`, location: "" };
|
|
68
|
+
}
|
|
69
|
+
const description = extractDescription(location);
|
|
70
|
+
return { name, description, location };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
39
74
|
function loadSkillContent(name: string, cwd: string): string {
|
|
40
75
|
if (isUnsafeName(name)) {
|
|
41
76
|
return `(Skill "${name}" skipped: name contains path traversal characters)`;
|
|
42
77
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
join(cwd, ".agents", "skills"), // project — Agent Skills spec
|
|
46
|
-
join(getAgentDir(), "skills"), // user — Pi standard
|
|
47
|
-
join(homedir(), ".agents", "skills"), // user — Agent Skills spec
|
|
48
|
-
join(homedir(), ".pi", "skills"), // legacy global, pre-Pi
|
|
49
|
-
];
|
|
50
|
-
for (const root of roots) {
|
|
51
|
-
const content = findInRoot(root, name);
|
|
78
|
+
for (const root of getSkillRoots(cwd)) {
|
|
79
|
+
const content = findInRoot(root, name, "content");
|
|
52
80
|
if (content !== undefined) return content;
|
|
53
81
|
}
|
|
54
82
|
return `(Skill "${name}" not found in .pi/skills/, .agents/skills/, or global skill locations)`;
|
|
55
83
|
}
|
|
56
84
|
|
|
57
|
-
function findInRoot(root: string, name: string): string | undefined {
|
|
58
|
-
if (isSymlink(root)) return undefined;
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
85
|
+
function findInRoot(root: string, name: string, mode: "content" | "location"): string | undefined {
|
|
86
|
+
if (isSymlink(root)) return undefined;
|
|
87
|
+
const flatPath = join(root, `${name}.md`);
|
|
88
|
+
if (mode === "location") {
|
|
89
|
+
if (existsSync(flatPath)) return flatPath;
|
|
90
|
+
} else {
|
|
91
|
+
const content = safeReadFile(flatPath)?.trim();
|
|
92
|
+
if (content !== undefined) return content;
|
|
93
|
+
}
|
|
94
|
+
return findSkillDirectory(root, name, mode);
|
|
62
95
|
}
|
|
63
96
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
97
|
+
/**
|
|
98
|
+
* BFS under `root` for a directory named `name` containing `SKILL.md`.
|
|
99
|
+
* Pi-conforming filters. Returns either the file content or the file path.
|
|
100
|
+
*/
|
|
101
|
+
function findSkillDirectory(root: string, name: string, mode: "content" | "location"): string | undefined {
|
|
66
102
|
if (!existsSync(root)) return undefined;
|
|
67
103
|
const queue: string[] = [root];
|
|
68
104
|
|
|
@@ -91,6 +127,7 @@ function findSkillDirectory(root: string, name: string): string | undefined {
|
|
|
91
127
|
|
|
92
128
|
if (isSkillDir) {
|
|
93
129
|
if (entry.name === name) {
|
|
130
|
+
if (mode === "location") return skillMd;
|
|
94
131
|
const content = safeReadFile(skillMd)?.trim();
|
|
95
132
|
if (content !== undefined) return content;
|
|
96
133
|
}
|
|
@@ -102,3 +139,42 @@ function findSkillDirectory(root: string, name: string): string | undefined {
|
|
|
102
139
|
}
|
|
103
140
|
return undefined;
|
|
104
141
|
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Find skill file location without reading content.
|
|
145
|
+
* Returns the full path to the SKILL.md or .md file, or undefined if not found.
|
|
146
|
+
*/
|
|
147
|
+
function findSkillLocation(name: string, cwd: string): string | undefined {
|
|
148
|
+
if (isUnsafeName(name)) return undefined;
|
|
149
|
+
for (const root of getSkillRoots(cwd)) {
|
|
150
|
+
const location = findInRoot(root, name, "location");
|
|
151
|
+
if (location !== undefined) return location;
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Extract description from SKILL.md frontmatter. */
|
|
157
|
+
function extractDescription(filePath: string): string {
|
|
158
|
+
try {
|
|
159
|
+
const content = safeReadFile(filePath);
|
|
160
|
+
if (!content) return "(no description)";
|
|
161
|
+
|
|
162
|
+
// Simple frontmatter extraction
|
|
163
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
164
|
+
if (!normalized.startsWith("---\n")) return "(no description)";
|
|
165
|
+
const endIndex = normalized.indexOf("\n---\n", 4);
|
|
166
|
+
if (endIndex === -1) return "(no description)";
|
|
167
|
+
|
|
168
|
+
const yamlString = normalized.slice(4, endIndex);
|
|
169
|
+
// Simple extraction of description field
|
|
170
|
+
const descMatch = yamlString.match(/^description:\s*["']?(.+?)["']?\s*$/m);
|
|
171
|
+
if (descMatch && descMatch[1]) {
|
|
172
|
+
// Truncate long descriptions
|
|
173
|
+
const desc = descMatch[1].trim();
|
|
174
|
+
return desc.length > 200 ? desc.slice(0, 197) + "..." : desc;
|
|
175
|
+
}
|
|
176
|
+
return "(no description)";
|
|
177
|
+
} catch {
|
|
178
|
+
return "(error reading description)";
|
|
179
|
+
}
|
|
180
|
+
}
|
package/src/stop-agent-tool.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
14
|
import { successResult, errorResult } from "./tool-execution.js";
|
|
15
15
|
import { manager } from "./index.js";
|
|
16
|
+
import { SHORT_ID_LENGTH } from "./types.js";
|
|
16
17
|
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// Running agents list helper
|
|
@@ -30,7 +31,7 @@ function formatRunningAgents(): string {
|
|
|
30
31
|
if (agents.length === 0) return "none";
|
|
31
32
|
|
|
32
33
|
return agents
|
|
33
|
-
.map((a) => `${a.type}·${a.id.slice(0,
|
|
34
|
+
.map((a) => `${a.type}·${a.id.slice(0, SHORT_ID_LENGTH)}`)
|
|
34
35
|
.join(", ");
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -69,7 +70,7 @@ export async function executeStopAgentTool(
|
|
|
69
70
|
|
|
70
71
|
// Attempt to stop the running/queued agent
|
|
71
72
|
if (manager.abort(agentId)) {
|
|
72
|
-
return successResult(`Stopped agent ${agentId.slice(0,
|
|
73
|
+
return successResult(`Stopped agent ${agentId.slice(0, SHORT_ID_LENGTH)}`);
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
return errorResult(`Failed to stop agent ${agentId}`);
|
package/src/tool-execution.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ExtensionContext, ToolCallEvent } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import type { Model } from "@earendil-works/pi-ai";
|
|
10
9
|
|
|
11
10
|
import type { AgentRecord } from "./types.js";
|
|
12
11
|
import type { SpawnOptions as AgentManagerSpawnOptions } from "./agent-manager.js";
|
|
@@ -151,7 +150,7 @@ function emitIndividualNudge(agentId: string, record?: AgentRecord): void {
|
|
|
151
150
|
piInstance.sendMessage(
|
|
152
151
|
{
|
|
153
152
|
customType: "subagent-result",
|
|
154
|
-
content: record.result ?? ""
|
|
153
|
+
content: `[Subagent "${record.type}" completed]\n\n${record.result ?? ""}`,
|
|
155
154
|
details,
|
|
156
155
|
display: true,
|
|
157
156
|
},
|
|
@@ -181,7 +180,6 @@ export async function executeAgentTool(
|
|
|
181
180
|
|
|
182
181
|
const prompt = params.prompt as string;
|
|
183
182
|
const description = params.description as string;
|
|
184
|
-
const resume = params.resume as string | undefined;
|
|
185
183
|
const runInBackground = params.run_in_background as boolean | undefined;
|
|
186
184
|
const isolated = params.isolated as boolean | undefined;
|
|
187
185
|
const maxTurns = params.max_turns as number | undefined ?? getAgentConfig(resolvedType)?.maxTurns;
|
|
@@ -199,10 +197,6 @@ export async function executeAgentTool(
|
|
|
199
197
|
const thinkingLevel = parseThinkingLevel(params.thinking as string | undefined)
|
|
200
198
|
?? getAgentConfig(resolvedType)?.thinking;
|
|
201
199
|
|
|
202
|
-
if (resume) {
|
|
203
|
-
return executeResumeAgent(resume, prompt);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
200
|
const spawnOptions: AgentManagerSpawnOptions = {
|
|
207
201
|
description,
|
|
208
202
|
model,
|
|
@@ -220,34 +214,23 @@ export async function executeAgentTool(
|
|
|
220
214
|
return executeSpawnForeground(resolvedType, prompt, ctx, spawnOptions);
|
|
221
215
|
}
|
|
222
216
|
|
|
223
|
-
async function executeResumeAgent(
|
|
224
|
-
resume: string,
|
|
225
|
-
prompt: string,
|
|
226
|
-
): Promise<any> {
|
|
227
|
-
const record = await manager.resume(resume, prompt);
|
|
228
|
-
if (!record) {
|
|
229
|
-
return errorResult(`Agent not found: ${resume}`);
|
|
230
|
-
}
|
|
231
|
-
return successResult(record.result ?? "");
|
|
232
|
-
}
|
|
233
|
-
|
|
234
217
|
async function executeSpawnBackground(
|
|
235
218
|
resolvedType: string,
|
|
236
219
|
prompt: string,
|
|
237
220
|
ctx: ExtensionContext,
|
|
238
221
|
spawnOptions: AgentManagerSpawnOptions,
|
|
239
222
|
): Promise<any> {
|
|
240
|
-
const { state
|
|
223
|
+
const { state, callbacks } = createActivityTracker(
|
|
241
224
|
spawnOptions.maxTurns,
|
|
242
225
|
);
|
|
243
226
|
|
|
244
227
|
const agentId = manager.spawn(piInstance, ctx, resolvedType, prompt, {
|
|
245
228
|
...spawnOptions,
|
|
246
229
|
isBackground: true,
|
|
247
|
-
...
|
|
230
|
+
...callbacks,
|
|
248
231
|
});
|
|
249
232
|
backgroundAgentIds.add(agentId);
|
|
250
|
-
agentActivity.set(agentId,
|
|
233
|
+
agentActivity.set(agentId, state);
|
|
251
234
|
widget?.ensureTimer();
|
|
252
235
|
widget?.update();
|
|
253
236
|
|
|
@@ -255,16 +238,11 @@ async function executeSpawnBackground(
|
|
|
255
238
|
if (!record) {
|
|
256
239
|
return errorResult("Failed to create agent");
|
|
257
240
|
}
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
241
|
+
const details: Record<string, unknown> = { type: resolvedType, description: spawnOptions.description };
|
|
242
|
+
const suffix = `A notification will arrive when done - User asks you not to poll or duplicate the delegated work.\n\nAgent ID: ${agentId}`;
|
|
243
|
+
const label = record.status === "queued" ? "Agent queued" : "Agent running";
|
|
261
244
|
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
return successResult(
|
|
265
|
-
`Agent running in background. A notification will arrive when done — User asks you not to poll, but wait for nudge.\n\nAgent ID: ${agentId}`,
|
|
266
|
-
bgDetails,
|
|
267
|
-
);
|
|
245
|
+
return successResult(`[${label}] ${suffix}`, details);
|
|
268
246
|
}
|
|
269
247
|
|
|
270
248
|
async function executeSpawnForeground(
|
package/src/types.ts
CHANGED
|
@@ -27,8 +27,10 @@ export interface AgentConfig {
|
|
|
27
27
|
disallowedTools?: string[];
|
|
28
28
|
/** true = inherit all, string[] = only listed, false = none */
|
|
29
29
|
extensions: true | string[] | false;
|
|
30
|
-
/** true =
|
|
30
|
+
/** Whitelist of allowed skills (metadata only in system prompt). true = all, string[] = listed, false = none */
|
|
31
31
|
skills: true | string[] | false;
|
|
32
|
+
/** Skills to preload with full content into system prompt. string[] = listed, false/undefined = none */
|
|
33
|
+
preloadSkills?: string[] | false;
|
|
32
34
|
model?: string;
|
|
33
35
|
thinking?: ThinkingLevel;
|
|
34
36
|
maxTurns?: number;
|
|
@@ -95,6 +97,9 @@ export interface EnvInfo {
|
|
|
95
97
|
platform: string;
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
/** How many characters of agent ID to show in display. */
|
|
101
|
+
export const SHORT_ID_LENGTH = 8;
|
|
102
|
+
|
|
98
103
|
/** Reason for a context compaction event. */
|
|
99
104
|
export type CompactionReason = "manual" | "threshold" | "overflow";
|
|
100
105
|
|