pi-subagents-lite 1.2.0 → 1.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.
Files changed (53) hide show
  1. package/README.md +184 -225
  2. package/package.json +1 -1
  3. package/src/{agent-discovery.ts → agents/agent-discovery.ts} +8 -5
  4. package/src/{agent-manager.ts → agents/agent-manager.ts} +34 -74
  5. package/src/{agent-runner.ts → agents/agent-runner.ts} +115 -173
  6. package/src/agents/agent-status.ts +50 -0
  7. package/src/agents/agent-types.ts +339 -0
  8. package/src/{default-agents.ts → agents/default-agents.ts} +2 -5
  9. package/src/{output-file.ts → agents/output-file.ts} +68 -1
  10. package/src/{tool-execution.ts → agents/tool-execution.ts} +61 -223
  11. package/src/agents/types.ts +54 -0
  12. package/src/{usage.ts → agents/usage.ts} +7 -0
  13. package/src/{config-io.ts → config/config-io.ts} +20 -3
  14. package/src/config/config-store.ts +472 -0
  15. package/src/config/types.ts +26 -0
  16. package/src/events.ts +185 -0
  17. package/src/index.ts +8 -271
  18. package/src/{model-precedence.ts → models/model-precedence.ts} +33 -0
  19. package/src/{model-selector.ts → models/model-selector.ts} +1 -1
  20. package/src/{context.ts → prompt/context.ts} +1 -1
  21. package/src/prompt/prompts.ts +180 -0
  22. package/src/prompt/skill-loader.ts +195 -0
  23. package/src/registration.ts +101 -0
  24. package/src/shell.ts +101 -0
  25. package/src/spawn/spawn-coordinator.ts +232 -0
  26. package/src/status-note.ts +10 -0
  27. package/src/types.ts +47 -71
  28. package/src/ui/agent-widget.ts +61 -49
  29. package/src/{format.ts → ui/format.ts} +64 -26
  30. package/src/ui/menu/helpers.ts +93 -0
  31. package/src/ui/menu/menu-concurrency.ts +192 -0
  32. package/src/ui/menu/menu-debug.ts +125 -0
  33. package/src/ui/menu/menu-model-settings.ts +208 -0
  34. package/src/ui/menu/menu-running-agents.ts +224 -0
  35. package/src/ui/menu/menu-spawn-options.ts +87 -0
  36. package/src/ui/menu/menu-spawn-wizard.ts +418 -0
  37. package/src/ui/menu/menu-system-prompt.ts +109 -0
  38. package/src/ui/menu/menu-widget-settings.ts +130 -0
  39. package/src/ui/menu/menus.ts +101 -0
  40. package/src/ui/menu/submenus/confirm.ts +47 -0
  41. package/src/ui/menu/submenus/model-select.ts +70 -0
  42. package/src/ui/menu/submenus/numeric-input.ts +98 -0
  43. package/src/ui/menu/wrappers/settings-list.ts +205 -0
  44. package/src/{renderer.ts → ui/renderer.ts} +7 -6
  45. package/src/{result-viewer.ts → ui/result-viewer.ts} +7 -2
  46. package/src/ui/types.ts +11 -0
  47. package/src/agent-types.ts +0 -184
  48. package/src/config-mutator.ts +0 -183
  49. package/src/menus.ts +0 -1333
  50. package/src/prompts.ts +0 -94
  51. package/src/skill-loader.ts +0 -178
  52. package/src/state.ts +0 -83
  53. /package/src/{worktree-validator.ts → spawn/worktree-validator.ts} +0 -0
@@ -1,57 +1,40 @@
1
+ import { getStatusNote } from "../status-note.js";
1
2
  /**
2
3
  * tool-execution.ts — Agent tool execution handlers.
3
4
  *
4
- * Contains the execute callbacks registered for the Agent tool,
5
- * plus nudge scheduling and activity tracking helpers.
5
+ * Contains the execute callbacks registered for the Agent tool.
6
+ * Spawn coordination, nudge scheduling, and live-view tracking have moved
7
+ * to spawn-coordinator.ts. buildAgentDetails stays here as a pure helper.
6
8
  */
7
9
 
8
10
  import type { ExtensionContext, ToolCallEvent } from "@earendil-works/pi-coding-agent";
9
11
 
