@ferris1225/pi-subagents 4.2.13 → 4.3.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/CHANGELOG.md +15 -0
- package/README.md +63 -57
- package/agents/{executor.md → artisan.md} +5 -12
- package/agents/{explorer.md → scout.md} +2 -3
- package/agents/steward.md +51 -0
- package/package.json +4 -3
- package/src/agents.ts +3 -10
- package/src/announcements.ts +12 -5
- package/src/config.ts +161 -27
- package/src/dispatch.ts +5 -16
- package/src/index.ts +1 -1
- package/src/monitor.ts +2 -2
- package/src/prompt.ts +13 -7
- package/src/setup.ts +121 -101
- package/src/thread-lifecycle.ts +8 -20
package/src/config.ts
CHANGED
|
@@ -13,14 +13,27 @@ import { dirname, join } from "node:path";
|
|
|
13
13
|
import { getAgentDir, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
14
14
|
|
|
15
15
|
/** Full catalog of agents shipped with the package (selectable in /subagents-setup). */
|
|
16
|
-
export const BUILTIN_AGENT_NAMES = ["
|
|
16
|
+
export const BUILTIN_AGENT_NAMES = ["scout", "artisan", "steward"] as const;
|
|
17
|
+
|
|
18
|
+
/** Every shipped role stays enabled. The setup wizard and load-time adopt both
|
|
19
|
+
* force the full team on so a stale allow-list cannot hide scout, artisan, or
|
|
20
|
+
* steward. */
|
|
21
|
+
export const REQUIRED_ENABLED_AGENTS = [...BUILTIN_AGENT_NAMES] as const;
|
|
17
22
|
|
|
18
23
|
/** Built-in agent names this package no longer ships. Loading an older config
|
|
19
24
|
* prunes them from every record so the setup wizard, dispatch catalog, and
|
|
20
25
|
* model-routing table never surface dead roles. Custom names stay untouched —
|
|
21
26
|
* except one that reuses a removed built-in name, which this cleanup cannot
|
|
22
27
|
* distinguish and deliberately treats as retired. */
|
|
23
|
-
export const REMOVED_BUILTIN_AGENT_NAMES = [
|
|
28
|
+
export const REMOVED_BUILTIN_AGENT_NAMES = [
|
|
29
|
+
"worker",
|
|
30
|
+
"cleaner",
|
|
31
|
+
"documenter",
|
|
32
|
+
"synthesizer",
|
|
33
|
+
"reviewer",
|
|
34
|
+
"explorer",
|
|
35
|
+
"executor",
|
|
36
|
+
] as const;
|
|
24
37
|
|
|
25
38
|
/** Agents enabled out of the box on a fresh install. */
|
|
26
39
|
export const DEFAULT_ENABLED_AGENTS: readonly string[] = [...BUILTIN_AGENT_NAMES];
|
|
@@ -31,7 +44,49 @@ export type AgentScope = (typeof AGENT_SCOPE_VALUES)[number];
|
|
|
31
44
|
/** Thinking levels accepted by pi's `--thinking` option. */
|
|
32
45
|
export const THINKING_LEVEL_VALUES = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
33
46
|
export type ThinkingLevel = (typeof THINKING_LEVEL_VALUES)[number];
|
|
34
|
-
export const DEFAULT_THINKING_LEVEL: ThinkingLevel = "
|
|
47
|
+
export const DEFAULT_THINKING_LEVEL: ThinkingLevel = "medium";
|
|
48
|
+
|
|
49
|
+
/** Role-owned default reasoning strength. A `/subagents-setup` override wins;
|
|
50
|
+
* there is no per-call or frontmatter thinking. */
|
|
51
|
+
export function roleThinkingLevel(agentName: string): ThinkingLevel {
|
|
52
|
+
switch (agentName) {
|
|
53
|
+
case "scout":
|
|
54
|
+
return "low";
|
|
55
|
+
case "artisan":
|
|
56
|
+
return "high";
|
|
57
|
+
case "steward":
|
|
58
|
+
return "medium";
|
|
59
|
+
default:
|
|
60
|
+
return DEFAULT_THINKING_LEVEL;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Short responsibility line shown next to each built-in in setup lists. */
|
|
65
|
+
export interface AgentProfile {
|
|
66
|
+
/** A few words for picker rows. */
|
|
67
|
+
summary: string;
|
|
68
|
+
/** What this role owns, for first-run copy and the configure step. */
|
|
69
|
+
remark: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const AGENT_PROFILES: Record<(typeof BUILTIN_AGENT_NAMES)[number], AgentProfile> = {
|
|
73
|
+
scout: {
|
|
74
|
+
summary: "read-only recon",
|
|
75
|
+
remark: "Maps unfamiliar code. Returns exact paths and leads — never proof. Broad search, symbols, dependencies.",
|
|
76
|
+
},
|
|
77
|
+
artisan: {
|
|
78
|
+
summary: "implement / fix",
|
|
79
|
+
remark: "Writes the change. One deliverable: implement, fix, refactor, or test. Confirms a named defect before editing.",
|
|
80
|
+
},
|
|
81
|
+
steward: {
|
|
82
|
+
summary: "tidy when needed",
|
|
83
|
+
remark: "Use only when that work exists: evidence-first cleanup, docs/comment sync, or merging fan-out results.",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export function agentProfile(name: string): AgentProfile | undefined {
|
|
88
|
+
return (AGENT_PROFILES as Record<string, AgentProfile | undefined>)[name];
|
|
89
|
+
}
|
|
35
90
|
|
|
36
91
|
/** How many lines of a sub-agent result the completion message may carry.
|
|
37
92
|
* Default: 40 — wide fan-outs multiply completion blocks, so deliveries stay
|
|
@@ -61,7 +116,8 @@ export interface SubagentsConfig {
|
|
|
61
116
|
knownAgents: string[];
|
|
62
117
|
/** Per-agent model override, keyed by agent name, as "provider/model-id". */
|
|
63
118
|
agentModels: Record<string, string>;
|
|
64
|
-
/** Optional per-agent thinking
|
|
119
|
+
/** Optional per-agent thinking override from `/subagents-setup`. Missing =
|
|
120
|
+
* the role default from `roleThinkingLevel`. */
|
|
65
121
|
agentThinkingLevels: Record<string, ThinkingLevel>;
|
|
66
122
|
/**
|
|
67
123
|
* Max lines of a sub-agent result carried in the completion message. Longer
|
|
@@ -77,6 +133,8 @@ export interface SubagentsConfig {
|
|
|
77
133
|
* off to the current main model. 0 disables the idle watchdog. Default: 90.
|
|
78
134
|
*/
|
|
79
135
|
idleTimeoutSec: number;
|
|
136
|
+
/** One-shot session notice after a catalog migration; announcements clears it. */
|
|
137
|
+
pendingSetupNotice?: string;
|
|
80
138
|
}
|
|
81
139
|
|
|
82
140
|
export const DEFAULT_CONFIG: SubagentsConfig = {
|
|
@@ -89,6 +147,15 @@ export const DEFAULT_CONFIG: SubagentsConfig = {
|
|
|
89
147
|
idleTimeoutSec: DEFAULT_IDLE_TIMEOUT_SEC,
|
|
90
148
|
};
|
|
91
149
|
|
|
150
|
+
export const FIRST_RUN_SETUP_HINT =
|
|
151
|
+
"Run /subagents-setup: pick a model for scout, artisan, and steward. " +
|
|
152
|
+
"scout maps code (leads, not proof). artisan implements, fixes, refactors, or tests. " +
|
|
153
|
+
"steward tidies or merges only when that work exists. All three stay on. " +
|
|
154
|
+
"Each role has a thinking default you can change in setup.";
|
|
155
|
+
|
|
156
|
+
export const TEAM_SETUP_NOTICE =
|
|
157
|
+
"The team is now scout, artisan, and steward. Run /subagents-setup to pick a model for each; thinking has a role default you can change there.";
|
|
158
|
+
|
|
92
159
|
export function getConfigPath(agentDir: string = getAgentDir()): string {
|
|
93
160
|
return join(agentDir, CONFIG_FILE_NAME);
|
|
94
161
|
}
|
|
@@ -178,6 +245,10 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
178
245
|
config.idleTimeoutSec = Math.max(0, Math.min(IDLE_TIMEOUT_SEC_LIMIT, Math.round(raw.idleTimeoutSec)));
|
|
179
246
|
}
|
|
180
247
|
|
|
248
|
+
if (typeof raw.pendingSetupNotice === "string" && raw.pendingSetupNotice.trim()) {
|
|
249
|
+
config.pendingSetupNotice = raw.pendingSetupNotice.trim();
|
|
250
|
+
}
|
|
251
|
+
|
|
181
252
|
return config;
|
|
182
253
|
}
|
|
183
254
|
|
|
@@ -190,6 +261,23 @@ function defaultConfig(): SubagentsConfig {
|
|
|
190
261
|
};
|
|
191
262
|
}
|
|
192
263
|
|
|
264
|
+
export function withRequiredAgents(enabled: readonly string[]): string[] {
|
|
265
|
+
const next = [...enabled];
|
|
266
|
+
for (const name of REQUIRED_ENABLED_AGENTS) {
|
|
267
|
+
if (!next.includes(name)) next.push(name);
|
|
268
|
+
}
|
|
269
|
+
return next;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function forceRequiredAgents(config: SubagentsConfig): SubagentsConfig {
|
|
273
|
+
const enabledAgents = withRequiredAgents(config.enabledAgents);
|
|
274
|
+
const knownAgents = [...config.knownAgents];
|
|
275
|
+
for (const name of REQUIRED_ENABLED_AGENTS) {
|
|
276
|
+
if (!knownAgents.includes(name)) knownAgents.push(name);
|
|
277
|
+
}
|
|
278
|
+
return { ...config, enabledAgents, knownAgents };
|
|
279
|
+
}
|
|
280
|
+
|
|
193
281
|
/**
|
|
194
282
|
* Drop every removed built-in role from an already-normalized config: enabled
|
|
195
283
|
* and known lists, plus per-agent model and thinking routes. The schema-upgrade
|
|
@@ -215,38 +303,82 @@ function pruneRemovedBuiltins(config: SubagentsConfig): SubagentsConfig {
|
|
|
215
303
|
|
|
216
304
|
/**
|
|
217
305
|
* A shipped agent the config has never recorded is new in this release; the
|
|
218
|
-
* stale allow-list must not keep it dark. Enable it and
|
|
219
|
-
*
|
|
220
|
-
* the fast light-task lane instead of silently spending the main model.
|
|
306
|
+
* stale allow-list must not keep it dark. Enable it. Model and thinking stay
|
|
307
|
+
* unset so the role default and current main model apply until setup.
|
|
221
308
|
*/
|
|
222
|
-
function adoptNewBuiltins(config: SubagentsConfig): SubagentsConfig {
|
|
309
|
+
function adoptNewBuiltins(config: SubagentsConfig): { config: SubagentsConfig; fresh: string[] } {
|
|
223
310
|
const known = new Set(config.knownAgents);
|
|
224
311
|
const fresh = BUILTIN_AGENT_NAMES.filter((name) => !known.has(name));
|
|
225
|
-
if (fresh.length === 0) return config;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
312
|
+
if (fresh.length === 0) return { config, fresh };
|
|
313
|
+
return {
|
|
314
|
+
config: {
|
|
315
|
+
...config,
|
|
316
|
+
enabledAgents: [...config.enabledAgents, ...fresh],
|
|
317
|
+
knownAgents: [...known, ...fresh],
|
|
318
|
+
},
|
|
319
|
+
fresh,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// --- v4 → current catalog migration. Delete this block in the next major. ---
|
|
324
|
+
|
|
325
|
+
const RENAMED_BUILTIN_AGENTS: Record<string, string> = {
|
|
326
|
+
explorer: "scout",
|
|
327
|
+
executor: "artisan",
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
function renameNameList(names: readonly string[]): { names: string[]; changed: boolean } {
|
|
331
|
+
let changed = false;
|
|
332
|
+
const out: string[] = [];
|
|
333
|
+
for (const name of names) {
|
|
334
|
+
const mapped = RENAMED_BUILTIN_AGENTS[name] ?? name;
|
|
335
|
+
if (mapped !== name) changed = true;
|
|
336
|
+
if (!out.includes(mapped)) out.push(mapped);
|
|
233
337
|
}
|
|
338
|
+
return { names: out, changed };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function renameKeyedRecord<T>(record: Record<string, T>): { record: Record<string, T>; changed: boolean } {
|
|
342
|
+
let changed = false;
|
|
343
|
+
const out: Record<string, T> = {};
|
|
344
|
+
for (const [key, value] of Object.entries(record)) {
|
|
345
|
+
const mapped = RENAMED_BUILTIN_AGENTS[key] ?? key;
|
|
346
|
+
if (mapped !== key) changed = true;
|
|
347
|
+
if (!(mapped in out)) out[mapped] = value;
|
|
348
|
+
}
|
|
349
|
+
return { record: out, changed };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Map retired built-in names onto the current catalog and keep their models
|
|
353
|
+
* and thinking overrides. Isolated so the next major can delete it. */
|
|
354
|
+
function migrateRetiredBuiltinNames(config: SubagentsConfig): { config: SubagentsConfig; changed: boolean } {
|
|
355
|
+
const enabled = renameNameList(config.enabledAgents);
|
|
356
|
+
const known = renameNameList(config.knownAgents);
|
|
357
|
+
const models = renameKeyedRecord(config.agentModels);
|
|
358
|
+
const thinking = renameKeyedRecord(config.agentThinkingLevels);
|
|
359
|
+
const changed = enabled.changed || known.changed || models.changed || thinking.changed;
|
|
360
|
+
if (!changed) return { config, changed: false };
|
|
234
361
|
return {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
362
|
+
changed: true,
|
|
363
|
+
config: {
|
|
364
|
+
...config,
|
|
365
|
+
enabledAgents: enabled.names,
|
|
366
|
+
knownAgents: known.names,
|
|
367
|
+
agentModels: models.record,
|
|
368
|
+
agentThinkingLevels: thinking.record,
|
|
369
|
+
},
|
|
240
370
|
};
|
|
241
371
|
}
|
|
242
372
|
|
|
373
|
+
// --- end v4 catalog migration ---
|
|
374
|
+
|
|
243
375
|
/**
|
|
244
376
|
* Load config. A missing file is a normal state and yields the defaults (not an error).
|
|
245
377
|
* A corrupt file also falls back to defaults rather than throwing, so startup never breaks.
|
|
246
378
|
* A file from an older version (missing newer keys or holding extra keys) is
|
|
247
379
|
* normalized and persisted back, so the on-disk config stays current. Built-in
|
|
248
|
-
* agents the file has never seen are adopted
|
|
249
|
-
*
|
|
380
|
+
* agents the file has never seen are adopted. Retired built-in names are
|
|
381
|
+
* renamed or pruned. Artisan and steward stay enabled.
|
|
250
382
|
*/
|
|
251
383
|
export async function loadConfig(configPath: string = getConfigPath()): Promise<SubagentsConfig> {
|
|
252
384
|
let text: string;
|
|
@@ -264,10 +396,12 @@ export async function loadConfig(configPath: string = getConfigPath()): Promise<
|
|
|
264
396
|
return defaultConfig();
|
|
265
397
|
}
|
|
266
398
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
399
|
+
const renamed = migrateRetiredBuiltinNames(normalizeConfig(parsed));
|
|
400
|
+
const adopted = adoptNewBuiltins(renamed.config);
|
|
401
|
+
let config = forceRequiredAgents(pruneRemovedBuiltins(adopted.config));
|
|
402
|
+
if ((renamed.changed || adopted.fresh.length > 0) && !config.pendingSetupNotice) {
|
|
403
|
+
config = { ...config, pendingSetupNotice: TEAM_SETUP_NOTICE };
|
|
404
|
+
}
|
|
271
405
|
|
|
272
406
|
// Schema upgrade: persist the normalized shape when the file gained fields
|
|
273
407
|
// (new version) or dropped invalid ones.
|
package/src/dispatch.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `subagent` tool: dispatches the enabled agents (
|
|
2
|
+
* The `subagent` tool: dispatches the enabled agents (scout, artisan, steward,
|
|
3
3
|
* plus custom roles) as isolated pi
|
|
4
4
|
* child processes, single or parallel. Owns the public dispatch contract and
|
|
5
5
|
* per-run status tracking. Stable thread generations, final integration, and
|
|
@@ -12,7 +12,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
12
12
|
import { join, resolve } from "node:path";
|
|
13
13
|
import { Type } from "typebox";
|
|
14
14
|
import { discoverAgents, isWriteCapableAgent, resolveAgentTools, type AgentConfig } from "./agents.ts";
|
|
15
|
-
import { loadConfig
|
|
15
|
+
import { loadConfig } from "./config.ts";
|
|
16
16
|
import { formatCompletionBlock, formatUsage, queuedResult } from "./format.ts";
|
|
17
17
|
import {
|
|
18
18
|
formatTaskSummary,
|
|
@@ -51,19 +51,12 @@ export { isWorktreeCapableAgent, runInManagedRepositoryLane } from "./thread-lif
|
|
|
51
51
|
const NON_BLANK_TASK_OPTIONS = { minLength: 1, pattern: "\\S" } as const;
|
|
52
52
|
|
|
53
53
|
const ISOLATION_DESCRIPTION =
|
|
54
|
-
"Filesystem isolation: shared uses the caller's working tree; worktree creates a detached temporary Git worktree (write-capable agents, including
|
|
54
|
+
"Filesystem isolation: shared uses the caller's working tree; worktree creates a detached temporary Git worktree (write-capable agents, including artisan and steward, only)";
|
|
55
55
|
|
|
56
56
|
const IsolationSchema = Type.Optional(
|
|
57
57
|
StringEnum(["shared", "worktree"] as const, { description: ISOLATION_DESCRIPTION }),
|
|
58
58
|
);
|
|
59
59
|
|
|
60
|
-
const ThinkingSchema = Type.Optional(
|
|
61
|
-
StringEnum(THINKING_LEVEL_VALUES, {
|
|
62
|
-
description:
|
|
63
|
-
"Optional reasoning strength for this task; omit to keep the agent's own level",
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
60
|
const WaitSchema = Type.Optional(
|
|
68
61
|
Type.Boolean({
|
|
69
62
|
description:
|
|
@@ -79,7 +72,6 @@ const TaskItem = Type.Object({
|
|
|
79
72
|
}),
|
|
80
73
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
|
|
81
74
|
isolation: IsolationSchema,
|
|
82
|
-
thinking: ThinkingSchema,
|
|
83
75
|
});
|
|
84
76
|
|
|
85
77
|
const SubagentParams = Type.Object({
|
|
@@ -90,14 +82,13 @@ const SubagentParams = Type.Object({
|
|
|
90
82
|
tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })),
|
|
91
83
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process (single mode)" })),
|
|
92
84
|
isolation: IsolationSchema,
|
|
93
|
-
thinking: ThinkingSchema,
|
|
94
85
|
wait: WaitSchema,
|
|
95
86
|
});
|
|
96
87
|
|
|
97
88
|
/** Roles that default to worktree isolation in parallel dispatches even when
|
|
98
89
|
* the live catalog cannot be consulted (render-only call sites). Custom
|
|
99
90
|
* write-capable agents join them via isWriteCapableAgent on the execute path. */
|
|
100
|
-
const WORKTREE_DEFAULT_AGENTS = new Set(["
|
|
91
|
+
const WORKTREE_DEFAULT_AGENTS = new Set(["artisan", "steward"]);
|
|
101
92
|
|
|
102
93
|
/** Resolve the default isolation for a dispatch. Precedence: an explicit
|
|
103
94
|
* per-call request, then the role's own frontmatter declaration (`worktree`
|
|
@@ -390,7 +381,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
390
381
|
"A configured child-model failure continues the retained session on the current main model.",
|
|
391
382
|
].join(" "),
|
|
392
383
|
promptSnippet:
|
|
393
|
-
"Dispatch isolated background agents for recon
|
|
384
|
+
"Dispatch isolated background agents for recon or implementation, and for cleanup, docs sync, or result merging only when that work exists; never blocks your turn, and completions wake you automatically.",
|
|
394
385
|
parameters: SubagentParams,
|
|
395
386
|
|
|
396
387
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
@@ -481,7 +472,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
481
472
|
catalogAgent ? isWriteCapableAgent(catalogAgent) : undefined,
|
|
482
473
|
catalogAgent?.isolation,
|
|
483
474
|
),
|
|
484
|
-
{ thinking: item.thinking as ThinkingLevel | undefined },
|
|
485
475
|
));
|
|
486
476
|
}
|
|
487
477
|
const startedRuns = results.filter((result) => result.exitCode === -1);
|
|
@@ -544,7 +534,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
544
534
|
singleCatalogAgent ? isWriteCapableAgent(singleCatalogAgent) : undefined,
|
|
545
535
|
singleCatalogAgent?.isolation,
|
|
546
536
|
),
|
|
547
|
-
{ thinking: params.thinking as ThinkingLevel | undefined },
|
|
548
537
|
);
|
|
549
538
|
if (result.exitCode !== -1) {
|
|
550
539
|
throw new Error(getResultOutput(result));
|
package/src/index.ts
CHANGED
|
@@ -72,7 +72,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
72
72
|
registerLookupTools(pi, runtime);
|
|
73
73
|
|
|
74
74
|
pi.registerCommand("subagents-setup", {
|
|
75
|
-
description: "Configure pi-subagents: agents,
|
|
75
|
+
description: "Configure pi-subagents: agents, models, and per-role thinking",
|
|
76
76
|
handler: async (_args, ctx) => {
|
|
77
77
|
await runSetup(ctx, configPath);
|
|
78
78
|
},
|
package/src/monitor.ts
CHANGED
|
@@ -52,7 +52,7 @@ export interface RunView {
|
|
|
52
52
|
model?: string;
|
|
53
53
|
/** Selected model ref when the run handed off to current main. */
|
|
54
54
|
modelFallbackFrom?: string;
|
|
55
|
-
/** Effective thinking strength this run was launched with (
|
|
55
|
+
/** Effective thinking strength this run was launched with (setup override or role default). */
|
|
56
56
|
thinking?: string;
|
|
57
57
|
isolation?: IsolationMode;
|
|
58
58
|
integrationStatus?: RunIntegrationStatus;
|
|
@@ -187,7 +187,7 @@ function tailGraphemes(segments: string[], maxWidth: number): string {
|
|
|
187
187
|
* One-line task preview, capped by `maxWidth` display columns (default 80).
|
|
188
188
|
* `keysOnly` (default): extracted key fragments (paths, quoted phrases,
|
|
189
189
|
* symbols) are shown bare — the agent name is already displayed next to the
|
|
190
|
-
* task line, so templated prose ("
|
|
190
|
+
* task line, so templated prose ("scout: trace how ...") adds nothing.
|
|
191
191
|
* `keysOnly: false` keeps the prose as `head…tail` (used for completion
|
|
192
192
|
* messages, where the Task line is the reader's only context).
|
|
193
193
|
* Grapheme-safe — CJK, ZWJ emoji and combining sequences are never split.
|
package/src/prompt.ts
CHANGED
|
@@ -19,20 +19,26 @@ export function buildDelegationDirective(
|
|
|
19
19
|
if (agents.length === 0) return "";
|
|
20
20
|
|
|
21
21
|
const catalog = agents.map(formatCatalogEntry).join("\n");
|
|
22
|
-
const
|
|
23
|
-
const
|
|
22
|
+
const hasScout = agents.some((agent) => agent.name === "scout");
|
|
23
|
+
const hasArtisan = agents.some((agent) => agent.name === "artisan");
|
|
24
|
+
const hasSteward = agents.some((agent) => agent.name === "steward");
|
|
24
25
|
|
|
25
26
|
const dispatchRules = [
|
|
26
|
-
`Delegate aggressively: child contexts are cheap, yours is scarce. A unit is delegable when it can proceed independently and return a compact result${
|
|
27
|
+
`Delegate aggressively: child contexts are cheap, yours is scarce. A unit is delegable when it can proceed independently and return a compact result${hasArtisan ? "; when the unit is a code change, default it to \`artisan\`" : ""}.`,
|
|
27
28
|
"Keep inline what fails either test — a lookup, a single focused edit, an answer already in context, or a single artifact you must absorb yourself (one issue, one spec), where delegation saves search, not that read. Cluster related questions into one brief instead of firing many small dispatches; a child loses context between runs.",
|
|
28
|
-
...(
|
|
29
|
+
...(hasScout
|
|
29
30
|
? [
|
|
30
|
-
"`
|
|
31
|
+
"`scout`: split a broad question into parallel scouts with disjoint scopes. Its findings are leads, never proof — re-read the cited line ranges before acting on them (a child you brief re-verifies).",
|
|
31
32
|
]
|
|
32
33
|
: []),
|
|
33
|
-
...(
|
|
34
|
+
...(hasArtisan
|
|
34
35
|
? [
|
|
35
|
-
"`
|
|
36
|
+
"`artisan`: brief it as the edit authorization for implement, fix, refactor, or test. Not cleanup, docs sync, or merging results.",
|
|
37
|
+
]
|
|
38
|
+
: []),
|
|
39
|
+
...(hasSteward
|
|
40
|
+
? [
|
|
41
|
+
"`steward`: dispatch only when the work is cleanup, documentation sync, or merging named result artifacts — do not invent a tidy pass. For cleanup, name the scope (uncommitted diff, Git range, directory). After a wide fan-out, pass the result-artifact paths to one steward and read its brief instead of every result yourself.",
|
|
36
42
|
]
|
|
37
43
|
: []),
|
|
38
44
|
"A discovered defect is not a change: re-read the current code and confirm it is not a false positive before you edit or brief a writer to edit.",
|