10
- import type { AgentRecord } from "./types.js";
11
- import { SHORT_ID_LENGTH } from "./types.js";
12
- import type { SpawnOptions as AgentManagerSpawnOptions } from "./agent-manager.js";
13
- import type { AgentActivity } from "./ui/agent-widget.js";
12
+ import type { AgentRecord } from "../types.js";
13
+ import { SHORT_ID_LENGTH } from "../types.js";
14
14
  import { resolveType, getAgentConfig, discoverNewAgents } from "./agent-types.js";
15
- import { resolveModel } from "./model-precedence.js";
16
- import { addUsage, getLifetimeTotal, getSessionContextPercent, type LifetimeUsage } from "./usage.js";
17
- import { validateWorktreePath } from "./worktree-validator.js";
15
+ import { getLifetimeTotal, getSessionContextPercent } from "./usage.js";
16
+ import { validateWorktreePath } from "../spawn/worktree-validator.js";
18
17
 
19
- // Shared state imported from state.ts
20
- import { parseModelKey, findModelInRegistry, parseThinkingLevel } from "./utils.js";
18
+ import { parseModelKey, findModelInRegistry, parseThinkingLevel } from "../utils.js";
21
19
  import {
22
- __config,
23
- sessionOverrides,
24
- piInstance,
25
- agentActivity,
20
+ getPiInstance,
21
+ getSessionCtx,
22
+ getStore,
23
+ getCoordinator,
26
24
  getManager,
27
- getWidget,
28
- sessionCtx,
29
- } from "./state.js";
30
-
31
- // ============================================================================
32
- // Module-level state
33
- // ============================================================================
34
-
35
- /** Agent IDs that were spawned as background — only these trigger a nudge on completion. */
36
- export const backgroundAgentIds = new Set<string>();
37
-
38
- const pendingNudges = new Set<string>();
39
- let nudgeTimer: ReturnType<typeof setTimeout> | null = null;
40
-
41
- /** Batch delay for nudges — only emit one update per batch window (ms). */
42
- const NUDGE_DELAY_MS = 200;
25
+ } from "../shell.js";
43
26
 
44
27
  // ============================================================================
45
28
  // Tool result helpers
46
29
  // ============================================================================
47
30
 
48
31
  /** Shortcut for a successful tool result. */
49
- export function successResult(text: string, details?: Record<string, unknown>) {
32
+ function successResult(text: string, details?: Record<string, unknown>) {
50
33
  return { content: [{ type: "text", text }], details };
51
34
  }
52
35
 
53
36
  /** Shortcut for an error tool result. */
54
- export function errorResult(text: string, details?: Record<string, unknown>) {
37
+ function errorResult(text: string, details?: Record<string, unknown>) {
55
38
  return { content: [{ type: "text", text }], isError: true as const, details };
56
39
  }
57
40
 
@@ -59,67 +42,6 @@ export function errorResult(text: string, details?: Record<string, unknown>) {
59
42
  // Activity tracking
60
43
  // ============================================================================
61
44
 
62
- /**
63
- * Create an AgentActivity state and spawn callbacks for tracking tool usage.
64
- * Used by both foreground and background paths to avoid duplication.
65
- * Exported for use by the menu spawn flow.
66
- */
67
- export function createActivityTracker(maxTurns?: number, onStreamUpdate?: () => void) {
68
- const state: AgentActivity = {
69
- activeTools: new Map(),
70
- toolUses: 0,
71
- turnCount: 1,
72
- maxTurns,
73
- responseText: "",
74
- session: undefined,
75
- lifetimeUsage: { input: 0, output: 0, cacheWrite: 0, cost: 0 },
76
- };
77
-
78
- const callbacks = {
79
- onToolActivity: (activity: { type: "start" | "end"; toolName: string }) => {
80
- if (activity.type === "start") {
81
- state.activeTools.set(`${activity.toolName}_${Date.now()}`, activity.toolName);
82
- } else {
83
- for (const [key, name] of state.activeTools) {
84
- if (name === activity.toolName) { state.activeTools.delete(key); break; }
85
- }
86
- state.toolUses++;
87
- }
88
- onStreamUpdate?.();
89
- },
90
- onTextDelta: (_delta: string, fullText: string) => {
91
- state.responseText = fullText;
92
- onStreamUpdate?.();
93
- },
94
- onTurnEnd: (turnCount: number) => {
95
- state.turnCount = turnCount;
96
- onStreamUpdate?.();
97
- },
98
- onSessionCreated: (session: unknown) => {
99
- state.session = session as Parameters<typeof getSessionContextPercent>[0];
100
- },
101
- onAssistantUsage: (usage: LifetimeUsage) => {
102
- addUsage(state.lifetimeUsage, usage);
103
- onStreamUpdate?.();
104
- },
105
- };
106
-
107
- return { state, callbacks };
108
- }
109
-
110
- // ============================================================================
111
- // buildAgentDetails — consolidated stats/details construction
112
- // ============================================================================
113
-
114
- interface AgentDetailsOptions {
115
- /** Include full stats (turns, tokens, context%, compactions, cost). Default: false. */
116
- includeStats?: boolean;
117
- /** Include status and outputFile. Default: false. */
118
- includeStatus?: boolean;
119
- /** Override the turnCount (e.g. from activity tracker). Default: record.turnCount. */
120
- turnCount?: number;
121
- }
122
-
123
45
  /**
124
46
  * Build a details Record from an AgentRecord, controlled by options.
125
47
  *
@@ -130,6 +52,13 @@ interface AgentDetailsOptions {
130
52
  * Consolidates the identical field-selection logic previously duplicated
131
53
  * across emitIndividualNudge, executeSpawnForeground, and executeSpawnBackground.
132
54
  */
55
+ interface AgentDetailsOptions {
56
+ /** Include full stats (turns, tokens, context%, compactions, cost). Default: false. */
57
+ includeStats?: boolean;
58
+ /** Include status and outputFile. Default: false. */
59
+ includeStatus?: boolean;
60
+ }
61
+
133
62
  export function buildAgentDetails(
134
63
  record: AgentRecord,
135
64
  options?: AgentDetailsOptions,
@@ -149,13 +78,13 @@ export function buildAgentDetails(
149
78
  }
150
79
 
151
80
  if (options?.includeStats) {
152
- const totalTokens = getLifetimeTotal(record.stats.lifetimeUsage);
153
81
  const elapsedMs = record.lifecycle.completedAt ? record.lifecycle.completedAt - record.lifecycle.startedAt : 0;
154
82
 
155
- details.turnCount = options.turnCount ?? record.stats.turnCount;
83
+ details.turnCount = record.stats.turnCount;
156
84
  details.maxTurns = record.stats.maxTurns;
157
85
  details.toolUses = record.stats.toolUses;
158
- details.tokens = totalTokens;
86
+ details.input = record.stats.lifetimeUsage.input;
87
+ details.output = record.stats.lifetimeUsage.output;
159
88
  details.contextPercent = getSessionContextPercent(record.execution.session);
160
89
  details.durationMs = elapsedMs;
161
90
  details.compactions = record.stats.compactionCount;
@@ -166,49 +95,6 @@ export function buildAgentDetails(
166
95
  return details;
167
96
  }
168
97
 
169
- // ============================================================================
170
- // Nudge scheduling — batch completion notifications within the hold window
171
- // ============================================================================
172
-
173
- export function scheduleNudge(agentId: string): void {
174
- pendingNudges.add(agentId);
175
-
176
- if (nudgeTimer) return;
177
-
178
- nudgeTimer = setTimeout(() => {
179
- nudgeTimer = null;
180
- const batch = [...pendingNudges];
181
- pendingNudges.clear();
182
-
183
- for (const id of batch) {
184
- emitIndividualNudge(id, getManager()?.getRecord(id));
185
- }
186
- }, NUDGE_DELAY_MS);
187
- }
188
-
189
- function emitIndividualNudge(agentId: string, record?: AgentRecord): void {
190
- if (!record) return;
191
-
192
- const details = buildAgentDetails(record, {
193
- includeStats: true,
194
- includeStatus: true,
195
- turnCount: record.stats.turnCount ?? agentActivity.get(agentId)?.turnCount,
196
- });
197
-
198
- piInstance.sendMessage(
199
- {
200
- customType: "subagent-result",
201
- content: `[Subagent "${record.display.type}" ${record.lifecycle.status}]\n\n${record.result ?? ""}`,
202
- details,
203
- display: true,
204
- },
205
- {
206
- deliverAs: "steer",
207
- triggerTurn: true,
208
- },
209
- );
210
- }
211
-
212
98
  // ============================================================================
213
99
  // Tool execute handlers
214
100
  // ============================================================================
@@ -226,8 +112,8 @@ export async function executeAgentTool(
226
112
  let worktreeLabel: string | undefined;
227
113
  if (rawWorktreePath && rawWorktreePath.trim() !== "") {
228
114
  try {
229
- const parentCwd = sessionCtx?.cwd ?? ctx.cwd;
230
- const validation = await validateWorktreePath(piInstance, rawWorktreePath, parentCwd);
115
+ const parentCwd = getSessionCtx()?.cwd ?? ctx.cwd;
116
+ const validation = await validateWorktreePath(getPiInstance(), rawWorktreePath, parentCwd);
231
117
  if (!validation.ok) {
232
118
  return errorResult(validation.error);
233
119
  }
@@ -253,7 +139,7 @@ export async function executeAgentTool(
253
139
  }
254
140
 
255
141
  const prompt = params.prompt as string;
256
- const description = params.description as string;
142
+ const description = (params.description as string | undefined) || prompt.split("\n")[0].slice(0, 80) || prompt.slice(0, 80);
257
143
  const runInBackground = params.run_in_background as boolean | undefined;
258
144
  const maxTurns = params.max_turns as number | undefined ?? getAgentConfig(resolvedType)?.maxTurns;
259
145
 
@@ -266,90 +152,44 @@ export async function executeAgentTool(
266
152
 
267
153
  // Resolve thinking: explicit param > agent config (frontmatter) > undefined (inherit)
268
154
  const thinkingLevel = parseThinkingLevel(params.thinking as string | undefined)
269
- ?? getAgentConfig(resolvedType)?.thinking;
155
+ ?? getAgentConfig(resolvedType)?.thinkingLevel;
270
156
 
271
- const spawnOptions: AgentManagerSpawnOptions = {
157
+ // Use SpawnCoordinator for unified spawn path
158
+ const coordinator = getCoordinator()!;
159
+ const result = await coordinator.spawn(getPiInstance(), ctx, {
160
+ type: resolvedType,
161
+ prompt,
272
162
  description,
273
163
  model,
164
+ modelKey,
274
165
  maxTurns,
275
166
  thinkingLevel,
276
- modelKey,
277
- invocation: { modelName },
278
- graceTurns: __config.agent.graceTurns,
167
+ graceTurns: getStore().agent.graceTurns,
279
168
  worktreePath: validatedWorktreePath,
280
169
  worktreeLabel,
281
- };
282
-
283
- if (runInBackground || __config.agent.forceBackground) {
284
- return executeSpawnBackground(resolvedType, prompt, ctx, spawnOptions);
285
- }
286
-
287
- return executeSpawnForeground(resolvedType, prompt, ctx, spawnOptions);
288
- }
289
-
290
- async function executeSpawnBackground(
291
- resolvedType: string,
292
- prompt: string,
293
- ctx: ExtensionContext,
294
- spawnOptions: AgentManagerSpawnOptions,
295
- ): Promise<any> {
296
- const { state, callbacks } = createActivityTracker(
297
- spawnOptions.maxTurns,
298
- );
299
-
300
- const agentId = getManager().spawn(piInstance, ctx, resolvedType, prompt, {
301
- ...spawnOptions,
302
- isBackground: true,
303
- ...callbacks,
304
- });
305
- backgroundAgentIds.add(agentId);
306
- agentActivity.set(agentId, state);
307
- getWidget()?.ensureTimer();
308
- getWidget()?.update();
309
-
310
- const record = getManager().getRecord(agentId)!;
311
- const details = buildAgentDetails(record);
312
- const suffix = `A notification will arrive when done - User asks you not to poll, check status or duplicate the delegated work.\n\nAgent ID: ${agentId}`;
313
- const label = record.lifecycle.status === "queued" ? "Agent queued" : "Agent running";
314
-
315
- return successResult(`[${label}] ${suffix}`, details);
316
- }
317
-
318
- async function executeSpawnForeground(
319
- resolvedType: string,
320
- prompt: string,
321
- ctx: ExtensionContext,
322
- spawnOptions: AgentManagerSpawnOptions,
323
- ): Promise<any> {
324
- const { state: fgState, callbacks: fgCallbacks } = createActivityTracker(
325
- spawnOptions.maxTurns,
326
- );
327
-
328
- const fgId = getManager().spawn(piInstance, ctx, resolvedType, prompt, {
329
- ...spawnOptions,
330
- ...fgCallbacks,
331
- isBackground: false,
170
+ invocation: { modelName },
171
+ runInBackground: runInBackground || getStore().agent.forceBackground,
332
172
  });
333
- agentActivity.set(fgId, fgState);
334
- getWidget()?.ensureTimer();
335
173
 
336
- const record = getManager().getRecord(fgId)!;
337
- await record.execution.promise;
174
+ const { agentId, record } = result;
338
175
 
339
- agentActivity.delete(fgId);
340
- getWidget()?.markFinished(fgId);
341
- getWidget()?.update();
176
+ if (runInBackground || getStore().agent.forceBackground) {
177
+ // Background: return immediately
178
+ const suffix = `A notification will arrive when done - User asks you not to poll, check status or duplicate the delegated work.\n\nAgent ID: ${agentId}`;
179
+ const label = record.lifecycle.status === "queued" ? "Agent queued" : "Agent running";
180
+ const details = buildAgentDetails(record);
181
+ return successResult(`[${label}] ${suffix}`, details);
182
+ }
342
183
 
343
- const stats = buildAgentDetails(record, {
344
- includeStats: true,
345
- turnCount: fgState.turnCount,
346
- });
184
+ // Foreground: record.execution.promise is already awaited by coordinator.spawn()
185
+ const details = buildAgentDetails(record, { includeStats: true });
347
186
 
348
187
  if (record.lifecycle.status === "error") {
349
- return errorResult(`Agent failed: ${record.error || "unknown error"}`, stats);
188
+ return errorResult(`Agent failed: ${record.error || "unknown error"}`, details);
350
189
  }
351
190
 
352
- return successResult(record.result ?? "", stats);
191
+ const statusNote = getStatusNote(record.lifecycle.status);
192
+ return successResult((record.result ?? "") + statusNote, details);
353
193
  }
354
194
 
355
195
  // ============================================================================
@@ -361,7 +201,7 @@ async function executeSpawnForeground(
361
201
  * Format: "type·short_id, type·short_id" — one line, easy for LLM to parse.
362
202
  */
363
203
  function formatRunningAgents(): string {
364
- const agents = getManager().listAgents().filter(
204
+ const agents = getManager()!.listAgents().filter(
365
205
  (a) => a.lifecycle.status === "running" || a.lifecycle.status === "queued",
366
206
  );
367
207
 
@@ -389,7 +229,7 @@ export async function executeStopAgentTool(
389
229
  return errorResult("agent_id is required");
390
230
  }
391
231
 
392
- const record = getManager().getRecord(agentId);
232
+ const record = getManager()!.getRecord(agentId);
393
233
 
394
234
  if (!record) {
395
235
  // Agent not found → return error + list of running agents
@@ -406,7 +246,7 @@ export async function executeStopAgentTool(
406
246
  }
407
247
 
408
248
  // Attempt to stop the running/queued agent
409
- if (getManager().abort(agentId)) {
249
+ if (getManager()!.abort(agentId)) {
410
250
  return successResult(`Stopped agent ${agentId.slice(0, SHORT_ID_LENGTH)}`);
411
251
  }
412
252
 
@@ -429,13 +269,11 @@ export async function toolCallListener(
429
269
 
430
270
  const parentModelId = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "";
431
271
 
432
- const effectiveModel = resolveModel({
433
- subagentType: subagentType ?? "general-purpose",
434
- agentConfig,
435
- config: __config,
272
+ const effectiveModel = getStore().modelFor(
273
+ subagentType ?? "general-purpose",
436
274
  parentModelId,
437
- sessionOverrides,
438
- });
275
+ agentConfig,
276
+ );
439
277
 
440
278
  if (effectiveModel) {
441
279
  input.model = effectiveModel;
@@ -447,7 +285,7 @@ export async function toolCallListener(
447
285
  }
448
286
 
449
287
  // Inject thinking from agent config if not explicitly passed
450
- if (input.thinking === undefined && agentConfig?.thinking !== undefined) {
451
- input.thinking = agentConfig.thinking;
288
+ if (input.thinking === undefined && agentConfig?.thinkingLevel !== undefined) {
289
+ input.thinking = agentConfig.thinkingLevel;
452
290
  }
453
291
  }
@@ -0,0 +1,54 @@
1
+ import type { ThinkingLevel } from "../types.js";
2
+
3
+ /** Agent type: any string name (built-in defaults or user-defined). */
4
+ export type SubagentType = string;
5
+
6
+ /** How the subagent system prompt is constructed. */
7
+ export type SystemPromptMode = "replace" | "inherit" | "custom";
8
+
9
+ /** Unified agent configuration — used for both default and user-defined agents. */
10
+ export interface AgentConfig {
11
+ name: string;
12
+ displayName?: string;
13
+ description: string;
14
+ /** Tools to register with the session (controls availability, not LLM visibility). */
15
+ registeredTools?: string[];
16
+ /**
17
+ * Controls which tool schemas the LLM sees. Can reference built-in tools
18
+ * and extension tools. true = all, string[] = listed, false = none.
19
+ * Supports ext/* syntax to include all tools from an extension.
20
+ * Mutually exclusive with excludeTools.
21
+ */
22
+ tools?: true | string[] | false;
23
+ /** Tool blacklist — all tools except these are visible. Mutually exclusive with tools (when tools is string[]). */
24
+ excludeTools?: string[];
25
+ /** true = inherit all, string[] = only listed, false = none. undefined = not set (uses global default). Mutually exclusive with excludeExtensions. */
26
+ extensions?: true | string[] | false;
27
+ /** Extension blacklist — all extensions except these load. Mutually exclusive with extensions (when extensions is string[]). */
28
+ excludeExtensions?: string[];
29
+ /** Whitelist of allowed skills (metadata only in system prompt). true = all, string[] = listed, false = none. undefined = not set (uses global default). */
30
+ skills?: true | string[] | false;
31
+ /** Skills to preload with full content into system prompt. string[] = listed, false/undefined = none */
32
+ preloadSkills?: string[] | false;
33
+ model?: string;
34
+ thinkingLevel?: ThinkingLevel;
35
+ maxTurns?: number;
36
+ /** Max output tokens per LLM response. Passed to provider as max_tokens or max_completion_tokens. */
37
+ maxTokens?: number;
38
+ systemPrompt: string;
39
+
40
+ /** true = this is an embedded default agent (informational) */
41
+ isDefault?: boolean;
42
+ /** true = agent is hidden from the schema enum but can still be called by name. */
43
+ hidden?: boolean;
44
+ /** Where this agent was loaded from */
45
+ source?: "project" | "global";
46
+ }
47
+
48
+ export interface AgentInvocation {
49
+ /** Short display name, e.g. "haiku" — only set when different from parent. */
50
+ modelName?: string;
51
+ thinkingLevel?: ThinkingLevel;
52
+ maxTurns?: number;
53
+ runInBackground?: boolean;
54
+ }
@@ -36,6 +36,13 @@ export function formatTokens(count: number): string {
36
36
  return `${count}`;
37
37
  }
38
38
 
39
+ /** Format token count for widget display: rounded to whole number for k. */
40
+ export function formatTokensCompact(count: number): string {
41
+ if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
42
+ if (count >= 1_000) return `${Math.round(count / 1_000)}k`;
43
+ return `${count}`;
44
+ }
45
+
39
46
  /** Format cost as a dollar amount: "$0.00", "$0.01", "$1.23". */
40
47
  export function formatCost(cost: number): string {
41
48
  return `$${cost.toFixed(2)}`;
@@ -7,26 +7,43 @@
7
7
 
8
8
  import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
- import type { SubagentsConfig } from "./model-precedence.js";
10
+ import type { SubagentsConfig } from "../models/model-precedence.js";
11
11
 
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 number of grace turns before an agent is force-stopped. */
16
+ export const DEFAULT_GRACE_TURNS = 6;
17
+
15
18
  /** Default configuration — used when config file doesn't exist or is invalid. */
16
19
  export const DEFAULT_CONFIG: SubagentsConfig = {
17
20
  agent: {
18
21
  default: null,
19
22
  forceBackground: false,
20
- graceTurns: 6,
23
+ graceTurns: DEFAULT_GRACE_TURNS,
21
24
  widgetMaxLines: 12,
22
25
  // widgetMaxLinesCompact intentionally omitted — derives from widgetMaxLines
26
+ widgetDescLengthFull: 50,
27
+ widgetDescLengthCompact: 30,
23
28
  widgetCompact: false,
24
29
  widgetShortcut: false,
30
+ systemPromptMode: "replace",
31
+ includeContextFiles: true,
32
+ disableDefaultAgents: false,
33
+ showTools: true,
34
+ showTurns: true,
35
+ showInput: true,
36
+ showOutput: true,
37
+ showContext: true,
38
+ showCost: false,
39
+ showTime: true,
25
40
  },
26
41
  concurrency: { default: 4 },
27
42
  };
28
43
 
29
- /** Read config from disk. Returns defaults if file doesn't exist or is invalid. */
44
+ /**
45
+ * Read config from disk. Returns defaults if file doesn't exist or is invalid.
46
+ */
30
47
  export function loadConfig(): SubagentsConfig {
31
48
  try {
32
49
  const raw = fs.readFileSync(CONFIG_PATH, "utf-8");